@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,67 @@
1
+ import type { FontMetrics } from "../utils/font-metrics";
2
+ import type { PDFPageConfig } from "./page-element";
3
+ import type { BoxConstraints, Offset, Size } from "../layout/box-constraints";
4
+ /**
5
+ * Everything the layout pass needs, threaded explicitly (no global singleton):
6
+ * font metrics for measuring, and the geometry of the page currently being laid out.
7
+ * `PageElement` sets `pageConfig` for its subtree, so each page flips against its own
8
+ * height. The PDF byte writer is deliberately absent - layout must not touch it.
9
+ */
10
+ export interface LayoutContext {
11
+ metrics: FontMetrics;
12
+ pageConfig: PDFPageConfig;
13
+ }
14
+ export declare abstract class PDFElement {
15
+ abstract getProps(): unknown;
16
+ /**
17
+ * Lays the element out: `constraints` bound its size (flows DOWN), `offset` is the
18
+ * absolute top-left position the parent assigns it, and the returned `Size` is the
19
+ * space it actually took (flows UP). Layout works in a top-left origin; the Y-flip
20
+ * happens once at the IR -> backend seam.
21
+ */
22
+ abstract calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
23
+ }
24
+ export declare abstract class SizedPDFElement extends PDFElement {
25
+ protected x: number;
26
+ protected y: number;
27
+ protected width?: number;
28
+ protected height?: number;
29
+ constructor(data: SizedElement);
30
+ getSize(): SizedElement;
31
+ }
32
+ export declare abstract class FlexiblePDFElement extends PDFElement {
33
+ protected flex: number;
34
+ protected verticalChildAlignment: VerticalAlignment;
35
+ constructor(data: FlexibleElement);
36
+ getFlex(): number;
37
+ }
38
+ export declare enum HorizontalAlignment {
39
+ left = "LEFT",
40
+ right = "RIGHT",
41
+ center = "CENTER",
42
+ block = "BLOCK"
43
+ }
44
+ export declare enum VerticalAlignment {
45
+ top = "TOP",
46
+ middle = "MIDDLE",
47
+ bottom = "BOTTOM"
48
+ }
49
+ export interface WithChildren {
50
+ children: PDFElement[];
51
+ }
52
+ export interface WithChild {
53
+ child: PDFElement;
54
+ }
55
+ export interface FlexibleElement {
56
+ flex: number;
57
+ verticalChildAlignment?: VerticalAlignment;
58
+ }
59
+ export interface SizedElement {
60
+ x: number;
61
+ y: number;
62
+ width?: number;
63
+ height?: number;
64
+ }
65
+ export declare function isSizedElement(obj: unknown): obj is SizedElement;
66
+ export declare function hasChildrenProp<T extends object>(obj: T): obj is T & WithChildren;
67
+ export declare function hasChildProp<T extends object>(obj: T): obj is T & WithChild;
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.VerticalAlignment = exports.HorizontalAlignment = exports.FlexiblePDFElement = exports.SizedPDFElement = exports.PDFElement = void 0;
4
+ exports.isSizedElement = isSizedElement;
5
+ exports.hasChildrenProp = hasChildrenProp;
6
+ exports.hasChildProp = hasChildProp;
7
+ class PDFElement {
8
+ }
9
+ exports.PDFElement = PDFElement;
10
+ class SizedPDFElement extends PDFElement {
11
+ constructor(data) {
12
+ super();
13
+ this.x = data.x;
14
+ this.y = data.y;
15
+ this.width = data.width;
16
+ this.height = data.height;
17
+ }
18
+ getSize() {
19
+ return { x: this.x, y: this.y, width: this.width, height: this.height };
20
+ }
21
+ }
22
+ exports.SizedPDFElement = SizedPDFElement;
23
+ class FlexiblePDFElement extends PDFElement {
24
+ constructor(data) {
25
+ super();
26
+ this.flex = data.flex;
27
+ this.verticalChildAlignment = data.verticalChildAlignment || VerticalAlignment.middle;
28
+ }
29
+ getFlex() {
30
+ return this.flex;
31
+ }
32
+ }
33
+ exports.FlexiblePDFElement = FlexiblePDFElement;
34
+ var HorizontalAlignment;
35
+ (function (HorizontalAlignment) {
36
+ HorizontalAlignment["left"] = "LEFT";
37
+ HorizontalAlignment["right"] = "RIGHT";
38
+ HorizontalAlignment["center"] = "CENTER";
39
+ HorizontalAlignment["block"] = "BLOCK";
40
+ })(HorizontalAlignment || (exports.HorizontalAlignment = HorizontalAlignment = {}));
41
+ var VerticalAlignment;
42
+ (function (VerticalAlignment) {
43
+ VerticalAlignment["top"] = "TOP";
44
+ VerticalAlignment["middle"] = "MIDDLE";
45
+ VerticalAlignment["bottom"] = "BOTTOM";
46
+ })(VerticalAlignment || (exports.VerticalAlignment = VerticalAlignment = {}));
47
+ function isSizedElement(obj) {
48
+ return typeof obj === "object" && obj !== null && "x" in obj && "y" in obj;
49
+ }
50
+ function hasChildrenProp(obj) {
51
+ return "children" in obj;
52
+ }
53
+ function hasChildProp(obj) {
54
+ return "child" in obj;
55
+ }
@@ -0,0 +1,55 @@
1
+ import { Color } from "../common/color";
2
+ import { BoxConstraints, Offset, Size } from "../layout/box-constraints";
3
+ import { Fragmentable, FragmentResult } from "../layout/fragmentation";
4
+ import { LayoutContext, PDFElement, SizedElement, SizedPDFElement, WithChildren } from "./pdf-element";
5
+ /** Per-side border colours. When set, each present side is stroked individually (sharp
6
+ * corners), instead of the uniform `color` border - this is what enables grid lines. */
7
+ export interface SideBorders {
8
+ top?: Color;
9
+ right?: Color;
10
+ bottom?: Color;
11
+ left?: Color;
12
+ }
13
+ interface RectangleElementParams extends SizedElement, WithChildren {
14
+ color?: Color;
15
+ backgroundColor?: Color;
16
+ borderWidth?: number;
17
+ /** Corner radius in points; 0 = sharp corners (default). */
18
+ radius?: number;
19
+ /** Individual side borders; overrides the uniform `color` border when present. */
20
+ sideBorders?: SideBorders;
21
+ }
22
+ export declare class RectangleElement extends SizedPDFElement implements Fragmentable {
23
+ private children;
24
+ private color;
25
+ private backgroundColor?;
26
+ private borderWidth;
27
+ private radius;
28
+ private sideBorders?;
29
+ private sizeMemory;
30
+ constructor({ children, color, backgroundColor, borderWidth, width, height, radius, sideBorders, }: RectangleElementParams);
31
+ /**
32
+ * Splits the bordered box across pages (box-decoration-break: clone - every fragment
33
+ * gets its own full border). The children stack is packed into the space left after
34
+ * reserving the border on top and bottom; each fragment then shrink-wraps its own
35
+ * content (explicit height = content + 2*border) so the border hugs the text instead of
36
+ * filling the page. An explicit box height is intentionally overridden here - a split
37
+ * box is sized by what it actually holds on each page.
38
+ */
39
+ fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
40
+ private cloneWithChildren;
41
+ calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
42
+ getProps(): {
43
+ x: number;
44
+ y: number;
45
+ width: number;
46
+ height: number | undefined;
47
+ children: PDFElement[];
48
+ color: Color;
49
+ backgroundColor: Color | undefined;
50
+ borderWidth: number;
51
+ radius: number;
52
+ sideBorders: SideBorders | undefined;
53
+ };
54
+ }
55
+ export {};
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RectangleElement = void 0;
4
+ const color_1 = require("../common/color");
5
+ const box_constraints_1 = require("../layout/box-constraints");
6
+ const fragmentation_1 = require("../layout/fragmentation");
7
+ const pdf_element_1 = require("./pdf-element");
8
+ class RectangleElement extends pdf_element_1.SizedPDFElement {
9
+ constructor({ children = [], color = new color_1.Color(0, 0, 0), backgroundColor, borderWidth, width, height, radius, sideBorders, }) {
10
+ super({ x: 0, y: 0, width, height });
11
+ this.children = [];
12
+ this.children = children;
13
+ this.color = color;
14
+ this.backgroundColor = backgroundColor;
15
+ // `?? 1` (not `|| 1`) so an explicit `0` means "no border" instead of snapping to 1.
16
+ this.borderWidth = borderWidth !== null && borderWidth !== void 0 ? borderWidth : 1;
17
+ this.radius = radius !== null && radius !== void 0 ? radius : 0;
18
+ this.sideBorders = sideBorders;
19
+ this.sizeMemory = { x: 0, y: 0, width, height };
20
+ }
21
+ /**
22
+ * Splits the bordered box across pages (box-decoration-break: clone - every fragment
23
+ * gets its own full border). The children stack is packed into the space left after
24
+ * reserving the border on top and bottom; each fragment then shrink-wraps its own
25
+ * content (explicit height = content + 2*border) so the border hugs the text instead of
26
+ * filling the page. An explicit box height is intentionally overridden here - a split
27
+ * box is sized by what it actually holds on each page.
28
+ */
29
+ fragment(maxHeight, width, ctx) {
30
+ var _a;
31
+ const boxWidth = (_a = this.sizeMemory.width) !== null && _a !== void 0 ? _a : width;
32
+ const innerWidth = Math.max(0, boxWidth - 2 * this.borderWidth);
33
+ // Border-box: the content is inset by the border on top and bottom, so a fragment
34
+ // holding `c` of content is `c + 2*border` tall. (Derived, not a fudge factor.)
35
+ const innerMaxHeight = maxHeight - 2 * this.borderWidth;
36
+ const { fitted, remainder } = (0, fragmentation_1.packChildren)(this.children, innerMaxHeight, innerWidth, ctx);
37
+ if (remainder.length === 0)
38
+ return { fitted: this, remainder: null };
39
+ const contentHeight = (kids) => kids.reduce((sum, child) => sum +
40
+ child.calculateLayout(box_constraints_1.BoxConstraints.loose(innerWidth, Infinity), { x: 0, y: 0 }, ctx)
41
+ .height, 0);
42
+ return {
43
+ fitted: this.cloneWithChildren(fitted, contentHeight(fitted) + 2 * this.borderWidth),
44
+ remainder: this.cloneWithChildren(remainder, contentHeight(remainder) + 2 * this.borderWidth),
45
+ };
46
+ }
47
+ cloneWithChildren(children, height) {
48
+ return new RectangleElement({
49
+ x: 0,
50
+ y: 0,
51
+ width: this.sizeMemory.width,
52
+ height,
53
+ children,
54
+ color: this.color,
55
+ backgroundColor: this.backgroundColor,
56
+ borderWidth: this.borderWidth,
57
+ radius: this.radius,
58
+ sideBorders: this.sideBorders,
59
+ });
60
+ }
61
+ calculateLayout(constraints, offset, ctx) {
62
+ var _a, _b, _c;
63
+ // Width: an explicit size wins (clamped), else fill the offered box. (Without this a
64
+ // fixed box would balloon to the parent's size.)
65
+ this.width =
66
+ this.sizeMemory.width !== undefined
67
+ ? constraints.constrainWidth(this.sizeMemory.width)
68
+ : constraints.hasBoundedWidth
69
+ ? constraints.maxWidth
70
+ : this.width;
71
+ // Height: explicit wins; otherwise FILL a bounded region (e.g. inside an Expanded) but
72
+ // SHRINK-WRAP the content in an unbounded one (a note box in a stack). Shrink-wrap is
73
+ // resolved after the children are measured, just below.
74
+ const shrinkWrapHeight = this.sizeMemory.height === undefined && !constraints.hasBoundedHeight;
75
+ this.height =
76
+ this.sizeMemory.height !== undefined
77
+ ? constraints.constrainHeight(this.sizeMemory.height)
78
+ : constraints.hasBoundedHeight
79
+ ? constraints.maxHeight
80
+ : this.height;
81
+ this.x = this.sizeMemory.x + offset.x;
82
+ this.y = this.sizeMemory.y + offset.y;
83
+ // Lay out children stacked inside the border (inset by the border width). Width is
84
+ // finalized here; height is left unbounded so each child sizes to its own content.
85
+ const innerWidth = Math.max(0, ((_a = this.width) !== null && _a !== void 0 ? _a : 0) - 2 * this.borderWidth);
86
+ let contentHeight = 0;
87
+ let yCursor = this.y + this.borderWidth;
88
+ for (const child of this.children) {
89
+ const childSize = child.calculateLayout(box_constraints_1.BoxConstraints.loose(innerWidth, Infinity), { x: this.x + this.borderWidth, y: yCursor }, ctx);
90
+ yCursor += childSize.height;
91
+ contentHeight += childSize.height;
92
+ }
93
+ // No explicit height and no bounded region: the border hugs its content.
94
+ if (shrinkWrapHeight)
95
+ this.height = contentHeight + 2 * this.borderWidth;
96
+ // Border-box model: width/height ARE the outer box (the rect we draw); the content
97
+ // is inset by the border on every side. Report the honest box size - no phantom
98
+ // border added on (that asymmetric "+border" was the source of the magic-3 fudge in
99
+ // fragment). Top-left coordinates; the Y-flip happens at the IR -> backend seam.
100
+ return {
101
+ width: (_b = this.width) !== null && _b !== void 0 ? _b : 0,
102
+ height: (_c = this.height) !== null && _c !== void 0 ? _c : 0,
103
+ };
104
+ }
105
+ getProps() {
106
+ return {
107
+ x: this.x,
108
+ y: this.y,
109
+ width: this.width,
110
+ height: this.height,
111
+ children: this.children,
112
+ color: this.color,
113
+ backgroundColor: this.backgroundColor,
114
+ borderWidth: this.borderWidth,
115
+ radius: this.radius,
116
+ sideBorders: this.sideBorders,
117
+ };
118
+ }
119
+ }
120
+ exports.RectangleElement = RectangleElement;
@@ -0,0 +1,42 @@
1
+ import { BoxConstraints, Offset, Size } from "../layout/box-constraints";
2
+ import { MainAlign, CrossAlign } from "../utils/flex-layout";
3
+ import { LayoutContext, PDFElement, SizedPDFElement, WithChildren } from "./pdf-element";
4
+ interface RowElementParams extends WithChildren {
5
+ /** Space inserted between children, in points. */
6
+ gap?: number;
7
+ /** Horizontal distribution of the children (main axis). */
8
+ main?: MainAlign;
9
+ /** Vertical alignment of each child (cross axis); defaults to `stretch`. */
10
+ cross?: CrossAlign;
11
+ }
12
+ /**
13
+ * Horizontal stack: the mirror of `ContainerElement` (Column). Children are laid out
14
+ * left-to-right via the shared `FlexLayoutHelper` on the horizontal axis; fixed children
15
+ * take their natural width, `ExpandedElement`/Spacer children split the leftover width by
16
+ * `flex`, and `gap` is inserted between them. The row fills the width it is offered and
17
+ * shrink-wraps its height to the tallest child (unless a height is forced on it).
18
+ *
19
+ * Cross/main alignment is the next foundation slice; today children sit at the top-left
20
+ * (cross start, main start). The row is atomic w.r.t. pagination - it reflows whole if it
21
+ * does not fit (handled by the parent's `packChildren`); synchronized cell splitting is a
22
+ * Grid/Table concern.
23
+ */
24
+ export declare class RowElement extends SizedPDFElement {
25
+ private children;
26
+ private gap;
27
+ private main;
28
+ private cross;
29
+ constructor({ children, gap, main, cross }: RowElementParams);
30
+ calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
31
+ getProps(): {
32
+ x: number;
33
+ y: number;
34
+ width: number | undefined;
35
+ height: number | undefined;
36
+ children: PDFElement[];
37
+ gap: number;
38
+ main: MainAlign;
39
+ cross: CrossAlign;
40
+ };
41
+ }
42
+ export {};
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RowElement = void 0;
4
+ const flex_layout_1 = require("../utils/flex-layout");
5
+ const pdf_element_1 = require("./pdf-element");
6
+ /**
7
+ * Horizontal stack: the mirror of `ContainerElement` (Column). Children are laid out
8
+ * left-to-right via the shared `FlexLayoutHelper` on the horizontal axis; fixed children
9
+ * take their natural width, `ExpandedElement`/Spacer children split the leftover width by
10
+ * `flex`, and `gap` is inserted between them. The row fills the width it is offered and
11
+ * shrink-wraps its height to the tallest child (unless a height is forced on it).
12
+ *
13
+ * Cross/main alignment is the next foundation slice; today children sit at the top-left
14
+ * (cross start, main start). The row is atomic w.r.t. pagination - it reflows whole if it
15
+ * does not fit (handled by the parent's `packChildren`); synchronized cell splitting is a
16
+ * Grid/Table concern.
17
+ */
18
+ class RowElement extends pdf_element_1.SizedPDFElement {
19
+ constructor({ children, gap, main, cross }) {
20
+ super({ x: 0, y: 0 });
21
+ this.children = children;
22
+ this.gap = gap !== null && gap !== void 0 ? gap : 0;
23
+ this.main = main !== null && main !== void 0 ? main : "start";
24
+ this.cross = cross !== null && cross !== void 0 ? cross : "stretch";
25
+ }
26
+ calculateLayout(constraints, offset, ctx) {
27
+ this.x = offset.x;
28
+ this.y = offset.y;
29
+ // Width fills the offered space (flex children split the leftover); height is the
30
+ // tallest child unless the parent bounds it.
31
+ const mainAvail = constraints.hasBoundedWidth ? constraints.maxWidth : Infinity;
32
+ const crossAvail = constraints.hasBoundedHeight ? constraints.maxHeight : Infinity;
33
+ let result = { mainUsed: 0, crossUsed: 0 };
34
+ if (this.children.length > 0) {
35
+ result = flex_layout_1.FlexLayoutHelper.layout(this.children, flex_layout_1.HORIZONTAL_AXIS, mainAvail, crossAvail, this.x, this.y, { gap: this.gap, main: this.main, cross: this.cross }, ctx);
36
+ }
37
+ this.width = constraints.hasBoundedWidth ? constraints.maxWidth : result.mainUsed;
38
+ this.height = constraints.hasBoundedHeight ? constraints.maxHeight : result.crossUsed;
39
+ return { width: this.width, height: this.height };
40
+ }
41
+ getProps() {
42
+ return {
43
+ x: this.x,
44
+ y: this.y,
45
+ width: this.width,
46
+ height: this.height,
47
+ children: this.children,
48
+ gap: this.gap,
49
+ main: this.main,
50
+ cross: this.cross,
51
+ };
52
+ }
53
+ }
54
+ exports.RowElement = RowElement;
@@ -0,0 +1,59 @@
1
+ import { Color } from "../common/color";
2
+ import { FontStyle } from "../utils/pdf-object-manager";
3
+ import { BoxConstraints, Offset, Size } from "../layout/box-constraints";
4
+ import { Fragmentable, FragmentResult } from "../layout/fragmentation";
5
+ import { HorizontalAlignment, LayoutContext, SizedPDFElement } from "./pdf-element";
6
+ export interface TextSegment {
7
+ content: string;
8
+ fontStyle?: FontStyle;
9
+ fontColor?: Color;
10
+ fontFamily?: string;
11
+ fontSize?: number;
12
+ }
13
+ interface TextElementParams {
14
+ id?: string;
15
+ fontSize: number;
16
+ fontFamily?: string;
17
+ fontStyle?: FontStyle;
18
+ content: string | TextSegment[];
19
+ color?: Color;
20
+ textAlignment?: HorizontalAlignment;
21
+ }
22
+ export declare class TextElement extends SizedPDFElement implements Fragmentable {
23
+ private fontSize;
24
+ private fontFamily;
25
+ private fontStyle;
26
+ private color;
27
+ private content;
28
+ private textAlignment;
29
+ constructor({ fontSize, content, fontFamily, fontStyle, color, textAlignment, }: TextElementParams);
30
+ /**
31
+ * Splits the paragraph at line boxes (Slice 1). The lines that fit in `maxHeight` stay;
32
+ * the rest become a remainder `TextElement` re-wrapped on the next page. If not even one
33
+ * line fits, nothing is forced here - the caller (the container) decides whether to move
34
+ * the whole element on for progress. Handles both plain strings and styled segments.
35
+ */
36
+ fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
37
+ private fragmentString;
38
+ private fragmentSegments;
39
+ private cloneWithContent;
40
+ calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
41
+ /** The unwrapped single-line width of the content (used when width is unbounded, e.g. inside a Row).
42
+ * Mirrors the renderer's advance EXACTLY: per-glyph, WITHOUT kerning. `getStringWidth` subtracts
43
+ * kerning, so it under-reserves and the text would wrap inside its own natural-width box - the cause
44
+ * of fixed Row children wrapping despite ample space. */
45
+ private naturalWidth;
46
+ getProps(): {
47
+ x: number;
48
+ y: number;
49
+ width: number | undefined;
50
+ height: number | undefined;
51
+ fontSize: number;
52
+ fontFamily: string;
53
+ fontStyle: FontStyle;
54
+ color: Color;
55
+ content: string | TextSegment[];
56
+ textAlignment: HorizontalAlignment;
57
+ };
58
+ }
59
+ export {};
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TextElement = void 0;
4
+ const color_1 = require("../common/color");
5
+ // Import the renderer DIRECTLY, not via the "../renderer" barrel: the barrel pulls in
6
+ // pdf-renderer (and every element) while this element module is still loading, which under
7
+ // ESM (Vite/vitest, and the future framework bindings) duplicates the element classes and
8
+ // breaks the constructor-keyed RendererRegistry. A direct import keeps the graph acyclic.
9
+ const text_renderer_1 = require("../renderer/text-renderer");
10
+ const pdf_object_manager_1 = require("../utils/pdf-object-manager");
11
+ const line_breaker_1 = require("../text/line-breaker");
12
+ const pdf_element_1 = require("./pdf-element");
13
+ class TextElement extends pdf_element_1.SizedPDFElement {
14
+ constructor({ fontSize, content, fontFamily = "Helvetica", fontStyle = pdf_object_manager_1.FontStyle.Normal, color = new color_1.Color(0, 0, 0), textAlignment = pdf_element_1.HorizontalAlignment.left, }) {
15
+ super({ x: 0, y: 0 });
16
+ this.fontSize = fontSize;
17
+ this.fontFamily = fontFamily;
18
+ this.fontStyle = fontStyle;
19
+ this.color = color;
20
+ this.content = content;
21
+ this.textAlignment = textAlignment;
22
+ }
23
+ /**
24
+ * Splits the paragraph at line boxes (Slice 1). The lines that fit in `maxHeight` stay;
25
+ * the rest become a remainder `TextElement` re-wrapped on the next page. If not even one
26
+ * line fits, nothing is forced here - the caller (the container) decides whether to move
27
+ * the whole element on for progress. Handles both plain strings and styled segments.
28
+ */
29
+ fragment(maxHeight, width, ctx) {
30
+ return typeof this.content === "string"
31
+ ? this.fragmentString(this.content, maxHeight, width, ctx)
32
+ : this.fragmentSegments(this.content, maxHeight, width, ctx);
33
+ }
34
+ // Plain string: every wrapped line is `fontSize` tall (matches calculateTextHeight),
35
+ // so `floor(maxHeight / fontSize)` lines fit.
36
+ fragmentString(content, maxHeight, width, ctx) {
37
+ const lines = (0, line_breaker_1.wrapStringIntoLines)(content, this.fontFamily, this.fontSize, this.fontStyle, width, ctx.metrics);
38
+ const fittedLineCount = Math.floor(maxHeight / this.fontSize);
39
+ if (fittedLineCount <= 0)
40
+ return { fitted: null, remainder: this };
41
+ if (fittedLineCount >= lines.length)
42
+ return { fitted: this, remainder: null };
43
+ return {
44
+ fitted: this.cloneWithContent(lines.slice(0, fittedLineCount).join(" ")),
45
+ remainder: this.cloneWithContent(lines.slice(fittedLineCount).join(" ")),
46
+ };
47
+ }
48
+ // Styled segments: each line's height is its tallest font (maxFontSize), so pack lines
49
+ // by cumulative leading. Rebuild the fitted/remainder halves back into TextSegment[].
50
+ fragmentSegments(content, maxHeight, width, ctx) {
51
+ const lines = (0, line_breaker_1.breakSegmentsIntoLines)(content, {
52
+ fontFamily: this.fontFamily,
53
+ fontSize: this.fontSize,
54
+ fontStyle: this.fontStyle,
55
+ }, width, ctx.metrics);
56
+ let used = 0;
57
+ let fittedLineCount = 0;
58
+ for (const line of lines) {
59
+ if (used + line.maxFontSize > maxHeight)
60
+ break;
61
+ used += line.maxFontSize;
62
+ fittedLineCount++;
63
+ }
64
+ if (fittedLineCount <= 0)
65
+ return { fitted: null, remainder: this };
66
+ if (fittedLineCount >= lines.length)
67
+ return { fitted: this, remainder: null };
68
+ return {
69
+ fitted: this.cloneWithContent((0, line_breaker_1.segmentLinesToSegments)(lines.slice(0, fittedLineCount))),
70
+ remainder: this.cloneWithContent((0, line_breaker_1.segmentLinesToSegments)(lines.slice(fittedLineCount))),
71
+ };
72
+ }
73
+ // A copy carrying the same style but different (already-wrapped) content. Re-wrapping at
74
+ // the same width reproduces exactly those lines (greedy is deterministic).
75
+ cloneWithContent(content) {
76
+ return new TextElement({
77
+ content,
78
+ fontSize: this.fontSize,
79
+ fontFamily: this.fontFamily,
80
+ fontStyle: this.fontStyle,
81
+ color: this.color,
82
+ textAlignment: this.textAlignment,
83
+ });
84
+ }
85
+ calculateLayout(constraints, offset, ctx) {
86
+ var _a;
87
+ this.x = offset.x;
88
+ this.y = offset.y;
89
+ // Bounded width (e.g. inside a Column) wraps to that width; an unbounded width
90
+ // (e.g. inside a Row) means the text takes its natural single-line width and does
91
+ // not wrap. Columns always bound the width, so this leaves their layout untouched.
92
+ this.width = constraints.hasBoundedWidth
93
+ ? constraints.maxWidth
94
+ : this.naturalWidth(ctx.metrics);
95
+ const wrapWidth = (_a = this.width) !== null && _a !== void 0 ? _a : 0;
96
+ this.height = text_renderer_1.TextRenderer.calculateTextHeight(this.content, this.fontSize, this.fontFamily, this.fontStyle, ctx.metrics, wrapWidth);
97
+ // Top-left coordinates (y = top of the text box). The baseline offset and the
98
+ // Y-flip are applied downstream (the line-builder positions baselines, the seam
99
+ // flips to PDF), so the element stays coordinate-system-blind.
100
+ return { width: wrapWidth, height: this.height };
101
+ }
102
+ /** The unwrapped single-line width of the content (used when width is unbounded, e.g. inside a Row).
103
+ * Mirrors the renderer's advance EXACTLY: per-glyph, WITHOUT kerning. `getStringWidth` subtracts
104
+ * kerning, so it under-reserves and the text would wrap inside its own natural-width box - the cause
105
+ * of fixed Row children wrapping despite ample space. */
106
+ naturalWidth(metrics) {
107
+ const advance = (text, family, size, style) => {
108
+ let width = 0;
109
+ for (const ch of text)
110
+ width += metrics.getCharWidth(ch, size, undefined, family, style);
111
+ return width;
112
+ };
113
+ if (typeof this.content === "string") {
114
+ return advance(this.content, this.fontFamily, this.fontSize, this.fontStyle);
115
+ }
116
+ return this.content.reduce((sum, seg) => {
117
+ var _a, _b, _c;
118
+ return sum +
119
+ advance(seg.content, (_a = seg.fontFamily) !== null && _a !== void 0 ? _a : this.fontFamily, (_b = seg.fontSize) !== null && _b !== void 0 ? _b : this.fontSize, (_c = seg.fontStyle) !== null && _c !== void 0 ? _c : this.fontStyle);
120
+ }, 0);
121
+ }
122
+ getProps() {
123
+ return {
124
+ x: this.x,
125
+ y: this.y,
126
+ width: this.width,
127
+ height: this.height,
128
+ fontSize: this.fontSize,
129
+ fontFamily: this.fontFamily,
130
+ fontStyle: this.fontStyle,
131
+ color: this.color,
132
+ content: this.content,
133
+ textAlignment: this.textAlignment,
134
+ };
135
+ }
136
+ }
137
+ exports.TextElement = TextElement;
@@ -0,0 +1,3 @@
1
+ export * from "./elements";
2
+ export * from "./renderer";
3
+ export * from "./api";
package/dist/index.js ADDED
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./elements"), exports);
18
+ __exportStar(require("./renderer"), exports);
19
+ __exportStar(require("./api"), exports);
20
+ // export * from './validator';
21
+ // export * from './utils';