@jasy/pdf 1.0.0-beta.3 → 1.0.0-beta.5

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 (246) hide show
  1. package/dist/api/color.d.ts +1 -24
  2. package/dist/api/color.js +2 -278
  3. package/dist/api/content.d.ts +10 -5
  4. package/dist/api/content.js +29 -13
  5. package/dist/api/descriptor.js +30 -3
  6. package/dist/api/flow.d.ts +55 -0
  7. package/dist/api/flow.js +71 -0
  8. package/dist/api/font-url.d.ts +1 -2
  9. package/dist/api/font-url.js +8 -74
  10. package/dist/api/forms.d.ts +3 -0
  11. package/dist/api/forms.js +3 -0
  12. package/dist/api/gradient.d.ts +1 -53
  13. package/dist/api/gradient.js +2 -97
  14. package/dist/api/index.d.ts +4 -0
  15. package/dist/api/index.js +3 -0
  16. package/dist/api/layout.d.ts +6 -0
  17. package/dist/api/layout.js +9 -3
  18. package/dist/api/page-builder.d.ts +4 -0
  19. package/dist/api/page-builder.js +4 -0
  20. package/dist/api/structure.d.ts +10 -2
  21. package/dist/api/structure.js +9 -6
  22. package/dist/api/table.d.ts +5 -3
  23. package/dist/api/table.js +101 -22
  24. package/dist/api/text.d.ts +8 -8
  25. package/dist/api/text.js +6 -0
  26. package/dist/canvas/painter.d.ts +2 -2
  27. package/dist/canvas/painter.js +2 -2
  28. package/dist/common/color-input.d.ts +24 -0
  29. package/dist/common/color-input.js +278 -0
  30. package/dist/common/diagnostics.d.ts +61 -0
  31. package/dist/common/diagnostics.js +77 -0
  32. package/dist/common/gradient.d.ts +53 -0
  33. package/dist/common/gradient.js +97 -0
  34. package/dist/edit/acroform-reader.js +7 -7
  35. package/dist/edit/appearance.js +3 -3
  36. package/dist/edit/attachments.d.ts +52 -0
  37. package/dist/edit/attachments.js +178 -0
  38. package/dist/edit/document.d.ts +38 -8
  39. package/dist/edit/document.js +208 -51
  40. package/dist/edit/fill.js +8 -23
  41. package/dist/edit/filters.d.ts +15 -0
  42. package/dist/edit/filters.js +157 -0
  43. package/dist/edit/index.d.ts +4 -1
  44. package/dist/edit/index.js +3 -1
  45. package/dist/edit/objects.d.ts +3 -4
  46. package/dist/edit/objects.js +5 -18
  47. package/dist/elements/canvas-element.d.ts +3 -0
  48. package/dist/elements/canvas-element.js +14 -3
  49. package/dist/elements/container-element.d.ts +8 -1
  50. package/dist/elements/container-element.js +49 -17
  51. package/dist/elements/forms/checkbox-element.d.ts +2 -0
  52. package/dist/elements/forms/checkbox-element.js +12 -3
  53. package/dist/elements/forms/choice-element.d.ts +6 -1
  54. package/dist/elements/forms/choice-element.js +17 -6
  55. package/dist/elements/forms/field-font.d.ts +8 -0
  56. package/dist/elements/forms/field-font.js +5 -0
  57. package/dist/elements/forms/push-button-element.d.ts +6 -1
  58. package/dist/elements/forms/push-button-element.js +16 -4
  59. package/dist/elements/forms/radio-element.d.ts +2 -0
  60. package/dist/elements/forms/radio-element.js +12 -3
  61. package/dist/elements/forms/signature-element.d.ts +6 -1
  62. package/dist/elements/forms/signature-element.js +16 -4
  63. package/dist/elements/forms/text-field-element.d.ts +6 -1
  64. package/dist/elements/forms/text-field-element.js +16 -4
  65. package/dist/elements/image-element.d.ts +70 -21
  66. package/dist/elements/image-element.js +173 -52
  67. package/dist/elements/index.d.ts +3 -0
  68. package/dist/elements/index.js +3 -0
  69. package/dist/elements/layout/anchor-element.d.ts +8 -10
  70. package/dist/elements/layout/anchor-element.js +10 -18
  71. package/dist/elements/layout/bookmark-element.d.ts +9 -11
  72. package/dist/elements/layout/bookmark-element.js +11 -20
  73. package/dist/elements/layout/column-break-element.d.ts +17 -0
  74. package/dist/elements/layout/column-break-element.js +26 -0
  75. package/dist/elements/layout/default-text-style-element.d.ts +11 -13
  76. package/dist/elements/layout/default-text-style-element.js +14 -31
  77. package/dist/elements/layout/deferred-element.d.ts +20 -4
  78. package/dist/elements/layout/deferred-element.js +41 -9
  79. package/dist/elements/layout/expanded-element.d.ts +11 -0
  80. package/dist/elements/layout/expanded-element.js +53 -18
  81. package/dist/elements/layout/flow-element.d.ts +109 -0
  82. package/dist/elements/layout/flow-element.js +317 -0
  83. package/dist/elements/layout/keep-together-element.d.ts +8 -18
  84. package/dist/elements/layout/keep-together-element.js +33 -45
  85. package/dist/elements/layout/link-element.d.ts +8 -13
  86. package/dist/elements/layout/link-element.js +9 -19
  87. package/dist/elements/layout/padding-element.d.ts +18 -1
  88. package/dist/elements/layout/padding-element.js +63 -28
  89. package/dist/elements/layout/page-break-element.d.ts +4 -1
  90. package/dist/elements/layout/page-break-element.js +6 -1
  91. package/dist/elements/layout/page-builder-element.d.ts +5 -0
  92. package/dist/elements/layout/page-builder-element.js +13 -2
  93. package/dist/elements/layout/positioned-element.d.ts +23 -4
  94. package/dist/elements/layout/positioned-element.js +42 -12
  95. package/dist/elements/layout/proxy-element.d.ts +47 -0
  96. package/dist/elements/layout/proxy-element.js +106 -0
  97. package/dist/elements/layout/region-element.d.ts +53 -0
  98. package/dist/elements/layout/region-element.js +108 -0
  99. package/dist/elements/layout/repeating-header-element.d.ts +9 -0
  100. package/dist/elements/layout/repeating-header-element.js +55 -13
  101. package/dist/elements/layout/rotated-box-element.d.ts +6 -0
  102. package/dist/elements/layout/rotated-box-element.js +27 -14
  103. package/dist/elements/layout/rotated-element.d.ts +9 -0
  104. package/dist/elements/layout/rotated-element.js +33 -2
  105. package/dist/elements/layout/struct-group.d.ts +9 -17
  106. package/dist/elements/layout/struct-group.js +10 -35
  107. package/dist/elements/line-element.d.ts +1 -0
  108. package/dist/elements/line-element.js +6 -0
  109. package/dist/elements/page-element.d.ts +10 -3
  110. package/dist/elements/page-element.js +39 -31
  111. package/dist/elements/pdf-document-element.d.ts +3 -0
  112. package/dist/elements/pdf-document-element.js +7 -0
  113. package/dist/elements/pdf-element.d.ts +105 -16
  114. package/dist/elements/pdf-element.js +118 -19
  115. package/dist/elements/rectangle-element.d.ts +22 -2
  116. package/dist/elements/rectangle-element.js +119 -55
  117. package/dist/elements/row-element.d.ts +17 -12
  118. package/dist/elements/row-element.js +89 -9
  119. package/dist/elements/svg-element.d.ts +30 -6
  120. package/dist/elements/svg-element.js +91 -26
  121. package/dist/elements/text-element.d.ts +48 -13
  122. package/dist/elements/text-element.js +161 -116
  123. package/dist/forms/acroform.d.ts +9 -1
  124. package/dist/forms/acroform.js +83 -34
  125. package/dist/forms/appearance.d.ts +17 -6
  126. package/dist/forms/appearance.js +18 -16
  127. package/dist/ir/display-list.d.ts +18 -3
  128. package/dist/layout/box-constraints.d.ts +17 -0
  129. package/dist/layout/box-constraints.js +31 -0
  130. package/dist/layout/collect-images.d.ts +6 -4
  131. package/dist/layout/collect-images.js +14 -8
  132. package/dist/layout/flex-item.d.ts +11 -0
  133. package/dist/layout/flex-item.js +51 -0
  134. package/dist/layout/fragmentation.d.ts +23 -9
  135. package/dist/layout/fragmentation.js +62 -19
  136. package/dist/layout/layout-cache.d.ts +53 -0
  137. package/dist/layout/layout-cache.js +110 -0
  138. package/dist/platform/browser-fs.d.ts +1 -2
  139. package/dist/platform/browser-fs.js +3 -6
  140. package/dist/platform/node-fs.d.ts +0 -1
  141. package/dist/platform/node-fs.js +0 -4
  142. package/dist/renderer/anchor-renderer.d.ts +2 -1
  143. package/dist/renderer/anchor-renderer.js +5 -4
  144. package/dist/renderer/bookmark-renderer.d.ts +2 -1
  145. package/dist/renderer/bookmark-renderer.js +5 -4
  146. package/dist/renderer/canvas-renderer.d.ts +2 -1
  147. package/dist/renderer/canvas-renderer.js +5 -2
  148. package/dist/renderer/column-break-renderer.d.ts +8 -0
  149. package/dist/renderer/column-break-renderer.js +15 -0
  150. package/dist/renderer/container-renderer.d.ts +2 -1
  151. package/dist/renderer/container-renderer.js +6 -5
  152. package/dist/renderer/default-text-style-renderer.d.ts +2 -1
  153. package/dist/renderer/default-text-style-renderer.js +2 -3
  154. package/dist/renderer/deferred-renderer.d.ts +2 -1
  155. package/dist/renderer/deferred-renderer.js +2 -3
  156. package/dist/renderer/emoji-image.js +3 -1
  157. package/dist/renderer/expanded-renderer.d.ts +2 -1
  158. package/dist/renderer/expanded-renderer.js +2 -3
  159. package/dist/renderer/flow-renderer.d.ts +16 -0
  160. package/dist/renderer/flow-renderer.js +21 -0
  161. package/dist/renderer/form-field-renderer.d.ts +2 -1
  162. package/dist/renderer/form-field-renderer.js +16 -3
  163. package/dist/renderer/image-renderer.d.ts +2 -1
  164. package/dist/renderer/image-renderer.js +33 -53
  165. package/dist/renderer/index.d.ts +2 -0
  166. package/dist/renderer/index.js +2 -0
  167. package/dist/renderer/keep-together-renderer.d.ts +2 -1
  168. package/dist/renderer/keep-together-renderer.js +2 -3
  169. package/dist/renderer/line-renderer.d.ts +2 -1
  170. package/dist/renderer/line-renderer.js +7 -2
  171. package/dist/renderer/link-renderer.d.ts +2 -1
  172. package/dist/renderer/link-renderer.js +36 -5
  173. package/dist/renderer/padding-renderer.d.ts +2 -1
  174. package/dist/renderer/padding-renderer.js +5 -5
  175. package/dist/renderer/page-break-renderer.d.ts +6 -5
  176. package/dist/renderer/page-break-renderer.js +14 -7
  177. package/dist/renderer/page-builder-renderer.d.ts +2 -1
  178. package/dist/renderer/page-builder-renderer.js +2 -3
  179. package/dist/renderer/page-renderer.js +30 -10
  180. package/dist/renderer/pdf-backend.d.ts +4 -3
  181. package/dist/renderer/pdf-backend.js +14 -7
  182. package/dist/renderer/pdf-document-renderer.d.ts +1 -8
  183. package/dist/renderer/pdf-document-renderer.js +17 -17
  184. package/dist/renderer/pdf-renderer.js +20 -11
  185. package/dist/renderer/positioned-renderer.d.ts +18 -1
  186. package/dist/renderer/positioned-renderer.js +43 -6
  187. package/dist/renderer/rectangle-renderer.d.ts +2 -1
  188. package/dist/renderer/rectangle-renderer.js +18 -8
  189. package/dist/renderer/repeating-header-renderer.d.ts +2 -1
  190. package/dist/renderer/repeating-header-renderer.js +6 -5
  191. package/dist/renderer/rotated-renderer.d.ts +2 -1
  192. package/dist/renderer/rotated-renderer.js +9 -5
  193. package/dist/renderer/row-renderer.d.ts +2 -1
  194. package/dist/renderer/row-renderer.js +6 -5
  195. package/dist/renderer/struct-group-renderer.d.ts +2 -1
  196. package/dist/renderer/struct-group-renderer.js +2 -3
  197. package/dist/renderer/svg-renderer.d.ts +2 -1
  198. package/dist/renderer/svg-renderer.js +8 -2
  199. package/dist/renderer/text-renderer.d.ts +4 -6
  200. package/dist/renderer/text-renderer.js +114 -127
  201. package/dist/svg/gradients.js +5 -0
  202. package/dist/svg/index.d.ts +1 -1
  203. package/dist/svg/index.js +36 -6
  204. package/dist/svg/style.d.ts +2 -0
  205. package/dist/svg/style.js +12 -1
  206. package/dist/text/advance.d.ts +4 -0
  207. package/dist/text/advance.js +17 -4
  208. package/dist/text/bidi.d.ts +0 -2
  209. package/dist/text/bidi.js +0 -10
  210. package/dist/text/glyph-coverage.d.ts +1 -1
  211. package/dist/text/glyph-coverage.js +7 -3
  212. package/dist/text/hyphen-breaks.d.ts +17 -0
  213. package/dist/text/hyphen-breaks.js +54 -0
  214. package/dist/text/line-breaker.d.ts +55 -29
  215. package/dist/text/line-breaker.js +453 -188
  216. package/dist/text/line-metrics.d.ts +8 -3
  217. package/dist/text/line-metrics.js +11 -2
  218. package/dist/text/pdf-text-string.d.ts +21 -0
  219. package/dist/text/pdf-text-string.js +114 -0
  220. package/dist/text/text-decoration.d.ts +5 -5
  221. package/dist/text/text-decoration.js +5 -5
  222. package/dist/text/word-splitting.d.ts +15 -2
  223. package/dist/text/word-splitting.js +45 -13
  224. package/dist/utils/afm-parser.d.ts +2 -0
  225. package/dist/utils/afm-parser.js +10 -1
  226. package/dist/utils/dest-registry.d.ts +1 -1
  227. package/dist/utils/dest-registry.js +3 -2
  228. package/dist/utils/fetch-bytes.d.ts +19 -0
  229. package/dist/utils/fetch-bytes.js +79 -0
  230. package/dist/utils/flex-layout.d.ts +16 -14
  231. package/dist/utils/flex-layout.js +427 -217
  232. package/dist/utils/font-metrics.d.ts +4 -3
  233. package/dist/utils/image-helper.d.ts +59 -11
  234. package/dist/utils/image-helper.js +209 -81
  235. package/dist/utils/pdf-object-manager.d.ts +17 -6
  236. package/dist/utils/pdf-object-manager.js +70 -20
  237. package/dist/utils/renderer-registry.d.ts +49 -2
  238. package/dist/utils/renderer-registry.js +40 -0
  239. package/dist/utils/struct-tree.d.ts +24 -0
  240. package/dist/utils/struct-tree.js +61 -9
  241. package/dist/utils/ttf-parser.d.ts +4 -0
  242. package/dist/utils/ttf-parser.js +30 -6
  243. package/dist/validators/element-validator.js +2 -1
  244. package/package.json +1 -1
  245. package/dist/elements/layout/sized-container-element.d.ts +0 -14
  246. package/dist/elements/layout/sized-container-element.js +0 -32
