@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,6 +1,17 @@
1
1
  import type { FieldAlign, FieldStyle } from "./field.js";
2
2
  import type { FontMetrics } from "../utils/font-metrics.js";
3
3
  import type { FontStyle } from "../utils/pdf-object-manager.js";
4
+ /**
5
+ * The font an appearance draws its text in: its resource name inside the appearance stream's own
6
+ * /Resources, and how a string becomes a `Tj` operand in it. One object, so the caller that picks the
7
+ * font also decides its encoding - a WinAnsi literal for Helvetica, glyph ids for an embedded font.
8
+ */
9
+ export interface FaceFont {
10
+ res: string;
11
+ encode(text: string): string;
12
+ }
13
+ /** The standard Helvetica (`/Helv`) a field's /DA names: WinAnsi literal strings. */
14
+ export declare const HELV: FaceFont;
4
15
  /** The unchecked appearance: just the box. */
5
16
  export declare function checkboxOff(w: number, h: number, style: FieldStyle): string;
6
17
  /** The checked appearance: the box plus a vector checkmark (font-free, so it renders everywhere). */
@@ -12,16 +23,16 @@ export declare function radioOn(w: number, h: number, style: FieldStyle): string
12
23
  /**
13
24
  * A push button's face: the box plus its caption, centred. The caller measures the caption (this module
14
25
  * stays free of font metrics and the writer) and passes `captionWidth` in points and `capHeight` as an em
15
- * fraction; the baseline is placed so the CAPITALS sit optically centred, not the em box. `fontRes` is
16
- * the font's resource name inside the appearance stream's own /Resources.
26
+ * fraction; the baseline is placed so the CAPITALS sit optically centred, not the em box. `font` is
27
+ * what the caption is drawn in.
17
28
  */
18
- export declare function pushButtonFace(w: number, h: number, style: FieldStyle, caption: string, captionWidth: number, capHeight: number, fontRes: string, size: number): string;
29
+ export declare function pushButtonFace(w: number, h: number, style: FieldStyle, caption: string, captionWidth: number, capHeight: number, font: FaceFont, size: number): string;
19
30
  /**
20
31
  * An EMPTY signature field: the box, a signing rule across the lower third, and an optional hint centred
21
32
  * just above it. This is the placeholder look - once someone actually signs, the signing tool replaces
22
33
  * this appearance with its own.
23
34
  */
24
- export declare function signatureFace(w: number, h: number, style: FieldStyle, label: string, labelWidth: number, fontRes: string, size: number): string;
35
+ export declare function signatureFace(w: number, h: number, style: FieldStyle, label: string, labelWidth: number, font: FaceFont, size: number): string;
25
36
  /** One pre-measured line of field text. The caller owns wrapping + measuring, so this module stays free
26
37
  * of font metrics (and can be reused verbatim when filling an existing PDF). */
27
38
  /**
@@ -50,9 +61,9 @@ export interface FieldLine {
50
61
  * single line sits optically centred; a multi-line field stacks from the top, both measured off the
51
62
  * font's cap height rather than a guessed ratio.
52
63
  */
53
- export declare function textFieldFace(w: number, h: number, style: FieldStyle, lines: FieldLine[], capHeight: number, size: number, fontRes: string, multiline: boolean, align?: FieldAlign): string;
64
+ export declare function textFieldFace(w: number, h: number, style: FieldStyle, lines: FieldLine[], capHeight: number, size: number, font: FaceFont, multiline: boolean, align?: FieldAlign): string;
54
65
  /**
55
66
  * A list box, baked: the visible options stacked from the top, with a highlight behind the selected
56
67
  * ones. A combo box (dropdown) uses `textFieldFace` instead - it shows only its current value.
57
68
  */
58
- export declare function listBoxFace(w: number, h: number, style: FieldStyle, lines: FieldLine[], capHeight: number, size: number, fontRes: string, align?: FieldAlign): string;
69
+ export declare function listBoxFace(w: number, h: number, style: FieldStyle, lines: FieldLine[], capHeight: number, size: number, font: FaceFont, align?: FieldAlign): string;
@@ -1,5 +1,7 @@
1
1
  import { escPdf, num2, pdfColor } from "./pdf.js";
2
2
  import { wrapStringIntoLines } from "../text/line-breaker.js";
3
+ /** The standard Helvetica (`/Helv`) a field's /DA names: WinAnsi literal strings. */
4
+ export const HELV = { res: "Helv", encode: (text) => `(${escPdf(text)})` };
3
5
  // Appearance streams (/AP): the actual drawn content of a field's box, in the field's OWN coordinate
4
6
  // space (bottom-left origin, [0 0 w h]). Shared between CREATE (bake the field's look now) and, later,
