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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. package/dist/api/content.d.ts +5 -5
  2. package/dist/api/content.js +12 -6
  3. package/dist/api/font-url.d.ts +1 -2
  4. package/dist/api/font-url.js +8 -74
  5. package/dist/api/forms.d.ts +3 -0
  6. package/dist/api/forms.js +3 -0
  7. package/dist/api/index.d.ts +2 -0
  8. package/dist/api/index.js +1 -0
  9. package/dist/api/layout.d.ts +6 -0
  10. package/dist/api/layout.js +7 -2
  11. package/dist/api/page-builder.d.ts +4 -0
  12. package/dist/api/page-builder.js +4 -0
  13. package/dist/api/structure.d.ts +10 -2
  14. package/dist/api/structure.js +9 -6
  15. package/dist/api/table.d.ts +5 -3
  16. package/dist/api/table.js +99 -22
  17. package/dist/common/diagnostics.d.ts +53 -0
  18. package/dist/common/diagnostics.js +77 -0
  19. package/dist/edit/acroform-reader.js +7 -7
  20. package/dist/edit/appearance.js +3 -3
  21. package/dist/edit/attachments.d.ts +52 -0
  22. package/dist/edit/attachments.js +178 -0
  23. package/dist/edit/document.d.ts +38 -8
  24. package/dist/edit/document.js +208 -51
  25. package/dist/edit/fill.js +8 -23
  26. package/dist/edit/filters.d.ts +15 -0
  27. package/dist/edit/filters.js +157 -0
  28. package/dist/edit/index.d.ts +4 -1
  29. package/dist/edit/index.js +3 -1
  30. package/dist/edit/objects.d.ts +3 -4
  31. package/dist/edit/objects.js +5 -18
  32. package/dist/elements/canvas-element.d.ts +3 -0
  33. package/dist/elements/canvas-element.js +14 -3
  34. package/dist/elements/container-element.d.ts +5 -1
  35. package/dist/elements/container-element.js +35 -15
  36. package/dist/elements/forms/checkbox-element.d.ts +2 -0
  37. package/dist/elements/forms/checkbox-element.js +12 -3
  38. package/dist/elements/forms/choice-element.d.ts +6 -1
  39. package/dist/elements/forms/choice-element.js +17 -6
  40. package/dist/elements/forms/field-font.d.ts +8 -0
  41. package/dist/elements/forms/field-font.js +5 -0
  42. package/dist/elements/forms/push-button-element.d.ts +6 -1
  43. package/dist/elements/forms/push-button-element.js +16 -4
  44. package/dist/elements/forms/radio-element.d.ts +2 -0
  45. package/dist/elements/forms/radio-element.js +12 -3
  46. package/dist/elements/forms/signature-element.d.ts +6 -1
  47. package/dist/elements/forms/signature-element.js +16 -4
  48. package/dist/elements/forms/text-field-element.d.ts +6 -1
  49. package/dist/elements/forms/text-field-element.js +16 -4
  50. package/dist/elements/image-element.d.ts +39 -1
  51. package/dist/elements/image-element.js +95 -12
  52. package/dist/elements/layout/anchor-element.d.ts +8 -10
  53. package/dist/elements/layout/anchor-element.js +10 -18
  54. package/dist/elements/layout/bookmark-element.d.ts +9 -11
  55. package/dist/elements/layout/bookmark-element.js +11 -20
  56. package/dist/elements/layout/default-text-style-element.d.ts +11 -13
  57. package/dist/elements/layout/default-text-style-element.js +14 -31
  58. package/dist/elements/layout/deferred-element.d.ts +12 -4
  59. package/dist/elements/layout/deferred-element.js +24 -7
  60. package/dist/elements/layout/expanded-element.d.ts +7 -0
  61. package/dist/elements/layout/expanded-element.js +40 -18
  62. package/dist/elements/layout/keep-together-element.d.ts +8 -18
  63. package/dist/elements/layout/keep-together-element.js +21 -45
  64. package/dist/elements/layout/link-element.d.ts +8 -13
  65. package/dist/elements/layout/link-element.js +9 -19
  66. package/dist/elements/layout/padding-element.d.ts +15 -1
  67. package/dist/elements/layout/padding-element.js +55 -27
  68. package/dist/elements/layout/page-break-element.d.ts +4 -1
  69. package/dist/elements/layout/page-break-element.js +6 -1
  70. package/dist/elements/layout/page-builder-element.d.ts +5 -0
  71. package/dist/elements/layout/page-builder-element.js +11 -0
  72. package/dist/elements/layout/positioned-element.d.ts +14 -2
  73. package/dist/elements/layout/positioned-element.js +36 -10
  74. package/dist/elements/layout/proxy-element.d.ts +43 -0
  75. package/dist/elements/layout/proxy-element.js +94 -0
  76. package/dist/elements/layout/repeating-header-element.d.ts +6 -0
  77. package/dist/elements/layout/repeating-header-element.js +40 -13
  78. package/dist/elements/layout/rotated-box-element.d.ts +6 -0
  79. package/dist/elements/layout/rotated-box-element.js +27 -14
  80. package/dist/elements/layout/rotated-element.d.ts +7 -0
  81. package/dist/elements/layout/rotated-element.js +25 -2
  82. package/dist/elements/layout/struct-group.d.ts +9 -17
  83. package/dist/elements/layout/struct-group.js +10 -35
  84. package/dist/elements/line-element.d.ts +1 -0
  85. package/dist/elements/line-element.js +6 -0
  86. package/dist/elements/page-element.d.ts +10 -3
  87. package/dist/elements/page-element.js +39 -31
  88. package/dist/elements/pdf-document-element.d.ts +3 -0
  89. package/dist/elements/pdf-document-element.js +7 -0
  90. package/dist/elements/pdf-element.d.ts +75 -15
  91. package/dist/elements/pdf-element.js +68 -18
  92. package/dist/elements/rectangle-element.d.ts +14 -0
  93. package/dist/elements/rectangle-element.js +97 -52
  94. package/dist/elements/row-element.d.ts +11 -0
  95. package/dist/elements/row-element.js +58 -8
  96. package/dist/elements/svg-element.d.ts +30 -6
  97. package/dist/elements/svg-element.js +88 -17
  98. package/dist/elements/text-element.d.ts +32 -13
  99. package/dist/elements/text-element.js +119 -97
  100. package/dist/forms/acroform.d.ts +9 -1
  101. package/dist/forms/acroform.js +83 -34
  102. package/dist/forms/appearance.d.ts +17 -6
  103. package/dist/forms/appearance.js +18 -16
  104. package/dist/ir/display-list.d.ts +12 -2
  105. package/dist/layout/box-constraints.d.ts +5 -0
  106. package/dist/layout/box-constraints.js +14 -0
  107. package/dist/layout/collect-images.d.ts +6 -4
  108. package/dist/layout/collect-images.js +6 -4
  109. package/dist/layout/flex-item.d.ts +9 -0
  110. package/dist/layout/flex-item.js +39 -0
  111. package/dist/layout/fragmentation.d.ts +12 -4
  112. package/dist/layout/fragmentation.js +37 -14
  113. package/dist/layout/layout-cache.d.ts +53 -0
  114. package/dist/layout/layout-cache.js +107 -0
  115. package/dist/renderer/anchor-renderer.d.ts +2 -1
  116. package/dist/renderer/anchor-renderer.js +5 -4
  117. package/dist/renderer/bookmark-renderer.d.ts +2 -1
  118. package/dist/renderer/bookmark-renderer.js +5 -4
  119. package/dist/renderer/canvas-renderer.d.ts +2 -1
  120. package/dist/renderer/canvas-renderer.js +5 -2
  121. package/dist/renderer/container-renderer.d.ts +2 -1
  122. package/dist/renderer/container-renderer.js +6 -5
  123. package/dist/renderer/default-text-style-renderer.d.ts +2 -1
  124. package/dist/renderer/default-text-style-renderer.js +2 -3
  125. package/dist/renderer/deferred-renderer.d.ts +2 -1
  126. package/dist/renderer/deferred-renderer.js +2 -3
  127. package/dist/renderer/expanded-renderer.d.ts +2 -1
  128. package/dist/renderer/expanded-renderer.js +2 -3
  129. package/dist/renderer/form-field-renderer.d.ts +2 -1
  130. package/dist/renderer/form-field-renderer.js +16 -3
  131. package/dist/renderer/image-renderer.d.ts +2 -1
  132. package/dist/renderer/image-renderer.js +5 -2
  133. package/dist/renderer/keep-together-renderer.d.ts +2 -1
  134. package/dist/renderer/keep-together-renderer.js +2 -3
  135. package/dist/renderer/line-renderer.d.ts +2 -1
  136. package/dist/renderer/line-renderer.js +7 -2
  137. package/dist/renderer/link-renderer.d.ts +2 -1
  138. package/dist/renderer/link-renderer.js +36 -5
  139. package/dist/renderer/padding-renderer.d.ts +2 -1
  140. package/dist/renderer/padding-renderer.js +5 -5
  141. package/dist/renderer/page-break-renderer.d.ts +6 -5
  142. package/dist/renderer/page-break-renderer.js +14 -7
  143. package/dist/renderer/page-builder-renderer.d.ts +2 -1
  144. package/dist/renderer/page-builder-renderer.js +2 -3
  145. package/dist/renderer/page-renderer.js +28 -7
  146. package/dist/renderer/pdf-backend.d.ts +4 -3
  147. package/dist/renderer/pdf-backend.js +5 -5
  148. package/dist/renderer/pdf-document-renderer.d.ts +1 -8
  149. package/dist/renderer/pdf-document-renderer.js +15 -16
  150. package/dist/renderer/pdf-renderer.js +11 -11
  151. package/dist/renderer/positioned-renderer.d.ts +6 -1
  152. package/dist/renderer/positioned-renderer.js +6 -5
  153. package/dist/renderer/rectangle-renderer.d.ts +2 -1
  154. package/dist/renderer/rectangle-renderer.js +9 -4
  155. package/dist/renderer/repeating-header-renderer.d.ts +2 -1
  156. package/dist/renderer/repeating-header-renderer.js +6 -5
  157. package/dist/renderer/rotated-renderer.d.ts +2 -1
  158. package/dist/renderer/rotated-renderer.js +8 -5
  159. package/dist/renderer/row-renderer.d.ts +2 -1
  160. package/dist/renderer/row-renderer.js +6 -5
  161. package/dist/renderer/struct-group-renderer.d.ts +2 -1
  162. package/dist/renderer/struct-group-renderer.js +2 -3
  163. package/dist/renderer/svg-renderer.d.ts +2 -1
  164. package/dist/renderer/svg-renderer.js +8 -2
  165. package/dist/renderer/text-renderer.d.ts +4 -6
  166. package/dist/renderer/text-renderer.js +101 -111
  167. package/dist/svg/gradients.js +5 -0
  168. package/dist/svg/index.js +10 -1
  169. package/dist/svg/style.d.ts +2 -0
  170. package/dist/svg/style.js +11 -0
  171. package/dist/text/advance.d.ts +4 -0
  172. package/dist/text/advance.js +17 -4
  173. package/dist/text/bidi.d.ts +0 -2
  174. package/dist/text/bidi.js +0 -10
  175. package/dist/text/glyph-coverage.d.ts +1 -1
  176. package/dist/text/glyph-coverage.js +7 -3
  177. package/dist/text/hyphen-breaks.d.ts +17 -0
  178. package/dist/text/hyphen-breaks.js +54 -0
  179. package/dist/text/line-breaker.d.ts +34 -31
  180. package/dist/text/line-breaker.js +364 -176
  181. package/dist/text/line-metrics.d.ts +6 -3
  182. package/dist/text/line-metrics.js +7 -2
  183. package/dist/text/pdf-text-string.d.ts +21 -0
  184. package/dist/text/pdf-text-string.js +114 -0
  185. package/dist/text/text-decoration.d.ts +5 -5
  186. package/dist/text/text-decoration.js +5 -5
  187. package/dist/text/word-splitting.d.ts +15 -2
  188. package/dist/text/word-splitting.js +34 -8
  189. package/dist/utils/afm-parser.d.ts +2 -0
  190. package/dist/utils/afm-parser.js +10 -1
  191. package/dist/utils/dest-registry.d.ts +1 -1
  192. package/dist/utils/dest-registry.js +3 -2
  193. package/dist/utils/fetch-bytes.d.ts +19 -0
  194. package/dist/utils/fetch-bytes.js +79 -0
  195. package/dist/utils/flex-layout.d.ts +13 -13
  196. package/dist/utils/flex-layout.js +318 -216
  197. package/dist/utils/font-metrics.d.ts +4 -3
  198. package/dist/utils/image-helper.js +2 -1
  199. package/dist/utils/pdf-object-manager.d.ts +11 -3
  200. package/dist/utils/pdf-object-manager.js +39 -13
  201. package/dist/utils/renderer-registry.d.ts +43 -2
  202. package/dist/utils/renderer-registry.js +40 -0
  203. package/dist/utils/struct-tree.d.ts +24 -0
  204. package/dist/utils/struct-tree.js +61 -9
  205. package/dist/utils/ttf-parser.d.ts +4 -0
  206. package/dist/utils/ttf-parser.js +27 -4
  207. package/dist/validators/element-validator.js +2 -1
  208. package/package.json +1 -1
  209. package/dist/elements/layout/sized-container-element.d.ts +0 -14
  210. package/dist/elements/layout/sized-container-element.js +0 -32
