@jasy/pdf 1.0.0-beta.2 → 1.0.0-beta.4

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 (210) hide show
  1. package/dist/api/content.d.ts +5 -5
  2. package/dist/api/content.js +12 -6
  3. package/dist/api/font-url.d.ts +1 -2
  4. package/dist/api/font-url.js +8 -74
  5. package/dist/api/forms.d.ts +3 -0
  6. package/dist/api/forms.js +3 -0
  7. package/dist/api/index.d.ts +2 -0
  8. package/dist/api/index.js +1 -0
  9. package/dist/api/layout.d.ts +6 -0
  10. package/dist/api/layout.js +7 -2
  11. package/dist/api/page-builder.d.ts +4 -0
  12. package/dist/api/page-builder.js +4 -0
  13. package/dist/api/structure.d.ts +10 -2
  14. package/dist/api/structure.js +9 -6
  15. package/dist/api/table.d.ts +5 -3
  16. package/dist/api/table.js +99 -22
  17. package/dist/common/diagnostics.d.ts +53 -0
  18. package/dist/common/diagnostics.js +77 -0
  19. package/dist/edit/acroform-reader.js +7 -7
  20. package/dist/edit/appearance.js +3 -3
  21. package/dist/edit/attachments.d.ts +52 -0
  22. package/dist/edit/attachments.js +178 -0
  23. package/dist/edit/document.d.ts +38 -8
  24. package/dist/edit/document.js +208 -51
  25. package/dist/edit/fill.js +8 -23
  26. package/dist/edit/filters.d.ts +15 -0
  27. package/dist/edit/filters.js +157 -0
  28. package/dist/edit/index.d.ts +4 -1
  29. package/dist/edit/index.js +3 -1
  30. package/dist/edit/objects.d.ts +3 -4
  31. package/dist/edit/objects.js +5 -18
  32. package/dist/elements/canvas-element.d.ts +3 -0
  33. package/dist/elements/canvas-element.js +14 -3
  34. package/dist/elements/container-element.d.ts +5 -1
  35. package/dist/elements/container-element.js +35 -15
  36. package/dist/elements/forms/checkbox-element.d.ts +2 -0
  37. package/dist/elements/forms/checkbox-element.js +12 -3
  38. package/dist/elements/forms/choice-element.d.ts +6 -1
  39. package/dist/elements/forms/choice-element.js +17 -6
  40. package/dist/elements/forms/field-font.d.ts +8 -0
  41. package/dist/elements/forms/field-font.js +5 -0
  42. package/dist/elements/forms/push-button-element.d.ts +6 -1
  43. package/dist/elements/forms/push-button-element.js +16 -4
  44. package/dist/elements/forms/radio-element.d.ts +2 -0
  45. package/dist/elements/forms/radio-element.js +12 -3
  46. package/dist/elements/forms/signature-element.d.ts +6 -1
  47. package/dist/elements/forms/signature-element.js +16 -4
  48. package/dist/elements/forms/text-field-element.d.ts +6 -1
  49. package/dist/elements/forms/text-field-element.js +16 -4
  50. package/dist/elements/image-element.d.ts +39 -1
  51. package/dist/elements/image-element.js +95 -12
  52. package/dist/elements/layout/anchor-element.d.ts +8 -10
  53. package/dist/elements/layout/anchor-element.js +10 -18
  54. package/dist/elements/layout/bookmark-element.d.ts +9 -11
  55. package/dist/elements/layout/bookmark-element.js +11 -20
  56. package/dist/elements/layout/default-text-style-element.d.ts +11 -13
  57. package/dist/elements/layout/default-text-style-element.js +14 -31
  58. package/dist/elements/layout/deferred-element.d.ts +12 -4
  59. package/dist/elements/layout/deferred-element.js +24 -7
  60. package/dist/elements/layout/expanded-element.d.ts +7 -0
  61. package/dist/elements/layout/expanded-element.js +40 -18
  62. package/dist/elements/layout/keep-together-element.d.ts +8 -18
  63. package/dist/elements/layout/keep-together-element.js +21 -45
  64. package/dist/elements/layout/link-element.d.ts +8 -13
  65. package/dist/elements/layout/link-element.js +9 -19
  66. package/dist/elements/layout/padding-element.d.ts +15 -1
  67. package/dist/elements/layout/padding-element.js +55 -27
  68. package/dist/elements/layout/page-break-element.d.ts +4 -1
  69. package/dist/elements/layout/page-break-element.js +6 -1
  70. package/dist/elements/layout/page-builder-element.d.ts +5 -0
  71. package/dist/elements/layout/page-builder-element.js +11 -0
  72. package/dist/elements/layout/positioned-element.d.ts +14 -2
  73. package/dist/elements/layout/positioned-element.js +36 -10
  74. package/dist/elements/layout/proxy-element.d.ts +43 -0
  75. package/dist/elements/layout/proxy-element.js +94 -0
  76. package/dist/elements/layout/repeating-header-element.d.ts +6 -0
  77. package/dist/elements/layout/repeating-header-element.js +40 -13
  78. package/dist/elements/layout/rotated-box-element.d.ts +6 -0
  79. package/dist/elements/layout/rotated-box-element.js +27 -14
  80. package/dist/elements/layout/rotated-element.d.ts +7 -0
  81. package/dist/elements/layout/rotated-element.js +25 -2
  82. package/dist/elements/layout/struct-group.d.ts +9 -17
  83. package/dist/elements/layout/struct-group.js +10 -35
  84. package/dist/elements/line-element.d.ts +1 -0
  85. package/dist/elements/line-element.js +6 -0
  86. package/dist/elements/page-element.d.ts +10 -3
  87. package/dist/elements/page-element.js +39 -31
  88. package/dist/elements/pdf-document-element.d.ts +3 -0
  89. package/dist/elements/pdf-document-element.js +7 -0
  90. package/dist/elements/pdf-element.d.ts +75 -15
  91. package/dist/elements/pdf-element.js +68 -18
  92. package/dist/elements/rectangle-element.d.ts +14 -0
  93. package/dist/elements/rectangle-element.js +97 -52
  94. package/dist/elements/row-element.d.ts +11 -0
  95. package/dist/elements/row-element.js +58 -8
  96. package/dist/elements/svg-element.d.ts +30 -6
  97. package/dist/elements/svg-element.js +88 -17
  98. package/dist/elements/text-element.d.ts +40 -13
  99. package/dist/elements/text-element.js +144 -99
  100. package/dist/forms/acroform.d.ts +9 -1
  101. package/dist/forms/acroform.js +83 -34
  102. package/dist/forms/appearance.d.ts +17 -6
  103. package/dist/forms/appearance.js +18 -16
  104. package/dist/ir/display-list.d.ts +12 -2
  105. package/dist/layout/box-constraints.d.ts +5 -0
  106. package/dist/layout/box-constraints.js +14 -0
  107. package/dist/layout/collect-images.d.ts +6 -4
  108. package/dist/layout/collect-images.js +6 -4
  109. package/dist/layout/flex-item.d.ts +9 -0
  110. package/dist/layout/flex-item.js +39 -0
  111. package/dist/layout/fragmentation.d.ts +12 -4
  112. package/dist/layout/fragmentation.js +37 -14
  113. package/dist/layout/layout-cache.d.ts +53 -0
  114. package/dist/layout/layout-cache.js +107 -0
  115. package/dist/renderer/anchor-renderer.d.ts +2 -1
  116. package/dist/renderer/anchor-renderer.js +5 -4
  117. package/dist/renderer/bookmark-renderer.d.ts +2 -1
  118. package/dist/renderer/bookmark-renderer.js +5 -4
  119. package/dist/renderer/canvas-renderer.d.ts +2 -1
  120. package/dist/renderer/canvas-renderer.js +5 -2
  121. package/dist/renderer/container-renderer.d.ts +2 -1
  122. package/dist/renderer/container-renderer.js +6 -5
  123. package/dist/renderer/default-text-style-renderer.d.ts +2 -1
  124. package/dist/renderer/default-text-style-renderer.js +2 -3
  125. package/dist/renderer/deferred-renderer.d.ts +2 -1
  126. package/dist/renderer/deferred-renderer.js +2 -3
  127. package/dist/renderer/expanded-renderer.d.ts +2 -1
  128. package/dist/renderer/expanded-renderer.js +2 -3
  129. package/dist/renderer/form-field-renderer.d.ts +2 -1
  130. package/dist/renderer/form-field-renderer.js +16 -3
  131. package/dist/renderer/image-renderer.d.ts +2 -1
  132. package/dist/renderer/image-renderer.js +5 -2
  133. package/dist/renderer/keep-together-renderer.d.ts +2 -1
  134. package/dist/renderer/keep-together-renderer.js +2 -3
  135. package/dist/renderer/line-renderer.d.ts +2 -1
  136. package/dist/renderer/line-renderer.js +7 -2
  137. package/dist/renderer/link-renderer.d.ts +2 -1
  138. package/dist/renderer/link-renderer.js +36 -5
  139. package/dist/renderer/padding-renderer.d.ts +2 -1
  140. package/dist/renderer/padding-renderer.js +5 -5
  141. package/dist/renderer/page-break-renderer.d.ts +6 -5
  142. package/dist/renderer/page-break-renderer.js +14 -7
  143. package/dist/renderer/page-builder-renderer.d.ts +2 -1
  144. package/dist/renderer/page-builder-renderer.js +2 -3
  145. package/dist/renderer/page-renderer.js +28 -7
  146. package/dist/renderer/pdf-backend.d.ts +4 -3
  147. package/dist/renderer/pdf-backend.js +5 -5
  148. package/dist/renderer/pdf-document-renderer.d.ts +1 -8
  149. package/dist/renderer/pdf-document-renderer.js +15 -16
  150. package/dist/renderer/pdf-renderer.js +11 -11
  151. package/dist/renderer/positioned-renderer.d.ts +6 -1
  152. package/dist/renderer/positioned-renderer.js +6 -5
  153. package/dist/renderer/rectangle-renderer.d.ts +2 -1
  154. package/dist/renderer/rectangle-renderer.js +9 -4
  155. package/dist/renderer/repeating-header-renderer.d.ts +2 -1
  156. package/dist/renderer/repeating-header-renderer.js +6 -5
  157. package/dist/renderer/rotated-renderer.d.ts +2 -1
  158. package/dist/renderer/rotated-renderer.js +8 -5
  159. package/dist/renderer/row-renderer.d.ts +2 -1
  160. package/dist/renderer/row-renderer.js +6 -5
  161. package/dist/renderer/struct-group-renderer.d.ts +2 -1
  162. package/dist/renderer/struct-group-renderer.js +2 -3
  163. package/dist/renderer/svg-renderer.d.ts +2 -1
  164. package/dist/renderer/svg-renderer.js +8 -2
  165. package/dist/renderer/text-renderer.d.ts +4 -6
  166. package/dist/renderer/text-renderer.js +101 -111
  167. package/dist/svg/gradients.js +5 -0
  168. package/dist/svg/index.js +10 -1
  169. package/dist/svg/style.d.ts +2 -0
  170. package/dist/svg/style.js +11 -0
  171. package/dist/text/advance.d.ts +4 -0
  172. package/dist/text/advance.js +49 -4
  173. package/dist/text/bidi.d.ts +0 -2
  174. package/dist/text/bidi.js +0 -10
  175. package/dist/text/glyph-coverage.d.ts +1 -1
  176. package/dist/text/glyph-coverage.js +7 -3
  177. package/dist/text/hyphen-breaks.d.ts +17 -0
  178. package/dist/text/hyphen-breaks.js +54 -0
  179. package/dist/text/line-breaker.d.ts +34 -31
  180. package/dist/text/line-breaker.js +364 -176
  181. package/dist/text/line-metrics.d.ts +6 -3
  182. package/dist/text/line-metrics.js +7 -2
  183. package/dist/text/pdf-text-string.d.ts +21 -0
  184. package/dist/text/pdf-text-string.js +114 -0
  185. package/dist/text/text-decoration.d.ts +5 -5
  186. package/dist/text/text-decoration.js +5 -5
  187. package/dist/text/word-splitting.d.ts +15 -2
  188. package/dist/text/word-splitting.js +34 -8
  189. package/dist/utils/afm-parser.d.ts +3 -0
  190. package/dist/utils/afm-parser.js +19 -1
  191. package/dist/utils/dest-registry.d.ts +1 -1
  192. package/dist/utils/dest-registry.js +3 -2
  193. package/dist/utils/fetch-bytes.d.ts +19 -0
  194. package/dist/utils/fetch-bytes.js +79 -0
  195. package/dist/utils/flex-layout.d.ts +13 -13
  196. package/dist/utils/flex-layout.js +318 -216
  197. package/dist/utils/font-metrics.d.ts +4 -3
  198. package/dist/utils/image-helper.js +2 -1
  199. package/dist/utils/pdf-object-manager.d.ts +11 -3
  200. package/dist/utils/pdf-object-manager.js +41 -15
  201. package/dist/utils/renderer-registry.d.ts +43 -2
  202. package/dist/utils/renderer-registry.js +40 -0
  203. package/dist/utils/struct-tree.d.ts +24 -0
  204. package/dist/utils/struct-tree.js +61 -9
  205. package/dist/utils/ttf-parser.d.ts +4 -0
  206. package/dist/utils/ttf-parser.js +27 -4
  207. package/dist/validators/element-validator.js +2 -1
  208. package/package.json +1 -1
  209. package/dist/elements/layout/sized-container-element.d.ts +0 -14
  210. package/dist/elements/layout/sized-container-element.js +0 -32
