@jasy/pdf 1.0.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/README.md +171 -0
  2. package/dist/api/args.d.ts +10 -0
  3. package/dist/api/args.js +13 -0
  4. package/dist/api/color.d.ts +24 -0
  5. package/dist/api/color.js +215 -0
  6. package/dist/api/content.d.ts +36 -0
  7. package/dist/api/content.js +50 -0
  8. package/dist/api/descriptor.d.ts +25 -0
  9. package/dist/api/descriptor.js +71 -0
  10. package/dist/api/index.d.ts +8 -0
  11. package/dist/api/index.js +27 -0
  12. package/dist/api/insets.d.ts +16 -0
  13. package/dist/api/insets.js +21 -0
  14. package/dist/api/layout.d.ts +72 -0
  15. package/dist/api/layout.js +99 -0
  16. package/dist/api/structure.d.ts +80 -0
  17. package/dist/api/structure.js +125 -0
  18. package/dist/api/table.d.ts +37 -0
  19. package/dist/api/table.js +87 -0
  20. package/dist/api/text.d.ts +28 -0
  21. package/dist/api/text.js +61 -0
  22. package/dist/assets/Courier-Bold.afm +342 -0
  23. package/dist/assets/Courier-BoldOblique.afm +342 -0
  24. package/dist/assets/Courier-Oblique.afm +342 -0
  25. package/dist/assets/Courier.afm +342 -0
  26. package/dist/assets/Helvetica-Bold.afm +2827 -0
  27. package/dist/assets/Helvetica-BoldOblique.afm +2827 -0
  28. package/dist/assets/Helvetica-Oblique.afm +3051 -0
  29. package/dist/assets/Helvetica.afm +3051 -0
  30. package/dist/assets/Symbol.afm +213 -0
  31. package/dist/assets/Times-Bold.afm +2588 -0
  32. package/dist/assets/Times-BoldItalic.afm +2384 -0
  33. package/dist/assets/Times-Italic.afm +2667 -0
  34. package/dist/assets/Times-Roman.afm +2419 -0
  35. package/dist/assets/ZapfDingbats.afm +225 -0
  36. package/dist/assets/agl.txt +695 -0
  37. package/dist/common/color.d.ts +37 -0
  38. package/dist/common/color.js +62 -0
  39. package/dist/constants/page-sizes.d.ts +44 -0
  40. package/dist/constants/page-sizes.js +92 -0
  41. package/dist/constants/pdf-parts.d.ts +5 -0
  42. package/dist/constants/pdf-parts.js +8 -0
  43. package/dist/elements/container-element.d.ts +35 -0
  44. package/dist/elements/container-element.js +91 -0
  45. package/dist/elements/image-element.d.ts +56 -0
  46. package/dist/elements/image-element.js +151 -0
  47. package/dist/elements/index.d.ts +10 -0
  48. package/dist/elements/index.js +28 -0
  49. package/dist/elements/layout/deferred-element.d.ts +19 -0
  50. package/dist/elements/layout/deferred-element.js +33 -0
  51. package/dist/elements/layout/expanded-element.d.ts +30 -0
  52. package/dist/elements/layout/expanded-element.js +68 -0
  53. package/dist/elements/layout/padding-element.d.ts +29 -0
  54. package/dist/elements/layout/padding-element.js +76 -0
  55. package/dist/elements/layout/repeating-header-element.d.ts +29 -0
  56. package/dist/elements/layout/repeating-header-element.js +60 -0
  57. package/dist/elements/layout/sized-container-element.d.ts +14 -0
  58. package/dist/elements/layout/sized-container-element.js +37 -0
  59. package/dist/elements/line-element.d.ts +31 -0
  60. package/dist/elements/line-element.js +44 -0
  61. package/dist/elements/page-element.d.ts +58 -0
  62. package/dist/elements/page-element.js +90 -0
  63. package/dist/elements/pdf-document-element.d.ts +13 -0
  64. package/dist/elements/pdf-document-element.js +22 -0
  65. package/dist/elements/pdf-element.d.ts +67 -0
  66. package/dist/elements/pdf-element.js +55 -0
  67. package/dist/elements/rectangle-element.d.ts +55 -0
  68. package/dist/elements/rectangle-element.js +120 -0
  69. package/dist/elements/row-element.d.ts +42 -0
  70. package/dist/elements/row-element.js +54 -0
  71. package/dist/elements/text-element.d.ts +59 -0
  72. package/dist/elements/text-element.js +137 -0
  73. package/dist/index.d.ts +3 -0
  74. package/dist/index.js +21 -0
  75. package/dist/ir/display-list.d.ts +74 -0
  76. package/dist/ir/display-list.js +2 -0
  77. package/dist/layout/box-constraints.d.ts +56 -0
  78. package/dist/layout/box-constraints.js +73 -0
  79. package/dist/layout/fragmentation.d.ts +42 -0
  80. package/dist/layout/fragmentation.js +61 -0
  81. package/dist/renderer/container-renderer.d.ts +6 -0
  82. package/dist/renderer/container-renderer.js +30 -0
  83. package/dist/renderer/deferred-renderer.d.ts +6 -0
  84. package/dist/renderer/deferred-renderer.js +25 -0
  85. package/dist/renderer/expanded-renderer.d.ts +6 -0
  86. package/dist/renderer/expanded-renderer.js +23 -0
  87. package/dist/renderer/image-renderer.d.ts +6 -0
  88. package/dist/renderer/image-renderer.js +85 -0
  89. package/dist/renderer/index.d.ts +10 -0
  90. package/dist/renderer/index.js +26 -0
  91. package/dist/renderer/line-renderer.d.ts +6 -0
  92. package/dist/renderer/line-renderer.js +30 -0
  93. package/dist/renderer/padding-renderer.d.ts +6 -0
  94. package/dist/renderer/padding-renderer.js +23 -0
  95. package/dist/renderer/page-renderer.d.ts +5 -0
  96. package/dist/renderer/page-renderer.js +81 -0
  97. package/dist/renderer/pdf-backend.d.ts +45 -0
  98. package/dist/renderer/pdf-backend.js +184 -0
  99. package/dist/renderer/pdf-config.d.ts +8 -0
  100. package/dist/renderer/pdf-config.js +17 -0
  101. package/dist/renderer/pdf-document-class.d.ts +42 -0
  102. package/dist/renderer/pdf-document-class.js +118 -0
  103. package/dist/renderer/pdf-document-renderer.d.ts +20 -0
  104. package/dist/renderer/pdf-document-renderer.js +104 -0
  105. package/dist/renderer/pdf-renderer.d.ts +5 -0
  106. package/dist/renderer/pdf-renderer.js +92 -0
  107. package/dist/renderer/rectangle-renderer.d.ts +8 -0
  108. package/dist/renderer/rectangle-renderer.js +61 -0
  109. package/dist/renderer/repeating-header-renderer.d.ts +6 -0
  110. package/dist/renderer/repeating-header-renderer.js +28 -0
  111. package/dist/renderer/row-renderer.d.ts +6 -0
  112. package/dist/renderer/row-renderer.js +30 -0
  113. package/dist/renderer/text-renderer.d.ts +9 -0
  114. package/dist/renderer/text-renderer.js +125 -0
  115. package/dist/text/line-breaker.d.ts +40 -0
  116. package/dist/text/line-breaker.js +106 -0
  117. package/dist/utils/afm-parser.d.ts +12 -0
  118. package/dist/utils/afm-parser.js +91 -0
  119. package/dist/utils/flex-layout.d.ts +53 -0
  120. package/dist/utils/flex-layout.js +119 -0
  121. package/dist/utils/font-metrics.d.ts +12 -0
  122. package/dist/utils/font-metrics.js +2 -0
  123. package/dist/utils/font-path.d.ts +1 -0
  124. package/dist/utils/font-path.js +19 -0
  125. package/dist/utils/image-helper.d.ts +43 -0
  126. package/dist/utils/image-helper.js +206 -0
  127. package/dist/utils/pdf-object-manager.d.ts +97 -0
  128. package/dist/utils/pdf-object-manager.js +645 -0
  129. package/dist/utils/renderer-registry.d.ts +6 -0
  130. package/dist/utils/renderer-registry.js +19 -0
  131. package/dist/utils/ttf-parser.d.ts +29 -0
  132. package/dist/utils/ttf-parser.js +191 -0
  133. package/dist/utils/ttf-subsetter.d.ts +1 -0
  134. package/dist/utils/ttf-subsetter.js +161 -0
  135. package/dist/utils/utf8-to-windows1252-encoder.d.ts +2 -0
  136. package/dist/utils/utf8-to-windows1252-encoder.js +55 -0
  137. package/dist/validators/element-validator.d.ts +8 -0
  138. package/dist/validators/element-validator.js +61 -0
  139. package/package.json +50 -0