@@ -0,0 +1,43 @@
1
+ import { BoxConstraints, Offset, Size } from "../../layout/box-constraints.js";
2
+ import { Fragmentable, FragmentResult } from "../../layout/fragmentation.js";
3
+ import { LayoutContext, PDFElement } from "../pdf-element.js";
4
+ /**
5
+ * A wrapper that is transparent to layout (Flutter's `RenderProxyBox`): its one child gets the same
6
+ * constraints, sits at the same offset and gives the same size. Every sizing and pagination signal the
7
+ * child sends is passed on, so wrapping something never changes how it is sized or broken across
8
+ * pages - a `PageBreak` inside a `Link` breaks, a `Bookmark` around a chapter splits like the chapter.
9
+ *
10
+ * What a wrapper adds (a link, a jump target, a role, inherited text defaults) it adds through two
11
+ * hooks: `childContext` for what the child is laid out in, and `rewrap` for the pieces of a split.
12
+ */
13
+ export declare abstract class ProxyElement extends PDFElement implements Fragmentable {
14
+ protected readonly child: PDFElement;
15
+ protected x: number;
16
+ protected y: number;
17
+ protected width: number;
18
+ protected height: number;
19
+ protected constructor(child: PDFElement);
20
+ /** The context the child is laid out in: unchanged, unless the wrapper hands something down. */
21
+ protected childContext(ctx: LayoutContext): LayoutContext;
22
+ /**
23
+ * This wrapper around a piece of its split child. `continued` is true for every piece but the first
24
+ * one that holds something: a wrapper that marks a single place (a jump target, an outline entry)
25
+ * returns the piece bare there, so the mark is not repeated on the next page.
26
+ */
27
+ protected abstract rewrap(child: PDFElement, continued: boolean): PDFElement;
28
+ calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
29
+ protected computeSize(constraints: BoxConstraints, ctx: LayoutContext): Size;
30
+ layoutChildren(): readonly PDFElement[];
31
+ /** Transparent to a surrounding Row or Column too: it sees the child's flex properties. */
32
+ proxied(): PDFElement;
33
+ relativeSizeFactor(horizontal: boolean): number | undefined;
34
+ needsBoundedMain(horizontal: boolean): boolean;
35
+ minIntrinsicMain(horizontal: boolean, ctx: LayoutContext): number;
36
+ isPageBreak(): boolean;
37
+ hasForcedBreak(): boolean;
38
+ breaksBefore(): boolean;
39
+ breaksAfter(): boolean;
40
+ /** Splittable exactly when the child is; otherwise the parent moves it to the next page whole. */
41
+ canFragment(): boolean;
42
+ fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
43
+ }
@@ -0,0 +1,94 @@
1
+ import { BoxConstraints } from "../../layout/box-constraints.js";
2
+ import { isFragmentable } from "../../layout/fragmentation.js";
3
+ import { PDFElement } from "../pdf-element.js";
4
+ /**
5
+ * A wrapper that is transparent to layout (Flutter's `RenderProxyBox`): its one child gets the same
6
+ * constraints, sits at the same offset and gives the same size. Every sizing and pagination signal the
7
+ * child sends is passed on, so wrapping something never changes how it is sized or broken across
8
+ * pages - a `PageBreak` inside a `Link` breaks, a `Bookmark` around a chapter splits like the chapter.
9
+ *
10
+ * What a wrapper adds (a link, a jump target, a role, inherited text defaults) it adds through two
11
+ * hooks: `childContext` for what the child is laid out in, and `rewrap` for the pieces of a split.
12
+ */
13
+ export class ProxyElement extends PDFElement {
14
+ constructor(child) {
15
+ super();
16
+ this.child = child;
17
+ // The box the child took, for renderers that mark it (a link's hit area, a jump target's top).
18
+ this.x = 0;
19
+ this.y = 0;
20
+ this.width = 0;
21
+ this.height = 0;
22
+ }
23
+ /** The context the child is laid out in: unchanged, unless the wrapper hands something down. */
24
+ childContext(ctx) {
25
+ return ctx;
26
+ }
27
+ calculateLayout(constraints, offset, ctx) {
28
+ const size = this.child.calculateLayout(constraints, offset, this.childContext(ctx));
29
+ this.x = offset.x;
30
+ this.y = offset.y;
31
+ this.width = size.width;
32
+ this.height = size.height;
33
+ return size;
34
+ }
35
+ computeSize(constraints, ctx) {
36
+ return this.child.measure(constraints, this.childContext(ctx));
37
+ }
38
+ layoutChildren() {
39
+ return [this.child];
40
+ }
41
+ /** Transparent to a surrounding Row or Column too: it sees the child's flex properties. */
42
+ proxied() {
43
+ return this.child;
44
+ }
45
+ relativeSizeFactor(horizontal) {
46
+ return this.child.relativeSizeFactor(horizontal);
47
+ }
48
+ needsBoundedMain(horizontal) {
49
+ return this.child.needsBoundedMain(horizontal);
50
+ }
51
+ minIntrinsicMain(horizontal, ctx) {
52
+ return this.child.minIntrinsicMain(horizontal, this.childContext(ctx));
53
+ }
54
+ isPageBreak() {
55
+ return this.child.isPageBreak();
56
+ }
57
+ hasForcedBreak() {
58
+ return this.child.hasForcedBreak();
59
+ }
60
+ breaksBefore() {
61
+ return this.child.breaksBefore();
62
+ }
63
+ breaksAfter() {
64
+ return this.child.breaksAfter();
65
+ }
66
+ /** Splittable exactly when the child is; otherwise the parent moves it to the next page whole. */
67
+ canFragment() {
68
+ return isFragmentable(this.child);
69
+ }
70
+ fragment(maxHeight, width, ctx) {
71
+ if (!isFragmentable(this.child))
72
+ return { fitted: null, remainder: this };
73
+ const childCtx = this.childContext(ctx);
74
+ const split = this.child.fragment(maxHeight, width, childCtx);
75
+ // The child handed itself back (it all fits, or none of it does): so does the wrapper, mark included.
76
+ const wrap = (piece, continued) => {
77
+ if (piece === this.child)
78
+ return this;
79
+ const wrapped = this.rewrap(piece, continued);
80
+ // A wrapper dropped from a continued piece still leaves that piece in its place in the flow.
81
+ return wrapped === piece ? piece.takeFlexItemFrom(this) : wrapped.asPieceOf(this);
82
+ };
83
+ // A fitted piece with no height (what a leading `PageBreak` leaves) holds nothing to mark: the
84
+ // mark goes on with the remainder, where the content is.
85
+ const placed = split.fitted !== null &&
86
+ (split.remainder === null ||
87
+ split.fitted.measure(BoxConstraints.loose(width, Infinity), childCtx).height > 0);
88
+ return {
89
+ fitted: split.fitted && wrap(split.fitted, !placed),
90
+ remainder: split.remainder && wrap(split.remainder, placed),
91
+ forceBreak: split.forceBreak,
92
+ };
93
+ }
94
+ }
@@ -17,7 +17,13 @@ export declare class RepeatingHeaderElement extends PDFElement implements Fragme
17
17
  private height;