@@ -1,7 +1,9 @@
1
+ import { flexibleOf } from "../layout/flex-item.js";
1
2
  import { extentSpecs, resolveSize, } from "../layout/box-constraints.js";
2
- import { FlexLayoutHelper, HORIZONTAL_AXIS } from "../utils/flex-layout.js";
3
+ import { FlexLayoutHelper, HORIZONTAL_AXIS, } from "../utils/flex-layout.js";
3
4
  import { stackMinIntrinsic, withDeclaredExtent } from "../layout/min-intrinsic.js";
4
- import { FlexiblePDFElement, SizedPDFElement, } from "./pdf-element.js";
5
+ import { diagnose, pageOf } from "../common/diagnostics.js";
6
+ import { SizedPDFElement } from "./pdf-element.js";
5
7
  /**
6
8
  * Horizontal stack: the mirror of `ContainerElement` (Column). Children are laid out
7
9
  * left-to-right via the shared `FlexLayoutHelper` on the horizontal axis; fixed children
@@ -45,7 +47,7 @@ export class RowElement extends SizedPDFElement {
45
47
  : [this.requested.height, this.requested.heightFactor];
46
48
  if (requested.some((v) => v !== undefined))
47
49
  return false;
48
- const ownFlexChild = horizontal && this.children.some((c) => c instanceof FlexiblePDFElement);
50
+ const ownFlexChild = horizontal && this.children.some((c) => flexibleOf(c) !== undefined);
49
51
  return ownFlexChild || this.children.some((c) => c.needsBoundedMain(horizontal));
50
52
  }
51
53
  /** A Row lays out side by side, so widths add up (gaps included) and heights overlap. */
