@jasy/pdf 1.0.0-beta.3 → 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 +32 -13
  99. package/dist/elements/text-element.js +119 -97
  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 +17 -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 +2 -0
  190. package/dist/utils/afm-parser.js +10 -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 +39 -13
  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
@@ -8,7 +8,8 @@ export declare enum BoxFit {
8
8
  fill = "FILL"
9
9
  }
10
10
  export declare abstract class CustomImage {
11
- abstract init(): void | Promise<void>;
11
+ /** Loads the image. `fresh`: a new render begins - a load that failed in an earlier one is tried again. */
12
+ abstract init(fresh?: boolean): void | Promise<void>;
12
13
  abstract getImageType(): string | Promise<string>;
13
14
  abstract getFileData(): string | Promise<string>;
14
15
  abstract getImageDimensions(): Promise<{
@@ -48,6 +49,38 @@ export declare class CustomBytesImage extends CustomImage {
48
49
  height: number;
49
50
  }>;
50
51
  }
52
+ /** Thrown when an image could not be fetched or is not one we can embed. Always says which URL. */
53
+ export declare class ImageUrlError extends Error {
54
+ constructor(message: string);
55
+ }
56
+ /** How long an image from a URL may take, and how large it may be - a raster image and an SVG alike. */
57
+ export declare const IMAGE_FETCH_LIMITS: {
58
+ timeoutMs: number;
59
+ maxBytes: number;
60
+ };
61
+ /** Whether an image source string is an address to fetch rather than a file path. */
62
+ export declare const isImageUrl: (src: string) => boolean;
63
+ /**
64
+ * An image from a URL - `http(s):` or a `data:` URL - fetched in the async pass before layout, with
65
+ * the same timeout and size ceiling as a font. What it IS comes from its bytes, never from the
66
+ * address, so `.../photo?id=7` works. A dead link stops the render with an `ImageUrlError` naming it.
67
+ */
68
+ export declare class CustomUrlImage extends CustomImage {
69
+ private readonly url;
70
+ private loading?;
71
+ private loaded?;
72
+ private failed;
73
+ constructor(url: string);
74
+ init(fresh?: boolean): Promise<void>;
75
+ private load;
76
+ private image;
77
+ getImageType(): Promise<string>;
78
+ getFileData(): string;
79
+ getImageDimensions(): Promise<{
80
+ width: number;
81
+ height: number;
82
+ }>;
83
+ }
51
84
  interface ImageElementParams {
52
85
  image: CustomImage;
53
86
  width?: number;
@@ -94,6 +127,11 @@ export declare class ImageElement extends SizedPDFElement {
94
127
  resolveIntrinsicSize(): Promise<void>;
95
128
  relativeSizeFactor(horizontal: boolean): number | undefined;
96
129
  calculateLayout(constraints: BoxConstraints, offset: Offset, _ctx: LayoutContext): Size;
130
+ protected computeSize(constraints: BoxConstraints): Size;
131
+ /** Its size under these constraints - the one computation both `measure` and layout use. */
132
+ private sizeFor;
133
+ /** The image's own pixel extent on one axis, clamped by its min/max; unknown before the pre-pass. */
134
+ private ownExtent;
97
135
  getProps(): {
98
136
  x: number;
99
137
  y: number;
@@ -1,6 +1,7 @@
1
1
  import { getImageDimensions } from "../utils/image-helper.js";
2
2
  import { latin1FromBytes } from "../utils/bytes.js";
3
3
  import { readFileBytesAsync } from "../platform/node-fs.js";
4
+ import { fetchBytes } from "../utils/fetch-bytes.js";
4
5
  import { extentSpecs, resolveSize, } from "../layout/box-constraints.js";
5
6
  import { SizedPDFElement } from "./pdf-element.js";
6
7
  // path.extname without node:path (browser-safe): the substring from the last dot, if it sits after the
@@ -28,11 +29,10 @@ export class CustomLocalImage extends CustomImage {
28
29
  if (this.fileBuffer)
29
30
  return; // idempotent: the pre-layout size pass and the renderer both call init()
30
31
  try {
31
- // Loading image and convert it to base64
32
32
  await this.loadImage(this.imagePath);
33
33
  }
34
- catch (error) {
35
- console.error("Error loading image:", error);
34
+ catch (e) {
35
+ throw new Error(`@jasy/pdf: could not read the image at ${this.imagePath}: ${String(e?.message ?? e)}`);
36
36
  }
37
37
  }
38
38
  async getImageType() {
@@ -64,9 +64,8 @@ export class CustomLocalImage extends CustomImage {
64
64
  async getImageDimensions() {
65
65
  if (this.dims)
66
66
  return this.dims; // reuse: the pre-layout size pass and the renderer both ask
67
- if (!this.fileBuffer) {
68
- throw new Error("You must first call the `loadAndConvertImage` method");
69
- }
67
+ if (!this.fileBuffer)
68
+ throw new Error("@jasy/pdf: an image was measured before it was loaded");
70
69
  this.dims = await getImageDimensions(this.fileBuffer);
71
70
  return this.dims;
72
71
  }
@@ -99,6 +98,64 @@ export class CustomBytesImage extends CustomImage {
99
98
  return this.dims;
100
99
  }
101
100
  }
101
+ /** Thrown when an image could not be fetched or is not one we can embed. Always says which URL. */
102
+ export class ImageUrlError extends Error {
103
+ constructor(message) {
104
+ super(`@jasy/pdf: ${message}`);
105
+ }
106
+ }
107
+ /** How long an image from a URL may take, and how large it may be - a raster image and an SVG alike. */
108
+ export const IMAGE_FETCH_LIMITS = { timeoutMs: 15000, maxBytes: 32 * 1024 * 1024 };
109
+ /** Whether an image source string is an address to fetch rather than a file path. */
110
+ export const isImageUrl = (src) => /^(https?:|data:)/i.test(src.trim());
111
+ /**
112
+ * An image from a URL - `http(s):` or a `data:` URL - fetched in the async pass before layout, with
113
+ * the same timeout and size ceiling as a font. What it IS comes from its bytes, never from the
114
+ * address, so `.../photo?id=7` works. A dead link stops the render with an `ImageUrlError` naming it.
115
+ */
116
+ export class CustomUrlImage extends CustomImage {
117
+ constructor(url) {
118
+ super();
119
+ this.url = url;
120
+ this.failed = false;
121
+ }
122
+ // One fetch per render, however often it is asked: the pre-layout size pass and the renderer both
123
+ // call init(). A failure sticks for that render only - the next one asks again (`fresh`).
124
+ async init(fresh = false) {
125
+ if (fresh && this.failed)
126
+ this.loading = undefined;
127
+ this.loading ?? (this.loading = this.load().then((image) => ((this.failed = false), image), (e) => {
128
+ this.failed = true;
129
+ throw e;
130
+ }));
131
+ this.loaded = await this.loading;
132
+ }
133
+ async load() {
134
+ const bytes = await fetchBytes(this.url, "image", ImageUrlError, IMAGE_FETCH_LIMITS);
135
+ const jpeg = bytes[0] === 0xff && bytes[1] === 0xd8;
136
+ const png = bytes[0] === 0x89 && bytes[1] === 0x50 && bytes[2] === 0x4e && bytes[3] === 0x47;
137
+ if (!jpeg && !png) {
138
+ // An error page answers with a 200 often enough to name it rather than fail deep inside.
139
+ const looksLikeMarkup = new TextDecoder().decode(bytes.subarray(0, 64)).trimStart()[0] === "<";
140
+ throw new ImageUrlError(`the file at ${this.url} is ${looksLikeMarkup ? "markup (an HTML page, or an SVG - pass that to Svg(url))" : "not an image we can embed"} - jasy embeds PNG and JPEG`);
141
+ }
142
+ return new CustomBytesImage(bytes);
143
+ }
144
+ image() {
145
+ if (!this.loaded)
146
+ throw new Error("@jasy/pdf: an image was measured before it was loaded");
147
+ return this.loaded;
148
+ }
149
+ getImageType() {
150
+ return this.image().getImageType();
151
+ }
152
+ getFileData() {
153
+ return this.image().getFileData();
154
+ }
155
+ getImageDimensions() {
156
+ return this.image().getImageDimensions();
157
+ }
158
+ }
102
159
  export class ImageElement extends SizedPDFElement {
103
160
  constructor({ image, width, height, widthFactor, heightFactor, fit = BoxFit.none, radius, alt, ...sizing }) {
104
161
  // Seed this.width/height from the request so getProps() reflects it before layout; calculateLayout
@@ -122,7 +179,7 @@ export class ImageElement extends SizedPDFElement {
122
179
  */
123
180
  async resolveIntrinsicSize() {
124
181
  try {
125
- await this.image.init();
182
+ await this.image.init(true); // a render begins here
126
183
  this.intrinsic = await this.image.getImageDimensions();
127
184
  }
128
185
  catch {
@@ -133,8 +190,20 @@ export class ImageElement extends SizedPDFElement {
133
190
  return horizontal ? this.widthFactor : this.heightFactor;
134
191
  }
135
192
  calculateLayout(constraints, offset, _ctx) {
193
+ const size = this.sizeFor(constraints);
136
194
  this.x = offset.x;
137
195
  this.y = offset.y;
196
+ // Kept as resolved, `undefined` included: the renderer reads an unset axis differently from 0.
197
+ this.width = size.width;
198
+ this.height = size.height;
199
+ return { width: size.width ?? 0, height: size.height ?? 0 };
200
+ }
201
+ computeSize(constraints) {
202
+ const size = this.sizeFor(constraints);
203
+ return { width: size.width ?? 0, height: size.height ?? 0 };
204
+ }
205
+ /** Its size under these constraints - the one computation both `measure` and layout use. */
206
+ sizeFor(constraints) {
138
207
  // Relative sizing: a fixed size or a fraction of the offered box (fraction only in a bounded axis).
139
208
  // Read from `requested` (not this.width/height, which we overwrite below) so every pass re-resolves.
140
209
  const specs = extentSpecs({
@@ -163,12 +232,26 @@ export class ImageElement extends SizedPDFElement {
163
232
  else if (h !== undefined && w === undefined)
164
233
  w = bounds.constrainWidth(h * ratio);
165
234
  }
166
- // Fall back to the prior behavior for an unpinned axis: a bounded axis fills, otherwise keep the
167
- // requested fixed size (or undefined). Read from `requested`, never the mutated this.width/height.
168
- this.width = w ?? (bounds.hasBoundedWidth ? bounds.maxWidth : this.requested.width);
169
- this.height = h ?? (bounds.hasBoundedHeight ? bounds.maxHeight : this.requested.height);
235
+ // An unpinned axis: a bounded one fills; an unbounded one takes the image's own extent (1px = 1pt) -
236
+ // what `fit: none` (the default) draws there, so the layout reserves exactly what is drawn. Read from
237
+ // `requested`, never the mutated this.width/height.
238
+ const width = w ??
239
+ (bounds.hasBoundedWidth
240
+ ? bounds.maxWidth
241
+ : (this.requested.width ?? this.ownExtent(bounds, "width")));
242
+ const height = h ??
243
+ (bounds.hasBoundedHeight
244
+ ? bounds.maxHeight
245
+ : (this.requested.height ?? this.ownExtent(bounds, "height")));
170
246
  // Top-left coordinates; the fit logic (renderer) and the Y-flip (seam) run later.
171
- return { width: this.width ?? 0, height: this.height ?? 0 };
247
+ return { width, height };
248
+ }
249
+ /** The image's own pixel extent on one axis, clamped by its min/max; unknown before the pre-pass. */
250
+ ownExtent(bounds, axis) {
251
+ const own = this.intrinsic?.[axis];
252
+ if (own === undefined)
253
+ return undefined;
254
+ return axis === "width" ? bounds.constrainWidth(own) : bounds.constrainHeight(own);
172
255
  }
173
256
  getProps() {
174
257
  return {
@@ -1,20 +1,18 @@
1
- import { PDFElement, LayoutContext } from "../pdf-element.js";
2
- import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
1
+ import { PDFElement } from "../pdf-element.js";
2
+ import { ProxyElement } from "./proxy-element.js";
3
3
  /**
4
- * Marks its child as a named jump target for an internal `Link({ to: name })`. Layout-transparent: it
5
- * delegates `calculateLayout` to the child and records the child's top, which the renderer turns into an
6
- * `Anchor` IR node (and PDFRenderer into a /Names /Dests entry). It draws nothing - the child renders
7
- * exactly as it would on its own.
4
+ * Marks its child as a named jump target for an internal `Link({ to: name })`. Layout-transparent (a
5
+ * `ProxyElement`); the renderer turns the child's top into an `Anchor` IR node (and PDFRenderer into a
6
+ * /Names /Dests entry). It draws nothing. Split across pages, only the first piece is the target - a
7
+ * name must lead to one place.
8
8
  */
9
- export declare class AnchorElement extends PDFElement {
9
+ export declare class AnchorElement extends ProxyElement {
10
10
  private name;
11
- private child;
12
- private y;
13
11
  constructor({ name, child }: {
14
12
  name: string;
15
13
  child: PDFElement;
16
14
  });
17
- calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
15
+ protected rewrap(child: PDFElement, continued: boolean): PDFElement;
18
16
  getProps(): {
19
17
  name: string;
20
18
  child: PDFElement;
@@ -1,27 +1,19 @@
1
- import { PDFElement } from "../pdf-element.js";
1
+ import { ProxyElement } from "./proxy-element.js";
2
2
  /**
3
- * Marks its child as a named jump target for an internal `Link({ to: name })`. Layout-transparent: it
4
- * delegates `calculateLayout` to the child and records the child's top, which the renderer turns into an
5
- * `Anchor` IR node (and PDFRenderer into a /Names /Dests entry). It draws nothing - the child renders
6
- * exactly as it would on its own.
3
+ * Marks its child as a named jump target for an internal `Link({ to: name })`. Layout-transparent (a
4
+ * `ProxyElement`); the renderer turns the child's top into an `Anchor` IR node (and PDFRenderer into a
5
+ * /Names /Dests entry). It draws nothing. Split across pages, only the first piece is the target - a
6
+ * name must lead to one place.
7
7
  */
8
- export class AnchorElement extends PDFElement {
8
+ export class AnchorElement extends ProxyElement {
9
9
  constructor({ name, child }) {
10
- super();
11
- this.y = 0;
10
+ super(child);
12
11
  this.name = name;
13
- this.child = child;
14
12
  }
15
- calculateLayout(constraints, offset, ctx) {
16
- const size = this.child.calculateLayout(constraints, offset, ctx);
17
- this.y = offset.y; // the child's top - the scroll target for a link that jumps here
18
- return size;
13
+ rewrap(child, continued) {
14
+ return continued ? child : new AnchorElement({ name: this.name, child });
19
15
  }
20
16
  getProps() {
21
- return {
22
- name: this.name,
23
- child: this.child,
24
- y: this.y,
25
- };
17
+ return { name: this.name, child: this.child, y: this.y };
26
18
  }
27
19
  }
@@ -1,23 +1,21 @@
1
- import { PDFElement, LayoutContext } from "../pdf-element.js";
2
- import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
1
+ import { PDFElement } from "../pdf-element.js";
2
+ import { ProxyElement } from "./proxy-element.js";
3
3
  /**
4
- * Adds a document-outline entry (a bookmark) that jumps to its child. Layout-transparent: it delegates
5
- * `calculateLayout` to the child and records the child's top, which the renderer turns into an `Outline`
6
- * IR node (and PDFRenderer into a /Outlines tree entry). `title` is the label shown in the viewer's
7
- * bookmark panel; `level` (1-based, default 1) nests it under the nearest preceding smaller level. It
8
- * draws nothing - the child renders exactly as it would without the bookmark.
4
+ * Adds a document-outline entry (a bookmark) that jumps to its child. Layout-transparent (a
5
+ * `ProxyElement`); the renderer turns the child's top into an `Outline` IR node (and PDFRenderer into a
6
+ * /Outlines tree entry). `title` is the label shown in the viewer's bookmark panel; `level` (1-based,
7
+ * default 1) nests it under the nearest preceding smaller level. It draws nothing. Split across pages,
8
+ * only the first piece carries the entry - a chapter is listed once.
9
9
  */
10
- export declare class BookmarkElement extends PDFElement {
10
+ export declare class BookmarkElement extends ProxyElement {
11
11
  private title;
12
12
  private level;
13
- private child;
14
- private y;
15
13
  constructor({ title, level, child }: {
16
14
  title: string;
17
15
  level?: number;
18
16
  child: PDFElement;
19
17
  });
20
- calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
18
+ protected rewrap(child: PDFElement, continued: boolean): PDFElement;
21
19
  getProps(): {
22
20
  title: string;
23
21
  level: number;
@@ -1,30 +1,21 @@
1
- import { PDFElement } from "../pdf-element.js";
1
+ import { ProxyElement } from "./proxy-element.js";
2
2
  /**
3
- * Adds a document-outline entry (a bookmark) that jumps to its child. Layout-transparent: it delegates
4
- * `calculateLayout` to the child and records the child's top, which the renderer turns into an `Outline`
5
- * IR node (and PDFRenderer into a /Outlines tree entry). `title` is the label shown in the viewer's
6
- * bookmark panel; `level` (1-based, default 1) nests it under the nearest preceding smaller level. It
7
- * draws nothing - the child renders exactly as it would without the bookmark.
3
+ * Adds a document-outline entry (a bookmark) that jumps to its child. Layout-transparent (a
4
+ * `ProxyElement`); the renderer turns the child's top into an `Outline` IR node (and PDFRenderer into a
5
+ * /Outlines tree entry). `title` is the label shown in the viewer's bookmark panel; `level` (1-based,
6
+ * default 1) nests it under the nearest preceding smaller level. It draws nothing. Split across pages,
7
+ * only the first piece carries the entry - a chapter is listed once.
8
8
  */
9
- export class BookmarkElement extends PDFElement {
9
+ export class BookmarkElement extends ProxyElement {
10
10
  constructor({ title, level = 1, child }) {
11
- super();
12
- this.y = 0;
11
+ super(child);
13
12
  this.title = title;
14
13
  this.level = level;
15
- this.child = child;
16
14
  }
17
- calculateLayout(constraints, offset, ctx) {
18
- const size = this.child.calculateLayout(constraints, offset, ctx);
19
- this.y = offset.y; // the child's top - the scroll target for the bookmark
20
- return size;
15
+ rewrap(child, continued) {
16
+ return continued ? child : new BookmarkElement({ title: this.title, level: this.level, child });
21
17
  }
22
18
  getProps() {
23
- return {
24
- title: this.title,
25
- level: this.level,
26
- child: this.child,
27
- y: this.y,
28
- };
19
+ return { title: this.title, level: this.level, child: this.child, y: this.y };
29
20
  }
30
21
  }
@@ -1,29 +1,27 @@
1
- import { PDFElement, LayoutContext, WithChild, SizedPDFElement } from "../pdf-element.js";
2
- import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
3
- import { Fragmentable, FragmentResult } from "../../layout/fragmentation.js";
1
+ import { PDFElement, LayoutContext, WithChild } from "../pdf-element.js";
2
+ import { ProxyElement } from "./proxy-element.js";
4
3
  import { ResolvedTextStyle } from "../../text/text-style.js";
5
4
  interface DefaultTextStyleParams extends WithChild {
6
5
  style: Partial<ResolvedTextStyle>;
7
6
  }
8
7
  /**
9
8
  * Provides default text properties (font/size/color/lineHeight/align/weight) to its whole subtree -
10
- * Flutter's `DefaultTextStyle`. Transparent to layout: the child takes the same constraints, offset
11
- * and size; only the inherited TextStyle changes. A `Text` below still wins per property, and these
9
+ * Flutter's `DefaultTextStyle`. Transparent to layout (a `ProxyElement`): only the inherited
10
+ * TextStyle changes. A `Text` below still wins per property, and these
12
11
  * overrides layer onto whatever the element already inherited from above.
13
12
  */
14
- export declare class DefaultTextStyleElement extends SizedPDFElement implements Fragmentable {
15
- private child;
13
+ export declare class DefaultTextStyleElement extends ProxyElement {
16
14
  private style;
17
15
  constructor({ child, style }: DefaultTextStyleParams);
18
- private childCtx;
19
- fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
20
- private cloneWithChild;
21
- calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
16
+ protected childContext(ctx: LayoutContext): LayoutContext;
17
+ private merged?;
18
+ private mergedOnto;
19
+ protected rewrap(child: PDFElement): PDFElement;
22
20
  getProps(): {
23
21
  x: number;
24
22
  y: number;
25
- width: number | undefined;
26
- height: number | undefined;
23
+ width: number;
24
+ height: number;
27
25
  child: PDFElement;
28
26
  };
29
27
  }
@@ -1,46 +1,29 @@
1
- import { SizedPDFElement } from "../pdf-element.js";
2
- import { isFragmentable } from "../../layout/fragmentation.js";
1
+ import { ProxyElement } from "./proxy-element.js";
3
2
  import { DEFAULT_TEXT_STYLE, mergeTextStyle } from "../../text/text-style.js";
4
3
  /**
5
4
  * Provides default text properties (font/size/color/lineHeight/align/weight) to its whole subtree -
6
- * Flutter's `DefaultTextStyle`. Transparent to layout: the child takes the same constraints, offset
7
- * and size; only the inherited TextStyle changes. A `Text` below still wins per property, and these
5
+ * Flutter's `DefaultTextStyle`. Transparent to layout (a `ProxyElement`): only the inherited
6
+ * TextStyle changes. A `Text` below still wins per property, and these
8
7
  * overrides layer onto whatever the element already inherited from above.
9
8
  */
10
- export class DefaultTextStyleElement extends SizedPDFElement {
9
+ export class DefaultTextStyleElement extends ProxyElement {
11
10
  constructor({ child, style }) {
12
- super({ x: 0, y: 0 });
13
- this.child = child;
11
+ super(child);
14
12
  this.style = style;
15
13
  }
16
- childCtx(ctx) {
17
- return {
18
- ...ctx,
19
- textStyle: mergeTextStyle(ctx.textStyle ?? DEFAULT_TEXT_STYLE, this.style),
20
- };
14
+ childContext(ctx) {
15
+ return { ...ctx, textStyle: this.mergedOnto(ctx.textStyle ?? DEFAULT_TEXT_STYLE) };
21
16
  }
22
- // Transparent to fragmentation too: split the child against the merged context, re-wrapping each
23
- // half so the remainder on the next page keeps the same defaults.
24
- fragment(maxHeight, width, ctx) {
25
- if (!isFragmentable(this.child))
26
- return { fitted: null, remainder: this };
27
- const split = this.child.fragment(maxHeight, width, this.childCtx(ctx));
28
- return {
29
- fitted: split.fitted ? this.cloneWithChild(split.fitted) : null,
30
- remainder: split.remainder ? this.cloneWithChild(split.remainder) : null,
31
- };
17
+ mergedOnto(inherited) {
18
+ if (this.merged?.onto !== inherited) {
19
+ this.merged = { onto: inherited, style: mergeTextStyle(inherited, this.style) };
20
+ }
21
+ return this.merged.style;
32
22
  }
33
- cloneWithChild(child) {
23
+ // Each piece of a split keeps the defaults, so the remainder on the next page looks the same.
24
+ rewrap(child) {
34
25
  return new DefaultTextStyleElement({ child, style: this.style });
35
26
  }
36
- calculateLayout(constraints, offset, ctx) {
37
- this.x = offset.x;
38
- this.y = offset.y;
39
- const size = this.child.calculateLayout(constraints, offset, this.childCtx(ctx));
40
- this.width = size.width;
41
- this.height = size.height;
42
- return size;
43
- }
44
27
  getProps() {
45
28
  return { x: this.x, y: this.y, width: this.width, height: this.height, child: this.child };
46
29
  }
@@ -2,17 +2,25 @@ import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
2
2
  import { Fragmentable, FragmentResult } from "../../layout/fragmentation.js";
3
3
  import { LayoutContext, PDFElement } from "../pdf-element.js";
4
4
  /**
5
- * Builds its subtree at layout time via `resolve(ctx)`, so the tree can depend on font
6
- * metrics (e.g. a Table resolving `"auto"` column widths from cell content). The engine
7
- * stays table-agnostic - the closure comes from the API layer.
5
+ * Builds its subtree at layout time via `resolve(ctx, width)`, so the tree can depend on font
6
+ * metrics and on the width it gets (e.g. a Table fitting its `"auto"` and `fr` columns to the cell
7
+ * content and its own width). The engine stays table-agnostic - the closure comes from the API layer.
8
8
  */
9
9
  export declare class DeferredElement extends PDFElement implements Fragmentable {
10
10
  private resolve;
11
11
  private composed?;
12
- constructor(resolve: (ctx: LayoutContext) => PDFElement);
12
+ private built;
13
+ constructor(resolve: (ctx: LayoutContext, width: number) => PDFElement);
14
+ /** The subtree for this context and width: built once, and again only when one of them changes. */
15
+ private subtree;
13
16
  private build;
14
17
  calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
18
+ /** Its subtree is unknown until built, and could hold a `PageBuilder`; until then assume it does. */
19
+ dependsOnPage(): boolean;
20
+ /** Sizes the subtree for this context - the same one a layout under it will draw. */
21
+ protected computeSize(constraints: BoxConstraints, ctx: LayoutContext): Size;
15
22
  fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
23
+ layoutChildren(): readonly PDFElement[];
16
24
  getProps(): {
17
25
  composed: PDFElement | undefined;
18
26
  };
@@ -1,28 +1,45 @@
1
1
  import { isFragmentable } from "../../layout/fragmentation.js";
2
2
  import { PDFElement } from "../pdf-element.js";
3
+ import { BuiltFromContext } from "../../layout/layout-cache.js";
3
4
  /**
4
- * Builds its subtree at layout time via `resolve(ctx)`, so the tree can depend on font
5
- * metrics (e.g. a Table resolving `"auto"` column widths from cell content). The engine
6
- * stays table-agnostic - the closure comes from the API layer.
5
+ * Builds its subtree at layout time via `resolve(ctx, width)`, so the tree can depend on font
6
+ * metrics and on the width it gets (e.g. a Table fitting its `"auto"` and `fr` columns to the cell
7
+ * content and its own width). The engine stays table-agnostic - the closure comes from the API layer.
7
8
  */
8
9
  export class DeferredElement extends PDFElement {
9
10
  constructor(resolve) {
10
11
  super();
11
12
  this.resolve = resolve;
13
+ this.built = new BuiltFromContext();
12
14
  }
13
- build(ctx) {
14
- this.composed = this.resolve(ctx);
15
+ /** The subtree for this context and width: built once, and again only when one of them changes. */
16
+ subtree(ctx, width) {
17
+ return this.built.get(ctx, width, () => this.resolve(ctx, width));
18
+ }
19
+ build(ctx, width) {
20
+ this.composed = this.subtree(ctx, width);
15
21
  return this.composed;
16
22
  }
17
23
  calculateLayout(constraints, offset, ctx) {
18
- return this.build(ctx).calculateLayout(constraints, offset, ctx);
24
+ return this.build(ctx, constraints.maxWidth).calculateLayout(constraints, offset, ctx);
25
+ }
26
+ /** Its subtree is unknown until built, and could hold a `PageBuilder`; until then assume it does. */
27
+ dependsOnPage() {
28
+ return this.composed?.dependsOnPage() ?? true;
29
+ }
30
+ /** Sizes the subtree for this context - the same one a layout under it will draw. */
31
+ computeSize(constraints, ctx) {
32
+ return this.subtree(ctx, constraints.maxWidth).measure(constraints, ctx);
19
33
  }
20
34
  fragment(maxHeight, width, ctx) {
21
- const c = this.build(ctx);
35
+ const c = this.build(ctx, width);
22
36
  return isFragmentable(c)
23
37
  ? c.fragment(maxHeight, width, ctx)
24
38
  : { fitted: this, remainder: null };
25
39
  }
40
+ layoutChildren() {
41
+ return this.composed ? [this.composed] : [];
42
+ }
26
43
  getProps() {
27
44
  return { composed: this.composed };
28
45
  }
@@ -13,6 +13,9 @@ export declare class ExpandedElement extends FlexiblePDFElement implements Fragm
13
13
  /** Layout-transparent: the floor is whatever the wrapped child needs. */
14
14
  minIntrinsicMain(horizontal: boolean, ctx: LayoutContext): number;
15
15
  calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
16
+ protected computeSize(constraints: BoxConstraints, ctx: LayoutContext): Size;
17
+ /** The one sizing path: with an `offset` the child is laid out there, with `null` only measured. */
18
+ private sizeWith;
16
19
  /**
17
20
  * When the column paginates, a flex region can't "fill" across pages, so it delegates to
18
21
  * its child: the child splits and each half is re-wrapped in an Expanded (which then
@@ -20,7 +23,11 @@ export declare class ExpandedElement extends FlexiblePDFElement implements Fragm
20
23
  * whole Expanded moves on.
21
24
  */
22
25
  fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
26
+ hasForcedBreak(): boolean;
27
+ breaksBefore(): boolean;
28
+ breaksAfter(): boolean;
23
29
  private cloneWithChild;
30
+ layoutChildren(): readonly PDFElement[];
24
31
  getProps(): {
25
32
  x: number;
26
33
  y: number;