@jasy/pdf 1.0.0-alpha.2 → 1.0.0-alpha.3

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 +3 -3
  2. package/dist/api/args.d.ts +1 -1
  3. package/dist/api/args.js +2 -5
  4. package/dist/api/color.d.ts +4 -4
  5. package/dist/api/color.js +11 -17
  6. package/dist/api/content.d.ts +8 -8
  7. package/dist/api/content.js +23 -24
  8. package/dist/api/descriptor.d.ts +2 -2
  9. package/dist/api/descriptor.js +75 -31
  10. package/dist/api/index.d.ts +8 -8
  11. package/dist/api/index.js +8 -24
  12. package/dist/api/insets.js +4 -8
  13. package/dist/api/layout.d.ts +18 -16
  14. package/dist/api/layout.js +41 -52
  15. package/dist/api/structure.d.ts +60 -13
  16. package/dist/api/structure.js +132 -88
  17. package/dist/api/table.d.ts +5 -5
  18. package/dist/api/table.js +28 -24
  19. package/dist/api/text.d.ts +27 -2
  20. package/dist/api/text.js +45 -27
  21. package/dist/assets/font-data.d.ts +2 -0
  22. package/dist/assets/font-data.js +6 -0
  23. package/dist/assets/font-data.ts +7 -0
  24. package/dist/common/color.js +1 -5
  25. package/dist/constants/page-sizes.js +3 -6
  26. package/dist/constants/pdf-parts.js +1 -4
  27. package/dist/elements/container-element.d.ts +4 -4
  28. package/dist/elements/container-element.js +9 -13
  29. package/dist/elements/image-element.d.ts +18 -2
  30. package/dist/elements/image-element.js +81 -105
  31. package/dist/elements/index.d.ts +12 -11
  32. package/dist/elements/index.js +12 -29
  33. package/dist/elements/layout/default-text-style-element.d.ts +30 -0
  34. package/dist/elements/layout/default-text-style-element.js +47 -0
  35. package/dist/elements/layout/deferred-element.d.ts +3 -3
  36. package/dist/elements/layout/deferred-element.js +4 -8
  37. package/dist/elements/layout/expanded-element.d.ts +3 -3
  38. package/dist/elements/layout/expanded-element.js +10 -14
  39. package/dist/elements/layout/padding-element.d.ts +3 -3
  40. package/dist/elements/layout/padding-element.js +9 -14
  41. package/dist/elements/layout/positioned-element.d.ts +17 -4
  42. package/dist/elements/layout/positioned-element.js +29 -25
  43. package/dist/elements/layout/repeating-header-element.d.ts +3 -3
  44. package/dist/elements/layout/repeating-header-element.js +8 -12
  45. package/dist/elements/layout/sized-container-element.d.ts +2 -2
  46. package/dist/elements/layout/sized-container-element.js +6 -11
  47. package/dist/elements/line-element.d.ts +3 -3
  48. package/dist/elements/line-element.js +5 -10
  49. package/dist/elements/page-element.d.ts +8 -6
  50. package/dist/elements/page-element.js +20 -23
  51. package/dist/elements/pdf-document-element.d.ts +10 -4
  52. package/dist/elements/pdf-document-element.js +11 -10
  53. package/dist/elements/pdf-element.d.ts +12 -3
  54. package/dist/elements/pdf-element.js +10 -19
  55. package/dist/elements/rectangle-element.d.ts +5 -5
  56. package/dist/elements/rectangle-element.js +19 -25
  57. package/dist/elements/row-element.d.ts +3 -3
  58. package/dist/elements/row-element.js +7 -11
  59. package/dist/elements/text-element.d.ts +37 -11
  60. package/dist/elements/text-element.js +64 -39
  61. package/dist/index.d.ts +3 -3
  62. package/dist/index.js +3 -19
  63. package/dist/ir/display-list.d.ts +4 -2
  64. package/dist/ir/display-list.js +1 -2
  65. package/dist/layout/box-constraints.js +2 -6
  66. package/dist/layout/fragmentation.d.ts +8 -1
  67. package/dist/layout/fragmentation.js +22 -10
  68. package/dist/platform/browser-fs.d.ts +2 -0
  69. package/dist/platform/browser-fs.js +9 -0
  70. package/dist/platform/browser-image.d.ts +5 -0
  71. package/dist/platform/browser-image.js +13 -0
  72. package/dist/platform/node-fs.d.ts +2 -0
  73. package/dist/platform/node-fs.js +10 -0
  74. package/dist/platform/node-image.d.ts +5 -0
  75. package/dist/platform/node-image.js +9 -0
  76. package/dist/renderer/container-renderer.d.ts +3 -3
  77. package/dist/renderer/container-renderer.js +12 -27
  78. package/dist/renderer/default-text-style-renderer.d.ts +6 -0
  79. package/dist/renderer/default-text-style-renderer.js +10 -0
  80. package/dist/renderer/deferred-renderer.d.ts +3 -3
  81. package/dist/renderer/deferred-renderer.js +8 -23
  82. package/dist/renderer/expanded-renderer.d.ts +3 -3
  83. package/dist/renderer/expanded-renderer.js +6 -21
  84. package/dist/renderer/image-renderer.d.ts +3 -3
  85. package/dist/renderer/image-renderer.js +77 -75
  86. package/dist/renderer/index.d.ts +10 -10
  87. package/dist/renderer/index.js +10 -26
  88. package/dist/renderer/line-renderer.d.ts +3 -3
  89. package/dist/renderer/line-renderer.js +13 -28
  90. package/dist/renderer/padding-renderer.d.ts +3 -3
  91. package/dist/renderer/padding-renderer.js +6 -21
  92. package/dist/renderer/page-renderer.d.ts +2 -2
  93. package/dist/renderer/page-renderer.js +61 -77
  94. package/dist/renderer/pdf-backend.d.ts +2 -2
  95. package/dist/renderer/pdf-backend.js +21 -19
  96. package/dist/renderer/pdf-config.js +4 -7
  97. package/dist/renderer/pdf-document-class.d.ts +5 -5
  98. package/dist/renderer/pdf-document-class.js +24 -41
  99. package/dist/renderer/pdf-document-renderer.d.ts +3 -3
  100. package/dist/renderer/pdf-document-renderer.js +71 -85
  101. package/dist/renderer/pdf-renderer.d.ts +2 -2
  102. package/dist/renderer/pdf-renderer.js +83 -93
  103. package/dist/renderer/positioned-renderer.d.ts +3 -3
  104. package/dist/renderer/positioned-renderer.js +8 -23
  105. package/dist/renderer/rectangle-renderer.d.ts +3 -3
  106. package/dist/renderer/rectangle-renderer.js +45 -52
  107. package/dist/renderer/repeating-header-renderer.d.ts +3 -3
  108. package/dist/renderer/repeating-header-renderer.js +11 -26
  109. package/dist/renderer/row-renderer.d.ts +3 -3
  110. package/dist/renderer/row-renderer.js +12 -27
  111. package/dist/renderer/text-renderer.d.ts +6 -5
  112. package/dist/renderer/text-renderer.js +33 -42
  113. package/dist/text/line-breaker.d.ts +8 -5
  114. package/dist/text/line-breaker.js +67 -16
  115. package/dist/text/text-style.d.ts +25 -0
  116. package/dist/text/text-style.js +29 -0
  117. package/dist/utils/afm-parser.js +3 -13
  118. package/dist/utils/bytes.d.ts +24 -0
  119. package/dist/utils/bytes.js +76 -0
  120. package/dist/utils/flex-layout.d.ts +2 -2
  121. package/dist/utils/flex-layout.js +15 -20
  122. package/dist/utils/font-metrics.d.ts +1 -1
  123. package/dist/utils/font-metrics.js +1 -2
  124. package/dist/utils/font-path.js +3 -6
  125. package/dist/utils/image-helper.d.ts +6 -5
  126. package/dist/utils/image-helper.js +101 -111
  127. package/dist/utils/md5.d.ts +4 -0
  128. package/dist/utils/md5.js +80 -0
  129. package/dist/utils/pdf-object-manager.d.ts +10 -6
  130. package/dist/utils/pdf-object-manager.js +89 -94
  131. package/dist/utils/renderer-registry.js +1 -5
  132. package/dist/utils/ttf-parser.d.ts +2 -2
  133. package/dist/utils/ttf-parser.js +32 -36
  134. package/dist/utils/ttf-subsetter.d.ts +1 -1
  135. package/dist/utils/ttf-subsetter.js +40 -42
  136. package/dist/utils/utf8-to-windows1252-encoder.js +1 -4
  137. package/dist/validators/element-validator.d.ts +2 -2
  138. package/dist/validators/element-validator.js +9 -13
  139. package/package.json +14 -2
