@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
@@ -16,8 +16,17 @@ export declare class RepeatingHeaderElement extends PDFElement implements Fragme
16
16
  private width;
17
17
  private height;
18
18
  constructor(header: PDFElement, body: PDFElement, gap?: number);
19
+ /** The header for the next piece: a copy, since two pieces may share a page (two Regions of a Flow)
20
+ * and one element has one position - that tags as the SAME header. */
21
+ private nextHeader;
19
22
  calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
23
+ protected computeSize(constraints: BoxConstraints, ctx: LayoutContext): Size;
24
+ /** The one sizing path: `layout` places header and body (relative to this box), `measure` sizes. */
25
+ private sizeWith;
20
26
  fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
27
+ /** A break inside the body is a break in the flow the table sits in. */
28
+ hasForcedBreak(): boolean;
29
+ layoutChildren(): readonly PDFElement[];
21
30
  getProps(): {
22
31
  x: number;
23
32
  y: number;
@@ -1,6 +1,14 @@
1
1
  import { BoxConstraints } from "../../layout/box-constraints.js";
2
- import { isFragmentable } from "../../layout/fragmentation.js";
2
+ import { insetContext, isFragmentable, } from "../../layout/fragmentation.js";
3
3
  import { PDFElement } from "../pdf-element.js";
4
+ /** Gives every element of `fresh` the tagging identity of its twin in `from` (the same shape). */
5
+ function adoptStructIds(fresh, from) {
6
+ fresh.adoptStructId(from);
7
+ const a = fresh.layoutChildren();
8
+ const b = from.layoutChildren();
9
+ for (let i = 0; i < a.length && i < b.length; i++)
10
+ adoptStructIds(a[i], b[i]);
11
+ }
4
12
  /**
5
13
  * Stacks a `header` above a `body` and, when it paginates, **repeats the header on every
6
14
  * fragment**. Used by `Table` so column headings reappear at the top of each page. Layout
@@ -18,31 +26,65 @@ export class RepeatingHeaderElement extends PDFElement {
18
26
  this.width = 0;
19
27
  this.height = 0;
20
28
  }
29
+ /** The header for the next piece: a copy, since two pieces may share a page (two Regions of a Flow)
30
+ * and one element has one position - that tags as the SAME header. */
31
+ nextHeader() {
32
+ const copy = this.header.clone();
33
+ adoptStructIds(copy, this.header);
34
+ return copy;
35
+ }
21
36
  calculateLayout(constraints, offset, ctx) {
37
+ const size = this.sizeWith(constraints, ctx, "layout");
22
38
  this.x = offset.x;
23
39
  this.y = offset.y;
40
+ this.width = size.width;
41
+ this.height = size.height;
42
+ return size;
43
+ }
44
+ computeSize(constraints, ctx) {
45
+ return this.sizeWith(constraints, ctx, "measure");
46
+ }
47
+ /** The one sizing path: `layout` places header and body (relative to this box), `measure` sizes. */
48
+ sizeWith(constraints, ctx, mode) {
24
49
  const width = constraints.hasBoundedWidth ? constraints.maxWidth : 0;
25
- const h = this.header.calculateLayout(BoxConstraints.loose(width, Infinity), { x: this.x, y: this.y }, ctx);
26
- const b = this.body.calculateLayout(BoxConstraints.loose(width, Infinity), { x: this.x, y: this.y + h.height + this.gap }, ctx);
27
- this.width = width;
28
- this.height = h.height + this.gap + b.height;
29
- return { width: this.width, height: this.height };
50
+ const inner = BoxConstraints.loose(width, Infinity);
51
+ const h = mode === "layout"
52
+ ? this.header.calculateLayout(inner, { x: 0, y: 0 }, ctx)
53
+ : this.header.measure(inner, ctx);
54
+ const b = mode === "layout"
55
+ ? this.body.calculateLayout(inner, { x: 0, y: h.height + this.gap }, ctx)
56
+ : this.body.measure(inner, ctx);
57
+ return { width, height: h.height + this.gap + b.height };
30
58
  }
31
59
  fragment(maxHeight, width, ctx) {
32
60
  // Reserve the header's height on every page; the body flows in what's left.
33
- const headerHeight = this.header.calculateLayout(BoxConstraints.loose(width, Infinity), { x: 0, y: 0 }, ctx).height;
61
+ const headerHeight = this.header.measure(BoxConstraints.loose(width, Infinity), ctx).height;
34
62
  if (!isFragmentable(this.body))
35
63
  return { fitted: this, remainder: null };
36
- const split = this.body.fragment(Math.max(0, maxHeight - headerHeight - this.gap), width, ctx);
37
- // Body fits whole → the whole thing fits on this page.
38
- if (split.remainder === null)
64
+ const chrome = headerHeight + this.gap;
65
+ const split = this.body.fragment(Math.max(0, maxHeight - chrome), width, insetContext(ctx, chrome));
66
+ // Body fits whole, no break inside → the whole thing fits on this page.
67
+ if (split.remainder === null && !split.forceBreak)
39
68
  return { fitted: this, remainder: null };
40
- // Re-wrap each body piece with the SAME header → it reappears on the next page too.
69
+ // Re-wrap each body piece with the SAME header → it reappears on the next page too. A forced
70
+ // break inside the body is passed on, so the page ends there.
41
71
  return {
42
- fitted: split.fitted ? new RepeatingHeaderElement(this.header, split.fitted, this.gap) : null,
43
- remainder: new RepeatingHeaderElement(this.header, split.remainder, this.gap),
72
+ fitted: split.fitted
73
+ ? new RepeatingHeaderElement(this.header, split.fitted, this.gap).asPieceOf(this)
74
+ : null,
75
+ remainder: split.remainder
76
+ ? new RepeatingHeaderElement(this.nextHeader(), split.remainder, this.gap).asPieceOf(this)
77
+ : null,
78
+ forceBreak: split.forceBreak,
44
79
  };
45
80
  }
81
+ /** A break inside the body is a break in the flow the table sits in. */
82
+ hasForcedBreak() {
83
+ return this.body.hasForcedBreak();
84
+ }
85
+ layoutChildren() {
86
+ return [this.header, this.body];
87
+ }
46
88
  getProps() {
47
89
  return {
48
90
  x: this.x,
@@ -23,7 +23,13 @@ export declare class RotatedBoxElement extends PDFElement {
23
23
  child: PDFElement;
24
24
  });
25
25
  calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
26
+ protected computeSize(constraints: BoxConstraints, ctx: LayoutContext): Size;
27
+ /** 90 or 270 degrees swap the axes: the child experiences the box's constraints turned. */
28
+ private get quarter();
29
+ private childConstraints;
30
+ private swap;
26
31
  /** Same shape as `RotatedElement` (angle + box), so both share `RotatedRenderer`. */
32
+ layoutChildren(): readonly PDFElement[];
27
33
  getProps(): {
28
34
  angle: number;
29
35
  child: PDFElement;
@@ -22,25 +22,38 @@ export class RotatedBoxElement extends PDFElement {
22
22
  this.child = child;
23
23
  }
24
24
  calculateLayout(constraints, offset, ctx) {
25
- const quarter = this.turns % 2 === 1; // 90 or 270: axes swap
26
- // A quarter-turned child experiences the box's constraints with the axes swapped.
27
- const childConstraints = quarter
28
- ? new BoxConstraints(constraints.minHeight, constraints.maxHeight, constraints.minWidth, constraints.maxWidth)
29
- : constraints;
30
25
  // Measure the child to learn its natural size, then take that size swapped as our own box.
31
- const child = this.child.calculateLayout(childConstraints, offset, ctx);
32
- this.width = quarter ? child.height : child.width;
33
- this.height = quarter ? child.width : child.height;
26
+ const child = this.child.measure(this.childConstraints(constraints), ctx);
27
+ const size = this.swap(child);
28
+ this.width = size.width;
29
+ this.height = size.height;
34
30
  this.x = offset.x;
35
31
  this.y = offset.y;
36
- // Re-place the child CENTERED on the box center: a rotation around that center then maps the
37
- // child's (w x h) box exactly onto our (swapped) box - so the shared renderer needs no special case.
38
- const cx = this.x + this.width / 2;
39
- const cy = this.y + this.height / 2;
40
- this.child.calculateLayout(childConstraints, { x: cx - child.width / 2, y: cy - child.height / 2 }, ctx);
41
- return { width: this.width, height: this.height };
32
+ // Place the child CENTERED on the box center (relative to our top-left): a rotation around that
33
+ // center then maps the child's (w x h) box exactly onto our (swapped) box - so the shared renderer
34
+ // needs no special case.
35
+ this.child.calculateLayout(this.childConstraints(constraints), { x: (this.width - child.width) / 2, y: (this.height - child.height) / 2 }, ctx);
36
+ return size;
37
+ }
38
+ computeSize(constraints, ctx) {
39
+ return this.swap(this.child.measure(this.childConstraints(constraints), ctx));
40
+ }
41
+ /** 90 or 270 degrees swap the axes: the child experiences the box's constraints turned. */
42
+ get quarter() {
43
+ return this.turns % 2 === 1;
44
+ }
45
+ childConstraints(constraints) {
46
+ return this.quarter
47
+ ? new BoxConstraints(constraints.minHeight, constraints.maxHeight, constraints.minWidth, constraints.maxWidth)
48
+ : constraints;
49
+ }
50
+ swap(child) {
51
+ return this.quarter ? { width: child.height, height: child.width } : child;
42
52
  }
43
53
  /** Same shape as `RotatedElement` (angle + box), so both share `RotatedRenderer`. */
54
+ layoutChildren() {
55
+ return [this.child];
56
+ }
44
57
  getProps() {
45
58
  return {
46
59
  angle: this.turns * 90,
@@ -21,6 +21,15 @@ export declare class RotatedElement extends PDFElement {
21
21
  child: PDFElement;
22
22
  });
23
23
  calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
24
+ protected computeSize(constraints: BoxConstraints, ctx: LayoutContext): Size;
25
+ layoutChildren(): readonly PDFElement[];
26
+ proxied(): PDFElement;
27
+ relativeSizeFactor(horizontal: boolean): number | undefined;
28
+ maxExtent(horizontal: boolean): number;
29
+ needsBoundedMain(horizontal: boolean): boolean;
30
+ minIntrinsicMain(horizontal: boolean, ctx: LayoutContext): number;
31
+ breaksBefore(): boolean;
32
+ breaksAfter(): boolean;
24
33
  getProps(): {
25
34
  angle: number;
26
35
  child: PDFElement;
@@ -19,15 +19,46 @@ export class RotatedElement extends PDFElement {
19
19
  this.child = child;
20
20
  }
21
21
  calculateLayout(constraints, offset, ctx) {
22
- // Delegate entirely to the child, then record its box (position + size) as our own - the
22
+ // Delegate entirely to the child (at our own top-left), then record its size as our own - the
23
23
  // rotation does not change what space we take, only how the child is painted.
24
- const size = this.child.calculateLayout(constraints, offset, ctx);
24
+ const size = this.child.calculateLayout(constraints, { x: 0, y: 0 }, ctx);
25
25
  this.x = offset.x;
26
26
  this.y = offset.y;
27
27
  this.width = size.width;
28
28
  this.height = size.height;
29
29
  return size;
30
30
  }
31
+ computeSize(constraints, ctx) {
32
+ return this.child.measure(constraints, ctx);
33
+ }
34
+ layoutChildren() {
35
+ return [this.child];
36
+ }
37
+ // The spin is paint-only, so to a surrounding Row or Column the child is the item (CSS: a `transform`
38
+ // leaves an item an item).
39
+ proxied() {
40
+ return this.child;
41
+ }
42
+ // It takes the child's space, so the child's sizing hints and its break-before/after are its own.
43
+ // It is never split - half a rotation on each page means nothing - so a break INSIDE stays inside.
44
+ relativeSizeFactor(horizontal) {
45
+ return this.child.relativeSizeFactor(horizontal);
46
+ }
47
+ maxExtent(horizontal) {
48
+ return this.child.maxExtent(horizontal);
49
+ }
50
+ needsBoundedMain(horizontal) {
51
+ return this.child.needsBoundedMain(horizontal);
52
+ }
53
+ minIntrinsicMain(horizontal, ctx) {
54
+ return this.child.minIntrinsicMain(horizontal, ctx);
55
+ }
56
+ breaksBefore() {
57
+ return this.child.breaksBefore();
58
+ }
59
+ breaksAfter() {
60
+ return this.child.breaksAfter();
61
+ }
31
62
  getProps() {
32
63
  return {
33
64
  angle: this.angle,
@@ -1,28 +1,20 @@
1
- import { PDFElement, LayoutContext } from "../pdf-element.js";
2
- import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
3
- import { Fragmentable, FragmentResult } from "../../layout/fragmentation.js";
1
+ import { PDFElement } from "../pdf-element.js";
2
+ import { ProxyElement } from "./proxy-element.js";
4
3
  /**
5
- * A layout-TRANSPARENT wrapper that assigns an accessibility structure role (Table / TR / TH / TD / …) to
6
- * its single child. It delegates ALL layout and fragmentation to the child, so the visual result is byte-
7
- * identical; only its renderer opens a structure element and nests the child's tags underneath.
4
+ * A layout-transparent wrapper (a `ProxyElement`) that assigns an accessibility structure role
5
+ * (Table / TR / TH / TD / …) to its single child; only its renderer opens a structure element and nests
6
+ * the child's tags underneath.
8
7
  *
9
- * Split across pages, the fitted and remainder halves share this group's `structId`, so it stays ONE
10
- * logical structure element (a table broken over pages is a single <Table>, exactly as Acrobat produces).
8
+ * Split across pages, the pieces share this group's `structId`, so it stays ONE logical structure
9
+ * element (a table broken over pages is a single <Table>, exactly as Acrobat produces).
11
10
  */
12
- export declare class StructGroup extends PDFElement implements Fragmentable {
11
+ export declare class StructGroup extends ProxyElement {
13
12
  private role;
14
- private child;
15
13
  constructor({ role, child }: {
16
14
  role: string;
17
15
  child: PDFElement;
18
16
  });
19
- /** Layout-transparent: the floor is whatever the wrapped child needs. */
20
- minIntrinsicMain(horizontal: boolean, ctx: LayoutContext): number;
21
- calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
22
- /** Only splittable when the wrapped child is - so a StructGroup(Row) is moved whole to the next page,
23
- * not clipped at the boundary (isFragmentable honours this veto). */
24
- canFragment(): boolean;
25
- fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
17
+ protected rewrap(child: PDFElement): PDFElement;
26
18
  getProps(): {
27
19
  role: string;
28
20
  child: PDFElement;
@@ -1,44 +1,19 @@
1
- import { PDFElement } from "../pdf-element.js";
2
- import { isFragmentable } from "../../layout/fragmentation.js";
1
+ import { ProxyElement } from "./proxy-element.js";
3
2
  /**
4
- * A layout-TRANSPARENT wrapper that assigns an accessibility structure role (Table / TR / TH / TD / …) to
5
- * its single child. It delegates ALL layout and fragmentation to the child, so the visual result is byte-
6
- * identical; only its renderer opens a structure element and nests the child's tags underneath.
3
+ * A layout-transparent wrapper (a `ProxyElement`) that assigns an accessibility structure role
4
+ * (Table / TR / TH / TD / …) to its single child; only its renderer opens a structure element and nests
5
+ * the child's tags underneath.
7
6
  *
8
- * Split across pages, the fitted and remainder halves share this group's `structId`, so it stays ONE
9
- * logical structure element (a table broken over pages is a single <Table>, exactly as Acrobat produces).
7
+ * Split across pages, the pieces share this group's `structId`, so it stays ONE logical structure
8
+ * element (a table broken over pages is a single <Table>, exactly as Acrobat produces).
10
9
  */
11
- export class StructGroup extends PDFElement {
10
+ export class StructGroup extends ProxyElement {
12
11
  constructor({ role, child }) {
13
- super();
12
+ super(child);
14
13
  this.role = role;
15
- this.child = child;
16
14
  }
17
- /** Layout-transparent: the floor is whatever the wrapped child needs. */
18
- minIntrinsicMain(horizontal, ctx) {
19
- return this.child.minIntrinsicMain(horizontal, ctx);
20
- }
21
- calculateLayout(constraints, offset, ctx) {
22
- return this.child.calculateLayout(constraints, offset, ctx);
23
- }
24
- /** Only splittable when the wrapped child is - so a StructGroup(Row) is moved whole to the next page,
25
- * not clipped at the boundary (isFragmentable honours this veto). */
26
- canFragment() {
27
- return isFragmentable(this.child);
28
- }
29
- fragment(maxHeight, width, ctx) {
30
- if (!isFragmentable(this.child))
31
- return { fitted: null, remainder: this };
32
- const { fitted, remainder } = this.child.fragment(maxHeight, width, ctx);
33
- // Re-wrap each half with the SAME role + identity, so both pages point at one structure element.
34
- return {
35
- fitted: fitted
36
- ? new StructGroup({ role: this.role, child: fitted }).adoptStructId(this)
37
- : null,
38
- remainder: remainder
39
- ? new StructGroup({ role: this.role, child: remainder }).adoptStructId(this)
40
- : null,
41
- };
15
+ rewrap(child) {
16
+ return new StructGroup({ role: this.role, child });
42
17
  }
43
18
  getProps() {
44
19
  return { role: this.role, child: this.child };
@@ -17,6 +17,7 @@ export declare class LineElement extends SizedPDFElement {
17
17
  private sizeMemory;
18
18
  constructor({ color, strokeWidth, x, y, xEnd, yEnd }: LineElementParams);
19
19
  calculateLayout(constraints: BoxConstraints, offset: Offset, _ctx: LayoutContext): Size;
20
+ protected computeSize(constraints: BoxConstraints): Size;
20
21
  getProps(): {
21
22
  x: number;
22
23
  y: number;
@@ -24,6 +24,12 @@ export class LineElement extends SizedPDFElement {
24
24
  // Top-left coordinates; the Y-flip happens once at the IR -> backend seam.
25
25
  return { width: this.width ?? 0, height: this.height ?? 0 };
26
26
  }
27
+ computeSize(constraints) {
28
+ if (!constraints.hasBoundedWidth) {
29
+ throw new Error("The LineElement must be placed inside a parent container that defines its width");
30
+ }
31
+ return { width: this.width ?? 0, height: this.height ?? 0 };
32
+ }
27
33
  getProps() {
28
34
  return {
29
35
  x: this.x,
@@ -59,19 +59,26 @@ export interface PageBands {
59
59
  * resolve a `Positioned` against this same box, so `top`/`bottom` mean the same thing in each.
60
60
  *
61
61
  * `layoutPageBands` lays the bands out, so its caller must hand it a context carrying this frame -
62
- * otherwise a `Positioned` in a band has nothing to resolve against (ISSUE-4). A caller that only
63
- * wants the band HEIGHTS may throw the frame away without draining it: an out-of-flow child
64
- * contributes no height.
62
+ * otherwise a `Positioned` in a band has nothing to resolve against (ISSUE-4). An out-of-flow child
63
+ * contributes no height, so a caller that only wants the band HEIGHTS needs nothing more.
65
64
  */
66
65
  export declare function pageFrame(config: PDFPageConfig): PositioningFrame;
67
66
  export declare function layoutPageBands(config: PDFPageConfig, header: PDFElement | undefined, footer: PDFElement | undefined, ctx: LayoutContext): PageBands;
68
67
  export declare class PageElement extends PDFElement {
68
+ private readonly ownConfig;
69
69
  private config;
70
70
  private children;
71
71
  private header?;
72
72
  private footer?;
73
73
  constructor({ children, config, header, footer }: PDFPageParams);
74
74
  calculateLayout(_constraints: BoxConstraints, _offset: Offset, ctx: LayoutContext): Size;
75
+ /** Its size is its own page config. */
76
+ dependsOnPage(): boolean;
77
+ /** A page is as large as its content box, whatever it holds. */
78
+ protected computeSize(_constraints: BoxConstraints, ctx: LayoutContext): Size;
79
+ /** The document defaults with this page's own settings on top. Pure: no pass has to run first. */
80
+ resolveConfig(defaults: PDFPageConfig | undefined): PDFPageConfig;
81
+ layoutChildren(): readonly PDFElement[];
75
82
  getProps(): PDFPageParams;
76
83
  addTextElement(element: TextElement): this;
77
84
  }
@@ -2,6 +2,7 @@ import { pageFormats, PageSize } from "../constants/page-sizes.js";
2
2
  import { Orientation } from "../renderer/pdf-config.js";
3
3
  import { BoxConstraints } from "../layout/box-constraints.js";
4
4
  import { PDFElement } from "./pdf-element.js";
5
+ import { positionedIn } from "./layout/positioned-element.js";
5
6
  /**
6
7
  * The full page (media box) size in points, orientation applied. This is what a `PageBuilder` sees as
7
8
  * `pageSize`; `resolvePageContentBox` below then subtracts the margins.
@@ -19,14 +20,14 @@ export function resolvePageSize(config) {
19
20
  * driver so they can never drift.
20
21
  */
21
22
  export function resolvePageContentBox(config) {
23
+ // The margins are the TURNED page's: `left` is the left edge of a landscape page, as `origin` says.
22
24
  const margin = config.margin;
23
- const [pageW, pageH] = config.customSize ?? pageFormats[config.pageSize];
24
- let width = pageW - margin.left - margin.right;
25
- let height = pageH - margin.top - margin.bottom;
26
- if (config.orientation === Orientation.landscape) {
27
- [width, height] = [height, width];
28
- }
29
- return { origin: { x: margin.left, y: margin.top }, width, height };
25
+ const page = resolvePageSize(config);
26
+ return {
27
+ origin: { x: margin.left, y: margin.top },
28
+ width: page.width - margin.left - margin.right,
29
+ height: page.height - margin.top - margin.bottom,
30
+ };
30
31
  }
31
32
  /**
32
33
  * Lays out the header (top) and footer (bottom) of a page and returns the body band left
@@ -41,13 +42,12 @@ export function resolvePageContentBox(config) {
41
42
  * resolve a `Positioned` against this same box, so `top`/`bottom` mean the same thing in each.
42
43
  *
43
44
  * `layoutPageBands` lays the bands out, so its caller must hand it a context carrying this frame -
44
- * otherwise a `Positioned` in a band has nothing to resolve against (ISSUE-4). A caller that only
45
- * wants the band HEIGHTS may throw the frame away without draining it: an out-of-flow child
46
- * contributes no height.
45
+ * otherwise a `Positioned` in a band has nothing to resolve against (ISSUE-4). An out-of-flow child
46
+ * contributes no height, so a caller that only wants the band HEIGHTS needs nothing more.
47
47
  */
48
48
  export function pageFrame(config) {
49
49
  const { origin, width, height } = resolvePageContentBox(config);
50
- return { origin, size: { width, height }, place: [] };
50
+ return { origin, size: { width, height } };
51
51
  }
52
52
  export function layoutPageBands(config, header, footer, ctx) {
53
53
  const { origin, width, height } = resolvePageContentBox(config);
@@ -57,13 +57,8 @@ export function layoutPageBands(config, header, footer, ctx) {
57
57
  }
58
58
  let footerHeight = 0;
59
59
  if (footer) {
60
- // Measure first to learn its height, then place it flush against the bottom edge. The MEASURING
61
- // pass gets a throwaway `place` list: an out-of-flow child registers a placement every time it is
62
- // laid out, and this subtree is laid out twice. Only the second, real pass may register.
63
- const measureCtx = ctx.frame
64
- ? { ...ctx, frame: { ...ctx.frame, place: [] } }
65
- : ctx;
66
- footerHeight = footer.calculateLayout(BoxConstraints.loose(width, Infinity), origin, measureCtx).height;
60
+ // Measure first to learn its height, then place it flush against the bottom edge.
61
+ footerHeight = footer.measure(BoxConstraints.loose(width, Infinity), ctx).height;
67
62
  footer.calculateLayout(BoxConstraints.loose(width, Infinity), { x: origin.x, y: origin.y + height - footerHeight }, ctx);
68
63
  }
69
64
  return {
@@ -78,20 +73,21 @@ export class PageElement extends PDFElement {
78
73
  constructor({ children, config, header, footer }) {
79
74
  super();
80
75
  this.children = children;
81
- this.config = config ?? {};
76
+ this.ownConfig = config ?? {};
77
+ this.config = this.ownConfig;
82
78
  this.header = header;
83
79
  this.footer = footer;
84
80
  }
85
81
  calculateLayout(_constraints, _offset, ctx) {
86
- // Merge the document defaults (carried in the context) with this page's overrides,
87
- // then hand descendants a context bound to THIS page's geometry. This is what
88
- // fixes the old last-page-wins global page-config bug.
89
- this.config = { ...ctx.pageConfig, ...this.config };
82
+ // Hand descendants a context bound to THIS page's geometry. This is what fixes the old
83
+ // last-page-wins global page-config bug.
84
+ this.config = this.resolveConfig(ctx.pageConfig);
90
85
  const pageCtx = {
91
86
  metrics: ctx.metrics,
92
87
  pageConfig: this.config,
93
88
  textStyle: ctx.textStyle,
94
89
  onOverflow: ctx.onOverflow,
90
+ diagnostics: ctx.diagnostics,
95
91
  pageInfo: ctx.pageInfo, // the render pass supplies it; absent during pagination
96
92
  };
97
93
  // The PAGE is a positioning frame, and the frame is the content box - not the body region left
@@ -110,16 +106,28 @@ export class PageElement extends PDFElement {
110
106
  const bands = layoutPageBands(this.config, this.header, this.footer, frameCtx);
111
107
  const childConstraints = BoxConstraints.loose(bands.bodyWidth, bands.bodyHeight);
112
108
  this.children.forEach((child) => child.calculateLayout(childConstraints, bands.bodyOrigin, frameCtx));
113
- // Drained last, once every band and the body have registered: an out-of-flow child is placed
114
- // against the final frame box and paints on top of the flow content.
115
- //
116
- // The callback gets `frameCtx`, not `pageCtx`: a `Positioned` NESTED inside an out-of-flow child
117
- // must still see a frame. It appends to `frame.place` while we walk it, and a `for..of` over an
118
- // array visits what was appended, so nested placements drain in the same loop.
119
- for (const place of frame.place)
120
- place(frame, frameCtx);
109
+ // Last, once the bands and the body are laid out: the page's out-of-flow descendants, placed
110
+ // relative to its content box. Each is drawn at its own place in the tree (tree order).
111
+ for (const p of positionedIn([this.header, ...this.children, this.footer]))
112
+ p.placeIn(frame.size, frameCtx);
121
113
  return { width: frame.size.width, height: frame.size.height };
122
114
  }
115
+ /** Its size is its own page config. */
116
+ dependsOnPage() {
117
+ return true;
118
+ }
119
+ /** A page is as large as its content box, whatever it holds. */
120
+ computeSize(_constraints, ctx) {
121
+ const { width, height } = resolvePageContentBox(this.resolveConfig(ctx.pageConfig));
122
+ return { width, height };
123
+ }
124
+ /** The document defaults with this page's own settings on top. Pure: no pass has to run first. */
125
+ resolveConfig(defaults) {
126
+ return { ...defaults, ...this.ownConfig };
127
+ }
128
+ layoutChildren() {
129
+ return [this.header, ...this.children, this.footer].filter((el) => el !== undefined);
130
+ }
123
131
  getProps() {
124
132
  return {
125
133
  children: this.children,
@@ -14,6 +14,9 @@ export declare class PDFDocumentElement extends PDFElement {
14
14
  /** The document-level text defaults; the renderer merges them into the cascade root. */
15
15
  getDefaultTextStyle(): Partial<ResolvedTextStyle> | undefined;
16
16
  calculateLayout(_constraints: BoxConstraints, _offset: Offset, ctx: LayoutContext): Size;
17
+ /** The root has no size of its own. */
18
+ protected computeSize(): Size;
19
+ layoutChildren(): readonly PDFElement[];
17
20
  getProps(): PDFDocumentParams;
18
21
  }
19
22
  export {};
@@ -17,6 +17,13 @@ export class PDFDocumentElement extends PDFElement {
17
17
  this.children.forEach((child) => child.calculateLayout(new BoxConstraints(), origin, ctx));
18
18
  return { width: 0, height: 0 };
19
19
  }
20
+ /** The root has no size of its own. */
21
+ computeSize() {
22
+ return { width: 0, height: 0 };
23
+ }
24
+ layoutChildren() {
25
+ return this.children;
26
+ }
20
27
  getProps() {
21
28
  return { children: this.children, defaultTextStyle: this.defaultTextStyle };
22
29
  }