@@ -18,7 +18,7 @@ export class PDFDocumentRenderer {
18
18
  // `Page` element produces one run of sheets, so the run's length is that section's total.
19
19
  const section = [];
20
20
  for (const page of document.getProps().children) {
21
- const sheets = PDFDocumentRenderer.paginateLogicalPage(page, ctx);
21
+ const sheets = PDFDocumentRenderer.paginateLogicalPage(page, ctx, physicalPages.length + 1);
22
22
  sheets.forEach((_, i) => section.push({ number: i + 1, total: sheets.length }));
23
23
  physicalPages.push(...sheets);
24
24
  }
@@ -47,17 +47,11 @@ export class PDFDocumentRenderer {
47
47
  objectManager.replaceObject(pagesObjectNumber, updatedPagesObject);
48
48
  return pagesObjectNumber;
49
49
  }
50
- /**
51
- * The page geometry a physical page will use. `PageElement.calculateLayout` has already folded the
52
- * document defaults into every page's config by the time we get here (`PDFRenderer` lays the document
53
- * out before it calls us, and it is our only caller). Merging again is therefore a no-op today - it is
54
- * here so this stays correct BY CONSTRUCTION rather than by call order: were that order ever changed,
55
- * an unmerged config would otherwise make `resolvePageSize` fall back to A4 and quietly report the
56
- * wrong page size.
57
- */
50
+ /** The page geometry a physical page will use: resolved, never read back from an earlier layout. */
58
51
  static configOf(physical, ctx) {
59
- const config = physical.kind === "whole" ? physical.page.getProps().config : physical.config;
60
- return { ...ctx.pageConfig, ...config };
52
+ return physical.kind === "whole"
53
+ ? physical.page.resolveConfig(ctx.pageConfig)
54
+ : physical.config;
61
55
  }
62
56
  /**
63
57
  * Splits one logical page into the physical pages it will occupy, without rendering.
@@ -66,13 +60,15 @@ export class PDFDocumentRenderer {
66
60
  * (a Container). Anything else stays a single page on the unchanged path, so non-overflowing
67
61
  * documents keep producing byte-identical output.
68
62
  */
69
- static paginateLogicalPage(page, ctx) {
70
- const { children, config, header, footer } = page.getProps();
63
+ static paginateLogicalPage(page, ctx, firstPage) {
64
+ const { children, header, footer } = page.getProps();
65
+ // Resolved here, not read back from an earlier layout pass that happened to merge it.
66
+ const config = page.resolveConfig(ctx.pageConfig);
71
67
  if (children.length !== 1 || !isFragmentable(children[0])) {
72
68
  return [{ kind: "whole", page }];
73
69
  }
74
70
  // Header/footer repeat on every physical page, so the body only ever flows into the
75
- // band between them. Resolve that band once (config is already merged by pass 1).
71
+ // band between them. Resolve that band once.
76
72
  // The frame is a throwaway: this pass only wants the band HEIGHTS, and an out-of-flow child
77
73
  // contributes none. It exists because a `Positioned` in a band demands one; pass 2 builds the
78
74
  // real frame (in `PageElement`) and drains it.
@@ -81,7 +77,9 @@ export class PDFDocumentRenderer {
81
77
  pageConfig: config,
82
78
  textStyle: ctx.textStyle,
83
79
  onOverflow: ctx.onOverflow,
80
+ diagnostics: ctx.diagnostics,
84
81
  frame: pageFrame(config),
82
+ paginatingPage: firstPage, // the bands are laid out here once, for the first sheet
85
83
  };
86
84
  const { bodyWidth: width, bodyHeight: height } = layoutPageBands(config, header, footer, pageCtx);
87
85
  // A `keepTogether` group compares its height against a FULL fresh page (this band), so thread it in.
@@ -97,13 +95,15 @@ export class PDFDocumentRenderer {
97
95
  }
98
96
  // pageCtx, not the document ctx: a descendant that reads `pageConfig` while being measured (a
99
97
  // `PageBuilder` sizing its provisional build) must see THIS page's geometry, not the document default.
98
+ pageCtx.paginatingPage = firstPage + pages.length;
100
99
  const { fitted, remainder, forceBreak } = region.fragment(height, width, pageCtx);
101
100
  // Everything fits on one page: keep the ORIGINAL page so output is unchanged. Measuring inside
102
101
  // fragment() left its children at the measuring origin; pass B lays the page out again, which
103
102
  // restores their real positions (layout is deterministic). A consumed trailing `PageBreak`
104
103
  // (forceBreak with no remainder) is the exception: render the packed `fitted` instead, which no
105
104
  // longer holds the break marker, so the original with the break does not leak into the render pass.
106
- if (isFirstRegion && remainder === null && !forceBreak) {
105
+ // So is a region that came back as a sized piece (a Flow): it is drawn as it was paginated.
106
+ if (isFirstRegion && remainder === null && !forceBreak && fitted === region) {
107
107
  pages.push({ kind: "whole", page });
108
108
  break;
109
109
  }
@@ -115,8 +115,8 @@ export class PDFDocumentRenderer {
115
115
  if (fitted === null) {
116
116
  // Measure the region's true height for the message (rare path, so the extra pass is free);
117
117
  // pass B lays it out again, so this mutation does not leak.
118
- const needed = region.calculateLayout(BoxConstraints.loose(width, Infinity), { x: 0, y: 0 }, pageCtx).height;
119
- reportOverflow(region, needed, height, pageCtx.onOverflow ?? "ignore");
118
+ const needed = region.measure(BoxConstraints.loose(width, Infinity), pageCtx).height;
119
+ reportOverflow(region, needed, height, pageCtx);
120
120
  pages.push({ kind: "fragment", config, content: region, header, footer });
121
121
  break;
122
122
  }
@@ -1,3 +1,4 @@
1
+ import { comparePdfText } from "../text/pdf-text-string.js";
1
2
  import { PDFDocumentRenderer } from "./pdf-document-renderer.js";
2
3
  import { RendererRegistry } from "../utils/renderer-registry.js";
3
4
  import { DefaultTextStyleElement, ExpandedElement, ImageElement, LineElement, CanvasElement, PaddingElement, SvgElement, TextElement, } from "../elements/index.js";
@@ -42,11 +43,14 @@ import { SignatureFieldElement } from "../elements/forms/signature-element.js";
42
43
  import { FormFieldRenderer } from "./form-field-renderer.js";
43
44
  import { PageBuilderElement } from "../elements/layout/page-builder-element.js";
44
45
  import { PageBuilderRenderer } from "./page-builder-renderer.js";
46
+ import { FlowElement, FlowPieceElement } from "../elements/layout/flow-element.js";
47
+ import { RegionElement } from "../elements/layout/region-element.js";
48
+ import { FlowPieceRenderer, FlowRenderer, RegionRenderer } from "./flow-renderer.js";
49
+ import { ColumnBreakElement } from "../elements/layout/column-break-element.js";
50
+ import { ColumnBreakRenderer } from "./column-break-renderer.js";
45
51
  import { RotatedElement } from "../elements/layout/rotated-element.js";
46
52
  import { RotatedBoxElement } from "../elements/layout/rotated-box-element.js";
47
53
  import { RotatedRenderer } from "./rotated-renderer.js";
48
- import { PdfBackend } from "./pdf-backend.js";
49
- import { BoxConstraints } from "../layout/box-constraints.js";
50
54
  import { collectImageElements } from "../layout/collect-images.js";
51
55
  import { DEFAULT_TEXT_STYLE, mergeTextStyle } from "../text/text-style.js";
52
56
  export class PDFRenderer {
@@ -79,27 +83,31 @@ export class PDFRenderer {
79
83
  RendererRegistry.register(BookmarkElement, BookmarkRenderer.render);
80
84
  RendererRegistry.register(AnchorElement, AnchorRenderer.render);
81
85
  RendererRegistry.register(PageBuilderElement, PageBuilderRenderer.render);
86
+ RendererRegistry.register(FlowElement, FlowRenderer.render);
87
+ RendererRegistry.register(FlowPieceElement, FlowPieceRenderer.render);
88
+ RendererRegistry.register(RegionElement, RegionRenderer.render);
89
+ RendererRegistry.register(ColumnBreakElement, ColumnBreakRenderer.render);
82
90
  RendererRegistry.register(RotatedElement, RotatedRenderer.render);
83
91
  RendererRegistry.register(RotatedBoxElement, RotatedRenderer.render);
84
92
  let pdfContent = "";
85
93
  // Header: version line + the PDF/A binary marker (the object manager owns it so its length
86
94
  // matches the xref offset calculation).
87
95
  pdfContent += objectManager.getHeader();
88
- // Layout pass: thread the context explicitly. The seed page config is the document
89
- // default; each PageElement overrides it for its own subtree.
96
+ // The layout context, threaded explicitly. The seed page config is the document default; each
97
+ // PageElement overrides it for its own subtree.
90
98
  const ctx = {
91
99
  metrics: objectManager,
92
100
  pageConfig: objectManager.getPDFConfig(),
93
101
  textStyle: mergeTextStyle(DEFAULT_TEXT_STYLE, document.getDefaultTextStyle()),
94
102
  onOverflow: objectManager.getOverflowPolicy(),
103
+ diagnostics: objectManager.diagnostics,
95
104
  };
96
105
  // Pre-layout: resolve every image's intrinsic pixel size (async), so the synchronous layout can
97
106
  // give a width-only image a proportional height. init() is idempotent, so the renderer reuses it.
98
107
  const images = collectImageElements(document);
99
108
  if (images.length > 0)
100
109
  await Promise.all(images.map((img) => img.resolveIntrinsicSize()));
101
- document.calculateLayout(new BoxConstraints(), { x: 0, y: 0 }, ctx);
102
- // Render pages and contents (the driver paginates overflowing pages).
110
+ // Paginate, then lay out and render page by page (the driver does both; nothing is laid out before).
103
111
  await PDFDocumentRenderer.render(document, objectManager, ctx);
104
112
  // Add the catalog. XMP metadata (/Metadata) and embedded files (/AF + /Names/EmbeddedFiles)
105
113
  // are added only when present; with neither, the catalog is byte-identical to before.
@@ -118,11 +126,12 @@ export class PDFRenderer {
118
126
  const namesParts = [];
119
127
  const attachments = objectManager.getAttachments();
120
128
  if (attachments.length > 0) {
121
- // The name-tree key is a PDF literal string, so it needs the same escaping the /Filespec's /F and
122
- // /UF get in attachFile(). Unescaped, a ")" or "\" in a file name closes the string early and the
123
- // rest of it leaks out as raw operators.
124
- const names = attachments
125
- .map((a) => `(${PdfBackend.escapePdfString(a.name)}) ${a.filespec} 0 R`)
129
+ // The name-tree key is a text string like the /Filespec's /F and /UF, so it goes through the same
130
+ // choke point - encoded and, in an encrypted file, enciphered. A name tree is SORTED by its keys
131
+ // (a reader may search it), so the keys are ordered by their bytes first.
132
+ const names = [...attachments]
133
+ .sort((a, b) => comparePdfText(a.name, b.name))
134
+ .map((a) => `${objectManager.pdfString(a.name)} ${a.filespec} 0 R`)
126
135
  .join(" ");
127
136
  const af = attachments.map((a) => `${a.filespec} 0 R`).join(" ");
128
137
  catalogParts.push(`/AF [${af}]`);
@@ -1,6 +1,23 @@
1
1
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
2
  import { PositionedElement } from "../elements/layout/positioned-element.js";
3
+ import type { PDFElement } from "../elements/pdf-element.js";
4
+ import { type RenderScope } from "../utils/renderer-registry.js";
3
5
  import { IRNode } from "../ir/display-list.js";
4
6
  export declare class PositionedRenderer {
5
- static render(positionedElement: PositionedElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
7
+ /**
8
+ * Rendered at its own place in the tree, from its frame: the child was laid out relative to the
9
+ * frame's top-left, not to the flow it sits in. Without a `zIndex` that place is also where it paints.
10
+ * With one it hands its drawing to the nearest stacking context (`paintStacked`), which sorts it - so
11
+ * tagging and everything else registered while rendering still sees the tree it stands in.
12
+ */
13
+ static render(positionedElement: PositionedElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
14
+ /**
15
+ * Paints the children of a stacking context (a frame, a clipping Box, a rotation, the page): the
16
+ * `Positioned` below it with a negative `zIndex` first, then the children in tree order, then the
17
+ * positive ones - lowest first, ties in tree order. Without a `zIndex` below it, this is the children
18
+ * in order.
19
+ */
20
+ static paintStacked(children: readonly PDFElement[], objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
21
+ /** Children in tree order - what any element that is not a stacking context draws. */
22
+ static paintInOrder(children: readonly PDFElement[], objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
6
23
  }
@@ -1,10 +1,47 @@
1
1
  import { RendererRegistry } from "../utils/renderer-registry.js";
2
2
  export class PositionedRenderer {
3
- static async render(positionedElement, objectManager) {
4
- // The child was already placed (by the frame) during layout; just emit its IR. It is drawn
5
- // after the frame's flow content, so it paints on top.
6
- const { child } = positionedElement.getProps();
7
- const renderer = RendererRegistry.getRenderer(child);
8
- return renderer(child, objectManager);
3
+ /**
4
+ * Rendered at its own place in the tree, from its frame: the child was laid out relative to the
5
+ * frame's top-left, not to the flow it sits in. Without a `zIndex` that place is also where it paints.
6
+ * With one it hands its drawing to the nearest stacking context (`paintStacked`), which sorts it - so
7
+ * tagging and everything else registered while rendering still sees the tree it stands in.
8
+ */
9
+ static async render(positionedElement, objectManager, scope) {
10
+ const { child, zIndex } = positionedElement.getProps();
11
+ const render = () => RendererRegistry.render(child, objectManager, { ...scope, origin: scope.frame });
12
+ if (zIndex === 0 || !scope.stacked)
13
+ return render();
14
+ // Its place in the stack is taken BEFORE its child renders: a nested Positioned with the same
15
+ // zIndex hands its drawing in during that, and must come after its outer one, in tree order.
16
+ const entry = { zIndex, nodes: [] };
17
+ scope.stacked.push(entry);
18
+ entry.nodes = await render();
19
+ return [];
20
+ }
21
+ /**
22
+ * Paints the children of a stacking context (a frame, a clipping Box, a rotation, the page): the
23
+ * `Positioned` below it with a negative `zIndex` first, then the children in tree order, then the
24
+ * positive ones - lowest first, ties in tree order. Without a `zIndex` below it, this is the children
25
+ * in order.
26
+ */
27
+ static async paintStacked(children, objectManager, scope) {
28
+ const stacked = [];
29
+ const flow = await PositionedRenderer.paintInOrder(children, objectManager, {
30
+ ...scope,
31
+ stacked,
32
+ });
33
+ stacked.sort((a, b) => a.zIndex - b.zIndex); // stable: ties keep tree order
34
+ return [
35
+ ...stacked.filter((s) => s.zIndex < 0).flatMap((s) => s.nodes),
36
+ ...flow,
37
+ ...stacked.filter((s) => s.zIndex > 0).flatMap((s) => s.nodes),
38
+ ];
39
+ }
40
+ /** Children in tree order - what any element that is not a stacking context draws. */
41
+ static async paintInOrder(children, objectManager, scope) {
42
+ const nodes = [];
43
+ for (const child of children)
44
+ nodes.push(...(await RendererRegistry.render(child, objectManager, scope)));
45
+ return nodes;
9
46
  }
10
47
  }
@@ -1,8 +1,9 @@
1
1
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
2
  import { RectangleElement } from "../elements/rectangle-element.js";
3
+ import type { RenderScope } from "../utils/renderer-registry.js";
3
4
  import { IRNode } from "../ir/display-list.js";
4
5
  export declare class RectangleRenderer {
5
- static render(rectangleElement: RectangleElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
6
+ static render(rectangleElement: RectangleElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
6
7
  /** One `line` node per present side, along the box edges (top-left coordinates). */
7
8
  private static sideLines;
8
9
  }
@@ -1,8 +1,11 @@
1
- import { RendererRegistry } from "../utils/renderer-registry.js";
2
- import { isGradientInput, resolveGradient } from "../api/gradient.js";
1
+ import { PositionedRenderer } from "./positioned-renderer.js";
2
+ import { isGradientInput, resolveGradient } from "../common/gradient.js";
3
3
  export class RectangleRenderer {
4
- static async render(rectangleElement, objectManager) {
5
- const { x, y, width, height, children, color, backgroundColor, borderWidth, radius, sideBorders, overflow, } = rectangleElement.getProps();
4
+ static async render(rectangleElement, objectManager, scope) {
5
+ const { x: ownX, y: ownY, width, height, children, color, backgroundColor, borderWidth, radius, sideBorders, overflow, relative, } = rectangleElement.getProps();
6
+ // Its own position is relative to `scope.origin`; the IR wants page coordinates.
7
+ const x = scope.origin.x + (ownX ?? 0);
8
+ const y = scope.origin.y + (ownY ?? 0);
6
9
  const h = height;
7
10
  const nodes = [];
8
11
  // A gradient is written box-relative (an angle and stops); this is the first place the box is
@@ -41,11 +44,18 @@ export class RectangleRenderer {
41
44
  if (clip) {
42
45
  nodes.push({ type: "clip-push", x, y, width, height: h, ...(radius ? { radius } : {}) });
43
46
  }
47
+ // Its children are placed relative to its own top-left; a `relative` box is also the frame its
48
+ // `Positioned` descendants were placed against.
49
+ const childScope = {
50
+ ...scope,
51
+ origin: { x, y },
52
+ frame: relative ? { x, y } : scope.frame,
53
+ };
54
+ // A frame or a clip paints its own `zIndex` order; any other box draws its children as they come.
44
55
  if (children)
45
- for (const child of children) {
46
- const renderer = RendererRegistry.getRenderer(child);
47
- nodes.push(...(await renderer(child, objectManager)));
48
- }
56
+ nodes.push(...(rectangleElement.isStackingContext()
57
+ ? await PositionedRenderer.paintStacked(children, objectManager, childScope)
58
+ : await PositionedRenderer.paintInOrder(children, objectManager, childScope)));
49
59
  if (clip)
50
60
  nodes.push({ type: "clip-pop" });
51
61
  return nodes;
@@ -1,6 +1,7 @@
1
1
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
2
  import { RepeatingHeaderElement } from "../elements/layout/repeating-header-element.js";
3
+ import { type RenderScope } from "../utils/renderer-registry.js";
3
4
  import { IRNode } from "../ir/display-list.js";
4
5
  export declare class RepeatingHeaderRenderer {
5
- static render(element: RepeatingHeaderElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
6
+ static render(element: RepeatingHeaderElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
6
7
  }
@@ -1,11 +1,12 @@
1
- import { RendererRegistry } from "../utils/renderer-registry.js";
1
+ import { RendererRegistry, within } from "../utils/renderer-registry.js";
2
2
  export class RepeatingHeaderRenderer {
3
- static async render(element, objectManager) {
4
- const { header, body } = element.getProps();
3
+ static async render(element, objectManager, scope) {
4
+ const { x, y, header, body } = element.getProps();
5
5
  const nodes = [];
6
+ // Header and body are placed relative to its own top-left.
7
+ const inner = within(scope, { x, y });
6
8
  for (const child of [header, body]) {
7
- const renderer = RendererRegistry.getRenderer(child);
8
- nodes.push(...(await renderer(child, objectManager)));
9
+ nodes.push(...(await RendererRegistry.render(child, objectManager, inner)));
9
10
  }
10
11
  return nodes;
11
12
  }
@@ -1,7 +1,8 @@
1
1
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
2
  import { RotatedElement } from "../elements/layout/rotated-element.js";
3
3
  import { RotatedBoxElement } from "../elements/layout/rotated-box-element.js";
4
+ import { type RenderScope } from "../utils/renderer-registry.js";
4
5
  import { IRNode } from "../ir/display-list.js";
5
6
  export declare class RotatedRenderer {
6
- static render(element: RotatedElement | RotatedBoxElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
7
+ static render(element: RotatedElement | RotatedBoxElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
7
8
  }
@@ -1,9 +1,13 @@
1
- import { RendererRegistry } from "../utils/renderer-registry.js";
1
+ import { within } from "../utils/renderer-registry.js";
2
+ import { PositionedRenderer } from "./positioned-renderer.js";
2
3
  export class RotatedRenderer {
3
4
  // Both `Rotated` (paint-only) and `RotatedBox` (layout-aware quarter-turns) expose the same
4
5
  // `{ angle, child, x, y, width, height }` and rotate the child around the box center - shared here.
5
- static async render(element, objectManager) {
6
- const { angle, child, x, y, width, height } = element.getProps();
6
+ static async render(element, objectManager, scope) {
7
+ const { angle, child, x: ownX, y: ownY, width, height } = element.getProps();
8
+ // Its own position is relative to `scope.origin`; the IR wants page coordinates.
9
+ const x = scope.origin.x + (ownX ?? 0);
10
+ const y = scope.origin.y + (ownY ?? 0);
7
11
  // Rotate around the box CENTER, in the engine's top-left coordinates (the IR->backend seam flips
8
12
  // the matrix to PDF space per page). A positive angle reads clockwise on the page.
9
13
  const cx = x + width / 2;
@@ -21,8 +25,8 @@ export class RotatedRenderer {
21
25
  cx - cos * cx + sin * cy,
22
26
  cy - sin * cx - cos * cy,
23
27
  ];
24
- const renderer = RendererRegistry.getRenderer(child);
25
- const childNodes = await renderer(child, objectManager);
28
+ // The child is placed relative to the box top-left.
29
+ const childNodes = await PositionedRenderer.paintStacked([child], objectManager, within(scope, { x: ownX, y: ownY }));
26
30
  // Wrap the child's drawing between a push (apply the matrix) and a pop (restore).
27
31
  return [{ type: "transform-push", matrix }, ...childNodes, { type: "transform-pop" }];
28
32
  }
@@ -1,6 +1,7 @@
1
1
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
2
  import { RowElement } from "../elements/row-element.js";
3
+ import { type RenderScope } from "../utils/renderer-registry.js";
3
4
  import { IRNode } from "../ir/display-list.js";
4
5
  export declare class RowRenderer {
5
- static render(rowElement: RowElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
6
+ static render(rowElement: RowElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
6
7
  }
@@ -1,12 +1,13 @@
1
- import { RendererRegistry } from "../utils/renderer-registry.js";
1
+ import { RendererRegistry, within } from "../utils/renderer-registry.js";
2
2
  export class RowRenderer {
3
- static async render(rowElement, objectManager) {
4
- const { children } = rowElement.getProps();
3
+ static async render(rowElement, objectManager, scope) {
4
+ const { x, y, children } = rowElement.getProps();
5
5
  const nodes = [];
6
+ // Its children are placed relative to its own top-left.
7
+ const inner = within(scope, { x, y });
6
8
  if (children)
7
9
  for (const child of children) {
8
- const renderer = RendererRegistry.getRenderer(child);
9
- nodes.push(...(await renderer(child, objectManager)));
10
+ nodes.push(...(await RendererRegistry.render(child, objectManager, inner)));
10
11
  }
11
12
  return nodes;
12
13
  }
@@ -1,10 +1,11 @@
1
1
  import { StructGroup } from "../elements/layout/struct-group.js";
2
2
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
3
3
  import { IRNode } from "../ir/display-list.js";
4
+ import { type RenderScope } from "../utils/renderer-registry.js";
4
5
  /**
5
6
  * Renders a StructGroup: opens its structure element, renders the child under it, closes it. With accessible
6
7
  * tagging off it is fully transparent - just the child's IR.
7
8
  */
8
9
  export declare class StructGroupRenderer {
9
- static render(group: StructGroup, objectManager: PDFObjectManager): Promise<IRNode[]>;
10
+ static render(group: StructGroup, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
10
11
  }
@@ -4,11 +4,10 @@ import { RendererRegistry } from "../utils/renderer-registry.js";
4
4
  * tagging off it is fully transparent - just the child's IR.
5
5
  */
6
6
  export class StructGroupRenderer {
7
- static async render(group, objectManager) {
7
+ static async render(group, objectManager, scope) {
8
8
  const { role, child } = group.getProps();
9
9
  const renderChild = async () => {
10
- const renderer = RendererRegistry.getRenderer(child);
11
- return renderer(child, objectManager);
10
+ return RendererRegistry.render(child, objectManager, scope);
12
11
  };
13
12
  if (!objectManager.struct.enabled)
14
13
  return renderChild();
@@ -1,3 +1,4 @@
1
+ import type { RenderScope } from "../utils/renderer-registry.js";
1
2
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
3
  import { SvgElement } from "../elements/svg-element.js";
3
4
  import type { IRNode } from "../ir/display-list.js";
@@ -7,5 +8,5 @@ import type { IRNode } from "../ir/display-list.js";
7
8
  * that colour emoji already draw through carries it.
8
9
  */
9
10
  export declare class SvgRenderer {
10
- static render(element: SvgElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
11
+ static render(element: SvgElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
11
12
  }
@@ -5,8 +5,14 @@ import { svgToIr } from "../svg/index.js";
5
5
  * that colour emoji already draw through carries it.
6
6
  */
7
7
  export class SvgRenderer {
8
- static async render(element, objectManager) {
9
- const { x, y, width, height, source, alt } = element.getProps();
8
+ static async render(element, objectManager, scope) {
9
+ await element.load(); // an SVG from a URL; already done when the pre-layout pass reached it
10
+ const { x: ownX, y: ownY, width, height, source, alt } = element.getProps();
11
+ if (source === undefined)
12
+ throw new Error("@jasy/pdf: an SVG was drawn before it was loaded");
13
+ // Its own position is relative to `scope.origin`; the IR wants page coordinates.
14
+ const x = scope.origin.x + (ownX ?? 0);
15
+ const y = scope.origin.y + (ownY ?? 0);
10
16
  const nodes = svgToIr(source, {
11
17
  x: x ?? 0,
12
18
  y: y ?? 0,
@@ -1,11 +1,9 @@
1
- import { TextElement, TextSegment } from "../elements/text-element.js";
2
- import { FontStyle, PDFObjectManager } from "../utils/pdf-object-manager.js";
3
- import type { FontMetrics } from "../utils/font-metrics.js";
1
+ import type { RenderScope } from "../utils/renderer-registry.js";
2
+ import { TextElement } from "../elements/text-element.js";
3
+ import { PDFObjectManager } from "../utils/pdf-object-manager.js";
4
4
  import { IRNode } from "../ir/display-list.js";
5
- import { type LineOptions, TextOverflow } from "../text/line-breaker.js";
6
5
  export declare class TextRenderer {
7
- static calculateTextHeight(content: string | TextSegment[], fontSize: number, fontFamily: string, fontStyle: FontStyle, objectManager: FontMetrics, maxWidth: number, maxLines?: number, overflow?: TextOverflow, lineHeight?: number, letterSpacing?: number, lineOptions?: LineOptions): number;
8
- static render(textElement: TextElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
6
+ static render(textElement: TextElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
9
7
  private static _expandColorGlyphs;
10
8
  private static _glyphToPath;
11
9
  private static _toGradient;