18
18
  constructor(header: PDFElement, body: PDFElement, gap?: number);
19
19
  calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
20
+ protected computeSize(constraints: BoxConstraints, ctx: LayoutContext): Size;
21
+ /** The one sizing path: `layout` places header and body (relative to this box), `measure` sizes. */
22
+ private sizeWith;
20
23
  fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
24
+ /** A break inside the body is a break in the flow the table sits in. */
25
+ hasForcedBreak(): boolean;
26
+ layoutChildren(): readonly PDFElement[];
21
27
  getProps(): {
22
28
  x: number;
23
29
  y: number;
@@ -1,5 +1,5 @@
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
4
  /**
5
5
  * Stacks a `header` above a `body` and, when it paginates, **repeats the header on every
@@ -19,30 +19,57 @@ export class RepeatingHeaderElement extends PDFElement {
19
19
  this.height = 0;
20
20
  }
21
21
  calculateLayout(constraints, offset, ctx) {
22
+ const size = this.sizeWith(constraints, ctx, "layout");
22
23
  this.x = offset.x;
23
24
  this.y = offset.y;
25
+ this.width = size.width;
26
+ this.height = size.height;
27
+ return size;
28
+ }
29
+ computeSize(constraints, ctx) {
30
+ return this.sizeWith(constraints, ctx, "measure");
31
+ }
32
+ /** The one sizing path: `layout` places header and body (relative to this box), `measure` sizes. */
33
+ sizeWith(constraints, ctx, mode) {
24
34
  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 };
35
+ const inner = BoxConstraints.loose(width, Infinity);
36
+ const h = mode === "layout"
37
+ ? this.header.calculateLayout(inner, { x: 0, y: 0 }, ctx)
38
+ : this.header.measure(inner, ctx);
39
+ const b = mode === "layout"
40
+ ? this.body.calculateLayout(inner, { x: 0, y: h.height + this.gap }, ctx)
41
+ : this.body.measure(inner, ctx);
42
+ return { width, height: h.height + this.gap + b.height };
30
43
  }