5
7
  // FILL-existing (regenerate a field's look after setting its value). This is why it is its own module.
@@ -63,34 +65,34 @@ export function radioOn(w, h, style) {
63
65
  /**
64
66
  * A push button's face: the box plus its caption, centred. The caller measures the caption (this module
65
67
  * stays free of font metrics and the writer) and passes `captionWidth` in points and `capHeight` as an em
66
- * fraction; the baseline is placed so the CAPITALS sit optically centred, not the em box. `fontRes` is
67
- * the font's resource name inside the appearance stream's own /Resources.
68
+ * fraction; the baseline is placed so the CAPITALS sit optically centred, not the em box. `font` is
69
+ * what the caption is drawn in.
68
70
  */
69
- export function pushButtonFace(w, h, style, caption, captionWidth, capHeight, fontRes, size) {
71
+ export function pushButtonFace(w, h, style, caption, captionWidth, capHeight, font, size) {
70
72
  // Baseline placed so the CAPITALS sit optically centred in the box, not the em square.
71
73
  const y = (h - capHeight * size) / 2;
72
- const caption_ = caption ? centredText(w, h, style, caption, captionWidth, y, fontRes, size) : "";
74
+ const caption_ = caption ? centredText(w, h, style, caption, captionWidth, y, font, size) : "";
73
75
  return `${box(w, h, style)}\n${caption_}`;
74
76
  }
75
77
  /** One centred, clipped text run inside a field box. Clipping means an over-long caption is cut at the
76
78
  * border instead of bleeding across the page. Shared by the push button and the signature placeholder. */
77
- function centredText(w, h, style, text, textWidth, baselineY, fontRes, size) {
79
+ function centredText(w, h, style, text, textWidth, baselineY, font, size) {
78
80
  return (`q 0 0 ${num2(w)} ${num2(h)} re W n ` +
79
- `BT /${fontRes} ${num2(size)} Tf ${pdfColor(style.color)} rg ` +
80
- `${num2((w - textWidth) / 2)} ${num2(baselineY)} Td (${escPdf(text)}) Tj ET Q`);
81
+ `BT /${font.res} ${num2(size)} Tf ${pdfColor(style.color)} rg ` +
82
+ `${num2((w - textWidth) / 2)} ${num2(baselineY)} Td ${font.encode(text)} Tj ET Q`);
81
83
  }
82
84
  /**
83
85
  * An EMPTY signature field: the box, a signing rule across the lower third, and an optional hint centred
84
86
  * just above it. This is the placeholder look - once someone actually signs, the signing tool replaces
85
87
  * this appearance with its own.
86
88
  */
87
- export function signatureFace(w, h, style, label, labelWidth, fontRes, size) {
89
+ export function signatureFace(w, h, style, label, labelWidth, font, size) {
88
90
  const ruleY = h * 0.3;
89
91
  const inset = w * 0.08;
90
92
  const stroke = style.border ?? style.color;
91
93
  const rule = `${pdfColor(stroke)} RG ${num2(Math.max(0.5, style.borderWidth * 0.75))} w ` +
92
94
  `${num2(inset)} ${num2(ruleY)} m ${num2(w - inset)} ${num2(ruleY)} l S`;
93
- const hint = label ? centredText(w, h, style, label, labelWidth, ruleY + 6, fontRes, size) : "";
95
+ const hint = label ? centredText(w, h, style, label, labelWidth, ruleY + 6, font, size) : "";
94
96
  return `${box(w, h, style)}\n${rule}\n${hint}`;
95
97
  }
96
98
  // Horizontal padding inside a field box, on top of the border - the same small inset Acrobat leaves so
@@ -147,11 +149,11 @@ function alignX(boxWidth, pad, width, align = "left") {
147
149
  * visibly shift on first click. The measurement matches: `getStringWidth` is the plain glyph sum, so
148
150
  * measured still equals drawn.
149
151
  */
150
- function textBlock(lines, style, size, fontRes) {
152
+ function textBlock(lines, style, size, font) {
151
153
  const runs = lines
152
- .map((l) => `1 0 0 1 ${num2(l.x)} ${num2(l.y)} Tm (${escPdf(l.text)}) Tj`)
154
+ .map((l) => `1 0 0 1 ${num2(l.x)} ${num2(l.y)} Tm ${font.encode(l.text)} Tj`)
153
155
  .join("\n");
154
- return `BT /${fontRes} ${num2(size)} Tf ${pdfColor(style.color)} rg\n${runs}\nET`;
156
+ return `BT /${font.res} ${num2(size)} Tf ${pdfColor(style.color)} rg\n${runs}\nET`;
155
157
  }
156
158
  /** Everything a field draws is clipped to its own box, so a long value is cut at the frame rather than
157
159
  * bleeding across the page. */
@@ -163,7 +165,7 @@ function clipped(w, h, ops) {
163
165
  * single line sits optically centred; a multi-line field stacks from the top, both measured off the
164
166
  * font's cap height rather than a guessed ratio.
165
167
  */
166
- export function textFieldFace(w, h, style, lines, capHeight, size, fontRes, multiline, align = "left") {
168
+ export function textFieldFace(w, h, style, lines, capHeight, size, font, multiline, align = "left") {
167
169
  const face = box(w, h, style);
168
170
  if (lines.length === 0)
169
171
  return face;
@@ -176,13 +178,13 @@ export function textFieldFace(w, h, style, lines, capHeight, size, fontRes, mult
176
178
  // Single line: centre the capitals in the box.
177
179
  y: multiline ? h - pad - capHeight * size - i * lineHeight : (h - capHeight * size) / 2,
178
180
  }));
179
- return `${face}\n${clipped(w, h, textBlock(placed, style, size, fontRes))}`;
181
+ return `${face}\n${clipped(w, h, textBlock(placed, style, size, font))}`;
180
182
  }
181
183
  /**
182
184
  * A list box, baked: the visible options stacked from the top, with a highlight behind the selected
183
185
  * ones. A combo box (dropdown) uses `textFieldFace` instead - it shows only its current value.
184
186
  */
185
- export function listBoxFace(w, h, style, lines, capHeight, size, fontRes, align = "left") {
187
+ export function listBoxFace(w, h, style, lines, capHeight, size, font, align = "left") {
186
188
  const pad = FIELD_PAD + style.borderWidth;
187
189
  const lineHeight = size * 1.15;
188
190
  const rowTop = (i) => h - pad - (i + 1) * lineHeight;
@@ -199,6 +201,6 @@ export function listBoxFace(w, h, style, lines, capHeight, size, fontRes, align
199
201
  // Baseline inside the row: lift it off the row bottom by the slack under the capitals.
200
202
  y: rowTop(i) + (lineHeight - capHeight * size) / 2,
201
203
  }));
202
- const inner = [highlights, textBlock(placed, style, size, fontRes)].filter(Boolean).join("\n");
204
+ const inner = [highlights, textBlock(placed, style, size, font)].filter(Boolean).join("\n");
203
205
  return `${box(w, h, style)}\n${clipped(w, h, inner)}`;
204
206
  }
@@ -9,8 +9,8 @@ import type { FormFieldSpec, FieldStyle } from "../forms/field.js";
9
9
  * are intentionally "dumb": absolute geometry + semantic style, with no PDF
10
10
  * operators, no font indices, and no object numbers - those are the backend's job.
11
11
  *
12
- * Coordinates are in PDF points (1/72"). Which origin they use is still the
13
- * producer's concern for now; centralizing the Y-flip at this seam is Phase 3.
12
+ * Coordinates are in PDF points (1/72") with the engine's top-left origin. The Y-flip to PDF's
13
+ * bottom-left happens once per page, at this seam (`PdfBackend.flipY`).
14
14
  */
15
15
  /**
16
16
  * Structure tag for accessible (PDF/UA) tagging. `role` is the PDF structure type the content maps to
@@ -140,6 +140,9 @@ export interface Link {
140
140
  height: number;
141
141
  href?: string;
142
142
  dest?: string;
143
+ /** Accessible tagging only: the `/Link` structure element the annotation belongs to, and its text. */
144
+ structKey?: number;
145
+ alt?: string;
143
146
  }
144
147
  /**
145
148
  * A named destination anchor - a jump TARGET for an internal `Link({ to })`. Like `Link` it draws
@@ -177,6 +180,13 @@ export interface FormFieldNode {
177
180
  height: number;
178
181
  field: FormFieldSpec;
179
182
  style: FieldStyle;
183
+ /** Accessible tagging only: the `/Form` structure element the widget belongs to. */
184
+ structKey?: number;
185
+ /** The font the field's text inherits; its baked appearance draws in it when it is embedded. */
186
+ font?: {
187
+ family: string;
188
+ style: FontStyle;
189
+ };
180
190
  }
181
191
  /**
182
192
  * Pushes an affine transform (a `q` + `cm` in the content stream): everything between this and the
@@ -84,6 +84,11 @@ export interface SizingParams {
84
84
  /** width / height. Derives whichever axis is left open; see `resolveSize`. */
85
85
  aspectRatio?: number;
86
86
  }
87
+ /**
88
+ * The width rules of a box, for a piece of it split across pages: it keeps how wide it may be, and
89
+ * its height comes from what it holds on that page (CSS `box-decoration-break: clone`).
90
+ */
91
+ export declare function widthRules(s: SizingParams): SizingParams;
87
92
  /** Split `SizingParams` into the per-axis form `resolveSize` takes. */
88
93
  export declare function extentSpecs(p: SizingParams): {
89
94
  width: ExtentSpec;
@@ -91,6 +91,20 @@ export function resolveExtent(fixed, factor, boundedMax, hasBounded) {
91
91
  return boundedMax * factor;
92
92
  return undefined;
93
93
  }
94
+ /**
95
+ * The width rules of a box, for a piece of it split across pages: it keeps how wide it may be, and
96
+ * its height comes from what it holds on that page (CSS `box-decoration-break: clone`).
97
+ */
98
+ export function widthRules(s) {
99
+ return {
100
+ width: s.width,
101
+ widthFactor: s.widthFactor,
102
+ minWidth: s.minWidth,
103
+ maxWidth: s.maxWidth,
104
+ minWidthFactor: s.minWidthFactor,
105
+ maxWidthFactor: s.maxWidthFactor,
106
+ };
107
+ }
94
108
  /** Split `SizingParams` into the per-axis form `resolveSize` takes. */
95
109
  export function extentSpecs(p) {
96
110
  return {
@@ -1,9 +1,11 @@
1
1
  import { PDFElement } from "../elements/pdf-element.js";
2
2
  import { ImageElement } from "../elements/image-element.js";
3
+ import { SvgElement } from "../elements/svg-element.js";
3
4
  /**
4
5
  * Walks the element tree (via `getProps`, the same shallow-reflection style as the validator) and
5
- * returns every `ImageElement`. A pre-layout pass uses this to resolve each image's intrinsic pixel
6
- * size asynchronously: layout is synchronous and cannot await jimp, yet it needs the aspect ratio to
7
- * give a width-only image its proportional height. Cheap - images are few and this runs once.
6
+ * returns every image - a bitmap, or an SVG (which needs this only when it comes from a URL). A
7
+ * pre-layout pass uses this to resolve each one's intrinsic size asynchronously: layout is synchronous
8
+ * and cannot await a decoder or a fetch, yet it needs the aspect ratio to give a width-only image its
9
+ * proportional height. Cheap - images are few and this runs once.
8
10
  */
9
- export declare function collectImageElements(root: PDFElement): ImageElement[];
11
+ export declare function collectImageElements(root: PDFElement): (ImageElement | SvgElement)[];
@@ -1,15 +1,17 @@
1
1
  import { PDFElement, hasChildrenProp, hasChildProp } from "../elements/pdf-element.js";
2
2
  import { ImageElement } from "../elements/image-element.js";
3
+ import { SvgElement } from "../elements/svg-element.js";
3
4
  /**
4
5
  * Walks the element tree (via `getProps`, the same shallow-reflection style as the validator) and
5
- * returns every `ImageElement`. A pre-layout pass uses this to resolve each image's intrinsic pixel
6
- * size asynchronously: layout is synchronous and cannot await jimp, yet it needs the aspect ratio to
7
- * give a width-only image its proportional height. Cheap - images are few and this runs once.
6
+ * returns every image - a bitmap, or an SVG (which needs this only when it comes from a URL). A
7
+ * pre-layout pass uses this to resolve each one's intrinsic size asynchronously: layout is synchronous
8
+ * and cannot await a decoder or a fetch, yet it needs the aspect ratio to give a width-only image its
9
+ * proportional height. Cheap - images are few and this runs once.
8
10
  */
9
11
  export function collectImageElements(root) {
10
12
  const out = [];
11
13
  const visit = (el) => {
12
- if (el instanceof ImageElement)
14
+ if (el instanceof ImageElement || el instanceof SvgElement)
13
15
  out.push(el);
14
16
  const props = el.getProps();
15
17
  if (hasChildrenProp(props))
@@ -0,0 +1,9 @@
1
+ import { FlexiblePDFElement, PDFElement } from "../elements/pdf-element.js";
2
+ import type { CrossAlign } from "./alignment.js";
3
+ /** The `Expanded`/`Spacer` that makes this child flexible, if any. */
4
+ export declare function flexibleOf(child: PDFElement): FlexiblePDFElement | undefined;
5
+ export declare function alignSelfOf(child: PDFElement): CrossAlign | undefined;
6
+ /** CSS `order`, 0 when unset. */
7
+ export declare function orderOf(child: PDFElement): number;
8
+ /** CSS `flex-shrink`, 1 when unset (the CSS default). */
9
+ export declare function flexShrinkOf(child: PDFElement): number;
@@ -0,0 +1,39 @@
1
+ import { FlexiblePDFElement } from "../elements/pdf-element.js";
2
+ /**
3
+ * How a Row or Column sees a child as a flex item. A wrapper that only marks its child (a `Link`, an
4
+ * `Anchor`, a `DefaultTextStyle` - see `PDFElement.proxied`) is looked through, as CSS
5
+ * `display: contents` is: an `Expanded`, `alignSelf`, `order` or `flexShrink` inside it acts as if the
6
+ * wrapper were not there. A value set on the wrapper itself wins over one set inside it.
7
+ */
8
+ /** The child, then each element it is transparent for, outermost first. */
9
+ function* itemChain(child) {
10
+ for (let el = child; el; el = el.proxied())
11
+ yield el;
12
+ }
13
+ /** The `Expanded`/`Spacer` that makes this child flexible, if any. */
14
+ export function flexibleOf(child) {
15
+ for (const el of itemChain(child))
16
+ if (el instanceof FlexiblePDFElement)
17
+ return el;
18
+ return undefined;
19
+ }
20
+ export function alignSelfOf(child) {
21
+ for (const el of itemChain(child))
22
+ if (el.alignSelf !== undefined)
23
+ return el.alignSelf;
24
+ return undefined;
25
+ }
26
+ /** CSS `order`, 0 when unset. */
27
+ export function orderOf(child) {
28
+ for (const el of itemChain(child))
29
+ if (el.order !== undefined)
30
+ return el.order;
31
+ return 0;
32
+ }
33
+ /** CSS `flex-shrink`, 1 when unset (the CSS default). */
34
+ export function flexShrinkOf(child) {
35
+ for (const el of itemChain(child))
36
+ if (el.flexShrink !== undefined)
37
+ return el.flexShrink;
38
+ return 1;
39
+ }
@@ -33,11 +33,19 @@ export declare function isFragmentable(element: PDFElement): element is PDFEleme
33
33
  * logs and clips, `"ignore"` clips silently.
34
34
  */
35
35
  export type OverflowPolicy = "error" | "warn" | "ignore";
36
- export declare function reportOverflow(child: PDFElement, childHeight: number, maxHeight: number, policy: OverflowPolicy): void;
37
36
  /**
38
- * True if packing `children` will hit a forced cut: a child that IS a break, contains one deeper in its
39
- * subtree, or asks to break before/after itself. A flow container reports this from `hasForcedBreak` so
40
- * its parent fragments it (runs `packChildren`) instead of placing it whole and swallowing the cut.
37
+ * The context for content inset by `chrome` on every page (a border, padding, a repeated header): a
38
+ * fresh page gives it that much less room. Every element that shrinks the region for its children
39
+ * hands this down with it.
40
+ */
41
+ export declare function insetContext(ctx: LayoutContext, chrome: number): LayoutContext;
42
+ export declare function reportOverflow(child: PDFElement, childHeight: number, maxHeight: number, ctx: LayoutContext): void;
43
+ /**
44
+ * True if packing `children` will hit a forced cut INSIDE them: a child that IS a break, contains one
45
+ * deeper in its subtree, or asks to break before/after itself. A flow container reports this from
46
+ * `hasForcedBreak` so its parent fragments it (runs `packChildren`) instead of placing it whole and
47
+ * swallowing the cut. A break before the FIRST child is not inside: the stack takes it as its own
48
+ * (`breaksBefore`), so it moves whole - which is what a `keepTogether` around it relies on.
41
49
  */
42
50
  export declare function childrenForceBreak(children: PDFElement[]): boolean;
43
51
  /**
@@ -1,4 +1,5 @@
1
1
  import { BoxConstraints } from "./box-constraints.js";
2
+ import { diagnose, pageOf } from "../common/diagnostics.js";
2
3
  export function isFragmentable(element) {
3
4
  if (!("fragment" in element) ||
4
5
  typeof element.fragment !== "function") {
@@ -10,24 +11,43 @@ export function isFragmentable(element) {
10
11
  const canFragment = element.canFragment;
11
12
  return typeof canFragment !== "function" || canFragment.call(element);
12
13
  }
13
- export function reportOverflow(child, childHeight, maxHeight, policy) {
14
- if (policy === "ignore")
15
- return;
14
+ /**
15
+ * The context for content inset by `chrome` on every page (a border, padding, a repeated header): a
16
+ * fresh page gives it that much less room. Every element that shrinks the region for its children
17
+ * hands this down with it.
18
+ */
19
+ export function insetContext(ctx, chrome) {
20
+ if (ctx.pageBodyHeight === undefined || chrome === 0)
21
+ return ctx;
22
+ return { ...ctx, pageBodyHeight: ctx.pageBodyHeight - chrome };
23
+ }
24
+ export function reportOverflow(child, childHeight, maxHeight, ctx) {
25
+ const policy = ctx.onOverflow ?? "ignore";
16
26
  const name = child.constructor.name.replace(/Element$/, "");
17
27
  const detail = `${name} is ${Math.round(childHeight)}pt tall but the page region is only ` +
18
28
  `${Math.round(maxHeight)}pt and it cannot be broken - reduce its size, give it a bounded ` +
19
29
  `height, or let it split across pages.`;
20
30
  if (policy === "error")
21
31
  throw new Error(`Layout overflow: ${detail}`);
22
- console.warn(`Layout overflow (clipped): ${detail}`);
32
+ diagnose(ctx.diagnostics, {
33
+ code: "overflow-clipped",
34
+ severity: "warning",
35
+ message: `Layout overflow (clipped): ${detail}`,
36
+ about: child,
37
+ page: pageOf(ctx),
38
+ // `ignore` means "no console" - an `onDiagnostic` handler still hears of it.
39
+ fallback: policy === "ignore" ? "silent" : "warn",
40
+ });
23
41
  }
24
42
  /**
25
- * True if packing `children` will hit a forced cut: a child that IS a break, contains one deeper in its
26
- * subtree, or asks to break before/after itself. A flow container reports this from `hasForcedBreak` so
27
- * its parent fragments it (runs `packChildren`) instead of placing it whole and swallowing the cut.
43
+ * True if packing `children` will hit a forced cut INSIDE them: a child that IS a break, contains one
44
+ * deeper in its subtree, or asks to break before/after itself. A flow container reports this from
45
+ * `hasForcedBreak` so its parent fragments it (runs `packChildren`) instead of placing it whole and
46
+ * swallowing the cut. A break before the FIRST child is not inside: the stack takes it as its own
47
+ * (`breaksBefore`), so it moves whole - which is what a `keepTogether` around it relies on.
28
48
  */
29
49
  export function childrenForceBreak(children) {
30
- return children.some((child) => child.hasForcedBreak() || child.breaksBefore() || child.breaksAfter());
50
+ return children.some((child, i) => child.hasForcedBreak() || (i > 0 && child.breaksBefore()) || child.breaksAfter());
31
51
  }
32
52
  /**
33
53
  * Packs a vertical stack of children into `maxHeight`, in order. Children are measured
@@ -64,7 +84,7 @@ export function packChildren(children, maxHeight, width, ctx, gap = 0) {
64
84
  // (CSS ignores break-before at the top of a page); that guard also prevents an empty page + a loop.
65
85
  if (child.breaksBefore() && fitted.length > 0)
66
86
  return spill(i, true);
67
- const childHeight = child.calculateLayout(BoxConstraints.loose(width, Infinity), { x: 0, y: 0 }, ctx).height;
87
+ const childHeight = child.measure(BoxConstraints.loose(width, Infinity), ctx).height;
68
88
  // A gap precedes every child except the first one placed in this region.
69
89
  const lead = fitted.length > 0 ? gap : 0;
70
90
  // Place whole only if it fits AND holds no forced break: a child that fits by height but contains
@@ -82,8 +102,10 @@ export function packChildren(children, maxHeight, width, ctx, gap = 0) {
82
102
  const remaining = maxHeight - usedHeight - lead;
83
103
  let placedPart = false;
84
104
  let childBroke = false;
105
+ const below = ctx.belowContent || fitted.length > 0;
85
106
  if (isFragmentable(child)) {
86
- const split = child.fragment(Math.max(0, remaining), width, ctx);
107
+ const childCtx = below && !ctx.belowContent ? { ...ctx, belowContent: true } : ctx;
108
+ const split = child.fragment(Math.max(0, remaining), width, childCtx);
87
109
  childBroke = split.forceBreak ?? false;
88
110
  if (split.fitted) {
89
111
  fitted.push(split.fitted);
@@ -101,10 +123,11 @@ export function packChildren(children, maxHeight, width, ctx, gap = 0) {
101
123
  }
102
124
  }
103
125
  if (!placedPart) {
104
- if (fitted.length === 0 && !childBroke) {
105
- // Taller than the whole region and unsplittable: force it on (it overflows and is clipped)
106
- // so the next region still advances - and surface it per the overflow policy.
107
- reportOverflow(child, childHeight, maxHeight, ctx.onOverflow ?? "ignore");
126
+ if (!below && !childBroke) {
127
+ // Taller than a whole fresh page and unsplittable: force it on (it overflows and is clipped)
128
+ // so the next region still advances - and surface it per the overflow policy. Below other
129
+ // content it simply waits for the next page.
130
+ reportOverflow(child, childHeight, maxHeight, ctx);
108
131
  fitted.push(child);
109
132
  }
110
133
  else {
@@ -0,0 +1,53 @@
1
+ import type { BoxConstraints, Size } from "./box-constraints.js";
2
+ import type { LayoutContext } from "../elements/pdf-element.js";
3
+ /**
4
+ * The layout cache - the one place a size is remembered. `PDFElement.measure` asks it before
5
+ * `computeSize` runs, and stores what `computeSize` returned.
6
+ *
7
+ * An element never changes after it is built, so its size is a pure function of its INPUTS: the
8
+ * constraints, plus the context fields listed in `Key` below. The same inputs again are answered from
9
+ * the element's own cache. That is what Flutter's dry-layout cache and Yoga's measure cache do; flex
10
+ * asks the same child the same question several times while it distributes space.
11
+ *
12
+ * What keeps it correct:
13
+ * - `computeSize` reads only the context fields in `Key`. A new field that can change a size is added
14
+ * HERE. `frame`, `belowContent`, `pageBodyHeight` and `onOverflow` steer placement and page breaks,
15
+ * never a size, so they are not part of it.
16
+ * - The page (`pageConfig`, `pageInfo`) is part of the key only for an element that
17
+ * `dependsOnPage()`. Every page and both passes bring new page objects, so keying everything on them
18
+ * would make the cache miss on each page.
19
+ * - Context values compare by IDENTITY. Code that derives one (a merged text style) returns the same
20
+ * object for the same input, or every element below it misses.
21
+ * - `fontEpoch` moves when a font face is added, and kerning can be switched on the same metrics; both
22
+ * change widths.
23
+ *
24
+ * The same key, without the constraints, decides when a subtree built at layout time can be reused
25
+ * (`BuiltFromContext`, below).
26
+ *
27
+ * Below this level sit value memos that are not size caches: glyph advances per font
28
+ * (`text/advance.ts`) and a Text's displayed content (`TextElement.display`).
29
+ */
30
+ export declare const layoutCache: {
31
+ /** Off only in the test that renders every pinned document both ways and demands the same bytes. */
32
+ enabled: boolean;
33
+ };
34
+ /** One element's remembered sizes, oldest overwritten first. */
35
+ export declare class SizeCache {
36
+ private entries;
37
+ private next;
38
+ get(constraints: BoxConstraints, ctx: LayoutContext, onPage: boolean): Size | undefined;
39
+ set(constraints: BoxConstraints, ctx: LayoutContext, onPage: boolean, size: Size): void;
40
+ }
41
+ /**
42
+ * A subtree an element builds from the context at layout time (`Deferred`: a table whose `auto`
43
+ * columns are measured from its cells and fitted to its width). It is built again only when the
44
+ * context key or the width changes - the size rules and the one constraint building reads. Whether the
45
+ * page belongs to the key is decided by what was built: a subtree without a `PageBuilder` is the same
46
+ * on every page.
47
+ */
48
+ export declare class BuiltFromContext<T extends {
49
+ dependsOnPage(): boolean;
50
+ }> {
51
+ private last?;
52
+ get(ctx: LayoutContext, width: number, build: () => T): T;
53
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * The layout cache - the one place a size is remembered. `PDFElement.measure` asks it before
3
+ * `computeSize` runs, and stores what `computeSize` returned.
4
+ *
5
+ * An element never changes after it is built, so its size is a pure function of its INPUTS: the
6
+ * constraints, plus the context fields listed in `Key` below. The same inputs again are answered from
7
+ * the element's own cache. That is what Flutter's dry-layout cache and Yoga's measure cache do; flex
8
+ * asks the same child the same question several times while it distributes space.
9
+ *
10
+ * What keeps it correct:
11
+ * - `computeSize` reads only the context fields in `Key`. A new field that can change a size is added
12
+ * HERE. `frame`, `belowContent`, `pageBodyHeight` and `onOverflow` steer placement and page breaks,
13
+ * never a size, so they are not part of it.
14
+ * - The page (`pageConfig`, `pageInfo`) is part of the key only for an element that
15
+ * `dependsOnPage()`. Every page and both passes bring new page objects, so keying everything on them
16
+ * would make the cache miss on each page.
17
+ * - Context values compare by IDENTITY. Code that derives one (a merged text style) returns the same
18
+ * object for the same input, or every element below it misses.
19
+ * - `fontEpoch` moves when a font face is added, and kerning can be switched on the same metrics; both
20
+ * change widths.
21
+ *
22
+ * The same key, without the constraints, decides when a subtree built at layout time can be reused
23
+ * (`BuiltFromContext`, below).
24
+ *
25
+ * Below this level sit value memos that are not size caches: glyph advances per font
26
+ * (`text/advance.ts`) and a Text's displayed content (`TextElement.display`).
27
+ */
28
+ export const layoutCache = {
29
+ /** Off only in the test that renders every pinned document both ways and demands the same bytes. */
30
+ enabled: true,
31
+ };
32
+ const contextKeyOf = (ctx, onPage) => ({
33
+ metrics: ctx.metrics,
34
+ fontEpoch: ctx.metrics?.fontEpoch ?? 0,
35
+ kerning: ctx.metrics?.kerningEnabled,
36
+ textStyle: ctx.textStyle,
37
+ pageConfig: onPage ? ctx.pageConfig : undefined,
38
+ pageInfo: onPage ? ctx.pageInfo : undefined,
39
+ boxPaddingBase: ctx.boxPaddingBase,
40
+ });
41
+ const sameContext = (a, b) => a.metrics === b.metrics &&
42
+ a.fontEpoch === b.fontEpoch &&
43
+ a.kerning === b.kerning &&
44
+ a.textStyle === b.textStyle &&
45
+ a.pageConfig === b.pageConfig &&
46
+ a.pageInfo === b.pageInfo &&
47
+ a.boxPaddingBase === b.boxPaddingBase;
48
+ const keyOf = (c, ctx, onPage) => ({
49
+ minWidth: c.minWidth,
50
+ maxWidth: c.maxWidth,
51
+ minHeight: c.minHeight,
52
+ maxHeight: c.maxHeight,
53
+ context: contextKeyOf(ctx, onPage),
54
+ });
55
+ const sameKey = (a, b) => a.minWidth === b.minWidth &&
56
+ a.maxWidth === b.maxWidth &&
57
+ a.minHeight === b.minHeight &&
58
+ a.maxHeight === b.maxHeight &&
59
+ sameContext(a.context, b.context);
60
+ /**
61
+ * How many different questions one element remembers. Flex asks a child at more than one width (its
62
+ * natural width, then its share), so one entry would evict itself.
63
+ */
64
+ const ENTRIES = 4;
65
+ /** One element's remembered sizes, oldest overwritten first. */
66
+ export class SizeCache {
67
+ constructor() {
68
+ this.entries = [];
69
+ this.next = 0;
70
+ }
71
+ get(constraints, ctx, onPage) {
72
+ if (!layoutCache.enabled)
73
+ return undefined;
74
+ const key = keyOf(constraints, ctx, onPage);
75
+ const hit = this.entries.find((e) => sameKey(e.key, key));
76
+ // A copy: a caller that adjusts the size it was handed must not change the remembered one.
77
+ return hit && { ...hit.size };
78
+ }
79
+ set(constraints, ctx, onPage, size) {
80
+ const entry = { key: keyOf(constraints, ctx, onPage), size: { ...size } };
81
+ if (this.entries.length < ENTRIES)
82
+ this.entries.push(entry);
83
+ else
84
+ this.entries[this.next] = entry;
85
+ this.next = (this.next + 1) % ENTRIES;
86
+ }
87
+ }
88
+ /**
89
+ * A subtree an element builds from the context at layout time (`Deferred`: a table whose `auto`
90
+ * columns are measured from its cells and fitted to its width). It is built again only when the
91
+ * context key or the width changes - the size rules and the one constraint building reads. Whether the
92
+ * page belongs to the key is decided by what was built: a subtree without a `PageBuilder` is the same
93
+ * on every page.
94
+ */
95
+ export class BuiltFromContext {
96
+ get(ctx, width, build) {
97
+ const last = this.last;
98
+ if (layoutCache.enabled &&
99
+ last &&
100
+ last.width === width &&
101
+ sameContext(last.key, contextKeyOf(ctx, last.value.dependsOnPage())))
102
+ return last.value;
103
+ const value = build();
104
+ this.last = { key: contextKeyOf(ctx, value.dependsOnPage()), width, value };
105
+ return value;
106
+ }
107
+ }
@@ -1,6 +1,7 @@
1
1
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
2
  import { AnchorElement } from "../elements/layout/anchor-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 AnchorRenderer {
5
- static render(element: AnchorElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
6
+ static render(element: AnchorElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
6
7
  }
@@ -1,9 +1,10 @@
1
1
  import { RendererRegistry } from "../utils/renderer-registry.js";
2
2
  export class AnchorRenderer {
3
- static async render(element, objectManager) {
4
- const { name, 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 { name, 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 Anchor IR node carries the name + scroll target for the page renderer
8
9
  // to register as a named destination (it produces no content-stream ops itself).
9
10
  return [...childNodes, { type: "anchor", y, name }];
@@ -1,6 +1,7 @@
1
1
  import { PDFObjectManager } from "../utils/pdf-object-manager.js";
2
2
  import { BookmarkElement } from "../elements/layout/bookmark-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 BookmarkRenderer {
5
- static render(element: BookmarkElement, objectManager: PDFObjectManager): Promise<IRNode[]>;
6
+ static render(element: BookmarkElement, objectManager: PDFObjectManager, scope: RenderScope): Promise<IRNode[]>;
6
7
  }