@@ -0,0 +1,81 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.PageRenderer = void 0;
13
+ const page_sizes_1 = require("../constants/page-sizes");
14
+ const renderer_registry_1 = require("../utils/renderer-registry");
15
+ const pdf_config_1 = require("./pdf-config");
16
+ const pdf_backend_1 = require("./pdf-backend");
17
+ class PageRenderer {
18
+ static render(page, objectManager) {
19
+ return __awaiter(this, void 0, void 0, function* () {
20
+ var _a;
21
+ const { children, config, header, footer } = page.getProps();
22
+ // Header (top band) and footer (bottom band) sit around the body and repeat on every
23
+ // physical page; they are placed by `PageElement.calculateLayout` / the page driver.
24
+ const renderables = [...(header ? [header] : []), ...children, ...(footer ? [footer] : [])];
25
+ // Page geometry (also the MediaBox below). Needed up front because flipping the
26
+ // display list to PDF coordinates uses the page height. config is fully resolved
27
+ // by the layout pass; fall back to the document default rather than asserting.
28
+ let [width, height] = page_sizes_1.pageFormats[(_a = config === null || config === void 0 ? void 0 : config.pageSize) !== null && _a !== void 0 ? _a : page_sizes_1.PageSize.A4];
29
+ if ((config === null || config === void 0 ? void 0 : config.orientation) === pdf_config_1.Orientation.landscape) {
30
+ [width, height] = [height, width];
31
+ }
32
+ // Collect the whole page as a display list (top-left coordinates), flip it to PDF
33
+ // coordinates at this one seam, then serialize once. Serializing registers the
34
+ // fonts/images used below, so it must run before the resource section.
35
+ const nodes = [];
36
+ for (const element of renderables) {
37
+ const renderer = renderer_registry_1.RendererRegistry.getRenderer(element);
38
+ if (renderer) {
39
+ nodes.push(...(yield renderer(element, objectManager)));
40
+ }
41
+ }
42
+ const pageContent = pdf_backend_1.PdfBackend.serialize(pdf_backend_1.PdfBackend.flipY(nodes, height), objectManager);
43
+ // Add the page content as a new object (FlateDecode-compressed when enabled). The /Length is
44
+ // computed inside, with an explicit EOL before `endstream` (PDF/A clause 6.1.7.1).
45
+ const contentObjectNumber = objectManager.addContentStream(pageContent);
46
+ // Get the parent object number dynamically (linked with the page object)
47
+ const parentObjectNumber = objectManager.getParentObjectNumber(); // Get parent object number
48
+ // Page object with MediaBox
49
+ // - Get all fonts and add it to the page (reference)
50
+ objectManager.registerFont("Helvetica");
51
+ const fontReferences = [];
52
+ objectManager.getAllFontsRaw().forEach((value, _key) => {
53
+ const fontRef = `/F${value.fontIndex} ${value.resourceIndex} 0 R`;
54
+ fontReferences.push(fontRef);
55
+ });
56
+ // - Get all images and add it to the page (reference)
57
+ const imageReferences = [];
58
+ objectManager.getAllImagesRaw().forEach((value) => {
59
+ const imageRef = `/IM${value} ${value} 0 R`;
60
+ imageReferences.push(imageRef);
61
+ });
62
+ const imageCode = imageReferences.length > 0
63
+ ? "/ProcSet [/PDF /Text /ImageB /ImageC /ImageI] /XObject <<\n" +
64
+ imageReferences.join("\n") +
65
+ "\n>>\n"
66
+ : "";
67
+ // - Transparency (ExtGState) references, registered during serialize above
68
+ const extGStateReferences = [];
69
+ objectManager.getAllExtGStatesRaw().forEach((objectNumber, name) => {
70
+ extGStateReferences.push(`/${name} ${objectNumber} 0 R`);
71
+ });
72
+ const extGStateCode = extGStateReferences.length > 0
73
+ ? "/ExtGState <<\n" + extGStateReferences.join("\n") + "\n>>\n"
74
+ : "";
75
+ const pageObject = `<< /Type /Page /Parent ${parentObjectNumber} 0 R /Contents ${contentObjectNumber} 0 R /Resources <<\n/Font <<\n${fontReferences.join("\n")}\n>>\n${imageCode}${extGStateCode}>>\n/MediaBox [0 0 ${width} ${height}] >>`;
76
+ // Add page as new object and return the page number
77
+ return objectManager.addObject(pageObject);
78
+ });
79
+ }
80
+ }
81
+ exports.PageRenderer = PageRenderer;
@@ -0,0 +1,45 @@
1
+ import { IRNode } from "../ir/display-list";
2
+ import { PDFObjectManager } from "../utils/pdf-object-manager";
3
+ /**
4
+ * PDF backend - turns display-list primitives into content-stream operators.
5
+ *
6
+ * This is "the renderer" in the pure sense: it consumes only `IRNode`s and knows
7
+ * nothing about components. Each primitive maps to the exact operators previously
8
+ * emitted inline by the per-element renderers, so output stays byte-identical while
9
+ * the renderers are migrated onto the IR one at a time.
10
+ */
11
+ export declare class PdfBackend {
12
+ /**
13
+ * Flip a display list from the engine's top-left origin (y grows downward) to PDF's
14
+ * bottom-left origin (y grows upward). This is the ONE place the Y axis is flipped -
15
+ * elements above this seam are coordinate-system-blind. Each primitive flips around
16
+ * its own anchor: a rect/image around its bottom edge, a text baseline / line point
17
+ * directly. (image is migrated in a later slice and still arrives pre-flipped.)
18
+ */
19
+ static flipY(nodes: IRNode[], pageHeight: number): IRNode[];
20
+ /** Serialize a whole display list into one content stream (page-level entry point). */
21
+ static serialize(nodes: IRNode[], om: PDFObjectManager): string;
22
+ /**
23
+ * Escapes a string for a PDF literal string `( ... )`. The backslash must be doubled
24
+ * first, then the parentheses that delimit the string. Without this, a ")" in the
25
+ * text closes the string early and the remaining characters leak as raw operators.
26
+ */
27
+ static escapePdfString(text: string): string;
28
+ /**
29
+ * Returns the `/GSx gs` operator that selects a transparency state, or `""` when both
30
+ * alphas are fully opaque. Opaque draws emit nothing here, so output stays
31
+ * byte-identical until transparency is actually used.
32
+ */
33
+ private static alphaPrefix;
34
+ /**
35
+ * Path operators for a rounded rectangle: bottom-left at (x,y), size w×h, corner
36
+ * radius `radius` (clamped to half the smaller side). Corners are 90° Bézier arcs
37
+ * (kappa ≈ 0.5523). Returns m/l/c/h ops WITHOUT the paint operator.
38
+ */
39
+ private static roundedRectPath;
40
+ /**
41
+ * Serialize a single display-list node to its content-stream operators.
42
+ * `om` is used only by primitives that allocate PDF resources (images, fonts).
43
+ */
44
+ static serializeNode(node: IRNode, om: PDFObjectManager): string;
45
+ }
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PdfBackend = void 0;
4
+ /**
5
+ * PDF backend - turns display-list primitives into content-stream operators.
6
+ *
7
+ * This is "the renderer" in the pure sense: it consumes only `IRNode`s and knows
8
+ * nothing about components. Each primitive maps to the exact operators previously
9
+ * emitted inline by the per-element renderers, so output stays byte-identical while
10
+ * the renderers are migrated onto the IR one at a time.
11
+ */
12
+ class PdfBackend {
13
+ /**
14
+ * Flip a display list from the engine's top-left origin (y grows downward) to PDF's
15
+ * bottom-left origin (y grows upward). This is the ONE place the Y axis is flipped -
16
+ * elements above this seam are coordinate-system-blind. Each primitive flips around
17
+ * its own anchor: a rect/image around its bottom edge, a text baseline / line point
18
+ * directly. (image is migrated in a later slice and still arrives pre-flipped.)
19
+ */
20
+ static flipY(nodes, pageHeight) {
21
+ return nodes.map((node) => {
22
+ switch (node.type) {
23
+ case "rect":
24
+ return Object.assign(Object.assign({}, node), { y: pageHeight - node.y - node.height });
25
+ case "line":
26
+ return Object.assign(Object.assign({}, node), { y1: pageHeight - node.y1, y2: pageHeight - node.y2 });
27
+ case "text":
28
+ // node.y is the baseline measured from the page top; flip it directly.
29
+ return Object.assign(Object.assign({}, node), { y: pageHeight - node.y });
30
+ case "image": {
31
+ // Flip the placement box (and the clip frame, if any) around its bottom edge.
32
+ const flipped = Object.assign(Object.assign({}, node), { y: pageHeight - node.y - node.height });
33
+ if (node.clip) {
34
+ flipped.clip = Object.assign(Object.assign({}, node.clip), { y: pageHeight - node.clip.y - node.clip.height });
35
+ }
36
+ return flipped;
37
+ }
38
+ default: {
39
+ const unknown = node;
40
+ return unknown;
41
+ }
42
+ }
43
+ });
44
+ }
45
+ /** Serialize a whole display list into one content stream (page-level entry point). */
46
+ static serialize(nodes, om) {
47
+ return nodes.map((node) => PdfBackend.serializeNode(node, om)).join("");
48
+ }
49
+ /**
50
+ * Escapes a string for a PDF literal string `( ... )`. The backslash must be doubled
51
+ * first, then the parentheses that delimit the string. Without this, a ")" in the
52
+ * text closes the string early and the remaining characters leak as raw operators.
53
+ */
54
+ static escapePdfString(text) {
55
+ return text.replace(/\\/g, "\\\\").replace(/\(/g, "\\(").replace(/\)/g, "\\)");
56
+ }
57
+ /**
58
+ * Returns the `/GSx gs` operator that selects a transparency state, or `""` when both
59
+ * alphas are fully opaque. Opaque draws emit nothing here, so output stays
60
+ * byte-identical until transparency is actually used.
61
+ */
62
+ static alphaPrefix(om, fillAlpha, strokeAlpha) {
63
+ if (fillAlpha >= 1 && strokeAlpha >= 1)
64
+ return "";
65
+ return `/${om.registerExtGState(fillAlpha, strokeAlpha)} gs\n`;
66
+ }
67
+ /**
68
+ * Path operators for a rounded rectangle: bottom-left at (x,y), size w×h, corner
69
+ * radius `radius` (clamped to half the smaller side). Corners are 90° Bézier arcs
70
+ * (kappa ≈ 0.5523). Returns m/l/c/h ops WITHOUT the paint operator.
71
+ */
72
+ static roundedRectPath(x, y, w, h, radius) {
73
+ const r = Math.min(radius, w / 2, h / 2);
74
+ const c = r * 0.5523; // control-point offset that approximates a quarter circle
75
+ const f = (n) => n.toFixed(3);
76
+ const xr = x + r;
77
+ const xwr = x + w - r;
78
+ const xw = x + w;
79
+ const yr = y + r;
80
+ const yhr = y + h - r;
81
+ const yh = y + h;
82
+ return (`${f(xr)} ${f(y)} m\n` +
83
+ `${f(xwr)} ${f(y)} l\n` +
84
+ `${f(xwr + c)} ${f(y)} ${f(xw)} ${f(yr - c)} ${f(xw)} ${f(yr)} c\n` +
85
+ `${f(xw)} ${f(yhr)} l\n` +
86
+ `${f(xw)} ${f(yhr + c)} ${f(xwr + c)} ${f(yh)} ${f(xwr)} ${f(yh)} c\n` +
87
+ `${f(xr)} ${f(yh)} l\n` +
88
+ `${f(xr - c)} ${f(yh)} ${f(x)} ${f(yhr + c)} ${f(x)} ${f(yhr)} c\n` +
89
+ `${f(x)} ${f(yr)} l\n` +
90
+ `${f(x)} ${f(yr - c)} ${f(xr - c)} ${f(y)} ${f(xr)} ${f(y)} c\n` +
91
+ `h`);
92
+ }
93
+ /**
94
+ * Serialize a single display-list node to its content-stream operators.
95
+ * `om` is used only by primitives that allocate PDF resources (images, fonts).
96
+ */
97
+ static serializeNode(node, om) {
98
+ var _a, _b, _c, _d, _e;
99
+ switch (node.type) {
100
+ case "line":
101
+ // q/Q isolates the graphics state; "[] 0 d" resets the dash pattern to solid.
102
+ return (`q\n` +
103
+ PdfBackend.alphaPrefix(om, 1, node.stroke.getAlpha()) +
104
+ `${node.strokeWidth} w\n` +
105
+ `${node.stroke.toPDFColorString()} RG\n` +
106
+ `[] 0 d\n` +
107
+ `${node.x1} ${node.y1} m\n` +
108
+ `${node.x2} ${node.y2} l\n` +
109
+ `S\n` +
110
+ `Q\n`);
111
+ case "rect": {
112
+ // Stroke only with a stroke colour AND a positive width - a 0-width border means
113
+ // "no border" (e.g. a filled box with no outline). Nothing to paint at all (no
114
+ // fill, no border) draws nothing. Paint: B = fill+stroke, f = fill, S = stroke.
115
+ const doStroke = !!node.stroke && ((_a = node.strokeWidth) !== null && _a !== void 0 ? _a : 0) > 0;
116
+ if (!node.fill && !doStroke)
117
+ return "";
118
+ let ops = "";
119
+ if (doStroke) {
120
+ ops += `${node.strokeWidth} w\n${node.stroke.toPDFColorString()} RG\n`;
121
+ }
122
+ if (node.fill)
123
+ ops += `${node.fill.toPDFColorString()} rg\n`;
124
+ const paint = node.fill ? (doStroke ? "B" : "f") : "S";
125
+ // Rounded corners emit a Bézier path; sharp corners keep the plain `re`
126
+ // (byte-identical when no radius is set).
127
+ const path = ((_b = node.radius) !== null && _b !== void 0 ? _b : 0) > 0
128
+ ? PdfBackend.roundedRectPath(node.x, node.y, node.width, node.height, node.radius)
129
+ : `${node.x} ${node.y} ${node.width} ${node.height} re`;
130
+ const body = ops + `${path} ${paint}\n`;
131
+ // Transparency needs an isolating q/Q so the state does not leak; opaque rects
132
+ // keep their bare operators (byte-identical).
133
+ const gs = PdfBackend.alphaPrefix(om, (_d = (_c = node.fill) === null || _c === void 0 ? void 0 : _c.getAlpha()) !== null && _d !== void 0 ? _d : 1, doStroke ? node.stroke.getAlpha() : 1);
134
+ return gs ? `q\n${gs}${body}Q\n` : body;
135
+ }
136
+ case "text": {
137
+ // One self-contained text block per run. The producer has already resolved
138
+ // absolute position, font and color; the backend only allocates the font
139
+ // resource and emits the operators. The text is escaped for PDF literal-string
140
+ // syntax so parentheses/backslashes can't break out of the string.
141
+ // Embedded (custom) fonts: pick the family variant for this style (falling back to the
142
+ // family's Normal), select its Identity-H Type0 resource and emit hex glyph ids - both
143
+ // from the SAME variant. Standard fonts keep the WinAnsi literal string, byte-identical.
144
+ const isCustom = om.isCustomFont(node.fontFamily, node.fontStyle);
145
+ const font = isCustom
146
+ ? om.getCustomFontResource(node.fontFamily, node.fontStyle)
147
+ : om.registerFont(node.fontFamily, node.fontStyle);
148
+ const textOp = isCustom
149
+ ? `<${om.encodeCustomText(node.fontFamily, node.text, node.fontStyle)}>`
150
+ : `(${PdfBackend.escapePdfString(node.text)})`;
151
+ const block = `BT\n` +
152
+ `${node.color.toPDFColorString()} rg ` +
153
+ `/F${font.fontIndex} ${node.fontSize} Tf ` +
154
+ `${node.x.toFixed(3)} ${node.y.toFixed(3)} Td ` +
155
+ `${textOp} Tj\n` +
156
+ `ET\n`;
157
+ // Transparent text gets an isolating q/Q + gs; opaque text is byte-identical.
158
+ const gs = PdfBackend.alphaPrefix(om, node.color.getAlpha(), 1);
159
+ return gs ? `q\n${gs}${block}Q\n` : block;
160
+ }
161
+ case "image": {
162
+ // The backend owns PDF resource creation: register the XObject (using the
163
+ // source pixel dimensions) and then place it with a scaling matrix.
164
+ const ref = om.registerImage(node.intrinsicWidth, node.intrinsicHeight, node.imageType, node.data);
165
+ const draw = `q\n${node.width} 0 0 ${node.height} ${node.x} ${node.y} cm\n` + `/IM${ref} Do\nQ\n`;
166
+ if (!node.clip)
167
+ return draw;
168
+ // Clip to the frame (re … W n); rounded when a radius is set. The rectangular
169
+ // path is byte-identical to before.
170
+ const c = node.clip;
171
+ const clipPath = ((_e = node.radius) !== null && _e !== void 0 ? _e : 0) > 0
172
+ ? PdfBackend.roundedRectPath(c.x, c.y, c.width, c.height, node.radius)
173
+ : `${c.x} ${c.y} ${c.width} ${c.height} re `;
174
+ return `q\n${clipPath}\nW n \n` + draw + `Q\n`;
175
+ }
176
+ default: {
177
+ // Exhaustiveness guard: if a new IRNode variant is added, this fails to compile.
178
+ const unknown = node;
179
+ throw new Error(`PdfBackend: unhandled IR node ${JSON.stringify(unknown)}`);
180
+ }
181
+ }
182
+ }
183
+ }
184
+ exports.PdfBackend = PdfBackend;
@@ -0,0 +1,8 @@
1
+ export declare enum Orientation {
2
+ portrait = "PORTRAIT",
3
+ landscape = "LANDSCAPE"
4
+ }
5
+ export declare enum ColorMode {
6
+ color = "COLOR",
7
+ grayscale = "GRAYSCALE"
8
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ // Leaf module: no imports, so it can never sit inside an import cycle. The page/color
3
+ // enums live here because they are runtime values used across modules that DO form
4
+ // cycles (object manager <-> document <-> elements); importing them from a cyclic
5
+ // module snapshots them as `undefined` under some load orders.
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.ColorMode = exports.Orientation = void 0;
8
+ var Orientation;
9
+ (function (Orientation) {
10
+ Orientation["portrait"] = "PORTRAIT";
11
+ Orientation["landscape"] = "LANDSCAPE";
12
+ })(Orientation || (exports.Orientation = Orientation = {}));
13
+ var ColorMode;
14
+ (function (ColorMode) {
15
+ ColorMode["color"] = "COLOR";
16
+ ColorMode["grayscale"] = "GRAYSCALE";
17
+ })(ColorMode || (exports.ColorMode = ColorMode = {}));
@@ -0,0 +1,42 @@
1
+ import { PageSize } from "../constants/page-sizes";
2
+ import { PDFDocumentElement } from "../elements";
3
+ import { FontStyle, PDFObjectManager } from "../utils/pdf-object-manager";
4
+ import { ColorMode, Orientation } from "./pdf-config";
5
+ export { ColorMode, Orientation } from "./pdf-config";
6
+ export interface Margin {
7
+ left: number;
8
+ top: number;
9
+ right: number;
10
+ bottom: number;
11
+ }
12
+ export interface DefaultFont {
13
+ fontFamily: string;
14
+ fontSize: number;
15
+ fontStyle: FontStyle;
16
+ }
17
+ export interface MetaData {
18
+ title?: string;
19
+ author?: string;
20
+ keywords: string[];
21
+ }
22
+ export interface PDFConfig {
23
+ pageSize?: PageSize;
24
+ orientation?: Orientation;
25
+ margin?: Margin;
26
+ colorMode?: ColorMode;
27
+ defaultFont?: DefaultFont;
28
+ metaData?: MetaData;
29
+ /** Register the 14 standard fonts (default true). Set false for PDF/A, where every font must be
30
+ * embedded - the non-embeddable standard-14 must not appear in the document at all. */
31
+ registerStandardFonts?: boolean;
32
+ }
33
+ export declare abstract class PDFDocument {
34
+ private _objectManager;
35
+ private child;
36
+ private registerStandardFonts;
37
+ constructor(config?: PDFConfig);
38
+ get objectManager(): PDFObjectManager;
39
+ abstract build(): PDFDocumentElement;
40
+ protected beforeRenderer(): void;
41
+ static render<T extends PDFDocument>(this: new () => T): Promise<string>;
42
+ }
@@ -0,0 +1,118 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.PDFDocument = exports.Orientation = exports.ColorMode = void 0;
13
+ const pdf_object_manager_1 = require("../utils/pdf-object-manager");
14
+ const pdf_renderer_1 = require("./pdf-renderer");
15
+ // Re-exported so existing `../renderer` consumers keep importing these from here.
16
+ var pdf_config_1 = require("./pdf-config");
17
+ Object.defineProperty(exports, "ColorMode", { enumerable: true, get: function () { return pdf_config_1.ColorMode; } });
18
+ Object.defineProperty(exports, "Orientation", { enumerable: true, get: function () { return pdf_config_1.Orientation; } });
19
+ class PDFDocument {
20
+ //#region Helper
21
+ // Method to register all standard fonts
22
+ registerStandardFonts(objectManager) {
23
+ const standardFonts = [
24
+ {
25
+ fontName: "Helvetica",
26
+ fontStyle: pdf_object_manager_1.FontStyle.Normal,
27
+ fullName: "Helvetica",
28
+ },
29
+ {
30
+ fontName: "Helvetica",
31
+ fontStyle: pdf_object_manager_1.FontStyle.Bold,
32
+ fullName: "Helvetica-Bold",
33
+ },
34
+ {
35
+ fontName: "Helvetica",
36
+ fontStyle: pdf_object_manager_1.FontStyle.Italic,
37
+ fullName: "Helvetica-Oblique",
38
+ },
39
+ {
40
+ fontName: "Helvetica",
41
+ fontStyle: pdf_object_manager_1.FontStyle.BoldItalic,
42
+ fullName: "Helvetica-BoldOblique",
43
+ },
44
+ { fontName: "Courier", fontStyle: pdf_object_manager_1.FontStyle.Normal, fullName: "Courier" },
45
+ {
46
+ fontName: "Courier",
47
+ fontStyle: pdf_object_manager_1.FontStyle.Bold,
48
+ fullName: "Courier-Bold",
49
+ },
50
+ {
51
+ fontName: "Courier",
52
+ fontStyle: pdf_object_manager_1.FontStyle.Italic,
53
+ fullName: "Courier-Oblique",
54
+ },
55
+ {
56
+ fontName: "Courier",
57
+ fontStyle: pdf_object_manager_1.FontStyle.BoldItalic,
58
+ fullName: "Courier-BoldOblique",
59
+ },
60
+ {
61
+ fontName: "Times-Roman",
62
+ fontStyle: pdf_object_manager_1.FontStyle.Normal,
63
+ fullName: "Times-Roman",
64
+ },
65
+ {
66
+ fontName: "Times-Roman",
67
+ fontStyle: pdf_object_manager_1.FontStyle.Bold,
68
+ fullName: "Times-Bold",
69
+ },
70
+ {
71
+ fontName: "Times-Roman",
72
+ fontStyle: pdf_object_manager_1.FontStyle.Italic,
73
+ fullName: "Times-Italic",
74
+ },
75
+ {
76
+ fontName: "Times-Roman",
77
+ fontStyle: pdf_object_manager_1.FontStyle.BoldItalic,
78
+ fullName: "Times-BoldItalic",
79
+ },
80
+ {
81
+ fontName: "Symbol",
82
+ fontStyle: pdf_object_manager_1.FontStyle.Normal,
83
+ fullName: "Symbol",
84
+ },
85
+ {
86
+ fontName: "ITC Zapf Dingbats",
87
+ fontStyle: pdf_object_manager_1.FontStyle.Normal,
88
+ fullName: "ZapfDingbats",
89
+ },
90
+ ];
91
+ standardFonts.forEach((font) => objectManager.registerFont(font.fontName, font.fontStyle, font.fullName));
92
+ }
93
+ //#endregion
94
+ constructor(config) {
95
+ // One object manager per document instance - no global singleton. Threaded
96
+ // explicitly into the renderer.
97
+ this._objectManager = new pdf_object_manager_1.PDFObjectManager();
98
+ // Add all standard font families - unless the document is PDF/A, which forbids non-embedded
99
+ // fonts (the caller then supplies embedded fonts for every name it uses).
100
+ if ((config === null || config === void 0 ? void 0 : config.registerStandardFonts) !== false) {
101
+ this.registerStandardFonts(this._objectManager);
102
+ }
103
+ if (config)
104
+ this._objectManager.changePDFConfig(config);
105
+ }
106
+ get objectManager() {
107
+ return this._objectManager;
108
+ }
109
+ beforeRenderer() { }
110
+ static render() {
111
+ return __awaiter(this, void 0, void 0, function* () {
112
+ const instance = new this();
113
+ instance.child = instance.build();
114
+ return yield pdf_renderer_1.PDFRenderer.render(instance.child, instance._objectManager);
115
+ });
116
+ }
117
+ }
118
+ exports.PDFDocument = PDFDocument;
@@ -0,0 +1,20 @@
1
+ import { PDFDocumentElement } from "../elements/pdf-document-element";
2
+ import { LayoutContext } from "../elements/pdf-element";
3
+ import { PDFObjectManager } from "../utils/pdf-object-manager";
4
+ export declare class PDFDocumentRenderer {
5
+ static render(document: PDFDocumentElement, objectManager: PDFObjectManager, ctx: LayoutContext): Promise<number>;
6
+ /**
7
+ * Renders one logical page, paginating it into one or more physical pages.
8
+ *
9
+ * Slice 0 only auto-paginates the common shape - a page whose single child is a
10
+ * fragmentation context (a Container). Anything else renders as a single page on the
11
+ * unchanged path, so non-overflowing documents stay byte-identical to pre-Slice-0.
12
+ */
13
+ private static renderLogicalPage;
14
+ /**
15
+ * Lays out one fragment on a fresh page of the same geometry and renders it. The
16
+ * header/footer are attached to every physical page so they repeat; `PageElement`
17
+ * re-places them and the body in the band between.
18
+ */
19
+ private static renderPhysicalPage;
20
+ }
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.PDFDocumentRenderer = void 0;
13
+ const page_element_1 = require("../elements/page-element");
14
+ const box_constraints_1 = require("../layout/box-constraints");
15
+ const fragmentation_1 = require("../layout/fragmentation");
16
+ const page_renderer_1 = require("./page-renderer");
17
+ class PDFDocumentRenderer {
18
+ static render(document, objectManager, ctx) {
19
+ return __awaiter(this, void 0, void 0, function* () {
20
+ const pageNumbers = [];
21
+ // Add the pages object first... we need its object number (resources). The count is
22
+ // a placeholder; it is replaced below with the real (post-pagination) page count.
23
+ const pagesObject = `<< /Type /Pages /Kids [] /Count ${document.getProps().children.length} >>`;
24
+ const pagesObjectNumber = objectManager.addObject(pagesObject);
25
+ // Now set the given object number all its childs
26
+ objectManager.setParentObjectNumber(pagesObjectNumber);
27
+ // The page driver: each logical PageElement may produce SEVERAL physical PDF pages
28
+ // when its content overflows (Slice 0: whole children reflow to the next page).
29
+ for (let page of document.getProps().children) {
30
+ const numbers = yield PDFDocumentRenderer.renderLogicalPage(page, objectManager, ctx);
31
+ pageNumbers.push(...numbers);
32
+ }
33
+ // We must update the pages object with the real physical page numbers and count.
34
+ const updatedPagesObject = `<< /Type /Pages /Kids [${pageNumbers
35
+ .map((num) => `${num} 0 R`)
36
+ .join(" ")}] /Count ${pageNumbers.length} >>`;
37
+ // Now we must replace it in the object manager
38
+ objectManager.replaceObject(pagesObjectNumber, updatedPagesObject);
39
+ return pagesObjectNumber;
40
+ });
41
+ }
42
+ /**
43
+ * Renders one logical page, paginating it into one or more physical pages.
44
+ *
45
+ * Slice 0 only auto-paginates the common shape - a page whose single child is a
46
+ * fragmentation context (a Container). Anything else renders as a single page on the
47
+ * unchanged path, so non-overflowing documents stay byte-identical to pre-Slice-0.
48
+ */
49
+ static renderLogicalPage(page, objectManager, ctx) {
50
+ return __awaiter(this, void 0, void 0, function* () {
51
+ const { children, config, header, footer } = page.getProps();
52
+ if (children.length !== 1 || !(0, fragmentation_1.isFragmentable)(children[0])) {
53
+ return [yield page_renderer_1.PageRenderer.render(page, objectManager)];
54
+ }
55
+ // Header/footer repeat on every physical page, so the body only ever flows into the
56
+ // band between them. Resolve that band once (config is already merged by pass 1).
57
+ const pageCtx = { metrics: ctx.metrics, pageConfig: config };
58
+ const { bodyWidth: width, bodyHeight: height } = (0, page_element_1.layoutPageBands)(config, header, footer, pageCtx);
59
+ const numbers = [];
60
+ let region = children[0];
61
+ let isFirstRegion = true;
62
+ while (region) {
63
+ if (!(0, fragmentation_1.isFragmentable)(region)) {
64
+ // A non-fragmentable remainder is placed whole on its own page.
65
+ numbers.push(yield PDFDocumentRenderer.renderPhysicalPage(config, region, header, footer, objectManager, ctx));
66
+ break;
67
+ }
68
+ const { fitted, remainder } = region.fragment(height, width, ctx);
69
+ // Everything fits on one page: render the ORIGINAL page so output is unchanged.
70
+ // Measuring inside fragment() laid the children out at the origin to size them, so
71
+ // re-run the page layout first to restore their real positions (deterministic).
72
+ if (isFirstRegion && remainder === null) {
73
+ page.calculateLayout(new box_constraints_1.BoxConstraints(), { x: 0, y: 0 }, ctx);
74
+ numbers.push(yield page_renderer_1.PageRenderer.render(page, objectManager));
75
+ break;
76
+ }
77
+ if (fitted) {
78
+ numbers.push(yield PDFDocumentRenderer.renderPhysicalPage(config, fitted, header, footer, objectManager, ctx));
79
+ }
80
+ region = remainder;
81
+ isFirstRegion = false;
82
+ }
83
+ return numbers;
84
+ });
85
+ }
86
+ /**
87
+ * Lays out one fragment on a fresh page of the same geometry and renders it. The
88
+ * header/footer are attached to every physical page so they repeat; `PageElement`
89
+ * re-places them and the body in the band between.
90
+ */
91
+ static renderPhysicalPage(config, content, header, footer, objectManager, ctx) {
92
+ return __awaiter(this, void 0, void 0, function* () {
93
+ const physicalPage = new page_element_1.PageElement({
94
+ config,
95
+ header,
96
+ footer,
97
+ children: [content],
98
+ });
99
+ physicalPage.calculateLayout(new box_constraints_1.BoxConstraints(), { x: 0, y: 0 }, ctx);
100
+ return page_renderer_1.PageRenderer.render(physicalPage, objectManager);
101
+ });
102
+ }
103
+ }
104
+ exports.PDFDocumentRenderer = PDFDocumentRenderer;
@@ -0,0 +1,5 @@
1
+ import { PDFDocumentElement } from "../elements/pdf-document-element";
2
+ import { PDFObjectManager } from "../utils/pdf-object-manager";
3
+ export declare class PDFRenderer {
4
+ static render(document: PDFDocumentElement, objectManager: PDFObjectManager): Promise<string>;
5
+ }