@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
@@ -1,13 +1,26 @@
1
1
  import { Validator } from "../../validators/element-validator.js";
2
- import { SizedPDFElement } from "../pdf-element.js";
2
+ import { SizedPDFElement, withPaddingBase, } from "../pdf-element.js";
3
3
  import { BoxConstraints } from "../../layout/box-constraints.js";
4
- import { isFragmentable } from "../../layout/fragmentation.js";
4
+ import { insetContext, isFragmentable, } from "../../layout/fragmentation.js";
5
5
  import { resolveEdges } from "../../layout/insets.js";
6
6
  export class PaddingElement extends SizedPDFElement {
7
- constructor({ margin, child }) {
7
+ constructor({ margin, child, ofBox = false }) {
8
8
  super({ x: 0, y: 0 });
9
9
  this.child = child;
10
10
  this.margin = margin;
11
+ this.ofBox = ofBox;
12
+ }
13
+ /** Some side is a percentage. */
14
+ get hasPercentSide() {
15
+ return this.margin.some((side) => typeof side !== "number" && side.factor !== undefined);
16
+ }
17
+ /** The width a percentage side resolves against, and the context the child sees: a Box's base is
18
+ * for this padding alone, so it does not travel further down. */
19
+ percentBase(width, ctx) {
20
+ return this.ofBox && ctx.boxPaddingBase !== undefined ? ctx.boxPaddingBase : width;
21
+ }
22
+ childContext(ctx) {
23
+ return withPaddingBase(ctx, undefined);
11
24
  }
12
25
  /**
13
26
  * Splits the padded box across pages (box-decoration-break: clone - every fragment
@@ -19,16 +32,23 @@ export class PaddingElement extends SizedPDFElement {
19
32
  if (!isFragmentable(this.child)) {
20
33
  return { fitted: null, remainder: this };
21
34
  }
22
- const [marginTop, marginRight, marginBottom, marginLeft] = resolveEdges(this.margin, width);
35
+ const [marginTop, marginRight, marginBottom, marginLeft] = resolveEdges(this.margin, this.percentBase(width, ctx));
23
36
  const childWidth = width - marginLeft - marginRight;
24
37
  const childMaxHeight = maxHeight - marginTop - marginBottom;
25
- const split = this.child.fragment(childMaxHeight, childWidth, ctx);
38
+ const split = this.child.fragment(childMaxHeight, childWidth, insetContext(this.childContext(ctx), marginTop + marginBottom));
26
39
  return {
27
40
  fitted: split.fitted ? this.cloneWithChild(split.fitted) : null,
28
41
  remainder: split.remainder ? this.cloneWithChild(split.remainder) : null,
29
42
  forceBreak: split.forceBreak,
30
43
  };
31
44
  }
45
+ /** A wrapper: it needs a bound exactly when its child does, and passes the bound on. */
46
+ needsBoundedMain(horizontal) {
47
+ return this.child.needsBoundedMain(horizontal);
48
+ }
49
+ needsRegion() {
50
+ return this.child.needsRegion();
51
+ }
32
52
  hasForcedBreak() {
33
53
  return this.child.hasForcedBreak();
34
54
  }
@@ -41,7 +61,7 @@ export class PaddingElement extends SizedPDFElement {
41
61
  return this.child.breaksAfter();
42
62
  }
43
63
  cloneWithChild(child) {
44
- return new PaddingElement({ margin: this.margin, child });
64
+ return new PaddingElement({ margin: this.margin, child, ofBox: this.ofBox }).asPieceOf(this);
45
65
  }
46
66
  /**
47
67
  * The child plus our own insets. A PERCENTAGE inset counts as zero here: it resolves against a width
@@ -52,33 +72,48 @@ export class PaddingElement extends SizedPDFElement {
52
72
  return (this.child.minIntrinsicMain(horizontal, ctx) + (horizontal ? left + right : top + bottom));
53
73
  }
54
74
  calculateLayout(constraints, offset, ctx) {
55
- // Padding takes the width it is offered; its height shrink-wraps the child.
56
- if (constraints.hasBoundedWidth)
57
- this.width = constraints.maxWidth;
75
+ const size = this.sizeWith(constraints, ctx, "layout");
58
76
  this.x = offset.x;
59
77
  this.y = offset.y;
60
- // Percentages resolve against the width we were OFFERED, so every pass gets the same answer.
61
- const [marginTop, marginRight, marginBottom, marginLeft] = resolveEdges(this.margin, constraints.maxWidth);
62
- // The child is inset by the margins: shifted down/right, narrowed by the
63
- // horizontal margins, and left height-unbounded so it sizes to its own content.
64
- const childOffset = {
65
- x: this.x + marginLeft,
66
- y: this.y + marginTop,
67
- };
78
+ this.width = size.width;
79
+ this.height = size.height;
80
+ Validator.validateSizedElement(this);
81
+ return size;
82
+ }
83
+ computeSize(constraints, ctx) {
84
+ return this.sizeWith(constraints, ctx, "measure");
85
+ }
86
+ /** The one sizing path: `layout` places the child (relative to this box), `measure` only sizes it. */
87
+ sizeWith(constraints, ctx, mode) {
88
+ // Percentages resolve against the width we were OFFERED (a Box's: what the Box was offered), so
89
+ // every pass gets the same answer.
90
+ const [marginTop, marginRight, marginBottom, marginLeft] = resolveEdges(this.margin, this.percentBase(constraints.maxWidth, ctx));
91
+ // The child is inset by the margins: shifted down/right, narrowed by the horizontal margins, and
92
+ // left height-unbounded so it sizes to its own content - unless it cannot lay itself out without a
93
+ // bound (a stack holding a Spacer): that one gets the height inside the margins, as it would without
94
+ // the padding.
68
95
  const childWidth = constraints.hasBoundedWidth
69
96
  ? Math.max(0, constraints.maxWidth - marginLeft - marginRight)
70
97
  : Infinity;
71
- const childConstraints = BoxConstraints.loose(childWidth, Infinity);
72
- const childSize = this.child.calculateLayout(childConstraints, childOffset, ctx);
73
- this.height = childSize.height + marginTop + marginBottom;
74
- // Unbounded width (e.g. a fixed child in a Row): shrink-wrap to the child + insets,
75
- // mirroring how Container/Row shrink-wrap. Bounded width was set above (unchanged).
76
- if (!constraints.hasBoundedWidth) {
77
- this.width = childSize.width + marginLeft + marginRight;
78
- }
79
- Validator.validateSizedElement(this);
80
- // Top-left coordinates; the Y-flip now happens once at the IR -> backend seam.
81
- return { width: this.width ?? 0, height: this.height };
98
+ const childHeight = constraints.hasBoundedHeight && this.child.needsBoundedMain(false)
99
+ ? Math.max(0, constraints.maxHeight - marginTop - marginBottom)
100
+ : Infinity;
101
+ const childConstraints = BoxConstraints.loose(childWidth, childHeight);
102
+ const childCtx = this.childContext(ctx);
103
+ const childSize = mode === "layout"
104
+ ? this.child.calculateLayout(childConstraints, { x: marginLeft, y: marginTop }, childCtx)
105
+ : this.child.measure(childConstraints, childCtx);
106
+ // Padding takes the width it is offered; its height shrink-wraps the child. Unbounded width (a
107
+ // fixed child in a Row) shrink-wraps to the child + insets, as Container/Row do.
108
+ return {
109
+ width: constraints.hasBoundedWidth
110
+ ? constraints.maxWidth
111
+ : childSize.width + marginLeft + marginRight,
112
+ height: childSize.height + marginTop + marginBottom,
113
+ };
114
+ }
115
+ layoutChildren() {
116
+ return [this.child];
82
117
  }
83
118
  getProps() {
84
119
  return {
@@ -10,7 +10,10 @@ import { LayoutContext, PDFElement } from "../pdf-element.js";
10
10
  * too. A break wins over a `keepTogether` (a forced break cannot be avoided) - CSS behaves the same.
11
11
  */
12
12
  export declare class PageBreakElement extends PDFElement {
13
- calculateLayout(_constraints: BoxConstraints, _offset: Offset, _ctx: LayoutContext): Size;
13
+ /** The page it was last laid out on - only so an ineffective break can name it. */
14
+ page?: number;
15
+ calculateLayout(_constraints: BoxConstraints, _offset: Offset, ctx: LayoutContext): Size;
16
+ protected computeSize(): Size;
14
17
  isPageBreak(): boolean;
15
18
  hasForcedBreak(): boolean;
16
19
  getProps(): Record<string, never>;
@@ -1,4 +1,5 @@
1
1
  import { PDFElement } from "../pdf-element.js";
2
+ import { pageOf } from "../../common/diagnostics.js";
2
3
  /**
3
4
  * A forced page break. Placed in a flow (a `Column`/`Row`/`Box`), it makes everything AFTER it start
4
5
  * on a fresh page, no matter how much space is left. It takes zero space and draws nothing - the
@@ -9,7 +10,11 @@ import { PDFElement } from "../pdf-element.js";
9
10
  * too. A break wins over a `keepTogether` (a forced break cannot be avoided) - CSS behaves the same.
10
11
  */
11
12
  export class PageBreakElement extends PDFElement {
12
- calculateLayout(_constraints, _offset, _ctx) {
13
+ calculateLayout(_constraints, _offset, ctx) {
14
+ this.page = pageOf(ctx);
15
+ return { width: 0, height: 0 };
16
+ }
17
+ computeSize() {
13
18
  return { width: 0, height: 0 };
14
19
  }
15
20
  isPageBreak() {
@@ -26,6 +26,11 @@ export declare class PageBuilderElement extends PDFElement {
26
26
  /** The real page info during the render pass; a provisional "1 of 1" while paginating. */
27
27
  private infoFrom;
28
28
  calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
29
+ /** It is built from the page it lands on. */
30
+ dependsOnPage(): boolean;
31
+ /** Builds a throwaway subtree to size it; only a real layout keeps what it built. */
32
+ protected computeSize(constraints: BoxConstraints, ctx: LayoutContext): Size;
33
+ layoutChildren(): readonly PDFElement[];
29
34
  getProps(): {
30
35
  composed: PDFElement | undefined;
31
36
  };
@@ -36,9 +36,20 @@ export class PageBuilderElement extends PDFElement {
36
36
  this.composed = this.build(this.infoFrom(ctx));
37
37
  return this.composed.calculateLayout(constraints, offset, ctx);
38
38
  }
39
+ /** It is built from the page it lands on. */
40
+ dependsOnPage() {
41
+ return true;
42
+ }
43
+ /** Builds a throwaway subtree to size it; only a real layout keeps what it built. */
44
+ computeSize(constraints, ctx) {
45
+ return this.build(this.infoFrom(ctx)).measure(constraints, ctx);
46
+ }
39
47
  // Named `composed` (not `child`) like DeferredElement: the pre-layout image walk keys on `child`, and
40
- // this subtree does not exist yet when it runs. So an Image inside a PageBuilder skips aspect-ratio
41
- // pre-resolution - give it both width and height, exactly as inside a Deferred/Table subtree.
48
+ // this subtree does not exist yet when it runs. An Image built here reads its own size when layout
49
+ // asks; only one from a URL cannot, and reports `image-unsized`.
50
+ layoutChildren() {
51
+ return this.composed ? [this.composed] : [];
52
+ }
42
53
  getProps() {
43
54
  return { composed: this.composed };
44
55
  }
@@ -11,6 +11,9 @@ export type PositionAnchor = "start" | "center" | "end";
11
11
  * An edge wins over an anchor on the same axis. With nothing set the child sits at the top-left.
12
12
  */
13
13
  export interface PositionedInsets {
14
+ /** Paint order within its stacking context: negative under the flow content, positive over it,
15
+ * higher over lower; 0 or unset paints in tree order where it stands (CSS `z-index`). */
16
+ zIndex?: number;
14
17
  top?: number;
15
18
  right?: number;
16
19
  bottom?: number;
@@ -38,10 +41,26 @@ interface PositionedElementParams extends WithChild, PositionedInsets {
38
41
  export declare class PositionedElement extends PDFElement {
39
42
  private child;
40
43
  private insets;
41
- constructor({ child, top, right, bottom, left, h, v, x, y }: PositionedElementParams);
44
+ private placeCtx?;
45
+ constructor({ child, top, right, bottom, left, h, v, x, y, zIndex }: PositionedElementParams);
46
+ /** Its paint order; 0 paints where it stands in the tree, anything else is sorted by its context. */
47
+ private readonly zIndex;
42
48
  calculateLayout(_constraints: BoxConstraints, _offset: Offset, ctx: LayoutContext): Size;
43
- /** Lays the child out at the resolved position inside (or overflowing) the frame box. */
44
- private placeInFrame;
45
- getProps(): WithChild;
49
+ /** Out of flow: it takes no room, and measuring registers nothing with the frame. */
50
+ protected computeSize(): Size;
51
+ layoutChildren(): readonly PDFElement[];
52
+ /**
53
+ * Lays the child out at the resolved position inside (or overflowing) a frame of `frameSize`. The
54
+ * offset is relative to the frame's top-left; its owner draws the child from there.
55
+ */
56
+ placeIn(frameSize: Size, frameCtx: LayoutContext): void;
57
+ getProps(): WithChild & {
58
+ zIndex: number;
59
+ };
46
60
  }
61
+ /**
62
+ * The `Positioned` elements a frame owns, in tree order: every one below `roots`, including those
63
+ * inside another `Positioned`, but none below a nested frame - that one places its own.
64
+ */
65
+ export declare function positionedIn(roots: readonly (PDFElement | undefined)[]): PositionedElement[];
47
66
  export {};
@@ -14,22 +14,38 @@ import { PDFElement } from "../pdf-element.js";
14
14
  * (ISSUE-4). Content that lands somewhere unasked is worse than content that does not render.
15
15
  */
16
16
  export class PositionedElement extends PDFElement {
17
- constructor({ child, top, right, bottom, left, h, v, x, y }) {
17
+ constructor({ child, top, right, bottom, left, h, v, x, y, zIndex }) {
18
18
  super();
19
+ if (zIndex !== undefined && !Number.isFinite(zIndex)) {
20
+ throw new Error(`Invalid zIndex ${zIndex}: it must be a number.`);
21
+ }
19
22
  this.child = child;
20
23
  this.insets = { top, right, bottom, left, h, v, x, y };
24
+ this.zIndex = zIndex ?? 0;
21
25
  }
22
26
  calculateLayout(_constraints, _offset, ctx) {
23
27
  if (!ctx.frame) {
24
28
  throw new Error("Positioned found no positioning frame. It must sit inside a Page (header, footer or body) " +
25
29
  "or inside a Box({ relative: true }).");
26
30
  }
27
- // Defer to the frame: it calls back once it has sized itself. Out of flow either way.
28
- ctx.frame.place.push((frame, frameCtx) => this.placeInFrame(frame, frameCtx));
31
+ // Out of flow: it takes no room here. The frame's owner places it once it knows its own size.
32
+ this.placeCtx = ctx;
33
+ return { width: 0, height: 0 };
34
+ }
35
+ /** Out of flow: it takes no room, and measuring registers nothing with the frame. */
36
+ computeSize() {
29
37
  return { width: 0, height: 0 };
30
38
  }
31
- /** Lays the child out at the resolved position inside (or overflowing) the frame box. */
32
- placeInFrame(frame, ctx) {
39
+ layoutChildren() {
40
+ return [this.child];
41
+ }
42
+ /**
43
+ * Lays the child out at the resolved position inside (or overflowing) a frame of `frameSize`. The
44
+ * offset is relative to the frame's top-left; its owner draws the child from there.
45
+ */
46
+ placeIn(frameSize, frameCtx) {
47
+ const ctx = this.placeCtx ?? frameCtx;
48
+ const frame = { origin: { x: 0, y: 0 }, size: frameSize };
33
49
  const { top, right, bottom, left, h, v, x: nudgeX, y: nudgeY } = this.insets;
34
50
  // An axis is PINNED (stretched) only when BOTH of its EDGES are given; otherwise the child
35
51
  // shrink-wraps its content (unbounded), the CSS rule for an absolutely-positioned element. An
@@ -42,12 +58,7 @@ export class PositionedElement extends PDFElement {
42
58
  : Infinity;
43
59
  const constraints = BoxConstraints.loose(width, height);
44
60
  // Measure first (so end/center anchors and right/bottom edges resolve against the child's size).
45
- // The measuring pass gets a throwaway `place` list: this subtree is laid out twice, and a nested
46
- // out-of-flow child would otherwise register its placement on both passes.
47
- const measureCtx = ctx.frame
48
- ? { ...ctx, frame: { ...ctx.frame, place: [] } }
49
- : ctx;
50
- const measured = this.child.calculateLayout(constraints, { x: 0, y: 0 }, measureCtx);
61
+ const measured = this.child.measure(constraints, ctx);
51
62
  // Resolve one axis: a near-edge pins to origin, a far-edge pins to the far side; otherwise the
52
63
  // anchor (default `start`) positions the child and the nudge shifts it from there.
53
64
  const place = (origin, frameExtent, childExtent, nearEdge, farEdge, anchor, nudge) => {
@@ -68,6 +79,25 @@ export class PositionedElement extends PDFElement {
68
79
  this.child.calculateLayout(constraints, { x, y }, ctx);
69
80
  }
70
81
  getProps() {
71
- return { child: this.child };
82
+ return { child: this.child, zIndex: this.zIndex };
72
83
  }
73
84
  }
85
+ /**
86
+ * The `Positioned` elements a frame owns, in tree order: every one below `roots`, including those
87
+ * inside another `Positioned`, but none below a nested frame - that one places its own.
88
+ */
89
+ export function positionedIn(roots) {
90
+ const found = [];
91
+ const walk = (el) => {
92
+ if (el instanceof PositionedElement)
93
+ found.push(el);
94
+ if (el.isPositioningFrame())
95
+ return;
96
+ for (const child of el.layoutChildren())
97
+ walk(child);
98
+ };
99
+ for (const root of roots)
100
+ if (root)
101
+ walk(root);
102
+ return found;
103
+ }
@@ -0,0 +1,47 @@
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
+ proxiedContext(ctx: LayoutContext): LayoutContext;
33
+ proxied(): PDFElement;
34
+ relativeSizeFactor(horizontal: boolean): number | undefined;
35
+ maxExtent(horizontal: boolean): number;
36
+ needsBoundedMain(horizontal: boolean): boolean;
37
+ minIntrinsicMain(horizontal: boolean, ctx: LayoutContext): number;
38
+ needsRegion(): boolean;
39
+ isPageBreak(): boolean;
40
+ isColumnBreak(): boolean;
41
+ hasForcedBreak(): boolean;
42
+ breaksBefore(): boolean;
43
+ breaksAfter(): boolean;
44
+ /** Splittable exactly when the child is; otherwise the parent moves it to the next page whole. */
45
+ canFragment(): boolean;
46
+ fragment(maxHeight: number, width: number, ctx: LayoutContext): FragmentResult;
47
+ }
@@ -0,0 +1,106 @@
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
+ proxiedContext(ctx) {
43
+ return this.childContext(ctx);
44
+ }
45
+ proxied() {
46
+ return this.child;
47
+ }
48
+ relativeSizeFactor(horizontal) {
49
+ return this.child.relativeSizeFactor(horizontal);
50
+ }
51
+ maxExtent(horizontal) {
52
+ return this.child.maxExtent(horizontal);
53
+ }
54
+ needsBoundedMain(horizontal) {
55
+ return this.child.needsBoundedMain(horizontal);
56
+ }
57
+ minIntrinsicMain(horizontal, ctx) {
58
+ return this.child.minIntrinsicMain(horizontal, this.childContext(ctx));
59
+ }
60
+ needsRegion() {
61
+ return this.child.needsRegion();
62
+ }
63
+ isPageBreak() {
64
+ return this.child.isPageBreak();
65
+ }
66
+ isColumnBreak() {
67
+ return this.child.isColumnBreak();
68
+ }
69
+ hasForcedBreak() {
70
+ return this.child.hasForcedBreak();
71
+ }
72
+ breaksBefore() {
73
+ return this.child.breaksBefore();
74
+ }
75
+ breaksAfter() {
76
+ return this.child.breaksAfter();
77
+ }
78
+ /** Splittable exactly when the child is; otherwise the parent moves it to the next page whole. */
79
+ canFragment() {
80
+ return isFragmentable(this.child);
81
+ }
82
+ fragment(maxHeight, width, ctx) {
83
+ if (!isFragmentable(this.child))
84
+ return { fitted: null, remainder: this };
85
+ const childCtx = this.childContext(ctx);
86
+ const split = this.child.fragment(maxHeight, width, childCtx);
87
+ // The child handed itself back (it all fits, or none of it does): so does the wrapper, mark included.
88
+ const wrap = (piece, continued) => {
89
+ if (piece === this.child)
90
+ return this;
91
+ const wrapped = this.rewrap(piece, continued);
92
+ // A wrapper dropped from a continued piece still leaves that piece in its place in the flow.
93
+ return wrapped === piece ? piece.takeFlexItemFrom(this) : wrapped.asPieceOf(this);
94
+ };
95
+ // A fitted piece with no height (what a leading `PageBreak` leaves) holds nothing to mark: the
96
+ // mark goes on with the remainder, where the content is.
97
+ const placed = split.fitted !== null &&
98
+ (split.remainder === null ||
99
+ split.fitted.measure(BoxConstraints.loose(width, Infinity), childCtx).height > 0);
100
+ return {
101
+ fitted: split.fitted && wrap(split.fitted, !placed),
102
+ remainder: split.remainder && wrap(split.remainder, placed),
103
+ forceBreak: split.forceBreak,
104
+ };
105
+ }
106
+ }
@@ -0,0 +1,53 @@
1
+ import { BoxConstraints, Offset, Size, SizingParams } from "../../layout/box-constraints.js";
2
+ import { LayoutContext, PDFElement, SizedPDFElement } from "../pdf-element.js";
3
+ interface RegionElementParams extends SizingParams {
4
+ widthFactor?: number;
5
+ heightFactor?: number;
6
+ }
7
+ /** A `Region` was laid out somewhere no `Flow` claimed it: outside a template, or inside the content. */
8
+ export declare class RegionOutsideFlowError extends Error {
9
+ constructor();
10
+ }
11
+ /** One Region instance handed to two pages of a Flow (or two Flows): both would draw the same content. */
12
+ export declare class RegionReusedError extends Error {
13
+ constructor();
14
+ }
15
+ /**
16
+ * A place in a `Flow`'s template that the content flows through. It has no size of its own: the layout
17
+ * around it sizes it (`Expanded`, `width`, `height`, `%`), and with nothing given it fills what it is
18
+ * offered, as a Canvas does. The Flow claims it, then hands it the piece of content that fits.
19
+ */
20
+ export declare class RegionElement extends SizedPDFElement {
21
+ private requested;
22
+ private sizing;
23
+ private widthFactor?;
24
+ private heightFactor?;
25
+ private owner?;
26
+ private content;
27
+ constructor(params?: RegionElementParams);
28
+ /** Marks it as part of one page of a Flow; a second page claiming it throws. */
29
+ private claim;
30
+ /** The content it holds on this page (`null`: the content ran out before it). */
31
+ hold(content: PDFElement | null): void;
32
+ get held(): PDFElement | null;
33
+ /** A height the author gave (points or `%`): kept on the Flow's last page instead of shrinking. */
34
+ hasOwnHeight(): boolean;
35
+ /** Where it has no size of its own it fills what it is offered, so the stack around it must offer
36
+ * something - as for a Spacer. */
37
+ needsBoundedMain(horizontal: boolean): boolean;
38
+ /** A `%` size is a share of the stack it sits in, which has to know to resolve it - as for a Box. */
39
+ relativeSizeFactor(horizontal: boolean): number | undefined;
40
+ calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
41
+ protected computeSize(constraints: BoxConstraints): Size;
42
+ /** Its size under these constraints - the one computation both `measure` and layout use. */
43
+ private sizeFor;
44
+ layoutChildren(): readonly PDFElement[];
45
+ getProps(): {
46
+ child?: PDFElement | undefined;
47
+ x: number;
48
+ y: number;
49
+ width: number | undefined;
50
+ height: number | undefined;
51
+ };
52
+ }
53
+ export {};
@@ -0,0 +1,108 @@
1
+ import { BoxConstraints, extentSpecs, resolveSize, } from "../../layout/box-constraints.js";
2
+ import { SizedPDFElement } from "../pdf-element.js";
3
+ /** A `Region` was laid out somewhere no `Flow` claimed it: outside a template, or inside the content. */
4
+ export class RegionOutsideFlowError extends Error {
5
+ constructor() {
6
+ super("@jasy/pdf: a Region only means something in a Flow's template - it marks where the content " +
7
+ "flows. Put it in the template (in @jasy/vue: the #page slot), not in the content or anywhere else.");
8
+ this.name = "RegionOutsideFlowError";
9
+ }
10
+ }
11
+ /** One Region instance handed to two pages of a Flow (or two Flows): both would draw the same content. */
12
+ export class RegionReusedError extends Error {
13
+ constructor() {
14
+ super("@jasy/pdf: a Region is used by two pages of a Flow (or by two Flows). A template is called for " +
15
+ "every page - build the Regions, and the layout around them, inside the function.");
16
+ this.name = "RegionReusedError";
17
+ }
18
+ }
19
+ /**
20
+ * A place in a `Flow`'s template that the content flows through. It has no size of its own: the layout
21
+ * around it sizes it (`Expanded`, `width`, `height`, `%`), and with nothing given it fills what it is
22
+ * offered, as a Canvas does. The Flow claims it, then hands it the piece of content that fits.
23
+ */
24
+ export class RegionElement extends SizedPDFElement {
25
+ constructor(params = {}) {
26
+ super({ x: 0, y: 0, width: params.width, height: params.height });
27
+ this.content = null;
28
+ const { widthFactor, heightFactor, ...sizing } = params;
29
+ this.widthFactor = widthFactor;
30
+ this.heightFactor = heightFactor;
31
+ this.sizing = sizing;
32
+ this.requested = { width: params.width, height: params.height };
33
+ }
34
+ /** Marks it as part of one page of a Flow; a second page claiming it throws. */
35
+ claim(owner) {
36
+ if (this.owner && this.owner !== owner)
37
+ throw new RegionReusedError();
38
+ this.owner = owner;
39
+ }
40
+ /** The content it holds on this page (`null`: the content ran out before it). */
41
+ hold(content) {
42
+ this.content = content;
43
+ }
44
+ get held() {
45
+ return this.content;
46
+ }
47
+ /** A height the author gave (points or `%`): kept on the Flow's last page instead of shrinking. */
48
+ hasOwnHeight() {
49
+ return this.requested.height !== undefined || this.heightFactor !== undefined;
50
+ }
51
+ /** Where it has no size of its own it fills what it is offered, so the stack around it must offer
52
+ * something - as for a Spacer. */
53
+ needsBoundedMain(horizontal) {
54
+ return horizontal
55
+ ? this.requested.width === undefined && this.widthFactor === undefined
56
+ : !this.hasOwnHeight();
57
+ }
58
+ /** A `%` size is a share of the stack it sits in, which has to know to resolve it - as for a Box. */
59
+ relativeSizeFactor(horizontal) {
60
+ return horizontal ? this.widthFactor : this.heightFactor;
61
+ }
62
+ calculateLayout(constraints, offset, ctx) {
63
+ // Laid out in a Flow's template, it belongs to that page; anywhere else it has no meaning.
64
+ if (!ctx.flowPage)
65
+ throw new RegionOutsideFlowError();
66
+ this.claim(ctx.flowPage);
67
+ const size = this.sizeFor(constraints);
68
+ this.x = offset.x;
69
+ this.y = offset.y;
70
+ this.width = size.width;
71
+ this.height = size.height;
72
+ // Its content is not the template: a Region in it is outside any Flow (or in its own inner one).
73
+ this.content?.calculateLayout(BoxConstraints.loose(size.width, size.height), { x: 0, y: 0 }, { ...ctx, flowPage: undefined });
74
+ return size;
75
+ }
76
+ computeSize(constraints) {
77
+ return this.sizeFor(constraints);
78
+ }
79
+ /** Its size under these constraints - the one computation both `measure` and layout use. */
80
+ sizeFor(constraints) {
81
+ const specs = extentSpecs({
82
+ ...this.sizing,
83
+ width: this.requested.width,
84
+ height: this.requested.height,
85
+ widthFactor: this.widthFactor,
86
+ heightFactor: this.heightFactor,
87
+ });
88
+ const resolved = resolveSize(specs.width, specs.height, this.sizing.aspectRatio, constraints);
89
+ const bounds = resolved.constraints;
90
+ return {
91
+ width: resolved.width ?? (bounds.hasBoundedWidth ? bounds.maxWidth : 0),
92
+ height: resolved.height ?? (bounds.hasBoundedHeight ? bounds.maxHeight : 0),
93
+ };
94
+ }
95
+ layoutChildren() {
96
+ return this.content ? [this.content] : [];
97
+ }
98
+ getProps() {
99
+ return {
100
+ x: this.x,
101
+ y: this.y,
102
+ width: this.width,
103
+ height: this.height,
104
+ // Only when it holds something: the pre-layout walks read `child` as an element.
105
+ ...(this.content ? { child: this.content } : {}),
106
+ };
107
+ }
108
+ }