31
44
  fragment(maxHeight, width, ctx) {
32
45
  // 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;
46
+ const headerHeight = this.header.measure(BoxConstraints.loose(width, Infinity), ctx).height;
34
47
  if (!isFragmentable(this.body))
35
48
  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)
49
+ const chrome = headerHeight + this.gap;
50
+ const split = this.body.fragment(Math.max(0, maxHeight - chrome), width, insetContext(ctx, chrome));
51
+ // Body fits whole, no break inside → the whole thing fits on this page.
52
+ if (split.remainder === null && !split.forceBreak)
39
53
  return { fitted: this, remainder: null };
40
- // Re-wrap each body piece with the SAME header → it reappears on the next page too.
54
+ // Re-wrap each body piece with the SAME header → it reappears on the next page too. A forced
55
+ // break inside the body is passed on, so the page ends there.
41
56
  return {
42
- fitted: split.fitted ? new RepeatingHeaderElement(this.header, split.fitted, this.gap) : null,
43
- remainder: new RepeatingHeaderElement(this.header, split.remainder, this.gap),
57
+ fitted: split.fitted
58
+ ? new RepeatingHeaderElement(this.header, split.fitted, this.gap).asPieceOf(this)
59
+ : null,
60
+ remainder: split.remainder
61
+ ? new RepeatingHeaderElement(this.header, split.remainder, this.gap).asPieceOf(this)
62
+ : null,
63
+ forceBreak: split.forceBreak,
44
64
  };