@@ -56,6 +58,53 @@ export class RowElement extends SizedPDFElement {
56
58
  calculateLayout(constraints, offset, ctx) {
57
59
  this.x = offset.x;
58
60
  this.y = offset.y;
61
+ this.reportIgnoredBreak(ctx);
62
+ const size = this.sizeWith(constraints, ctx, "layout");
63
+ this.width = size.width;
64
+ this.height = size.height;
65
+ return size;
66
+ }
67
+ computeSize(constraints, ctx) {
68
+ return this.sizeWith(constraints, ctx, "measure");
69
+ }
70
+ /**
71
+ * A row is one line: nothing inside it starts a new page. A `breakBefore` / `breakAfter` in a cell
72
+ * (a table row is a Row too) is therefore ignored - say so. A `PageBreak` reports itself.
73
+ */
74
+ reportIgnoredBreak(ctx) {
75
+ if (this.declaredBreak === undefined) {
76
+ const find = (el, depth) => {
77
+ if (el.breaksBefore() || el.breaksAfter())
78
+ return el;
79
+ if (depth > 64)
80
+ return null;
81
+ for (const child of el.layoutChildren()) {
82
+ const hit = find(child, depth + 1);
83
+ if (hit)
84
+ return hit;
85
+ }
86
+ return null;
87
+ };
88
+ this.declaredBreak = null;
89
+ for (const child of this.layoutChildren()) {
90
+ this.declaredBreak = find(child, 0);
91
+ if (this.declaredBreak)
92
+ break;
93
+ }
94
+ }
95
+ if (!this.declaredBreak)
96
+ return;
97
+ diagnose(ctx.diagnostics, {
98
+ code: "break-ignored",
99
+ severity: "warning",
100
+ message: "breakBefore / breakAfter inside a Row (or a table row) has no effect: a row is one line and " +
101
+ "never starts a new page. Put the break on the Row itself, or on a child of a Column.",
102
+ about: this.declaredBreak,
103
+ page: pageOf(ctx),
104
+ });
105
+ }
106
+ /** The one sizing path: `layout` places the children (relative to this box), `measure` only sizes them. */
107
+ sizeWith(constraints, ctx, mode) {
59
108
  // Relative sizing: a pinned extent (fixed or a fraction of the offered box, clamped) wins; else
60
109
  // width fills the offered space (flex children split the leftover) and height is the tallest child.
61
110
  const specs = extentSpecs(this.requested);
@@ -77,18 +126,19 @@ export class RowElement extends SizedPDFElement {
77
126
  const crossAvail = boundedHeight ?? Infinity;
78
127
  let result = { mainUsed: 0, crossUsed: 0 };
79
128
  if (this.children.length > 0) {
80
- result = FlexLayoutHelper.layout(this.children, HORIZONTAL_AXIS, mainAvail, crossAvail, this.x, this.y, {
129
+ result = FlexLayoutHelper.layout(this.children, HORIZONTAL_AXIS, mainAvail, crossAvail, {
81
130
  gap: this.gap,
82
131
  main: this.main,
83
132
  cross: this.cross,
84
133
  reverse: this.reverse,
85
134
  wrap: this.wrap,
86
135
  alignContent: this.alignContent,
87
- }, ctx);
136
+ }, ctx, mode);
88
137
  }
89
- this.width = boundedWidth ?? result.mainUsed;
90
- this.height = boundedHeight ?? result.crossUsed;
91
- return { width: this.width, height: this.height };
138
+ return { width: boundedWidth ?? result.mainUsed, height: boundedHeight ?? result.crossUsed };
139
+ }
140
+ layoutChildren() {
141
+ return this.children;
92
142
  }
93
143
  getProps() {
94
144
  return {
@@ -1,7 +1,7 @@
1
1
  import { type SvgSize } from "../svg/index.js";
2
2
  import { BoxConstraints, Offset, Size, SizingParams } from "../layout/box-constraints.js";
3
3
  import { LayoutContext, SizedPDFElement } from "./pdf-element.js";
4
- /** Markup, a file path (Node only), or the file's bytes. */
4
+ /** Markup, a URL (`http(s):`, `data:`), a file path (Node only), or the file's bytes. */
5
5
  export type SvgSource = string | Uint8Array;
6
6
  interface SvgElementParams extends SizingParams {
7
7
  source: SvgSource;
@@ -16,26 +16,50 @@ interface SvgElementParams extends SizingParams {
16
16
  *
17
17
  * Unlike a bitmap it needs no decoder, so its intrinsic size is known SYNCHRONOUSLY, in the
18
18
  * constructor - which also means a broken file fails on the line that asked for it rather than deep
19
- * inside a later render, the same bargain `addFont` makes.
19
+ * inside a later render, the same bargain `addFont` makes. An SVG from a URL is the exception: like
20
+ * an image from a URL it is fetched when the document renders (`resolveIntrinsicSize`, or the
21
+ * renderer for a subtree built during layout), and a dead link stops the render naming it.
20
22
  */
21
23
  export declare class SvgElement extends SizedPDFElement {
22
- private source;
23
- private intrinsic;
24
+ private source?;
25
+ private intrinsic?;
26
+ private readonly url?;
27
+ private loading?;
28
+ private failed;
24
29
  private alt?;
25
30
  private requested;
26
31
  private sizing;
27
32
  private widthFactor?;
28
33
  private heightFactor?;
29
34
  constructor(params: SvgElementParams);
35
+ /**
36
+ * Parsed on purpose where the source is given: an unreadable file is an error where it was NAMED,
37
+ * not inside a render three call frames later. The whole document is walked, not just its root, so
38
+ * an element outside the subset surfaces here too. A logo parses in well under a millisecond.
39
+ */
40
+ private parse;
41
+ /**
42
+ * Fetches and parses an SVG from a URL - once per render, however often it is asked. A no-op
43
+ * otherwise. A failure sticks for that render only: `fresh` (a new render begins) asks again.
44
+ */
45
+ load(fresh?: boolean): Promise<void>;
46
+ /**
47
+ * The pre-layout pass, as for an `ImageElement`: best-effort, so a failure leaves the size unknown
48
+ * and the renderer, which loads again, surfaces the real error.
49
+ */
50
+ resolveIntrinsicSize(): Promise<void>;
30
51
  getProps(): {
31
52
  x: number;
32
53
  y: number;
33
54
  width: number | undefined;
34
55
  height: number | undefined;
35
- source: string;
36
- intrinsic: SvgSize;
56
+ source: string | undefined;
57
+ intrinsic: SvgSize | undefined;
37
58
  alt: string | undefined;
38
59
  };
39
60
  calculateLayout(constraints: BoxConstraints, offset: Offset, _ctx: LayoutContext): Size;
61
+ protected computeSize(constraints: BoxConstraints): Size;
62
+ /** Its size under these constraints - the one computation both `measure` and layout use. */
63
+ private sizeFor;
40
64
  }
41
65
  export {};
@@ -2,6 +2,8 @@ import { readFileBytes } from "../platform/node-fs.js";
2
2
  import { SvgParseError, svgSize, svgToIr } from "../svg/index.js";
3
3
  import { extentSpecs, resolveSize, } from "../layout/box-constraints.js";
4
4
  import { SizedPDFElement } from "./pdf-element.js";
5
+ import { IMAGE_FETCH_LIMITS, ImageUrlError, isImageUrl } from "./image-element.js";
6
+ import { fetchBytes } from "../utils/fetch-bytes.js";
5
7
  /** Markup is recognised by its first non-space character - anything else is treated as a path. */
6
8
  const looksLikeMarkup = (value) => /^\s*[<]/.test(value);
7
9
  /**
@@ -24,30 +26,78 @@ function readSource(path) {
24
26
  *
25
27
  * Unlike a bitmap it needs no decoder, so its intrinsic size is known SYNCHRONOUSLY, in the
26
28
  * constructor - which also means a broken file fails on the line that asked for it rather than deep
27
- * inside a later render, the same bargain `addFont` makes.
29
+ * inside a later render, the same bargain `addFont` makes. An SVG from a URL is the exception: like
30
+ * an image from a URL it is fetched when the document renders (`resolveIntrinsicSize`, or the
31
+ * renderer for a subtree built during layout), and a dead link stops the render naming it.
28
32
  */
29
33
  export class SvgElement extends SizedPDFElement {
30
34
  constructor(params) {
31
35
  super({ x: 0, y: 0, width: params.width, height: params.height });
36
+ this.failed = false;
32
37
  const { source, alt, widthFactor, heightFactor, ...sizing } = params;
33
38
  this.widthFactor = widthFactor;
34
39
  this.heightFactor = heightFactor;
35
- this.source =
36
- typeof source === "string"
40
+ if (typeof source === "string" && isImageUrl(source))
41
+ this.url = source;
42
+ else
43
+ this.parse(typeof source === "string"
37
44
  ? looksLikeMarkup(source)
38
45
  ? source
39
46
  : new TextDecoder().decode(readSource(source))
40
- : new TextDecoder().decode(source);
41
- // Parsed here on purpose: an unreadable file is an error where it was NAMED, not inside a render
42
- // three call frames later. The whole document is walked, not just its root, so an element outside
43
- // the subset surfaces here too - which is what makes the error predictable rather than a lottery
44
- // between construction and render time. A logo parses in well under a millisecond.
45
- this.intrinsic = svgSize(this.source);
46
- svgToIr(this.source, { x: 0, y: 0, width: 1, height: 1 });
47
+ : new TextDecoder().decode(source));
47
48
  this.alt = alt;
48
49
  this.sizing = sizing;
49
50
  this.requested = { width: params.width, height: params.height };
50
51
  }
52
+ /**
53
+ * Parsed on purpose where the source is given: an unreadable file is an error where it was NAMED,
54
+ * not inside a render three call frames later. The whole document is walked, not just its root, so
55
+ * an element outside the subset surfaces here too. A logo parses in well under a millisecond.
56
+ */
57
+ parse(source) {
58
+ this.intrinsic = svgSize(source);
59
+ svgToIr(source, { x: 0, y: 0, width: 1, height: 1 });
60
+ this.source = source;
61
+ }
62
+ /**
63
+ * Fetches and parses an SVG from a URL - once per render, however often it is asked. A no-op
64
+ * otherwise. A failure sticks for that render only: `fresh` (a new render begins) asks again.
65
+ */
66
+ async load(fresh = false) {
67
+ const url = this.url;
68
+ if (url === undefined)
69
+ return;
70
+ if (fresh && this.failed)
71
+ this.loading = undefined;
72
+ this.loading ?? (this.loading = fetchBytes(url, "SVG", ImageUrlError, IMAGE_FETCH_LIMITS)
73
+ .then((bytes) => {
74
+ try {
75
+ this.parse(new TextDecoder().decode(bytes));
76
+ }
77
+ catch (e) {
78
+ throw new ImageUrlError(`the SVG at ${url} could not be read: ${String(e?.message ?? e)}`);
79
+ }
80
+ })
81
+ .then(() => {
82
+ this.failed = false;
83
+ }, (e) => {
84
+ this.failed = true;
85
+ throw e;
86
+ }));
87
+ await this.loading;
88
+ }
89
+ /**
90
+ * The pre-layout pass, as for an `ImageElement`: best-effort, so a failure leaves the size unknown
91
+ * and the renderer, which loads again, surfaces the real error.
92
+ */
93
+ async resolveIntrinsicSize() {
94
+ try {
95
+ await this.load(true); // a render begins here
96
+ }
97
+ catch {
98
+ // the renderer reports it
99
+ }
100
+ }
51
101
  getProps() {
52
102
  return {
53
103
  x: this.x,
@@ -60,8 +110,18 @@ export class SvgElement extends SizedPDFElement {
60
110
  };
61
111
  }
62
112
  calculateLayout(constraints, offset, _ctx) {
113
+ const size = this.sizeFor(constraints);
63
114
  this.x = offset.x;
64
115
  this.y = offset.y;
116
+ this.width = size.width;
117
+ this.height = size.height;
118
+ return size;
119
+ }
120
+ computeSize(constraints) {
121
+ return this.sizeFor(constraints);
122
+ }
123
+ /** Its size under these constraints - the one computation both `measure` and layout use. */
124
+ sizeFor(constraints) {
65
125
  const specs = extentSpecs({
66
126
  ...this.sizing,
67
127
  width: this.requested.width,
@@ -73,12 +133,22 @@ export class SvgElement extends SizedPDFElement {
73
133
  let w = resolved.width;
74
134
  let h = resolved.height;
75
135
  const bounds = resolved.constraints;
136
+ // An SVG from a URL that was never fetched sits in content built DURING layout (a PageBuilder, an auto
137
+ // table column), which the pass before layout cannot reach. Its ratio is unknown here, so it needs
138
+ // its whole box - a named error instead of a drawing sized to nothing. A FAILED fetch is not this
139
+ // case: the renderer reports that one by its URL.
140
+ if (this.url !== undefined &&
141
+ this.loading === undefined &&
142
+ (w === undefined || h === undefined)) {
143
+ throw new ImageUrlError(`the SVG at ${this.url} sits in content built during layout (a PageBuilder or an auto table ` +
144
+ `column), where its size cannot be read before it is placed - give it both a width and a ` +
145
+ `height, or an aspectRatio`);
146
+ }
76
147
  // One pinned axis derives the other from the drawing's own ratio (CSS `width: 120; height: auto`).
77
148
  // A bitmap needs a decode pass before it can do this; an SVG knows it from its viewBox.
78
- if (this.sizing.aspectRatio === undefined &&
79
- this.intrinsic.width > 0 &&
80
- this.intrinsic.height > 0) {
81
- const ratio = this.intrinsic.width / this.intrinsic.height;
149
+ const intrinsic = this.intrinsic ?? { width: 0, height: 0 };
150
+ if (this.sizing.aspectRatio === undefined && intrinsic.width > 0 && intrinsic.height > 0) {
151
+ const ratio = intrinsic.width / intrinsic.height;
82
152
  if (w !== undefined && h === undefined)
83
153
  h = bounds.constrainHeight(w / ratio);
84
154
  else if (h !== undefined && w === undefined)
@@ -86,8 +156,9 @@ export class SvgElement extends SizedPDFElement {
86
156
  }
87
157
  // Nothing pinned: draw at the intrinsic size, as an unsized `<img>` does - never fill the region,
88
158
  // which would blow a 24pt icon up to the whole page.
89
- this.width = w ?? bounds.constrainWidth(this.intrinsic.width);
90
- this.height = h ?? bounds.constrainHeight(this.intrinsic.height);
91
- return { width: this.width, height: this.height };
159
+ return {
160
+ width: w ?? bounds.constrainWidth(intrinsic.width),
161
+ height: h ?? bounds.constrainHeight(intrinsic.height),
162
+ };
92
163
  }
93
164
  }
@@ -4,7 +4,7 @@ import { BoxConstraints, Offset, Size } from "../layout/box-constraints.js";
4
4
  import type { Direction } from "../text/bidi.js";
5
5
  import { type TextTransform } from "../text/text-style.js";
6
6
  import { Fragmentable, FragmentResult } from "../layout/fragmentation.js";
7
- import { TextOverflow } from "../text/line-breaker.js";
7
+ import { type SegmentDefaults, TextOverflow } from "../text/line-breaker.js";
8
8
  import { HorizontalAlignment, LayoutContext, SizedPDFElement } from "./pdf-element.js";
9
9
  import type { Hyphenator } from "../text/word-splitting.js";
10
10
  export interface TextSegment {
@@ -83,6 +83,9 @@ interface TextElementParams {
83
83
  hyphenate?: Hyphenator;
84
84
  /** Accessibility role for the tagged structure tree (heading level or paragraph; default `"p"`). */
85
85
  role?: TextRole;
86
+ /** Set by pagination, not by authors: this element is the first N lines of a paragraph that
87
+ * continues on the next page. See `LineOptions.shownLines`. */
88
+ shownLines?: number;
86
89
  }
87
90
  export declare class TextElement extends SizedPDFElement implements Fragmentable {
88
91
  private readonly rawFontSize?;
@@ -123,13 +126,26 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
123
126
  private ligatures;
124
127
  private hyphenate?;
125
128
  private content;
129
+ /** What `covered` had to remove, reported where the text is laid out on its page (see
130
+ * `calculateLayout`). A split paragraph's first piece carries its parent's, as `lostBefore`. */
131
+ private lostGlyphs;
132
+ private lostBefore;
126
133
  private maxLines?;
127
134
  private orphans?;
128
135
  private widows?;
129
136
  private overflow;
130
137
  private readonly role?;
131
- constructor({ fontSize, content, fontFamily, fontStyle, color, textAlignment, maxLines, orphans, widows, overflow, lineHeight, underline, strikethrough, skipInk, letterSpacing, direction, fontFallback, textTransform, wordSpacing, textIndent, breakWord, hyphenate, ligatures, role, }: TextElementParams);
138
+ private readonly shownLines?;
139
+ constructor({ fontSize, content, fontFamily, fontStyle, color, textAlignment, maxLines, orphans, widows, overflow, lineHeight, underline, strikethrough, skipInk, letterSpacing, direction, fontFallback, textTransform, wordSpacing, textIndent, breakWord, hyphenate, ligatures, role, shownLines, }: TextElementParams);
132
140
  private resolveStyle;
141
+ /**
142
+ * The last `display(metrics)` result, with what it depended on - recasing, coverage and the fallback
143
+ * split each walk the whole text, and every measure, break and draw asks again.
144
+ *
145
+ * COMPARED, not cleared in `resolveStyle`: that runs on every pass and resolves to the same values,
146
+ * so clearing would drop the answer exactly when it is about to be reused.
147
+ */
148
+ private displayed?;
133
149
  /** Justified lines may be squeezed to keep a word; every other alignment gets no slack. Read by the
134
150
  * breaker in BOTH passes, or a measured line and a drawn one would disagree about where it ends. */
135
151
  /**
@@ -145,7 +161,7 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
145
161
  * back unchanged, so an ordinary document is byte-identical.
146
162
  */
147
163
  private covered;
148
- /** The content with `text-transform` applied, and nothing else. */
164
+ /** The content as spans, with `text-transform` applied, and nothing else. */
149
165
  private recased;
150
166
  private lineOptions;
151
167
  private applyStyle;
@@ -153,23 +169,29 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
153
169
  * Splits the paragraph at line boxes (Slice 1). The lines that fit in `maxHeight` stay;
154
170
  * the rest become a remainder `TextElement` re-wrapped on the next page. If not even one
155
171
  * line fits, nothing is forced here - the caller (the container) decides whether to move
156
- * the whole element on for progress. Handles both plain strings and styled segments.
172
+ * the whole element on for progress.
157
173
  */
158
174
  fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
159
- private fragmentString;
160
- private fragmentSegments;
175
+ /** How the text is broken: the knobs `textHeight` and the renderer pass too. */
176
+ private segmentDefaults;
161
177
  /** The orphan/widow minimums for this paragraph; unset means the CSS default of 2 lines each. */
162
178
  private breakRule;
163
179
  private cloneWithContent;
164
180
  calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
181
+ /**
182
+ * The size `calculateLayout` would take, without placing anything. Resolving the inherited style
183
+ * and filling the memos below are its only writes, and both are a pure function of the context:
184
+ * the same inputs leave the same state, which a later layout reads back unchanged.
185
+ */
186
+ /** A text's size reads only the width it may take: it wraps to it, and a height bound never changes
187
+ * the lines (a box that is too short cuts them, it does not reflow them). */
188
+ protected sizeInputs(constraints: BoxConstraints): BoxConstraints;
189
+ protected computeSize(constraints: BoxConstraints, ctx: LayoutContext): Size;
190
+ private measuredHeight;
165
191
  /** The unwrapped single-line width of the content (used when width is unbounded, e.g. inside a Row).
166
- * Must match the LINE-BREAKER's one-line measure EXACTLY - not just algebraically but BIT-for-bit,
167
- * so a text laid out at this width never re-wraps inside its own natural-width box. The breaker
168
- * accumulates `currentWidth += wordWidth + spaceWidth`, grouping the word and its trailing space
169
- * into one term; we must group the same way. Adding word and space as two separate steps is
170
- * algebraically equal but, because floating-point addition is not associative, drifts by a sub-ULP
171
- * - enough to tip a borderline string (e.g. "20 Jun 2026", wider than "04 Jul 2026" only because
172
- * 'n' beats 'l') one bit over its own width, dropping the last word onto a second line. */
192
+ * Must match the breaker's one-line sum BIT for bit - it adds `width + (space + word)`, and the same
193
+ * numbers grouped differently drift by a sub-ULP, enough to wrap a text inside its own natural-width
194
+ * box. `singleLineWidth` is that sum. */
173
195
  private naturalWidth;
174
196
  /**
175
197
  * The widest piece of this text that cannot be broken - CSS's `min-content` width. A shrinking flex
@@ -190,6 +212,10 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
190
212
  fontStyle: FontStyle;
191
213
  color: Color;
192
214
  content: string | TextSegment[];
215
+ /** The content as it is measured and drawn: spans, recased, split by font, drawable glyphs only. */
216
+ spans: TextSegment[];
217
+ /** How the text is broken - the settings it is measured with, so drawing breaks it the same. */
218
+ breaking: SegmentDefaults;
193
219
  textAlignment: HorizontalAlignment;
194
220
  maxLines: number | undefined;
195
221
  orphans: number | undefined;
@@ -207,6 +233,7 @@ export declare class TextElement extends SizedPDFElement implements Fragmentable
207
233
  ligatures: boolean;
208
234
  hyphenate: Hyphenator | undefined;
209
235
  role: TextRole | undefined;
236
+ shownLines: number | undefined;
210
237
  };
211
238
  }
212
239
  export {};