@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,9 +1,10 @@
1
1
  import { RendererRegistry } from "../utils/renderer-registry.js";
2
2
  export class BookmarkRenderer {
3
- static async render(element, objectManager) {
4
- const { title, level, child, y } = element.getProps();
5
- const renderer = RendererRegistry.getRenderer(child);
6
- const childNodes = await renderer(child, objectManager);
3
+ static async render(element, objectManager, scope) {
4
+ const { title, level, child, y: ownY } = element.getProps();
5
+ // Its own position is relative to `scope.origin`; the IR wants page coordinates.
6
+ const y = scope.origin.y + (ownY ?? 0);
7
+ const childNodes = await RendererRegistry.render(child, objectManager, scope);
7
8
  // The child draws normally; an Outline IR node carries the title + nesting + scroll target for the
8
9
  // page renderer to collect into the /Outlines tree (it produces no content-stream ops itself).
9
10
  return [...childNodes, { type: "outline", y, title, level }];
@@ -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 { CanvasElement } from "../elements/canvas-element.js";
3
4
  import type { IRNode } from "../ir/display-list.js";
@@ -10,5 +11,5 @@ import type { IRNode } from "../ir/display-list.js";
10
11
  * past its own edge would otherwise bleed into the layout beside it.
11
12
  */
12
13
  export declare class CanvasRenderer {
13
- static render(element: CanvasElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
14
+ static render(element: CanvasElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
14
15
  }
@@ -8,8 +8,11 @@ import { CanvasPainter } from "../canvas/painter.js";
8
8
  * past its own edge would otherwise bleed into the layout beside it.
9
9
  */
10
10
  export class CanvasRenderer {
11
- static async render(element, objectManager) {
12
- const { x, y, width, height, paint, alt } = element.getProps();
11
+ static async render(element, objectManager, scope) {
12
+ const { x: ownX, y: ownY, width, height, paint, alt } = element.getProps();
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);
13
16
  // The layout bounds are kept as plain numbers, and the painter and the callback each get their
14
17
  // OWN size object. Sharing one meant a callback that wrote to `size.width` moved the clip away
15
18
  // from the box layout had reserved - the drawing would then bleed into what sits beside it.
@@ -1,6 +1,7 @@
1
1
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
2
  import { ContainerElement } from "../elements/container-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 ContainerRenderer {
5
- static render(containerElement: ContainerElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
6
+ static render(containerElement: ContainerElement, 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 ContainerRenderer {
3
- static async render(containerElement, objectManager) {
4
- const { children } = containerElement.getProps();
3
+ static async render(containerElement, objectManager, scope) {
4
+ const { x, y, children } = containerElement.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,6 +1,7 @@
1
1
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
+ import { type RenderScope } from "../utils/renderer-registry.js";
2
3
  import { DefaultTextStyleElement } from "../elements/layout/default-text-style-element.js";
3
4
  import { IRNode } from "../ir/display-list.js";
4
5
  export declare class DefaultTextStyleRenderer {
5
- static render(element: DefaultTextStyleElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
6
+ static render(element: DefaultTextStyleElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
6
7
  }
@@ -2,9 +2,8 @@ import { RendererRegistry } from "../utils/renderer-registry.js";
2
2
  // Transparent wrapper: the text style was already resolved onto the descendants at layout time, so
3
3
  // the renderer just emits the child's display list (like PaddingRenderer).
4
4
  export class DefaultTextStyleRenderer {
5
- static async render(element, objectManager) {
5
+ static async render(element, objectManager, scope) {
6
6
  const { child } = element.getProps();
7
- const renderer = RendererRegistry.getRenderer(child);
8
- return renderer(child, objectManager);
7
+ return RendererRegistry.render(child, objectManager, scope);
9
8
  }
10
9
  }
@@ -1,6 +1,7 @@
1
1
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
2
  import { DeferredElement } from "../elements/layout/deferred-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 DeferredRenderer {
5
- static render(element: DeferredElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
6
+ static render(element: DeferredElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
6
7
  }
@@ -1,10 +1,9 @@
1
1
  import { RendererRegistry } from "../utils/renderer-registry.js";
2
2
  export class DeferredRenderer {
3
- static async render(element, objectManager) {
3
+ static async render(element, objectManager, scope) {
4
4
  const { composed } = element.getProps();
5
5
  if (!composed)
6
6
  return [];
7
- const renderer = RendererRegistry.getRenderer(composed);
8
- return renderer(composed, objectManager);
7
+ return RendererRegistry.render(composed, objectManager, scope);
9
8
  }
10
9
  }
@@ -1,6 +1,7 @@
1
1
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
+ import { type RenderScope } from "../utils/renderer-registry.js";
2
3
  import { ExpandedElement } from "../elements/index.js";
3
4
  import { IRNode } from "../ir/display-list.js";
4
5
  export declare class ExpandedRenderer {
5
- static render(expandedElement: ExpandedElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
6
+ static render(expandedElement: ExpandedElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
6
7
  }
@@ -1,8 +1,7 @@
1
1
  import { RendererRegistry } from "../utils/renderer-registry.js";
2
2
  export class ExpandedRenderer {
3
- static async render(expandedElement, objectManager) {
3
+ static async render(expandedElement, objectManager, scope) {
4
4
  const { child } = expandedElement.getProps();
5
- const renderer = RendererRegistry.getRenderer(child);
6
- return renderer(child, objectManager);
5
+ return RendererRegistry.render(child, objectManager, scope);
7
6
  }
8
7
  }
@@ -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 { TextFieldElement } from "../elements/forms/text-field-element.js";
3
4
  import { CheckboxElement } from "../elements/forms/checkbox-element.js";
@@ -15,6 +16,6 @@ type FormFieldElement = TextFieldElement | CheckboxElement | RadioElement | Choi
15
16
  * discriminates downstream).
16
17
  */
17
18
  export declare class FormFieldRenderer {
18
- static render(element: FormFieldElement, _objectManager: PDFObjectManager): Promise<IRNode[]>;
19
+ static render(element: FormFieldElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
19
20
  }
20
21
  export {};
@@ -5,8 +5,21 @@
5
5
  * discriminates downstream).
6
6
  */
7
7
  export class FormFieldRenderer {
8
- static async render(element, _objectManager) {
9
- const { x, y, width, height, spec, style } = element.getProps();
10
- return [{ type: "formfield", x, y, width, height, field: spec, style }];
8
+ static async render(element, objectManager, scope) {
9
+ const props = element.getProps();
10
+ const { x: ownX, y: ownY, width, height, spec, style } = props;
11
+ const font = "font" in props ? props.font : undefined;
12
+ // Its own position is relative to `scope.origin`; the IR wants page coordinates.
13
+ const x = scope.origin.x + (ownX ?? 0);
14
+ const y = scope.origin.y + (ownY ?? 0);
15
+ // Tagged: the widget goes into a /Form element, named by its tooltip (/TU) or else its field name
16
+ // (PDF/UA 7.18.1, 7.18.4). A radio button is one answer of its group.
17
+ const name = spec.kind === "radio" ? `${spec.group}: ${spec.value}` : spec.name;
18
+ const structKey = objectManager.struct.enabled
19
+ ? objectManager.struct.openElement(element.structId, "Form", {
20
+ alt: spec.tooltip === undefined ? name : undefined,
21
+ })
22
+ : undefined;
23
+ return [{ type: "formfield", x, y, width, height, field: spec, style, structKey, font }];
11
24
  }
12
25
  }
@@ -1,6 +1,7 @@
1
+ import type { RenderScope } from "../utils/renderer-registry.js";
1
2
  import { ImageElement } from "../elements/image-element.js";
2
3
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
3
4
  import { IRNode } from "../ir/display-list.js";
4
5
  export declare class ImageRenderer {
5
- static render(imageElement: ImageElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
6
+ static render(imageElement: ImageElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
6
7
  }
@@ -3,9 +3,12 @@ import { bytesFromLatin1 } from "../utils/bytes.js";
3
3
  import { applyContainFit, applyCoverFit, applyFillFit, applyFitNone, decodePngToRgbFlate, } from "../utils/image-helper.js";
4
4
  import { isRounded } from "../ir/display-list.js";
5
5
  export class ImageRenderer {
6
- static async render(imageElement, objectManager) {
6
+ static async render(imageElement, objectManager, scope) {
7
7
  // Load the image and convert it in a binary string
8
- let { x, y, width, height, image, fit, radius, alt } = imageElement.getProps();
8
+ let { x: ownX, y: ownY, width, height, image, fit, radius, alt } = imageElement.getProps();
9
+ // Its own position is relative to `scope.origin`; the IR wants page coordinates.
10
+ let x = scope.origin.x + (ownX ?? 0);
11
+ let y = scope.origin.y + (ownY ?? 0);
9
12
  await image.init(); // Load and initialize the image
10
13
  const imageType = await image.getImageType(); // For the moment we can handle `png` and `jpg/jpeg` files
11
14
  const fileData = await image.getFileData();
@@ -1,10 +1,11 @@
1
1
  import { KeepTogetherElement } from "../elements/layout/keep-together-element.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
  * A `keepTogether` is layout-transparent: it just renders its child. All of its work happens in the
6
7
  * layout pass (`fragment`), never here.
7
8
  */
8
9
  export declare class KeepTogetherRenderer {
9
- static render(group: KeepTogetherElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
10
+ static render(group: KeepTogetherElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
10
11
  }
@@ -4,9 +4,8 @@ import { RendererRegistry } from "../utils/renderer-registry.js";
4
4
  * layout pass (`fragment`), never here.
5
5
  */
6
6
  export class KeepTogetherRenderer {
7
- static async render(group, objectManager) {
7
+ static async render(group, objectManager, scope) {
8
8
  const { child } = group.getProps();
9
- const renderer = RendererRegistry.getRenderer(child);
10
- return renderer(child, objectManager);
9
+ return RendererRegistry.render(child, objectManager, scope);
11
10
  }
12
11
  }
@@ -1,6 +1,7 @@
1
+ import type { RenderScope } from "../utils/renderer-registry.js";
1
2
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
3
  import { LineElement } from "../elements/index.js";
3
4
  import { IRNode } from "../ir/display-list.js";
4
5
  export declare class LineRenderer {
5
- static render(lineElement: LineElement, _objectManager: PDFObjectManager): Promise<IRNode[]>;
6
+ static render(lineElement: LineElement, _objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
6
7
  }
@@ -1,6 +1,11 @@
1
1
  export class LineRenderer {
2
- static async render(lineElement, _objectManager) {
3
- const { x, y, xEnd, yEnd, color, strokeWidth } = lineElement.getProps();
2
+ static async render(lineElement, _objectManager, scope) {
3
+ const { x: ownX, y: ownY, xEnd: ownXEnd, yEnd: ownYEnd, color, strokeWidth, } = lineElement.getProps();
4
+ // Its own position is relative to `scope.origin`; the IR wants page coordinates.
5
+ const x = scope.origin.x + (ownX ?? 0);
6
+ const y = scope.origin.y + (ownY ?? 0);
7
+ const xEnd = scope.origin.x + (ownXEnd ?? 0);
8
+ const yEnd = scope.origin.y + (ownYEnd ?? 0);
4
9
  const node = {
5
10
  type: "line",
6
11
  x1: x,
@@ -1,6 +1,7 @@
1
1
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
2
  import { LinkElement } from "../elements/layout/link-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 LinkRenderer {
5
- static render(element: LinkElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
6
+ static render(element: LinkElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
6
7
  }
@@ -1,11 +1,42 @@
1
1
  import { RendererRegistry } from "../utils/renderer-registry.js";
2
+ import { TextElement } from "../elements/text-element.js";
3
+ /** The text a link covers, as read - not run by run, where a justified line is one run per word. */
4
+ function textOf(element) {
5
+ if (element instanceof TextElement) {
6
+ return [
7
+ element
8
+ .getProps()
9
+ .spans.map((s) => s.content)
10
+ .join(""),
11
+ ];
12
+ }
13
+ return element.layoutChildren().flatMap(textOf);
14
+ }
2
15
  export class LinkRenderer {
3
- static async render(element, objectManager) {
4
- const { href, dest, child, x, y, width, height } = element.getProps();
5
- const renderer = RendererRegistry.getRenderer(child);
6
- const childNodes = await renderer(child, objectManager);
16
+ static async render(element, objectManager, scope) {
17
+ const { href, dest, child, x: ownX, y: ownY, width, height } = element.getProps();
18
+ // Its own position is relative to `scope.origin`; the IR wants page coordinates.
19
+ const x = scope.origin.x + (ownX ?? 0);
20
+ const y = scope.origin.y + (ownY ?? 0);
21
+ const renderChild = () => RendererRegistry.render(child, objectManager, scope);
7
22
  // The child draws normally; a Link IR node carries the clickable rect + target (external href or
8
23
  // internal dest) for the page renderer to emit as a /Link annotation (no content-stream ops itself).
9
- return [...childNodes, { type: "link", x, y, width, height, href, dest }];
24
+ if (!objectManager.struct.enabled) {
25
+ return [...(await renderChild()), { type: "link", x, y, width, height, href, dest }];
26
+ }
27
+ // Tagged: the link's content sits in a /Link element, which also takes the annotation, and the
28
+ // annotation describes itself by the text it covers (PDF/UA 7.18.5). A split link is ONE element.
29
+ const struct = objectManager.struct;
30
+ const structKey = struct.openElement(element.structId, "Link");
31
+ struct.push(structKey);
32
+ let childNodes;
33
+ try {
34
+ childNodes = await renderChild();
35
+ }
36
+ finally {
37
+ struct.pop();
38
+ }
39
+ const alt = textOf(child).join(" ").replace(/\s+/g, " ").trim() || (href ?? dest ?? "");
40
+ return [...childNodes, { type: "link", x, y, width, height, href, dest, structKey, alt }];
10
41
  }
11
42
  }
@@ -1,6 +1,7 @@
1
1
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
+ import { type RenderScope } from "../utils/renderer-registry.js";
2
3
  import { PaddingElement } from "../elements/layout/padding-element.js";
3
4
  import { IRNode } from "../ir/display-list.js";
4
5
  export declare class PaddingRenderer {
5
- static render(paddingElement: PaddingElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
6
+ static render(paddingElement: PaddingElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
6
7
  }
@@ -1,8 +1,8 @@
1
- import { RendererRegistry } from "../utils/renderer-registry.js";
1
+ import { RendererRegistry, within } from "../utils/renderer-registry.js";
2
2
  export class PaddingRenderer {
3
- static async render(paddingElement, objectManager) {
4
- const { child } = paddingElement.getProps();
5
- const renderer = RendererRegistry.getRenderer(child);
6
- return renderer(child, objectManager);
3
+ static async render(paddingElement, objectManager, scope) {
4
+ const { x, y, child } = paddingElement.getProps();
5
+ // Its child is placed relative to its own top-left, inset by the margins.
6
+ return RendererRegistry.render(child, objectManager, within(scope, { x, y }));
7
7
  }
8
8
  }
@@ -1,14 +1,15 @@
1
+ import type { RenderScope } from "../utils/renderer-registry.js";
1
2
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
3
  import { PageBreakElement } from "../elements/layout/page-break-element.js";
3
4
  import { IRNode } from "../ir/display-list.js";
4
5
  /**
5
6
  * A `PageBreak` draws nothing - the pagination packer consumes it at the cut, so in a vertical flow it
6
7
  * never reaches this pass. Reaching it therefore means the break was ineffective: it sat somewhere that
7
- * does not paginate, like inside a horizontal `Row` (a row is one line, you cannot split it across
8
- * pages - CSS and react-pdf ignore a forced break there too). We degrade to drawing nothing, but warn
9
- * once so the mistake is not silent. This one choke-point catches every ineffective break; no element
10
- * needs a special case.
8
+ * does not paginate - inside a horizontal `Row` (one line; CSS and react-pdf ignore a forced break there
9
+ * too), or inside a `PageBuilder`, whose content is built for the page it lands on and placed whole. We
10
+ * degrade to drawing nothing, but warn so the mistake is not silent. This one choke-point catches every
11
+ * ineffective break; no element needs a special case.
11
12
  */
12
13
  export declare class PageBreakRenderer {
13
- static render(_element: PageBreakElement, _objectManager: PDFObjectManager): Promise<IRNode[]>;
14
+ static render(element: PageBreakElement, objectManager: PDFObjectManager, _scope: RenderScope): Promise<IRNode[]>;
14
15
  }
@@ -1,15 +1,22 @@
1
1
  /**
2
2
  * A `PageBreak` draws nothing - the pagination packer consumes it at the cut, so in a vertical flow it
3
3
  * never reaches this pass. Reaching it therefore means the break was ineffective: it sat somewhere that
4
- * does not paginate, like inside a horizontal `Row` (a row is one line, you cannot split it across
5
- * pages - CSS and react-pdf ignore a forced break there too). We degrade to drawing nothing, but warn
6
- * once so the mistake is not silent. This one choke-point catches every ineffective break; no element
7
- * needs a special case.
4
+ * does not paginate - inside a horizontal `Row` (one line; CSS and react-pdf ignore a forced break there
5
+ * too), or inside a `PageBuilder`, whose content is built for the page it lands on and placed whole. We
6
+ * degrade to drawing nothing, but warn so the mistake is not silent. This one choke-point catches every
7
+ * ineffective break; no element needs a special case.
8
8
  */
9
9
  export class PageBreakRenderer {
10
- static async render(_element, _objectManager) {
11
- console.warn("PageBreak had no effect: it is not in a paginating flow (e.g. it is inside a Row, which is a " +
12
- "single horizontal line). Put the break as a sibling in a Column to break after that content.");
10
+ static async render(element, objectManager, _scope) {
11
+ objectManager.diagnostics.report({
12
+ code: "page-break-ignored",
13
+ severity: "warning",
14
+ message: "PageBreak had no effect: it is not in a paginating flow - it is inside a Row (a single " +
15
+ "horizontal line) or a PageBuilder (built for one page and placed whole, Columns inside it " +
16
+ "included). Move the break out, into a Column that flows across pages.",
17
+ about: element,
18
+ page: element.page,
19
+ });
13
20
  return [];
14
21
  }
15
22
  }
@@ -1,6 +1,7 @@
1
1
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
2
  import { PageBuilderElement } from "../elements/layout/page-builder-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 PageBuilderRenderer {
5
- static render(element: PageBuilderElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
6
+ static render(element: PageBuilderElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
6
7
  }
@@ -1,11 +1,10 @@
1
1
  import { RendererRegistry } from "../utils/renderer-registry.js";
2
2
  export class PageBuilderRenderer {
3
- static async render(element, objectManager) {
3
+ static async render(element, objectManager, scope) {
4
4
  // `composed` is whatever the closure built for THIS page during the render pass's layout.
5
5
  const { composed } = element.getProps();
6
6
  if (!composed)
7
7
  return [];
8
- const renderer = RendererRegistry.getRenderer(composed);
9
- return renderer(composed, objectManager);
8
+ return RendererRegistry.render(composed, objectManager, scope);
10
9
  }
11
10
  }
@@ -1,10 +1,18 @@
1
1
  import { pageFormats, PageSize } from "../constants/page-sizes.js";
2
+ import { resolvePageContentBox } from "../elements/page-element.js";
2
3
  import { RendererRegistry } from "../utils/renderer-registry.js";
3
4
  import { Orientation } from "./pdf-config.js";
4
5
  import { PdfBackend } from "./pdf-backend.js";
5
6
  export class PageRenderer {
6
7
  static async render(page, objectManager) {
7
8
  const { children, config, header, footer } = page.getProps();
9
+ RendererRegistry.beginPage(objectManager); // an element may be drawn once per page
10
+ // A page is the outermost box: its children's offsets are page coordinates. Its content box is the
11
+ // frame a `Positioned` with no `relative` ancestor was placed against.
12
+ const scope = {
13
+ origin: { x: 0, y: 0 },
14
+ frame: resolvePageContentBox(config).origin, // config is resolved by layout
15
+ };
8
16
  // Header (top band) and footer (bottom band) sit around the body and repeat on every
9
17
  // physical page; they are placed by `PageElement.calculateLayout` / the page driver.
10
18
  const renderables = [...(header ? [header] : []), ...children, ...(footer ? [footer] : [])];
@@ -20,8 +28,7 @@ export class PageRenderer {
20
28
  // fonts/images used below, so it must run before the resource section.
21
29
  const nodes = [];
22
30
  for (const element of renderables) {
23
- const renderer = RendererRegistry.getRenderer(element);
24
- nodes.push(...(await renderer(element, objectManager)));
31
+ nodes.push(...(await RendererRegistry.render(element, objectManager, scope)));
25
32
  }
26
33
  // Accessible tagging: one struct context per page (allocates its StructParents index), threaded into
27
34
  // serialize so each drawable node is wrapped in marked content. Undefined = tagging off (byte-identical).
@@ -72,21 +79,35 @@ export class PageRenderer {
72
79
  const num = (n) => Number(n.toFixed(2));
73
80
  const annotRefs = [];
74
81
  for (const node of flipped) {
82
+ if (node.type !== "link" && node.type !== "formfield")
83
+ continue;
84
+ // Tagged, every annotation names its structure element (PDF/UA 7.18).
85
+ const structKey = structCtx ? node.structKey : undefined;
86
+ const structParent = structKey === undefined ? undefined : objectManager.struct.annotationParent(structKey);
87
+ const tag = (obj) => {
88
+ if (structKey !== undefined) {
89
+ objectManager.struct.linkAnnotation(structKey, obj, structCtx.structParents);
90
+ }
91
+ };
75
92
  // A form field: build its Widget annotation + register it as an /AcroForm field (the logic lives in
76
93
  // forms/acroform.ts), then reference it here in the page /Annots - the same side channel as a link.
77
94
  if (node.type === "formfield") {
78
- annotRefs.push(`${objectManager.acroform.addField(node, objectManager)} 0 R`);
95
+ const widget = objectManager.acroform.addField(node, objectManager, structParent);
96
+ tag(widget);
97
+ annotRefs.push(`${widget} 0 R`);
79
98
  continue;
80
99
  }
81
- if (node.type !== "link")
82
- continue;
83
100
  const rect = `[${num(node.x)} ${num(node.y)} ${num(node.x + node.width)} ${num(node.y + node.height)}]`;
84
101
  // Either an external URL (/URI) or an internal named destination (/GoTo, resolved via /Names /Dests).
85
102
  // The Link IR guarantees exactly one is set; prefer `dest` when present.
86
103
  const action = node.dest !== undefined
87
104
  ? `/A << /S /GoTo /D ${objectManager.pdfString(node.dest)} >>`
88
- : `/A << /S /URI /URI ${objectManager.pdfString(node.href ?? "")} >>`;
89
- const annot = objectManager.addObject(`<< /Type /Annot /Subtype /Link /Rect ${rect} /Border [0 0 0] ${action} >>`);
105
+ : `/A << /S /URI /URI ${objectManager.pdfUri(node.href ?? "")} >>`;
106
+ const tagged = structParent === undefined
107
+ ? ""
108
+ : ` /StructParent ${structParent} /Contents ${objectManager.pdfString(node.alt ?? "")}`;
109
+ const annot = objectManager.addObject(`<< /Type /Annot /Subtype /Link /Rect ${rect} /Border [0 0 0] ${action}${tagged} >>`);
110
+ tag(annot);
90
111
  annotRefs.push(`${annot} 0 R`);
91
112
  }
92
113
  const annotsCode = annotRefs.length > 0 ? ` /Annots [${annotRefs.join(" ")}]` : "";
@@ -70,9 +70,10 @@ export declare class PdfBackend {
70
70
  */
71
71
  private static roundedRectPath;
72
72
  /**
73
- * Clamp the four radii into the box. Each is capped at half the box, and two radii sharing an edge
74
- * are scaled down together when they would overlap - the CSS rule, and what keeps the path from
75
- * folding back on itself.
73
+ * Clamp the four radii into the box: two radii sharing an edge are scaled down together when they
74
+ * would overlap - the CSS rule (css-backgrounds-3, corner overlap), and what keeps the path from
75
+ * folding back on itself. A single corner is NOT capped at half the box: on a 200x40 box a lone
76
+ * 30pt corner stays 30, as in CSS; equal corners still end at half the short side.
76
77
  */
77
78
  private static clampRadii;
78
79
  /**
@@ -279,14 +279,14 @@ export class PdfBackend {
279
279
  `h`);
280
280
  }
281
281
  /**
282
- * Clamp the four radii into the box. Each is capped at half the box, and two radii sharing an edge
283
- * are scaled down together when they would overlap - the CSS rule, and what keeps the path from
284
- * folding back on itself.
282
+ * Clamp the four radii into the box: two radii sharing an edge are scaled down together when they
283
+ * would overlap - the CSS rule (css-backgrounds-3, corner overlap), and what keeps the path from
284
+ * folding back on itself. A single corner is NOT capped at half the box: on a 200x40 box a lone
285
+ * 30pt corner stays 30, as in CSS; equal corners still end at half the short side.
285
286
  */
286
287
  static clampRadii(radius, w, h) {
287
288
  const n = typeof radius === "number" ? radius : 0;
288
- const cap = Math.max(0, Math.min(w, h) / 2);
289
- const at = (v) => Math.max(0, Math.min(v ?? n, cap));
289
+ const at = (v) => Math.max(0, v ?? n);
290
290
  const r = typeof radius === "number"
291
291
  ? { tl: at(radius), tr: at(radius), br: at(radius), bl: at(radius) }
292
292
  : { tl: at(radius.tl), tr: at(radius.tr), br: at(radius.br), bl: at(radius.bl) };
@@ -3,14 +3,7 @@ import { LayoutContext } from "../elements/pdf-element.js";
3
3
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
4
4
  export declare class PDFDocumentRenderer {
5
5
  static render(document: PDFDocumentElement, objectManager: PDFObjectManager, ctx: LayoutContext): Promise<number>;
6
- /**
7
- * The page geometry a physical page will use. `PageElement.calculateLayout` has already folded the
8
- * document defaults into every page's config by the time we get here (`PDFRenderer` lays the document
9
- * out before it calls us, and it is our only caller). Merging again is therefore a no-op today - it is
10
- * here so this stays correct BY CONSTRUCTION rather than by call order: were that order ever changed,
11
- * an unmerged config would otherwise make `resolvePageSize` fall back to A4 and quietly report the
12
- * wrong page size.
13
- */
6
+ /** The page geometry a physical page will use: resolved, never read back from an earlier layout. */
14
7
  private static configOf;
15
8
  /**
16
9
  * Splits one logical page into the physical pages it will occupy, without rendering.