45
65
  }
66
+ /** A break inside the body is a break in the flow the table sits in. */
67
+ hasForcedBreak() {
68
+ return this.body.hasForcedBreak();
69
+ }
70
+ layoutChildren() {
71
+ return [this.header, this.body];
72
+ }
46
73
  getProps() {
47
74
  return {
48
75
  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,13 @@ 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
+ relativeSizeFactor(horizontal: boolean): number | undefined;
27
+ needsBoundedMain(horizontal: boolean): boolean;
28
+ minIntrinsicMain(horizontal: boolean, ctx: LayoutContext): number;
29
+ breaksBefore(): boolean;
30
+ breaksAfter(): boolean;
24
31
  getProps(): {
25
32
  angle: number;
26
33
  child: PDFElement;
@@ -19,15 +19,38 @@ 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
+ // It takes the child's space, so the child's sizing hints and its break-before/after are its own.
38
+ // It is never split - half a rotation on each page means nothing - so a break INSIDE stays inside.
39
+ relativeSizeFactor(horizontal) {
40
+ return this.child.relativeSizeFactor(horizontal);
41
+ }
42
+ needsBoundedMain(horizontal) {
43
+ return this.child.needsBoundedMain(horizontal);
44
+ }
45
+ minIntrinsicMain(horizontal, ctx) {
46
+ return this.child.minIntrinsicMain(horizontal, ctx);
47
+ }
48
+ breaksBefore() {
49
+ return this.child.breaksBefore();
50
+ }
51
+ breaksAfter() {
52
+ return this.child.breaksAfter();
53
+ }
31
54
  getProps() {
32
55
  return {
33
56
  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 {};