@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,8 +1,11 @@
1
+ import { widthRules } from "../layout/box-constraints.js";
1
2
  import { Color } from "../common/color.js";
2
3
  import { BoxConstraints, extentSpecs, resolveSize, } from "../layout/box-constraints.js";
3
4
  import { stackMinIntrinsic, withDeclaredExtent } from "../layout/min-intrinsic.js";
4
- import { childrenForceBreak, packChildren, } from "../layout/fragmentation.js";
5
- import { SizedPDFElement, } from "./pdf-element.js";
5
+ import { positionedIn } from "./layout/positioned-element.js";
6
+ import { PaddingElement } from "./layout/padding-element.js";
7
+ import { childrenForceBreak, insetContext, packChildren, packedUnchanged, } from "../layout/fragmentation.js";
8
+ import { SizedPDFElement, withPaddingBase, } from "./pdf-element.js";
6
9
  export class RectangleElement extends SizedPDFElement {
7
10
  constructor({ children = [], color = new Color(0, 0, 0), backgroundColor, borderWidth, width, height, radius, sideBorders, relative, overflow, breakBefore, breakAfter, ...sizing }) {
8
11
  super({ x: 0, y: 0, width, height });
@@ -20,8 +23,10 @@ export class RectangleElement extends SizedPDFElement {
20
23
  this.breakAfter = breakAfter ?? false;
21
24
  this.sizeMemory = { ...sizing, x: 0, y: 0, width, height };
22
25
  }
26
+ // A break before the FIRST child is a break before this stack (CSS propagates it): the whole stack
27
+ // moves to the next page, instead of leaving an empty piece - or an empty border - behind.
23
28
  breaksBefore() {
24
- return this.breakBefore;
29
+ return this.breakBefore || (this.children[0]?.breaksBefore() ?? false);
25
30
  }
26
31
  breaksAfter() {
27
32
  return this.breakAfter;
@@ -35,31 +40,39 @@ export class RectangleElement extends SizedPDFElement {
35
40
  * box is sized by what it actually holds on each page.
36
41
  */
37
42
  fragment(maxHeight, width, ctx) {
38
- // A percentage width resolves against the region width the page driver hands us, so a
39
- // paginating %-box keeps its fraction on every page.
40
- const boxWidth = this.sizeMemory.width ??
41
- (this.sizeMemory.widthFactor !== undefined ? width * this.sizeMemory.widthFactor : width);
43
+ // The box's own width rules (fixed, %, min/max) resolved against the region - the same sizing
44
+ // path as `measure`, so the content is packed at the width it will be drawn at.
45
+ const boxWidth = this.measure(BoxConstraints.loose(width, Infinity), ctx).width;
42
46
  const innerWidth = Math.max(0, boxWidth - 2 * this.borderWidth);
43
47
  // Border-box: the content is inset by the border on top and bottom, so a fragment
44
48
  // holding `c` of content is `c + 2*border` tall. (Derived, not a fudge factor.)
45
49
  const innerMaxHeight = maxHeight - 2 * this.borderWidth;
46
- const { fitted, remainder, forceBreak } = packChildren(this.children, innerMaxHeight, innerWidth, ctx);
50
+ const { fitted, remainder, forceBreak } = packChildren(this.children, innerMaxHeight, innerWidth, insetContext(this.paddingContext(ctx, width), 2 * this.borderWidth));
47
51
  // Fits with no forced cut: return the whole box unchanged. A trailing forced break is the
48
52
  // exception - fall through so the box is rebuilt from the packed children (without the consumed
49
53
  // break marker), rather than handing back the original that still holds it.
50
- if (remainder.length === 0 && !forceBreak)
51
- return { fitted: this, remainder: null };
52
- const contentHeight = (kids) => kids.reduce((sum, child) => sum +
53
- child.calculateLayout(BoxConstraints.loose(innerWidth, Infinity), { x: 0, y: 0 }, ctx)
54
- .height, 0);
54
+ if (remainder.length === 0 && !forceBreak && packedUnchanged(this.children, fitted)) {
55
+ // The content fits, but a fixed height may not - such a box cannot shrink, so it moves on whole.
56
+ const own = this.measure(BoxConstraints.loose(width, Infinity), ctx).height;
57
+ return own <= maxHeight
58
+ ? { fitted: this, remainder: null }
59
+ : { fitted: null, remainder: this };
60
+ }
61
+ // Nothing fit below the content above: move on whole, not as an empty bordered box.
62
+ if (fitted.length === 0 && !forceBreak)
63
+ return { fitted: null, remainder: this };
64
+ const childCtx = this.paddingContext(ctx, width);
65
+ const contentHeight = (kids) => kids.reduce((sum, child) => sum + child.measure(BoxConstraints.loose(innerWidth, Infinity), childCtx).height, 0);
55
66
  return {
56
67
  fitted: this.cloneWithChildren(fitted, contentHeight(fitted) + 2 * this.borderWidth),
57
68
  // A trailing forced break leaves nothing to carry over: no remainder box, no extra page.
58
69
  // Only the CONTINUATION carries `breakAfter` (a box that splits still breaks after its LAST
59
- // fragment, not its first); `breakBefore` is dropped, already honoured before the split.
70
+ // fragment, not its first); `breakBefore` is dropped, already honored before the split.
71
+ // It gets no height, as a Column's piece does: it is sized where it lands, which may be a
72
+ // narrower or wider region (a Flow's), or one that gives a region-sized rest its height.
60
73
  remainder: remainder.length === 0
61
74
  ? null
62
- : this.cloneWithChildren(remainder, contentHeight(remainder) + 2 * this.borderWidth, this.breakAfter),
75
+ : this.cloneWithChildren(remainder, undefined, this.breakAfter),
63
76
  forceBreak,
64
77
  };
65
78
  }
@@ -70,10 +83,9 @@ export class RectangleElement extends SizedPDFElement {
70
83
  return new RectangleElement({
71
84
  x: 0,
72
85
  y: 0,
73
- width: this.sizeMemory.width,
74
- widthFactor: this.sizeMemory.widthFactor,
75
- // A split box is sized by its per-page content (height passed in), so the height factor
76
- // is intentionally dropped here - box-decoration-break, same as an explicit height.
86
+ ...widthRules(this.sizeMemory),
87
+ // A split box is sized by its per-page content (height passed in), so the height rules are
88
+ // intentionally dropped here - box-decoration-break, same as an explicit height.
77
89
  height,
78
90
  children,
79
91
  color: this.color,
@@ -84,11 +96,14 @@ export class RectangleElement extends SizedPDFElement {
84
96
  relative: this.relative,
85
97
  overflow: this.overflow,
86
98
  breakAfter,
87
- });
99
+ }).asPieceOf(this);
88
100
  }
89
101
  relativeSizeFactor(horizontal) {
90
102
  return horizontal ? this.sizeMemory.widthFactor : this.sizeMemory.heightFactor;
91
103
  }
104
+ maxExtent(horizontal) {
105
+ return (horizontal ? this.sizeMemory.maxWidth : this.sizeMemory.maxHeight) ?? Infinity;
106
+ }
92
107
  /** A box has no flex children of its own, but it must not swallow the need of the stack inside it: a
93
108
  * `Spacer` in a box can only resolve if the box itself was given a bounded extent to pass down. With an
94
109
  * explicit size on an axis the box already knows its extent there and asks for nothing. */
@@ -100,12 +115,41 @@ export class RectangleElement extends SizedPDFElement {
100
115
  return false;
101
116
  return this.children.some((c) => c.needsBoundedMain(horizontal));
102
117
  }
118
+ /** As a Column: a height of its own is the region; without one, a region-sized child needs the page's. */
119
+ needsRegion() {
120
+ if (this.sizeMemory.height !== undefined || this.sizeMemory.heightFactor !== undefined) {
121
+ return false;
122
+ }
123
+ return this.children.some((c) => c.needsRegion());
124
+ }
125
+ /** A Box's own padding resolves a percentage against its containing block, not against the inner
126
+ * width it hands its children - so it is told: the base a surrounding Row set, else what the Box was
127
+ * offered. Neither (an unbounded offer outside a Row) leaves the Box's own width. */
128
+ paddingContext(ctx, offered) {
129
+ if (!this.hasPercentPadding())
130
+ return withPaddingBase(ctx, undefined);
131
+ return withPaddingBase(ctx, ctx.boxPaddingBase ?? (Number.isFinite(offered) ? offered : undefined));
132
+ }
133
+ hasPercentPadding() {
134
+ const own = this.children[0];
135
+ return own instanceof PaddingElement && own.ofBox && own.hasPercentSide;
136
+ }
103
137
  /** A Box stacks its children vertically inside its border, which is on both sides of the axis. */
104
138
  minIntrinsicMain(horizontal, ctx) {
105
139
  const content = stackMinIntrinsic(this.children, 0, !horizontal, horizontal, ctx) + 2 * this.borderWidth;
106
140
  return withDeclaredExtent(content, horizontal ? this.sizeMemory.width : this.sizeMemory.height, horizontal ? this.sizeMemory.minWidth : this.sizeMemory.minHeight);
107
141
  }
108
142
  calculateLayout(constraints, offset, ctx) {
143
+ return this.sizeWith(constraints, ctx, offset);
144
+ }
145
+ computeSize(constraints, ctx) {
146
+ return this.sizeWith(constraints, ctx, null);
147
+ }
148
+ /**
149
+ * The one sizing path. With an `offset` it lays the children out and keeps its geometry; with
150
+ * `null` it only measures - no field written, no child placed, no frame drained.
151
+ */
152
+ sizeWith(constraints, ctx, offset) {
109
153
  // An explicit extent is a fixed point size or a fraction of the offered box (relative sizing);
110
154
  // the fraction only resolves in a bounded region. `undefined` = fill / shrink-wrap below.
111
155
  const specs = extentSpecs(this.sizeMemory);
@@ -118,12 +162,11 @@ export class RectangleElement extends SizedPDFElement {
118
162
  // fixed box would balloon to the parent's size.)
119
163
  // Whether there is a region to fill is decided by the constraints we were GIVEN: a `maxWidth` caps
120
164
  // a box, it does not turn a shrink-wrapping one into a filling one (CSS `max-width` does not either).
121
- this.width =
122
- explicitWidth !== undefined
123
- ? bounds.constrainWidth(explicitWidth)
124
- : constraints.hasBoundedWidth
125
- ? bounds.maxWidth
126
- : this.width;
165
+ let width = explicitWidth !== undefined
166
+ ? bounds.constrainWidth(explicitWidth)
167
+ : constraints.hasBoundedWidth
168
+ ? bounds.maxWidth
169
+ : undefined;
127
170
  // Width shrink-wrap: no explicit width AND an unbounded region (e.g. a `Box` badge inside a
128
171
  // `Positioned`). Resolved after the children are measured, just below.
129
172
  const shrinkWrapWidth = explicitWidth === undefined && !constraints.hasBoundedWidth;
@@ -131,43 +174,46 @@ export class RectangleElement extends SizedPDFElement {
131
174
  // SHRINK-WRAP the content in an unbounded one (a note box in a stack). Shrink-wrap is
132
175
  // resolved after the children are measured, just below.
133
176
  const shrinkWrapHeight = explicitHeight === undefined && !constraints.hasBoundedHeight;
134
- this.height =
135
- explicitHeight !== undefined
136
- ? bounds.constrainHeight(explicitHeight)
137
- : constraints.hasBoundedHeight
138
- ? bounds.maxHeight
139
- : this.height;
140
- this.x = this.sizeMemory.x + offset.x;
141
- this.y = this.sizeMemory.y + offset.y;
142
- // A `relative` box is a positioning frame: thread a fresh frame to the subtree so any
143
- // `Positioned` descendant registers against it (out of flow), then drain it below once the
144
- // box is sized. A plain box leaves `ctx` untouched -> byte-identical.
145
- const frame = this.relative
146
- ? { origin: { x: 0, y: 0 }, size: { width: 0, height: 0 }, place: [] }
177
+ let height = explicitHeight !== undefined
178
+ ? bounds.constrainHeight(explicitHeight)
179
+ : constraints.hasBoundedHeight
180
+ ? bounds.maxHeight
181
+ : undefined;
182
+ const x = this.sizeMemory.x + (offset?.x ?? 0);
183
+ const y = this.sizeMemory.y + (offset?.y ?? 0);
184
+ // A `relative` box is a positioning frame: its subtree is told it has one, and once the box is
185
+ // sized it lays out the `Positioned` descendants against itself (below). Measuring places
186
+ // nothing, so it needs no frame. A plain box leaves `ctx` untouched.
187
+ const frame = this.relative && offset
188
+ ? { origin: { x: 0, y: 0 }, size: { width: 0, height: 0 } }
147
189
  : undefined;
148
- const childCtx = frame ? { ...ctx, frame } : ctx;
190
+ const childCtx = this.paddingContext(frame ? { ...ctx, frame } : ctx, constraints.maxWidth);
149
191
  // Lay out children stacked inside the border (inset by the border width). Width is finalized here;
150
192
  // height is left unbounded so each child sizes to its own content - EXCEPT for a child that cannot
151
193
  // lay itself out without a bound (a stack holding an `Expanded`/`Spacer`). That one gets the height
152
194
  // still free inside the box, so its flex child has real leftover space instead of an infinite one.
153
195
  const innerWidth = shrinkWrapWidth
154
196
  ? Infinity
155
- : Math.max(0, (this.width ?? 0) - 2 * this.borderWidth);
197
+ : Math.max(0, (width ?? 0) - 2 * this.borderWidth);
156
198
  const innerHeight = shrinkWrapHeight
157
199
  ? Infinity
158
- : Math.max(0, (this.height ?? 0) - 2 * this.borderWidth);
200
+ : Math.max(0, (height ?? 0) - 2 * this.borderWidth);
159
201
  let contentWidth = 0;
160
202
  let contentHeight = 0;
161
- let yCursor = this.y + this.borderWidth;
203
+ // Children are placed relative to this box's top-left, inside the border.
204
+ let yCursor = this.borderWidth;
162
205
  for (const child of this.children) {
163
- const consumed = yCursor - (this.y + this.borderWidth);
206
+ const consumed = yCursor - this.borderWidth;
164
207
  // Same rule the flex helper applies: a child gets a finite main extent when it cannot lay itself
165
208
  // out without one - a stack holding an `Expanded`/`Spacer`, or a child sized as a percentage of us.
166
209
  const needsBound = child.needsBoundedMain(false) || child.relativeSizeFactor(false) !== undefined;
167
210
  const childHeight = needsBound
168
211
  ? Math.max(0, innerHeight - consumed) // Infinity - consumed stays Infinity, as it should
169
212
  : Infinity;
170
- const childSize = child.calculateLayout(BoxConstraints.loose(innerWidth, childHeight), { x: this.x + this.borderWidth, y: yCursor }, childCtx);
213
+ const childConstraints = BoxConstraints.loose(innerWidth, childHeight);
214
+ const childSize = offset
215
+ ? child.calculateLayout(childConstraints, { x: this.borderWidth, y: yCursor }, childCtx)
216
+ : child.measure(childConstraints, childCtx);
171
217
  yCursor += childSize.height;
172
218
  contentHeight += childSize.height;
173
219
  contentWidth = Math.max(contentWidth, childSize.width);
@@ -175,32 +221,49 @@ export class RectangleElement extends SizedPDFElement {
175
221
  // No explicit width and no bounded region: shrink-wrap to the widest child.
176
222
  // Clamped, so a min/max still applies to a box that sized itself to its content.
177
223
  if (shrinkWrapWidth)
178
- this.width = bounds.constrainWidth(contentWidth + 2 * this.borderWidth);
224
+ width = bounds.constrainWidth(contentWidth + 2 * this.borderWidth);
179
225
  // A ratio on a shrink-wrapping box can only be applied HERE: before the children were measured
180
226
  // there was no width to derive the height from.
181
227
  const ratio = this.sizeMemory.aspectRatio;
182
228
  const ratioSizesHeight = ratio !== undefined && ratio > 0 && shrinkWrapWidth && shrinkWrapHeight;
183
229
  // No explicit height and no bounded region: the border hugs its content.
184
230
  if (ratioSizesHeight)
185
- this.height = bounds.constrainHeight((this.width ?? 0) / ratio);
231
+ height = bounds.constrainHeight((width ?? 0) / ratio);
186
232
  else if (shrinkWrapHeight)
187
- this.height = bounds.constrainHeight(contentHeight + 2 * this.borderWidth);
188
- // The box is sized now: place any out-of-flow `Positioned` descendants against its box.
233
+ height = bounds.constrainHeight(contentHeight + 2 * this.borderWidth);
234
+ if (offset) {
235
+ this.x = x;
236
+ this.y = y;
237
+ this.width = width;
238
+ this.height = height;
239
+ }
240
+ // The box is sized now: place its out-of-flow `Positioned` descendants, relative to its own
241
+ // top-left. Each is drawn at its own place in the tree (tree order).
189
242
  if (frame) {
190
- frame.origin = { x: this.x, y: this.y };
191
- frame.size = { width: this.width ?? 0, height: this.height ?? 0 };
192
- for (const place of frame.place)
193
- place(frame, ctx);
243
+ const box = { width: width ?? 0, height: height ?? 0 };
244
+ for (const p of positionedIn(this.children))
245
+ p.placeIn(box, childCtx);
194
246
  }
195
247
  // Border-box model: width/height ARE the outer box (the rect we draw); the content
196
248
  // is inset by the border on every side. Report the honest box size - no phantom
197
249
  // border added on (that asymmetric "+border" was the source of the magic-3 fudge in
198
250
  // fragment). Top-left coordinates; the Y-flip happens at the IR -> backend seam.
199
251
  return {
200
- width: this.width ?? 0,
201
- height: this.height ?? 0,
252
+ width: width ?? 0,
253
+ height: height ?? 0,
202
254
  };
203
255
  }
256
+ isPositioningFrame() {
257
+ return this.relative;
258
+ }
259
+ /** Sorts the `zIndex` of the `Positioned` below it: a frame does, and so does a clip - what it crops
260
+ * stays cropped, whatever its `zIndex`. */
261
+ isStackingContext() {
262
+ return this.relative || this.overflow === "hidden";
263
+ }
264
+ layoutChildren() {
265
+ return this.children ?? [];
266
+ }
204
267
  getProps() {
205
268
  return {
206
269
  x: this.x,
@@ -214,6 +277,7 @@ export class RectangleElement extends SizedPDFElement {
214
277
  radius: this.radius,
215
278
  sideBorders: this.sideBorders,
216
279
  overflow: this.overflow,
280
+ relative: this.relative,
217
281
  };
218
282
  }
219
283
  }
@@ -35,18 +35,6 @@ interface RowElementParams extends WithChildren {
35
35
  /** Start everything after this row on a fresh page (CSS `break-after: page`). */
36
36
  breakAfter?: boolean;
37
37
  }
38
- /**
39
- * Horizontal stack: the mirror of `ContainerElement` (Column). Children are laid out
40
- * left-to-right via the shared `FlexLayoutHelper` on the horizontal axis; fixed children
41
- * take their natural width, `ExpandedElement`/Spacer children split the leftover width by
42
- * `flex`, and `gap` is inserted between them. The row fills the width it is offered and
43
- * shrink-wraps its height to the tallest child (unless a height is forced on it).
44
- *
45
- * Cross/main alignment is the next foundation slice; today children sit at the top-left
46
- * (cross start, main start). The row is atomic w.r.t. pagination - it reflows whole if it
47
- * does not fit (handled by the parent's `packChildren`); synchronized cell splitting is a
48
- * Grid/Table concern.
49
- */
50
38
  export declare class RowElement extends SizedPDFElement {
51
39
  private children;
52
40
  private gap;
@@ -62,12 +50,29 @@ export declare class RowElement extends SizedPDFElement {
62
50
  breaksBefore(): boolean;
63
51
  breaksAfter(): boolean;
64
52
  relativeSizeFactor(horizontal: boolean): number | undefined;
53
+ maxExtent(horizontal: boolean): number;
65
54
  /** The mirror of `ContainerElement`: a Row's OWN flex child needs a bounded width, and the need of any
66
55
  * descendant propagates on both axes (see the Column for the reasoning). */
67
56
  needsBoundedMain(horizontal: boolean): boolean;
68
57
  /** A Row lays out side by side, so widths add up (gaps included) and heights overlap. */
69
58
  minIntrinsicMain(horizontal: boolean, ctx: LayoutContext): number;
70
59
  calculateLayout(constraints: BoxConstraints, offset: Offset, ctx: LayoutContext): Size;
60
+ protected computeSize(constraints: BoxConstraints, ctx: LayoutContext): Size;
61
+ /** The first element inside this row that asks for a page break by flag, once found (or `null`). */
62
+ private declaredBreak?;
63
+ /**
64
+ * A row is one line: nothing inside it starts a new page. A `breakBefore` / `breakAfter` in a cell
65
+ * (a table row is a Row too) is therefore ignored - say so. A `PageBreak` reports itself.
66
+ */
67
+ private reportIgnoredBreak;
68
+ /** The one sizing path: `layout` places the children (relative to this box), `measure` only sizes them. */
69
+ private sizeWith;
70
+ private percentPaddingChild?;
71
+ /** A child's `%` padding resolves against this Row's width (CSS: the containing block), which the Row
72
+ * never offers - its children get their natural size - so it is told. Only when a child uses it,
73
+ * since every key below would otherwise change with the Row's width. */
74
+ private childContext;
75
+ layoutChildren(): readonly PDFElement[];
71
76
  getProps(): {
72
77
  x: number;
73
78
  y: number;
@@ -1,7 +1,9 @@
1
+ import { flexibleOf, hasPercentPaddingItem } from "../layout/flex-item.js";
1
2
  import { extentSpecs, resolveSize, } from "../layout/box-constraints.js";
2
- import { FlexLayoutHelper, HORIZONTAL_AXIS } from "../utils/flex-layout.js";
3
+ import { FlexLayoutHelper, HORIZONTAL_AXIS, } from "../utils/flex-layout.js";
3
4
  import { stackMinIntrinsic, withDeclaredExtent } from "../layout/min-intrinsic.js";
4
- import { FlexiblePDFElement, SizedPDFElement, } from "./pdf-element.js";
5
+ import { diagnose, pageOf } from "../common/diagnostics.js";
6
+ import { SizedPDFElement, withPaddingBase, } from "./pdf-element.js";
5
7
  /**
6
8
  * Horizontal stack: the mirror of `ContainerElement` (Column). Children are laid out
7
9
  * left-to-right via the shared `FlexLayoutHelper` on the horizontal axis; fixed children
@@ -14,6 +16,8 @@ import { FlexiblePDFElement, SizedPDFElement, } from "./pdf-element.js";
14
16
  * does not fit (handled by the parent's `packChildren`); synchronized cell splitting is a
15
17
  * Grid/Table concern.
16
18
  */
19
+ /** How far a line may run past its box before it is reported, in points (floating-point slack). */
20
+ const WIDE_EPSILON = 0.01;
17
21
  export class RowElement extends SizedPDFElement {
18
22
  constructor({ children, gap, main, cross, reverse, wrap, alignContent, width, height, breakBefore, breakAfter, ...sizing }) {
19
23
  super({ x: 0, y: 0 });
@@ -37,6 +41,9 @@ export class RowElement extends SizedPDFElement {
37
41
  relativeSizeFactor(horizontal) {
38
42
  return horizontal ? this.requested.widthFactor : this.requested.heightFactor;
39
43
  }
44
+ maxExtent(horizontal) {
45
+ return (horizontal ? this.requested.maxWidth : this.requested.maxHeight) ?? Infinity;
46
+ }
40
47
  /** The mirror of `ContainerElement`: a Row's OWN flex child needs a bounded width, and the need of any
41
48
  * descendant propagates on both axes (see the Column for the reasoning). */
42
49
  needsBoundedMain(horizontal) {
@@ -45,7 +52,7 @@ export class RowElement extends SizedPDFElement {
45
52
  : [this.requested.height, this.requested.heightFactor];
46
53
  if (requested.some((v) => v !== undefined))
47
54
  return false;
48
- const ownFlexChild = horizontal && this.children.some((c) => c instanceof FlexiblePDFElement);
55
+ const ownFlexChild = horizontal && this.children.some((c) => flexibleOf(c) !== undefined);
49
56
  return ownFlexChild || this.children.some((c) => c.needsBoundedMain(horizontal));
50
57
  }
51
58
  /** A Row lays out side by side, so widths add up (gaps included) and heights overlap. */
@@ -56,6 +63,53 @@ export class RowElement extends SizedPDFElement {
56
63
  calculateLayout(constraints, offset, ctx) {
57
64
  this.x = offset.x;
58
65
  this.y = offset.y;
66
+ this.reportIgnoredBreak(ctx);
67
+ const size = this.sizeWith(constraints, ctx, "layout");
68
+ this.width = size.width;
69
+ this.height = size.height;
70
+ return size;
71
+ }
72
+ computeSize(constraints, ctx) {
73
+ return this.sizeWith(constraints, ctx, "measure");
74
+ }
75
+ /**
76
+ * A row is one line: nothing inside it starts a new page. A `breakBefore` / `breakAfter` in a cell
77
+ * (a table row is a Row too) is therefore ignored - say so. A `PageBreak` reports itself.
78
+ */
79
+ reportIgnoredBreak(ctx) {
80
+ if (this.declaredBreak === undefined) {
81
+ const find = (el, depth) => {
82
+ if (el.breaksBefore() || el.breaksAfter())
83
+ return el;
84
+ if (depth > 64)
85
+ return null;
86
+ for (const child of el.layoutChildren()) {
87
+ const hit = find(child, depth + 1);
88
+ if (hit)
89
+ return hit;
90
+ }
91
+ return null;
92
+ };
93
+ this.declaredBreak = null;
94
+ for (const child of this.layoutChildren()) {
95
+ this.declaredBreak = find(child, 0);
96
+ if (this.declaredBreak)
97
+ break;
98
+ }
99
+ }
100
+ if (!this.declaredBreak)
101
+ return;
102
+ diagnose(ctx.diagnostics, {
103
+ code: "break-ignored",
104
+ severity: "warning",
105
+ message: "breakBefore / breakAfter inside a Row (or a table row) has no effect: a row is one line and " +
106
+ "never starts a new page. Put the break on the Row itself, or on a child of a Column.",
107
+ about: this.declaredBreak,
108
+ page: pageOf(ctx),
109
+ });
110
+ }
111
+ /** The one sizing path: `layout` places the children (relative to this box), `measure` only sizes them. */
112
+ sizeWith(constraints, ctx, mode) {
59
113
  // Relative sizing: a pinned extent (fixed or a fraction of the offered box, clamped) wins; else
60
114
  // width fills the offered space (flex children split the leftover) and height is the tallest child.
61
115
  const specs = extentSpecs(this.requested);
@@ -75,20 +129,46 @@ export class RowElement extends SizedPDFElement {
75
129
  // Horizontal stack: main = width (children fill it), cross = height (children can stretch to it).
76
130
  const mainAvail = boundedWidth ?? Infinity;
77
131
  const crossAvail = boundedHeight ?? Infinity;
78
- let result = { mainUsed: 0, crossUsed: 0 };
132
+ let result = {
133
+ mainUsed: 0,
134
+ crossUsed: 0,
135
+ };
79
136
  if (this.children.length > 0) {
80
- result = FlexLayoutHelper.layout(this.children, HORIZONTAL_AXIS, mainAvail, crossAvail, this.x, this.y, {
137
+ result = FlexLayoutHelper.layout(this.children, HORIZONTAL_AXIS, mainAvail, crossAvail, {
81
138
  gap: this.gap,
82
139
  main: this.main,
83
140
  cross: this.cross,
84
141
  reverse: this.reverse,
85
142
  wrap: this.wrap,
86
143
  alignContent: this.alignContent,
87
- }, ctx);
144
+ }, this.childContext(ctx, mainAvail), mode);
145
+ }
146
+ const used = result.widestLine ?? result.mainUsed;
147
+ if (mode === "layout" && used > mainAvail + WIDE_EPSILON) {
148
+ diagnose(ctx.diagnostics, {
149
+ code: "overflow-wide",
150
+ severity: "warning",
151
+ message: `A Row's children need ${Math.round(used)}pt but it is ` +
152
+ `${Math.round(mainAvail)}pt wide; they are drawn past its edge. Let them wrap ` +
153
+ '(`breakWord`, `wrap`), make room, or clip with a Box\'s `overflow: "hidden"`.',
154
+ about: this,
155
+ page: pageOf(ctx),
156
+ fallback: "silent",
157
+ });
88
158
  }
89
- this.width = boundedWidth ?? result.mainUsed;
90
- this.height = boundedHeight ?? result.crossUsed;
91
- return { width: this.width, height: this.height };
159
+ return { width: boundedWidth ?? result.mainUsed, height: boundedHeight ?? result.crossUsed };
160
+ }
161
+ /** A child's `%` padding resolves against this Row's width (CSS: the containing block), which the Row
162
+ * never offers - its children get their natural size - so it is told. Only when a child uses it,
163
+ * since every key below would otherwise change with the Row's width. */
164
+ childContext(ctx, mainAvail) {
165
+ this.percentPaddingChild ?? (this.percentPaddingChild = this.children.some(hasPercentPaddingItem));
166
+ if (!this.percentPaddingChild)
167
+ return ctx;
168
+ return withPaddingBase(ctx, Number.isFinite(mainAvail) ? mainAvail : undefined);
169
+ }
170
+ layoutChildren() {
171
+ return this.children;
92
172
  }
93
173
  getProps() {
94
174
  return {
@@ -1,7 +1,7 @@
1
1
  import { type SvgSize } from "../svg/index.js";
2
2
  import { BoxConstraints, Offset, Size, SizingParams } from "../layout/box-constraints.js";
3
3
  import { LayoutContext, SizedPDFElement } from "./pdf-element.js";
4
- /** Markup, a file path (Node only), or the file's bytes. */
4
+ /** Markup, a URL (`http(s):`, `data:`), a file path (Node only), or the file's bytes. */
5
5
  export type SvgSource = string | Uint8Array;
6
6
  interface SvgElementParams extends SizingParams {
7
7
  source: SvgSource;
@@ -16,26 +16,50 @@ interface SvgElementParams extends SizingParams {
16
16
  *
17
17
  * Unlike a bitmap it needs no decoder, so its intrinsic size is known SYNCHRONOUSLY, in the
18
18
  * constructor - which also means a broken file fails on the line that asked for it rather than deep
19
- * inside a later render, the same bargain `addFont` makes.
19
+ * inside a later render, the same bargain `addFont` makes. An SVG from a URL is the exception: like
20
+ * an image from a URL it is fetched when the document renders (`resolveIntrinsicSize`, or the
21
+ * renderer for a subtree built during layout), and a dead link stops the render naming it.
20
22
  */
21
23
  export declare class SvgElement extends SizedPDFElement {
22
- private source;
23
- private intrinsic;
24
+ private source?;
25
+ private intrinsic?;
26
+ private readonly url?;
27
+ private loading?;
28
+ private failed;
24
29
  private alt?;
25
30
  private requested;
26
31
  private sizing;
27
32
  private widthFactor?;
28
33
  private heightFactor?;
29
34
  constructor(params: SvgElementParams);
35
+ /**
36
+ * Parsed on purpose where the source is given: an unreadable file is an error where it was NAMED,
37
+ * not inside a render three call frames later. The whole document is walked, not just its root, so
38
+ * an element outside the subset surfaces here too. A logo parses in well under a millisecond.
39
+ */
40
+ private parse;
41
+ /**
42
+ * Fetches and parses an SVG from a URL - once per render, however often it is asked. A no-op
43
+ * otherwise. A failure sticks for that render only: `fresh` (a new render begins) asks again.
44
+ */
45
+ load(fresh?: boolean): Promise<void>;
46
+ /**
47
+ * The pre-layout pass, as for an `ImageElement`: best-effort, so a failure leaves the size unknown
48
+ * and the renderer, which loads again, surfaces the real error.
49
+ */
50
+ resolveIntrinsicSize(): Promise<void>;
30
51
  getProps(): {
31
52
  x: number;
32
53
  y: number;
33
54
  width: number | undefined;
34
55
  height: number | undefined;
35
- source: string;
36
- intrinsic: SvgSize;
56
+ source: string | undefined;
57
+ intrinsic: SvgSize | undefined;
37
58
  alt: string | undefined;
38
59
  };
39
60
  calculateLayout(constraints: BoxConstraints, offset: Offset, _ctx: LayoutContext): Size;
61
+ protected computeSize(constraints: BoxConstraints): Size;
62
+ /** Its size under these constraints - the one computation both `measure` and layout use. */
63
+ private sizeFor;
40
64
  }
41
65
  export {};