@@ -1,23 +1,20 @@
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, relative, overflow, }) {
1
+ import { Color } from "../common/color.js";
2
+ import { BoxConstraints } from "../layout/box-constraints.js";
3
+ import { packChildren } from "../layout/fragmentation.js";
4
+ import { SizedPDFElement, } from "./pdf-element.js";
5
+ export class RectangleElement extends SizedPDFElement {
6
+ constructor({ children = [], color = new Color(0, 0, 0), backgroundColor, borderWidth, width, height, radius, sideBorders, relative, overflow, }) {
10
7
  super({ x: 0, y: 0, width, height });
11
8
  this.children = [];
12
9
  this.children = children;
13
10
  this.color = color;
14
11
  this.backgroundColor = backgroundColor;
15
12
  // `?? 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;
13
+ this.borderWidth = borderWidth ?? 1;
14
+ this.radius = radius ?? 0;
18
15
  this.sideBorders = sideBorders;
19
- this.relative = relative !== null && relative !== void 0 ? relative : false;
20
- this.overflow = overflow !== null && overflow !== void 0 ? overflow : "visible";
16
+ this.relative = relative ?? false;
17
+ this.overflow = overflow ?? "visible";
21
18
  this.sizeMemory = { x: 0, y: 0, width, height };
22
19
  }
23
20
  /**
@@ -29,17 +26,16 @@ class RectangleElement extends pdf_element_1.SizedPDFElement {
29
26
  * box is sized by what it actually holds on each page.
30
27
  */
31
28
  fragment(maxHeight, width, ctx) {
32
- var _a;
33
- const boxWidth = (_a = this.sizeMemory.width) !== null && _a !== void 0 ? _a : width;
29
+ const boxWidth = this.sizeMemory.width ?? width;
34
30
  const innerWidth = Math.max(0, boxWidth - 2 * this.borderWidth);
35
31
  // Border-box: the content is inset by the border on top and bottom, so a fragment
36
32
  // holding `c` of content is `c + 2*border` tall. (Derived, not a fudge factor.)
37
33
  const innerMaxHeight = maxHeight - 2 * this.borderWidth;
38
- const { fitted, remainder } = (0, fragmentation_1.packChildren)(this.children, innerMaxHeight, innerWidth, ctx);
34
+ const { fitted, remainder } = packChildren(this.children, innerMaxHeight, innerWidth, ctx);
39
35
  if (remainder.length === 0)
40
36
  return { fitted: this, remainder: null };
41
37
  const contentHeight = (kids) => kids.reduce((sum, child) => sum +
42
- child.calculateLayout(box_constraints_1.BoxConstraints.loose(innerWidth, Infinity), { x: 0, y: 0 }, ctx)
38
+ child.calculateLayout(BoxConstraints.loose(innerWidth, Infinity), { x: 0, y: 0 }, ctx)
43
39
  .height, 0);
44
40
  return {
45
41
  fitted: this.cloneWithChildren(fitted, contentHeight(fitted) + 2 * this.borderWidth),
@@ -63,7 +59,6 @@ class RectangleElement extends pdf_element_1.SizedPDFElement {
63
59
  });
64
60
  }
65
61
  calculateLayout(constraints, offset, ctx) {
66
- var _a, _b, _c, _d, _e;
67
62
  // Width: an explicit size wins (clamped), else fill the offered box. (Without this a
68
63
  // fixed box would balloon to the parent's size.)
69
64
  this.width =
@@ -93,17 +88,17 @@ class RectangleElement extends pdf_element_1.SizedPDFElement {
93
88
  const frame = this.relative
94
89
  ? { origin: { x: 0, y: 0 }, size: { width: 0, height: 0 }, place: [] }
95
90
  : undefined;
96
- const childCtx = frame ? Object.assign(Object.assign({}, ctx), { frame }) : ctx;
91
+ const childCtx = frame ? { ...ctx, frame } : ctx;
97
92
  // Lay out children stacked inside the border (inset by the border width). Width is
98
93
  // finalized here; height is left unbounded so each child sizes to its own content.
99
94
  const innerWidth = shrinkWrapWidth
100
95
  ? Infinity
101
- : Math.max(0, ((_a = this.width) !== null && _a !== void 0 ? _a : 0) - 2 * this.borderWidth);
96
+ : Math.max(0, (this.width ?? 0) - 2 * this.borderWidth);
102
97
  let contentWidth = 0;
103
98
  let contentHeight = 0;
104
99
  let yCursor = this.y + this.borderWidth;
105
100
  for (const child of this.children) {
106
- const childSize = child.calculateLayout(box_constraints_1.BoxConstraints.loose(innerWidth, Infinity), { x: this.x + this.borderWidth, y: yCursor }, childCtx);
101
+ const childSize = child.calculateLayout(BoxConstraints.loose(innerWidth, Infinity), { x: this.x + this.borderWidth, y: yCursor }, childCtx);
107
102
  yCursor += childSize.height;
108
103
  contentHeight += childSize.height;
109
104
  contentWidth = Math.max(contentWidth, childSize.width);
@@ -117,7 +112,7 @@ class RectangleElement extends pdf_element_1.SizedPDFElement {
117
112
  // The box is sized now: place any out-of-flow `Positioned` descendants against its box.
118
113
  if (frame) {
119
114
  frame.origin = { x: this.x, y: this.y };
120
- frame.size = { width: (_b = this.width) !== null && _b !== void 0 ? _b : 0, height: (_c = this.height) !== null && _c !== void 0 ? _c : 0 };
115
+ frame.size = { width: this.width ?? 0, height: this.height ?? 0 };
121
116
  for (const place of frame.place)
122
117
  place(frame, ctx);
123
118
  }
@@ -126,8 +121,8 @@ class RectangleElement extends pdf_element_1.SizedPDFElement {
126
121
  // border added on (that asymmetric "+border" was the source of the magic-3 fudge in
127
122
  // fragment). Top-left coordinates; the Y-flip happens at the IR -> backend seam.
128
123
  return {
129
- width: (_d = this.width) !== null && _d !== void 0 ? _d : 0,
130
- height: (_e = this.height) !== null && _e !== void 0 ? _e : 0,
124
+ width: this.width ?? 0,
125
+ height: this.height ?? 0,
131
126
  };
132
127
  }
133
128
  getProps() {
@@ -146,4 +141,3 @@ class RectangleElement extends pdf_element_1.SizedPDFElement {
146
141
  };
147
142
  }
148
143
  }
149
- exports.RectangleElement = RectangleElement;
@@ -1,6 +1,6 @@
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";
1
+ import { BoxConstraints, Offset, Size } from "../layout/box-constraints.js";
2
+ import { MainAlign, CrossAlign } from "../utils/flex-layout.js";
3
+ import { LayoutContext, PDFElement, SizedPDFElement, WithChildren } from "./pdf-element.js";
4
4
  interface RowElementParams extends WithChildren {
5
5
  /** Space inserted between children, in points. */
6
6
  gap?: number;
@@ -1,8 +1,5 @@
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");
1
+ import { FlexLayoutHelper, HORIZONTAL_AXIS } from "../utils/flex-layout.js";
2
+ import { SizedPDFElement } from "./pdf-element.js";
6
3
  /**
7
4
  * Horizontal stack: the mirror of `ContainerElement` (Column). Children are laid out
8
5
  * left-to-right via the shared `FlexLayoutHelper` on the horizontal axis; fixed children
@@ -15,13 +12,13 @@ const pdf_element_1 = require("./pdf-element");
15
12
  * does not fit (handled by the parent's `packChildren`); synchronized cell splitting is a
16
13
  * Grid/Table concern.
17
14
  */
18
- class RowElement extends pdf_element_1.SizedPDFElement {
15
+ export class RowElement extends SizedPDFElement {
19
16
  constructor({ children, gap, main, cross }) {
20
17
  super({ x: 0, y: 0 });
21
18
  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";
19
+ this.gap = gap ?? 0;
20
+ this.main = main ?? "start";
21
+ this.cross = cross ?? "stretch";
25
22
  }
26
23
  calculateLayout(constraints, offset, ctx) {
27
24
  this.x = offset.x;
@@ -32,7 +29,7 @@ class RowElement extends pdf_element_1.SizedPDFElement {
32
29
  const crossAvail = constraints.hasBoundedHeight ? constraints.maxHeight : Infinity;
33
30
  let result = { mainUsed: 0, crossUsed: 0 };
34
31
  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);
32
+ result = FlexLayoutHelper.layout(this.children, HORIZONTAL_AXIS, mainAvail, crossAvail, this.x, this.y, { gap: this.gap, main: this.main, cross: this.cross }, ctx);
36
33
  }
37
34
  this.width = constraints.hasBoundedWidth ? constraints.maxWidth : result.mainUsed;
38
35
  this.height = constraints.hasBoundedHeight ? constraints.maxHeight : result.crossUsed;
@@ -51,4 +48,3 @@ class RowElement extends pdf_element_1.SizedPDFElement {
51
48
  };
52
49
  }
53
50
  }
54
- exports.RowElement = RowElement;
@@ -1,8 +1,9 @@
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";
1
+ import { Color } from "../common/color.js";
2
+ import { FontStyle } from "../utils/pdf-object-manager.js";
3
+ import { BoxConstraints, Offset, Size } from "../layout/box-constraints.js";
4
+ import { Fragmentable, FragmentResult } from "../layout/fragmentation.js";
5
+ import { TextOverflow } from "../text/line-breaker.js";
6
+ import { HorizontalAlignment, LayoutContext, SizedPDFElement } from "./pdf-element.js";
6
7
  export interface TextSegment {
7
8
  content: string;
8
9
  fontStyle?: FontStyle;
@@ -12,21 +13,39 @@ export interface TextSegment {
12
13
  }
13
14
  interface TextElementParams {
14
15
  id?: string;
15
- fontSize: number;
16
+ /** Unset (undefined) inherits the cascaded size; see ResolvedTextStyle. */
17
+ fontSize?: number;
16
18
  fontFamily?: string;
17
19
  fontStyle?: FontStyle;
18
20
  content: string | TextSegment[];
19
21
  color?: Color;
20
22
  textAlignment?: HorizontalAlignment;
23
+ /** Cap the wrapped lines (default: unlimited / open-end). */
24
+ maxLines?: number;
25
+ /** What to do past `maxLines`: `"clip"` (default) drops them, `"ellipsis"` ends with "…". */
26
+ overflow?: TextOverflow;
27
+ /** Line-height multiplier: each line is `fontSize * lineHeight` tall (default `1`). */
28
+ lineHeight?: number;
21
29
  }
22
30
  export declare class TextElement extends SizedPDFElement implements Fragmentable {
31
+ private readonly rawFontSize?;
32
+ private readonly rawFontFamily?;
33
+ private readonly rawFontStyle?;
34
+ private readonly rawColor?;
35
+ private readonly rawTextAlignment?;
36
+ private readonly rawLineHeight?;
23
37
  private fontSize;
24
38
  private fontFamily;
25
39
  private fontStyle;
26
40
  private color;
27
- private content;
28
41
  private textAlignment;
29
- constructor({ fontSize, content, fontFamily, fontStyle, color, textAlignment, }: TextElementParams);
42
+ private lineHeight;
43
+ private content;
44
+ private maxLines?;
45
+ private overflow;
46
+ constructor({ fontSize, content, fontFamily, fontStyle, color, textAlignment, maxLines, overflow, lineHeight, }: TextElementParams);
47
+ private resolveStyle;
48
+ private applyStyle;
30
49
  /**
31
50
  * Splits the paragraph at line boxes (Slice 1). The lines that fit in `maxHeight` stay;
32
51
  * the rest become a remainder `TextElement` re-wrapped on the next page. If not even one
@@ -39,9 +58,13 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
39
58
  private cloneWithContent;
40
59
  calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
41
60
  /** 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. */
61
+ * Must match the LINE-BREAKER's one-line measure EXACTLY - not just algebraically but BIT-for-bit,
62
+ * so a text laid out at this width never re-wraps inside its own natural-width box. The breaker
63
+ * accumulates `currentWidth += wordWidth + spaceWidth`, grouping the word and its trailing space
64
+ * into one term; we must group the same way. Adding word and space as two separate steps is
65
+ * algebraically equal but, because floating-point addition is not associative, drifts by a sub-ULP
66
+ * - enough to tip a borderline string (e.g. "20 Jun 2026", wider than "04 Jul 2026" only because
67
+ * 'n' beats 'l') one bit over its own width, dropping the last word onto a second line. */
45
68
  private naturalWidth;
46
69
  getProps(): {
47
70
  x: number;
@@ -54,6 +77,9 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
54
77
  color: Color;
55
78
  content: string | TextSegment[];
56
79
  textAlignment: HorizontalAlignment;
80
+ maxLines: number | undefined;
81
+ overflow: TextOverflow;
82
+ lineHeight: number;
57
83
  };
58
84
  }
59
85
  export {};
@@ -1,24 +1,36 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TextElement = void 0;
4
- const color_1 = require("../common/color");
5
1
  // Import the renderer DIRECTLY, not via the "../renderer" barrel: the barrel pulls in
6
2
  // pdf-renderer (and every element) while this element module is still loading, which under
7
3
  // ESM (Vite/vitest, and the future framework bindings) duplicates the element classes and
8
4
  // 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, }) {
5
+ import { TextRenderer } from "../renderer/text-renderer.js";
6
+ import { DEFAULT_TEXT_STYLE } from "../text/text-style.js";
7
+ import { wrapStringIntoLines, breakSegmentsIntoLines, segmentLinesToSegments, } from "../text/line-breaker.js";
8
+ import { SizedPDFElement } from "./pdf-element.js";
9
+ export class TextElement extends SizedPDFElement {
10
+ constructor({ fontSize, content, fontFamily, fontStyle, color, textAlignment, maxLines, overflow = "clip", lineHeight, }) {
15
11
  super({ x: 0, y: 0 });
16
- this.fontSize = fontSize;
17
- this.fontFamily = fontFamily;
18
- this.fontStyle = fontStyle;
19
- this.color = color;
12
+ this.rawFontSize = fontSize;
13
+ this.rawFontFamily = fontFamily;
14
+ this.rawFontStyle = fontStyle;
15
+ this.rawColor = color;
16
+ this.rawTextAlignment = textAlignment;
17
+ this.rawLineHeight = lineHeight;
20
18
  this.content = content;
21
- this.textAlignment = textAlignment;
19
+ this.maxLines = maxLines;
20
+ this.overflow = overflow;
21
+ this.applyStyle(DEFAULT_TEXT_STYLE);
22
+ }
23
+ // Resolve the author-set values against the cascade: explicit > inherited (ctx) > built-in default.
24
+ resolveStyle(ctx) {
25
+ this.applyStyle(ctx.textStyle ?? DEFAULT_TEXT_STYLE);
26
+ }
27
+ applyStyle(ts) {
28
+ this.fontSize = this.rawFontSize ?? ts.fontSize;
29
+ this.fontFamily = this.rawFontFamily ?? ts.fontFamily;
30
+ this.fontStyle = this.rawFontStyle ?? ts.fontStyle;
31
+ this.color = this.rawColor ?? ts.color;
32
+ this.textAlignment = this.rawTextAlignment ?? ts.textAlignment;
33
+ this.lineHeight = this.rawLineHeight ?? ts.lineHeight;
22
34
  }
23
35
  /**
24
36
  * Splits the paragraph at line boxes (Slice 1). The lines that fit in `maxHeight` stay;
@@ -27,6 +39,7 @@ class TextElement extends pdf_element_1.SizedPDFElement {
27
39
  * the whole element on for progress. Handles both plain strings and styled segments.
28
40
  */
29
41
  fragment(maxHeight, width, ctx) {
42
+ this.resolveStyle(ctx);
30
43
  return typeof this.content === "string"
31
44
  ? this.fragmentString(this.content, maxHeight, width, ctx)
32
45
  : this.fragmentSegments(this.content, maxHeight, width, ctx);
@@ -34,8 +47,8 @@ class TextElement extends pdf_element_1.SizedPDFElement {
34
47
  // Plain string: every wrapped line is `fontSize` tall (matches calculateTextHeight),
35
48
  // so `floor(maxHeight / fontSize)` lines fit.
36
49
  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);
50
+ const lines = wrapStringIntoLines(content, this.fontFamily, this.fontSize, this.fontStyle, width, ctx.metrics, this.maxLines, this.overflow);
51
+ const fittedLineCount = Math.floor(maxHeight / (this.fontSize * this.lineHeight));
39
52
  if (fittedLineCount <= 0)
40
53
  return { fitted: null, remainder: this };
41
54
  if (fittedLineCount >= lines.length)
@@ -48,17 +61,18 @@ class TextElement extends pdf_element_1.SizedPDFElement {
48
61
  // Styled segments: each line's height is its tallest font (maxFontSize), so pack lines
49
62
  // by cumulative leading. Rebuild the fitted/remainder halves back into TextSegment[].
50
63
  fragmentSegments(content, maxHeight, width, ctx) {
51
- const lines = (0, line_breaker_1.breakSegmentsIntoLines)(content, {
64
+ const lines = breakSegmentsIntoLines(content, {
52
65
  fontFamily: this.fontFamily,
53
66
  fontSize: this.fontSize,
54
67
  fontStyle: this.fontStyle,
55
- }, width, ctx.metrics);
68
+ }, width, ctx.metrics, this.maxLines, this.overflow);
56
69
  let used = 0;
57
70
  let fittedLineCount = 0;
58
71
  for (const line of lines) {
59
- if (used + line.maxFontSize > maxHeight)
72
+ const lineBox = line.maxFontSize * this.lineHeight;
73
+ if (used + lineBox > maxHeight)
60
74
  break;
61
- used += line.maxFontSize;
75
+ used += lineBox;
62
76
  fittedLineCount++;
63
77
  }
64
78
  if (fittedLineCount <= 0)
@@ -66,8 +80,8 @@ class TextElement extends pdf_element_1.SizedPDFElement {
66
80
  if (fittedLineCount >= lines.length)
67
81
  return { fitted: this, remainder: null };
68
82
  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))),
83
+ fitted: this.cloneWithContent(segmentLinesToSegments(lines.slice(0, fittedLineCount))),
84
+ remainder: this.cloneWithContent(segmentLinesToSegments(lines.slice(fittedLineCount))),
71
85
  };
72
86
  }
73
87
  // A copy carrying the same style but different (already-wrapped) content. Re-wrapping at
@@ -80,10 +94,13 @@ class TextElement extends pdf_element_1.SizedPDFElement {
80
94
  fontStyle: this.fontStyle,
81
95
  color: this.color,
82
96
  textAlignment: this.textAlignment,
97
+ maxLines: this.maxLines,
98
+ overflow: this.overflow,
99
+ lineHeight: this.lineHeight,
83
100
  });
84
101
  }
85
102
  calculateLayout(constraints, offset, ctx) {
86
- var _a;
103
+ this.resolveStyle(ctx);
87
104
  this.x = offset.x;
88
105
  this.y = offset.y;
89
106
  // Bounded width (e.g. inside a Column) wraps to that width; an unbounded width
@@ -92,32 +109,38 @@ class TextElement extends pdf_element_1.SizedPDFElement {
92
109
  this.width = constraints.hasBoundedWidth
93
110
  ? constraints.maxWidth
94
111
  : 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);
112
+ const wrapWidth = this.width ?? 0;
113
+ this.height = TextRenderer.calculateTextHeight(this.content, this.fontSize, this.fontFamily, this.fontStyle, ctx.metrics, wrapWidth, this.maxLines, this.overflow, this.lineHeight);
97
114
  // Top-left coordinates (y = top of the text box). The baseline offset and the
98
115
  // Y-flip are applied downstream (the line-builder positions baselines, the seam
99
116
  // flips to PDF), so the element stays coordinate-system-blind.
100
117
  return { width: wrapWidth, height: this.height };
101
118
  }
102
119
  /** 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. */
120
+ * Must match the LINE-BREAKER's one-line measure EXACTLY - not just algebraically but BIT-for-bit,
121
+ * so a text laid out at this width never re-wraps inside its own natural-width box. The breaker
122
+ * accumulates `currentWidth += wordWidth + spaceWidth`, grouping the word and its trailing space
123
+ * into one term; we must group the same way. Adding word and space as two separate steps is
124
+ * algebraically equal but, because floating-point addition is not associative, drifts by a sub-ULP
125
+ * - enough to tip a borderline string (e.g. "20 Jun 2026", wider than "04 Jul 2026" only because
126
+ * 'n' beats 'l') one bit over its own width, dropping the last word onto a second line. */
106
127
  naturalWidth(metrics) {
107
- const advance = (text, family, size, style) => {
128
+ const oneLine = (text, family, size, style) => {
129
+ const words = text.split(" ");
130
+ const space = metrics.getCharWidth(" ", size, undefined, family, style);
108
131
  let width = 0;
109
- for (const ch of text)
110
- width += metrics.getCharWidth(ch, size, undefined, family, style);
132
+ words.forEach((word, i) => {
133
+ const w = metrics.getStringWidth(word, family, size, style);
134
+ // Group (word + space) as one term, exactly like the breaker - see the note above.
135
+ width += i < words.length - 1 ? w + space : w;
136
+ });
111
137
  return width;
112
138
  };
113
139
  if (typeof this.content === "string") {
114
- return advance(this.content, this.fontFamily, this.fontSize, this.fontStyle);
140
+ return oneLine(this.content, this.fontFamily, this.fontSize, this.fontStyle);
115
141
  }
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);
142
+ return this.content.reduce((sum, seg) => sum +
143
+ oneLine(seg.content, seg.fontFamily ?? this.fontFamily, seg.fontSize ?? this.fontSize, seg.fontStyle ?? this.fontStyle), 0);
121
144
  }
122
145
  getProps() {
123
146
  return {
@@ -131,7 +154,9 @@ class TextElement extends pdf_element_1.SizedPDFElement {
131
154
  color: this.color,
132
155
  content: this.content,
133
156
  textAlignment: this.textAlignment,
157
+ maxLines: this.maxLines,
158
+ overflow: this.overflow,
159
+ lineHeight: this.lineHeight,
134
160
  };
135
161
  }
136
162
  }
137
- exports.TextElement = TextElement;
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- export * from "./elements";
2
- export * from "./renderer";
3
- export * from "./api";
1
+ export * from "./elements/index.js";
2
+ export * from "./renderer/index.js";
3
+ export * from "./api/index.js";
package/dist/index.js CHANGED
@@ -1,21 +1,5 @@
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);
1
+ export * from "./elements/index.js";
2
+ export * from "./renderer/index.js";
3
+ export * from "./api/index.js";
20
4
  // export * from './validator';
21
5
  // export * from './utils';
@@ -1,5 +1,5 @@
1
- import { Color } from "../common/color";
2
- import { FontStyle } from "../utils/pdf-object-manager";
1
+ import { Color } from "../common/color.js";
2
+ import { FontStyle } from "../utils/pdf-object-manager.js";
3
3
  /**
4
4
  * Display list - the seam between layout and the PDF backend.
5
5
  *
@@ -60,6 +60,8 @@ export interface Image {
60
60
  intrinsicHeight: number;
61
61
  data: string;
62
62
  imageType: string;
63
+ /** Flate-compressed DeviceGray alpha channel for a transparent PNG, embedded as the XObject's /SMask. */
64
+ smask?: string;
63
65
  /** cover/contain fits clip the placement to the element's original frame. */
64
66
  clip?: {
65
67
  x: number;
@@ -1,2 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
1
+ export {};
@@ -1,6 +1,3 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.BoxConstraints = void 0;
4
1
  /**
5
2
  * The size constraints handed DOWN the tree during layout (Flutter's BoxConstraints
6
3
  * model). Pure geometry: a min/max range per axis, no position. An element receives a
@@ -15,7 +12,7 @@ exports.BoxConstraints = void 0;
15
12
  * remaining vertical space becomes a `maxHeight`, and "does this fit?" is a constraint
16
13
  * check, not a special case.
17
14
  */
18
- class BoxConstraints {
15
+ export class BoxConstraints {
19
16
  constructor(minWidth = 0, maxWidth = Infinity, minHeight = 0, maxHeight = Infinity) {
20
17
  this.minWidth = minWidth;
21
18
  this.maxWidth = maxWidth;
@@ -28,7 +25,7 @@ class BoxConstraints {
28
25
  }
29
26
  /** Tight only on the axes given; the others stay unbounded (0..Infinity). */
30
27
  static tightFor({ width, height }) {
31
- return new BoxConstraints(width !== null && width !== void 0 ? width : 0, width !== null && width !== void 0 ? width : Infinity, height !== null && height !== void 0 ? height : 0, height !== null && height !== void 0 ? height : Infinity);
28
+ return new BoxConstraints(width ?? 0, width ?? Infinity, height ?? 0, height ?? Infinity);
32
29
  }
33
30
  /** Caps each axis at `max` but allows anything down to zero (shrink-wrap). */
34
31
  static loose(maxWidth, maxHeight) {
@@ -70,4 +67,3 @@ class BoxConstraints {
70
67
  return new BoxConstraints(Math.max(parent.minWidth, Math.min(this.minWidth, parent.maxWidth)), Math.max(parent.minWidth, Math.min(this.maxWidth, parent.maxWidth)), Math.max(parent.minHeight, Math.min(this.minHeight, parent.maxHeight)), Math.max(parent.minHeight, Math.min(this.maxHeight, parent.maxHeight)));
71
68
  }
72
69
  }
73
- exports.BoxConstraints = BoxConstraints;
@@ -1,4 +1,4 @@
1
- import { LayoutContext, PDFElement } from "../elements/pdf-element";
1
+ import { LayoutContext, PDFElement } from "../elements/pdf-element.js";
2
2
  /**
3
3
  * The result of splitting an element against a fragmentation region (a page, later a
4
4
  * column). `fitted` is the part that fits in the region; `remainder` describes what did
@@ -22,6 +22,13 @@ export interface Fragmentable {
22
22
  fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
23
23
  }
24
24
  export declare function isFragmentable(element: PDFElement): element is PDFElement & Fragmentable;
25
+ /**
26
+ * What to do when an element is taller than the whole page region and cannot be broken. We always
27
+ * still place it (clipped) so pagination terminates - this only controls how loud we are about it:
28
+ * `"error"` throws (the default; an unbreakable overflow is almost always a layout bug), `"warn"`
29
+ * logs and clips, `"ignore"` clips silently.
30
+ */
31
+ export type OverflowPolicy = "error" | "warn" | "ignore";
25
32
  /**
26
33
  * Packs a vertical stack of children into `maxHeight`, in order. Children are measured
27
34
  * against `width` (unbounded height) and added until one would overflow; that straddling
@@ -1,12 +1,19 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.isFragmentable = isFragmentable;
4
- exports.packChildren = packChildren;
5
- const box_constraints_1 = require("./box-constraints");
6
- function isFragmentable(element) {
1
+ import { BoxConstraints } from "./box-constraints.js";
2
+ export function isFragmentable(element) {
7
3
  return ("fragment" in element &&
8
4
  typeof element.fragment === "function");
9
5
  }
6
+ function reportOverflow(child, childHeight, maxHeight, policy) {
7
+ if (policy === "ignore")
8
+ return;
9
+ const name = child.constructor.name.replace(/Element$/, "");
10
+ const detail = `${name} is ${Math.round(childHeight)}pt tall but the page region is only ` +
11
+ `${Math.round(maxHeight)}pt and it cannot be broken - reduce its size, give it a bounded ` +
12
+ `height, or let it split across pages.`;
13
+ if (policy === "error")
14
+ throw new Error(`Layout overflow: ${detail}`);
15
+ console.warn(`Layout overflow (clipped): ${detail}`);
16
+ }
10
17
  /**
11
18
  * Packs a vertical stack of children into `maxHeight`, in order. Children are measured
12
19
  * against `width` (unbounded height) and added until one would overflow; that straddling
@@ -21,13 +28,13 @@ function isFragmentable(element) {
21
28
  * Shared by every element that lays out a vertical stack and can split it across regions
22
29
  * (Container, and the decorated boxes Padding/Rectangle).
23
30
  */
24
- function packChildren(children, maxHeight, width, ctx, gap = 0) {
31
+ export function packChildren(children, maxHeight, width, ctx, gap = 0) {
25
32
  const fitted = [];
26
33
  const remainder = [];
27
34
  let usedHeight = 0;
28
35
  for (let i = 0; i < children.length; i++) {
29
36
  const child = children[i];
30
- const childHeight = child.calculateLayout(box_constraints_1.BoxConstraints.loose(width, Infinity), { x: 0, y: 0 }, ctx).height;
37
+ const childHeight = child.calculateLayout(BoxConstraints.loose(width, Infinity), { x: 0, y: 0 }, ctx).height;
31
38
  // A gap precedes every child except the first one placed in this region.
32
39
  const lead = fitted.length > 0 ? gap : 0;
33
40
  if (usedHeight + lead + childHeight <= maxHeight) {
@@ -48,10 +55,15 @@ function packChildren(children, maxHeight, width, ctx, gap = 0) {
48
55
  }
49
56
  }
50
57
  if (!placedPart) {
51
- if (fitted.length === 0)
58
+ if (fitted.length === 0) {
59
+ // Taller than the whole region and unsplittable: force it on (it overflows and is clipped)
60
+ // so the next region still advances - and surface it per the overflow policy.
61
+ reportOverflow(child, childHeight, maxHeight, ctx.onOverflow ?? "ignore");
52
62
  fitted.push(child);
53
- else
63
+ }
64
+ else {
54
65
  remainder.push(child);
66
+ }
55
67
  }
56
68
  for (let j = i + 1; j < children.length; j++)
57
69
  remainder.push(children[j]);
@@ -0,0 +1,2 @@
1
+ export declare function readFileBytes(_path: string): Uint8Array;
2
+ export declare function readFileBytesAsync(_path: string): Promise<Uint8Array>;
@@ -0,0 +1,9 @@
1
+ // Browser stand-in for platform/node-fs.ts (selected by the package.json "browser" field). The browser has
2
+ // no file paths, so loading from one throws a clear, actionable error - pass bytes instead.
3
+ const hint = "needs Node. In the browser pass bytes (Uint8Array / ArrayBuffer) instead - e.g. import the file as a URL and fetch() it, or register a font from its bytes.";
4
+ export function readFileBytes(_path) {
5
+ throw new Error(`Loading a font from a file path ${hint}`);
6
+ }
7
+ export function readFileBytesAsync(_path) {
8
+ throw new Error(`Loading an image from a file path ${hint}`);
9
+ }
@@ -0,0 +1,5 @@
1
+ export declare function pngToRgba(bytes: Uint8Array): Promise<{
2
+ width: number;
3
+ height: number;
4
+ rgba: Uint8Array;
5
+ }>;
@@ -0,0 +1,13 @@
1
+ // PNG decode in the browser: the platform's own image decoder, via an OffscreenCanvas, to raw RGBA. No
2
+ // jimp, no Buffer - keeps the browser bundle lean and works wherever Canvas does. The package `browser`
3
+ // field swaps `node-image.ts` for this module at bundle time.
4
+ export async function pngToRgba(bytes) {
5
+ const bitmap = await createImageBitmap(new Blob([new Uint8Array(bytes)], { type: "image/png" }));
6
+ const { width, height } = bitmap;
7
+ const ctx = new OffscreenCanvas(width, height).getContext("2d");
8
+ if (!ctx)
9
+ throw new Error("@jasy/pdf: no 2D canvas context available to decode the PNG.");
10
+ ctx.drawImage(bitmap, 0, 0);
11
+ bitmap.close();
12
+ return { width, height, rgba: new Uint8Array(ctx.getImageData(0, 0, width, height).data.buffer) };
13
+ }
@@ -0,0 +1,2 @@
1
+ export declare function readFileBytes(path: string): Uint8Array;
2
+ export declare function readFileBytesAsync(path: string): Promise<Uint8Array>;