@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,11 +1,15 @@
1
- import { FlexiblePDFElement } from "../elements/pdf-element.js";
1
+ import { alignSelfOf, flexibleOf, flexShrinkOf, orderOf } from "../layout/flex-item.js";
2
2
  import { BoxConstraints } from "../layout/box-constraints.js";
3
+ const sameConstraints = (a, b) => a.minWidth === b.minWidth &&
4
+ a.maxWidth === b.maxWidth &&
5
+ a.minHeight === b.minHeight &&
6
+ a.maxHeight === b.maxHeight;
3
7
  export const VERTICAL_AXIS = {
4
8
  mainHorizontal: false,
5
9
  mainOf: (s) => s.height,
6
10
  crossOf: (s) => s.width,
7
11
  measureConstraints: (crossAvail, mainMax = Infinity) => BoxConstraints.loose(crossAvail, mainMax),
8
- flexConstraints: (mainExtent, crossAvail) => BoxConstraints.loose(crossAvail, mainExtent),
12
+ flexConstraints: (mainExtent, crossAvail) => new BoxConstraints(0, crossAvail, mainExtent, mainExtent),
9
13
  offsetAt: (mainPos, crossPos) => ({ x: crossPos, y: mainPos }),
10
14
  };
11
15
  export const HORIZONTAL_AXIS = {
@@ -13,7 +17,7 @@ export const HORIZONTAL_AXIS = {
13
17
  mainOf: (s) => s.width,
14
18
  crossOf: (s) => s.height,
15
19
  measureConstraints: (crossAvail, mainMax = Infinity) => BoxConstraints.loose(mainMax, crossAvail),
16
- flexConstraints: (mainExtent, crossAvail) => BoxConstraints.loose(mainExtent, crossAvail),
20
+ flexConstraints: (mainExtent, crossAvail) => new BoxConstraints(mainExtent, mainExtent, 0, crossAvail),
17
21
  offsetAt: (mainPos, crossPos) => ({ x: mainPos, y: crossPos }),
18
22
  };
19
23
  /** Cross-axis offset of a child of size `childCross` within `crossExtent`. */
@@ -33,21 +37,24 @@ function crossOffset(align, crossExtent, childCross) {
33
37
  * container where nobody sets one comes back with the identical array.
34
38
  */
35
39
  function inLayoutOrder(children, reverse) {
36
- const ordered = children.some((c) => c.order !== 0)
37
- ? [...children].sort((a, b) => a.order - b.order)
40
+ const ordered = children.some((c) => orderOf(c) !== 0)
41
+ ? [...children].sort((a, b) => orderOf(a) - orderOf(b))
38
42
  : children;
39
43
  return reverse ? [...ordered].reverse() : ordered;
40
44
  }
45
+ /**
46
+ * The main-axis alignment as the container means it. Reversed, the axis runs from the other end: `start`
47
+ * is where the first child of the REVERSED order sits - the right of a Row, the bottom of a Column (CSS
48
+ * `row-reverse` with `flex-start`, Flutter's `MainAxisAlignment.start` on a reversed axis). `center`,
49
+ * `between` and `around` are symmetric and stay.
50
+ */
51
+ function mainAlignOf(options) {
52
+ const main = options.main ?? "start";
53
+ if (!options.reverse)
54
+ return main;
55
+ return main === "start" ? "end" : main === "end" ? "start" : main;
56
+ }
41
57
  export class FlexLayoutHelper {
42
- /**
43
- * Lays out a flex line along `axis`, IN SOURCE ORDER, and places every child.
44
- * Fixed children take their natural main extent; flex (`ExpandedElement`) children
45
- * split the leftover main space by their `flex`. `gap` is inserted between children.
46
- * `main` distributes any leftover when there is no flex child; `cross` positions/sizes
47
- * each child across the line. Returns the total main extent consumed and the cross
48
- * extent occupied. Vertical with `gap 0`, `main start`, `cross stretch` reproduces the
49
- * previous Column layout exactly.
50
- */
51
58
  /**
52
59
  * Lays out the children along `axis`, on one line or several.
53
60
  *
@@ -55,12 +62,12 @@ export class FlexLayoutHelper {
55
62
  * straight through, which is what keeps every existing document byte-identical. With `wrap` the
56
63
  * children are split into lines first and the SAME engine runs once per line.
57
64
  */
58
- static layout(children, axis, mainAvail, crossAvail, mainStart, crossOrigin, options, ctx) {
65
+ static layout(children, axis, mainAvail, crossAvail, options, ctx, mode = "layout") {
59
66
  if (!options.wrap || children.length === 0 || !Number.isFinite(mainAvail)) {
60
67
  // An unbounded main axis has no edge to wrap at, so wrapping there is meaningless, not an error.
61
- return FlexLayoutHelper.layoutLine(children, axis, mainAvail, crossAvail, mainStart, crossOrigin, options, ctx);
68
+ return FlexLayoutHelper.layoutLine(children, axis, mainAvail, crossAvail, 0, options, ctx, mode);
62
69
  }
63
- return FlexLayoutHelper.layoutWrapped(children, axis, mainAvail, crossAvail, mainStart, crossOrigin, options, ctx);
70
+ return FlexLayoutHelper.layoutWrapped(children, axis, mainAvail, crossAvail, options, ctx, mode);
64
71
  }
65
72
  /**
66
73
  * The wrapping path: split into lines, lay each one out, then place the lines across the axis.
@@ -69,21 +76,19 @@ export class FlexLayoutHelper {
69
76
  * is the same measure-then-place shape the single-line engine already uses, and it is what
70
77
  * `alignContent` needs: the block of lines can only be centred once its total is known.
71
78
  */
72
- static layoutWrapped(children, axis, mainAvail, crossAvail, mainStart, crossOrigin, options, ctx) {
79
+ static layoutWrapped(children, axis, mainAvail, crossAvail, options, ctx, mode) {
73
80
  const gap = options.gap ?? 0;
74
81
  const ordered = inLayoutOrder(children, options.reverse ?? false);
75
- // Which children share a line. A child is measured at its natural main extent; a flex child
76
- // contributes its BASIS, which is 0 unless it asked for one - so it never forces a break by itself.
77
- // A child's natural main extent. A PERCENTAGE child has no fixed answer: its size depends on how
78
- // many gaps the line ends up with, which depends on who is on it. So it is asked again for every
79
- // candidate membership below rather than measured once.
80
- // A plain child's extent does not depend on the line, so it is laid out ONCE and remembered.
81
- // Without this each membership probe re-lays every child already on the line - O(n^2) subtree
82
- // layouts, which is expensive the moment those children hold a Table or a wrapping paragraph.
82
+ // Which children share a line. A child counts with its natural main extent; a flex child with its
83
+ // BASIS, which is 0 unless it asked for one - so it never forces a break by itself. A PERCENTAGE
84
+ // child's size depends on how many gaps the line ends up with, so it is asked again for every
85
+ // candidate membership. A plain child's extent does not, so it is measured ONCE and remembered -
86
+ // otherwise each membership probe re-lays every child already on the line (O(n^2) subtree layouts).
83
87
  const measured = new Map();
84
88
  const naturalMain = (child, base) => {
85
- if (child instanceof FlexiblePDFElement)
86
- return child.getBasis(base);
89
+ const flexible = flexibleOf(child);
90
+ if (flexible)
91
+ return flexible.getBasis(base);
87
92
  const factor = child.relativeSizeFactor(axis.mainHorizontal);
88
93
  if (factor !== undefined)
89
94
  return base * factor;
@@ -92,9 +97,9 @@ export class FlexLayoutHelper {
92
97
  return cached;
93
98
  // Measured UNCAPPED, the same way the single-line engine measures a plain child. Handing the
94
99
  // line width in as a cap would silently squash a child wider than the line - CSS lets such a
95
- // child overflow, and so does our own non-wrapping path.
96
- const needsBound = child.needsBoundedMain(axis.mainHorizontal);
97
- const main = axis.mainOf(child.calculateLayout(axis.measureConstraints(crossAvail, needsBound ? mainAvail : Infinity), axis.offsetAt(mainStart, crossOrigin), ctx));
100
+ // child overflow, and so does our own non-wrapping path. A stack holding a Spacer counts with its
101
+ // content too: its Spacer only grows into what is left once the line is laid out.
102
+ const main = axis.mainOf(child.measure(axis.measureConstraints(crossAvail, Infinity), ctx));
98
103
  measured.set(child, main);
99
104
  return main;
100
105
  };
@@ -117,16 +122,31 @@ export class FlexLayoutHelper {
117
122
  }
118
123
  // Each line keeps SOURCE order internally: the ordering was applied above, and applying it again
119
124
  // per line would reverse twice.
120
- const lineOptions = { ...options, wrap: false, reverse: false };
121
- const measure = lines.map((line) => FlexLayoutHelper.layoutLine(line, axis, mainAvail, Infinity, // let each line report the cross extent it actually needs
122
- mainStart, crossOrigin, lineOptions, ctx));
123
- const heights = measure.map((m) => m.crossUsed);
125
+ const lineOptions = { ...options, wrap: false, reverse: false, main: mainAlignOf(options) };
126
+ const measureAt = (line, cross) => FlexLayoutHelper.layoutLine(line, axis, mainAvail, cross, 0, lineOptions, ctx, "measure", true);
127
+ // Across a Column a child gets the column's width, as a block does in CSS (a paragraph wraps inside
128
+ // it); across a Row it is as tall as its content. Either way the line is only as big as what is on
129
+ // it, so `stretch` fills the line and not the container. A region-sized child (a Flow) has no
130
+ // content height: its line shares what the other lines leave of the Row's height.
131
+ const regionLine = (line) => axis.mainHorizontal && line.some((c) => c.needsRegion());
132
+ const measure = lines.map((line) => regionLine(line) ? null : measureAt(line, axis.mainHorizontal ? Infinity : crossAvail));
133
+ const regionLines = lines.filter(regionLine).length;
134
+ if (regionLines > 0) {
135
+ const taken = measure.reduce((n, m) => n + (m?.crossUsed ?? 0), 0) + Math.max(0, lines.length - 1) * gap;
136
+ const share = Math.max(0, crossAvail - taken) / regionLines;
137
+ lines.forEach((line, i) => {
138
+ if (regionLine(line))
139
+ measure[i] = measureAt(line, share);
140
+ });
141
+ }
142
+ const sized = measure;
143
+ const heights = sized.map((m) => m.crossUsed);
124
144
  const totalCross = heights.reduce((n, h) => n + h, 0) + Math.max(0, lines.length - 1) * gap;
125
145
  // `alignContent` distributes the BLOCK of lines across the axis, using the same vocabulary the main
126
146
  // axis uses. Only meaningful in a bounded cross axis with room left over.
127
147
  const slack = Number.isFinite(crossAvail) ? crossAvail - totalCross : 0;
128
148
  const align = options.alignContent ?? "start";
129
- let cursor = crossOrigin;
149
+ let cursor = 0;
130
150
  let between = gap;
131
151
  if (slack > 0) {
132
152
  if (align === "center")
@@ -143,203 +163,393 @@ export class FlexLayoutHelper {
143
163
  }
144
164
  lines.forEach((line, i) => {
145
165
  FlexLayoutHelper.layoutLine(line, axis, mainAvail, heights[i], // the line's own extent, so `stretch` fills the line and not the container
146
- mainStart, cursor, lineOptions, ctx);
166
+ cursor, lineOptions, ctx, mode);
147
167
  cursor += heights[i];
148
168
  if (i < lines.length - 1)
149
169
  cursor += between;
150
170
  });
151
- return { mainUsed: mainAvail, crossUsed: Math.max(totalCross, cursor - crossOrigin) };
171
+ return {
172
+ mainUsed: mainAvail,
173
+ crossUsed: Math.max(totalCross, cursor),
174
+ // A child wider than the line runs past it, in a line of its own.
175
+ widestLine: Math.max(...sized.map((m) => m.mainUsed)),
176
+ };
152
177
  }
153
- static layoutLine(children, axis, mainAvail, crossAvail, mainStart, crossOrigin, options, ctx) {
178
+ /**
179
+ * Lays out ONE flex line along `axis` and places every child. Fixed children take their natural main
180
+ * extent; flex children split the leftover by their `flex`; `gap` goes between children. `main`
181
+ * distributes leftover when nothing flexes; `cross` positions or stretches each child across the line.
182
+ * Returns the main extent consumed and the cross extent occupied.
183
+ *
184
+ * The steps run in this order, and the order is part of the result: `leftover` is taken after the
185
+ * shrink and before the share, and the main-axis spacing reads exactly that value.
186
+ */
187
+ static layoutLine(children, axis, mainAvail, crossAvail, crossOrigin, options, ctx, mode,
188
+ // The line's cross extent is its content, not `crossAvail`: one line of a wrapping container.
189
+ crossFromContent = false) {
154
190
  const gap = options.gap ?? 0;
155
- const main = options.main ?? "start";
156
- const cross = options.cross ?? "stretch";
157
- children = inLayoutOrder(children, options.reverse ?? false);
158
- const count = children.length;
159
- const totalGap = Math.max(0, count - 1) * gap;
160
- // The main extent a percentage child resolves against: the line's own extent minus the gaps
161
- // between the items. So N children at (100/N)% + gaps fit exactly, instead of overflowing by the
162
- // gaps (the flexbox `width: 33%` + gap trap). Only finite when the line's main axis is bounded -
163
- // a fraction of an unbounded main axis has no meaning, so `%` children fall back to shrink-wrap.
164
- const percentBase = mainAvail !== Infinity ? Math.max(0, mainAvail - totalGap) : Infinity;
165
- // The main cap to hand a child: `percentBase` for a percentage child (so its fraction resolves) and
166
- // for a child that cannot lay itself out without a bound (a nested stack holding an `Expanded`/
167
- // `Spacer`); unbounded for everyone else, who keep their natural size and never fill the line.
168
- // Filled by the shrink pass below; read by `mainCapFor` in pass 2. Declared here so the closure
169
- // can see it - it is empty for every document that does not ask for shrinking.
170
- const shrunkCap = new Map();
171
- const mainCapFor = (child) => {
172
- const target = shrunkCap.get(child);
173
- if (target !== undefined)
174
- return target;
175
- if (percentBase === Infinity)
176
- return Infinity;
177
- const needsBound = child.relativeSizeFactor(axis.mainHorizontal) !== undefined ||
178
- child.needsBoundedMain(axis.mainHorizontal);
179
- return needsBound ? percentBase : Infinity;
191
+ const ordered = inLayoutOrder(children, options.reverse ?? false);
192
+ const totalGap = Math.max(0, ordered.length - 1) * gap;
193
+ const line = {
194
+ children: ordered,
195
+ axis,
196
+ mainAvail,
197
+ crossAvail,
198
+ ctx,
199
+ totalGap,
200
+ // The main extent a percentage child resolves against: the line minus the gaps between its items,
201
+ // so N children at (100/N)% + gaps fit exactly (the flexbox `width: 33%` + gap trap). Only finite
202
+ // on a bounded main axis - a fraction of an unbounded one has no meaning, so `%` shrink-wraps.
203
+ percentBase: mainAvail !== Infinity ? Math.max(0, mainAvail - totalGap) : Infinity,
204
+ fixedMain: 0,
205
+ crossUsed: 0,
206
+ totalFlex: 0,
207
+ totalBasis: 0,
208
+ sizes: new Map(),
209
+ under: new Map(),
210
+ shrunkCap: new Map(),
211
+ sharedCap: new Map(),
212
+ shrunkBasis: new Map(),
213
+ flexFloor: new Map(),
214
+ flexCeil: new Map(),
215
+ flexExtent: new Map(),
180
216
  };
181
- // Pass 1: measure the fixed children (main extent + cross size) and total the flex.
182
- // A flex child's BASIS is reserved here too: it is main extent the leftover no longer contains,
183
- // exactly like a fixed child's size. With the default basis of 0 this line adds nothing.
184
- let fixedMain = 0;
185
- let totalFlex = 0;
186
- let totalBasis = 0;
187
- let crossUsed = 0;
188
- const fixedSize = new Map();
189
- for (const child of children) {
190
- if (child instanceof FlexiblePDFElement) {
191
- totalFlex += child.getFlex();
192
- totalBasis += child.getBasis(percentBase);
193
- }
194
- else {
195
- const size = child.calculateLayout(axis.measureConstraints(crossAvail, mainCapFor(child)), axis.offsetAt(mainStart, crossOrigin), ctx);
196
- fixedMain += axis.mainOf(size);
197
- crossUsed = Math.max(crossUsed, axis.crossOf(size));
198
- fixedSize.set(child, size);
199
- }
200
- }
201
- // Shrinking: the line overflows and some children said they would give space back. CSS weights a
202
- // shrinker's share by `shrink x its own size`, so a big item yields more than a small one - which is
203
- // why this cannot reuse the grow maths above. Nobody shrinks by default, so a document that never
204
- // asks for it never enters this block.
205
- const shrinkers = children.filter((c) => c.flexShrink > 0 && !(c instanceof FlexiblePDFElement));
206
- const overflow = fixedMain + totalBasis + totalGap - mainAvail;
207
- if (shrinkers.length > 0 && Number.isFinite(mainAvail) && overflow > 0) {
208
- // Every child is floored at what it can actually hold - its `min-content` extent, which is CSS's
209
- // automatic `min-width` on a flex item. Without it a badly overflowing line would grind a
210
- // paragraph down to one word per line: the box gets narrow, the text gets tall, and nothing is
211
- // gained. Past that floor a child overflows again, which is what a browser does too.
212
- const naturalOf = (c) => axis.mainOf(fixedSize.get(c));
213
- const floorOf = new Map(shrinkers.map((c) => [
214
- c,
215
- // Never above its natural size: the shrink pass may only take space away, never hand it out.
216
- // Never below zero either - when the GAPS alone outgrow the line a proportional share asks
217
- // for more than a child has, and a negative constraint is nonsense even though
218
- // `constrainWidth` would floor it further down anyway.
219
- Math.min(naturalOf(c), Math.max(0, c.minIntrinsicMain(axis.mainHorizontal, ctx))),
220
- ]));
221
- // CSS resolves this iteratively (Flexbox 9.7): a child that lands on its floor is FROZEN there
222
- // and the share it could not absorb is handed to the rest. A single pass would leave that share
223
- // unspent, so one unsqueezable child would keep the whole line overflowing while its neighbours
224
- // still had room to give.
225
- const target = new Map(shrinkers.map((c) => [c, naturalOf(c)]));
226
- const frozen = new Set();
227
- let flexible = [...shrinkers];
228
- // What the frozen ones took off the overflow; the rest is always shared out from scratch, never
229
- // from what a previous round already assigned.
230
- let absorbedByFrozen = 0;
231
- while (flexible.length > 0) {
232
- const toAbsorb = overflow - absorbedByFrozen;
233
- if (toAbsorb <= 1e-9)
234
- break;
235
- const weight = (c) => c.flexShrink * naturalOf(c);
236
- const totalWeight = flexible.reduce((n, c) => n + weight(c), 0);
237
- if (totalWeight <= 0)
238
- break;
239
- const froze = [];
240
- for (const child of flexible) {
241
- const natural = naturalOf(child);
242
- const floor = floorOf.get(child);
243
- const wanted = natural - (weight(child) / totalWeight) * toAbsorb;
244
- if (wanted < floor) {
245
- target.set(child, floor);
246
- froze.push(child);
247
- absorbedByFrozen += natural - floor;
248
- }
249
- else {
250
- target.set(child, wanted);
251
- }
252
- }
253
- if (froze.length === 0)
254
- break;
255
- for (const c of froze)
256
- frozen.add(c);
257
- flexible = flexible.filter((c) => !frozen.has(c));
258
- }
259
- for (const child of shrinkers) {
260
- const natural = naturalOf(child);
261
- const capped = target.get(child);
262
- if (capped >= natural)
263
- continue;
264
- shrunkCap.set(child, capped);
265
- // Re-measure NOW, not in pass 2: a narrower child may wrap to more lines, and the line's
266
- // cross extent is settled just below.
267
- const size = child.calculateLayout(axis.measureConstraints(crossAvail, capped), axis.offsetAt(mainStart, crossOrigin), ctx);
268
- fixedMain += axis.mainOf(size) - natural;
269
- crossUsed = Math.max(crossUsed, axis.crossOf(size));
270
- fixedSize.set(child, size);
271
- }
272
- }
273
- const leftover = mainAvail - fixedMain - totalBasis - totalGap;
217
+ measureLine(line);
218
+ shrinkLine(line);
219
+ const leftover = mainAvail - line.fixedMain - line.totalBasis - totalGap;
274
220
  // A flex child on an UNBOUNDED main axis has no leftover space to claim. It must collapse to zero,
275
221
  // never to `Infinity`: an infinite extent would become the offset of every following sibling and get
276
222
  // written into the content stream verbatim, silently corrupting the page from that point on.
277
223
  const remaining = Number.isFinite(leftover) ? Math.max(leftover, 0) : 0;
278
- // Main-axis distribution only kicks in with no flex child and bounded, positive space.
279
- let leadingSpace = 0;
280
- let betweenSpace = gap;
281
- if (totalFlex === 0 && mainAvail !== Infinity && leftover > 0) {
282
- if (main === "center")
283
- leadingSpace = leftover / 2;
284
- else if (main === "end")
285
- leadingSpace = leftover;
286
- else if (main === "between" && count > 1)
287
- betweenSpace = gap + leftover / (count - 1);
288
- else if (main === "around") {
289
- const unit = leftover / count;
290
- leadingSpace = unit / 2;
291
- betweenSpace = gap + unit;
292
- }
293
- }
294
- // Measure flex children too (at their main share) so a tall/wrapping flex cell counts
295
- // toward the line's cross extent - they're placed in pass 2, but crossExtent needs them now.
296
- if (totalFlex > 0) {
297
- for (const child of children) {
298
- if (child instanceof FlexiblePDFElement) {
299
- const mainExtent = child.getBasis(percentBase) + (child.getFlex() / totalFlex) * remaining;
300
- const size = child.calculateLayout(axis.flexConstraints(mainExtent, crossAvail), axis.offsetAt(mainStart, crossOrigin), ctx);
301
- crossUsed = Math.max(crossUsed, axis.crossOf(size));
302
- }
224
+ const weight = shareLeftover(line, remaining);
225
+ const spacing = mainSpacing(mainAlignOf(options), gap, ordered.length, mainAvail, leftover, weight);
226
+ shareFlex(line, weight, remaining);
227
+ measureFlex(line, options.cross ?? "stretch");
228
+ // The cross extent children align within: the bounded line size, else the tallest child.
229
+ const crossExtent = crossAvail !== Infinity && !crossFromContent ? crossAvail : line.crossUsed;
230
+ return placeLine(line, options.cross ?? "stretch", crossExtent, crossOrigin, spacing, mode);
231
+ }
232
+ }
233
+ /** A flex child's basis on this line: what it asked for, or less once the line has shrunk it. */
234
+ function basisOf(line, child, flexible) {
235
+ return line.shrunkBasis.get(child) ?? flexible.getBasis(line.percentBase);
236
+ }
237
+ /**
238
+ * The main cap to hand a child: its shrunk or shared cap once the line is settled; `percentBase` for a
239
+ * percentage child, so its fraction resolves; unbounded for everyone else, who keep their natural size.
240
+ */
241
+ function mainCapFor(line, child) {
242
+ const target = line.shrunkCap.get(child) ?? line.sharedCap.get(child);
243
+ if (target !== undefined)
244
+ return target;
245
+ if (line.percentBase === Infinity)
246
+ return Infinity;
247
+ return child.relativeSizeFactor(line.axis.mainHorizontal) !== undefined
248
+ ? line.percentBase
249
+ : Infinity;
250
+ }
251
+ /** Measures `child` and remembers the size with the constraints it answers. */
252
+ function measureInto(line, child, constraints) {
253
+ const size = child.measure(constraints, line.ctx);
254
+ line.sizes.set(child, size);
255
+ line.under.set(child, constraints);
256
+ return size;
257
+ }
258
+ /** Measures a fixed child again at a new main cap, moving the line's totals with it. */
259
+ function remeasure(line, child, cap) {
260
+ const before = line.axis.mainOf(line.sizes.get(child));
261
+ const size = measureInto(line, child, line.axis.measureConstraints(line.crossAvail, cap));
262
+ line.fixedMain += line.axis.mainOf(size) - before;
263
+ line.crossUsed = Math.max(line.crossUsed, line.axis.crossOf(size));
264
+ }
265
+ /**
266
+ * Step 1: measure the fixed children (main extent + cross size) and total the flex. A flex child's BASIS
267
+ * is reserved here too: it is main extent the leftover no longer contains, exactly like a fixed child's
268
+ * size. With the default basis of 0 it adds nothing.
269
+ */
270
+ function measureLine(line) {
271
+ const { axis } = line;
272
+ for (const child of line.children) {
273
+ const flexible = flexibleOf(child);
274
+ if (flexible) {
275
+ line.totalFlex += flexible.getFlex();
276
+ line.totalBasis += flexible.getBasis(line.percentBase);
277
+ if (Number.isFinite(line.mainAvail)) {
278
+ // Down a column, a flex child keeps its content's height (CSS `min-height: auto`): squeezed
279
+ // below it, it would draw over the sibling that follows.
280
+ const content = axis.mainHorizontal
281
+ ? 0
282
+ : axis.mainOf(child.measure(axis.measureConstraints(line.crossAvail, Infinity), line.ctx));
283
+ const floor = Math.max(0, child.minIntrinsicMain(axis.mainHorizontal, line.ctx), content);
284
+ line.flexFloor.set(child, floor);
285
+ // A max below the floor loses, as in CSS.
286
+ line.flexCeil.set(child, Math.max(floor, child.maxExtent(axis.mainHorizontal)));
303
287
  }
288
+ continue;
304
289
  }
305
- // The cross extent children align within: the bounded line size, else the tallest child.
306
- const crossExtent = crossAvail !== Infinity ? crossAvail : crossUsed;
307
- // `stretch` caps a child's cross to `crossExtent` (not `crossAvail`) so siblings end up
308
- // equal across the axis. Bounded lines have crossExtent == crossAvail (byte-identical);
309
- // only an unbounded line (a shrink-wrap Row) now equalises instead of staying natural.
310
- // A child may override the container with its own `alignSelf` (CSS), so the decision is per child.
311
- const alignFor = (child) => child.alignSelf ?? cross;
312
- // Pass 2: place each child at the running main position, offset across by its alignment.
313
- let mainPos = mainStart + leadingSpace;
314
- let placedCross = 0;
315
- children.forEach((child, index) => {
316
- const align = alignFor(child);
317
- const stretch = align === "stretch";
318
- let mainExtent;
319
- if (child instanceof FlexiblePDFElement) {
320
- // `totalFlex` is 0 when every flex child asked for `flex: 0` (a pure `flexBasis` slot). The
321
- // share is then 0/0 - a NaN that becomes the offset of every later sibling, which is the
322
- // shape of the old Spacer bug. Such a child simply takes its basis.
323
- const share = totalFlex > 0 ? (child.getFlex() / totalFlex) * remaining : 0;
324
- mainExtent = child.getBasis(percentBase) + share;
325
- // A flex child fills the MAIN axis, and its cross size is only known after layout - there is
326
- // nothing to align against. So alignSelf is a no-op here, and that has to hold for the CROSS
327
- // CONSTRAINT too: reading the per-child alignment would hand an `alignSelf: "start"` flex child
328
- // an unbounded cross axis on a shrink-wrapping line, where the container's own rule gives it
329
- // the line's extent. A no-op that changes the constraints is not a no-op.
330
- const size = child.calculateLayout(axis.flexConstraints(mainExtent, cross === "stretch" ? crossExtent : crossAvail), axis.offsetAt(mainPos, crossOrigin), ctx);
331
- placedCross = Math.max(placedCross, axis.crossOf(size));
290
+ const size = measureInto(line, child, axis.measureConstraints(line.crossAvail, mainCapFor(line, child)));
291
+ line.fixedMain += axis.mainOf(size);
292
+ line.crossUsed = Math.max(line.crossUsed, axis.crossOf(size));
293
+ }
294
+ }
295
+ /**
296
+ * Step 2: the line overflows and some children said they would give space back. CSS weights a shrinker's
297
+ * share by `shrink x its own size`, so a big item yields more than a small one - for a flex child that
298
+ * size is its basis, so a Spacer (basis 0) gives nothing. A flex child counts at least at its floor, so
299
+ * its siblings make room for what it holds. A document that never overflows never gets past the first
300
+ * test.
301
+ */
302
+ function shrinkLine(line) {
303
+ const { axis } = line;
304
+ let reserved = 0;
305
+ for (const [child, floor] of line.flexFloor) {
306
+ reserved += Math.max(0, floor - flexibleOf(child).getBasis(line.percentBase));
307
+ }
308
+ const overflow = line.fixedMain + line.totalBasis + reserved + line.totalGap - line.mainAvail;
309
+ if (!Number.isFinite(line.mainAvail) || !(overflow > 0))
310
+ return;
311
+ const shrinkers = line.children.filter((c) => {
312
+ if (!(flexShrinkOf(c) > 0))
313
+ return false;
314
+ const flexible = flexibleOf(c);
315
+ return !flexible || flexible.getBasis(line.percentBase) > 0;
316
+ });
317
+ if (shrinkers.length === 0)
318
+ return;
319
+ // Every child is floored at what it can actually hold - its `min-content` extent, which is CSS's
320
+ // automatic `min-width` on a flex item. Without it a badly overflowing line would grind a paragraph
321
+ // down to one word per line. Past that floor a child overflows again, as in a browser.
322
+ const naturalOf = (c) => {
323
+ const flexible = flexibleOf(c);
324
+ return flexible ? flexible.getBasis(line.percentBase) : axis.mainOf(line.sizes.get(c));
325
+ };
326
+ const floorOf = new Map(shrinkers.map((c) => [
327
+ c,
328
+ // Never above its natural size (the shrink only takes space away) and never below zero (when the
329
+ // GAPS alone outgrow the line, a proportional share asks for more than a child has).
330
+ Math.min(naturalOf(c), Math.max(0, c.minIntrinsicMain(axis.mainHorizontal, line.ctx))),
331
+ ]));
332
+ // CSS resolves this iteratively (Flexbox 9.7): a child that lands on its floor is FROZEN there and the
333
+ // share it could not absorb is handed to the rest. A single pass would leave that share unspent.
334
+ const target = new Map(shrinkers.map((c) => [c, naturalOf(c)]));
335
+ const frozen = new Set();
336
+ let flexible = [...shrinkers];
337
+ // What the frozen ones took off the overflow; the rest is always shared out from scratch.
338
+ let absorbedByFrozen = 0;
339
+ while (flexible.length > 0) {
340
+ const toAbsorb = overflow - absorbedByFrozen;
341
+ if (toAbsorb <= 1e-9)
342
+ break;
343
+ const weight = (c) => flexShrinkOf(c) * naturalOf(c);
344
+ const totalWeight = flexible.reduce((n, c) => n + weight(c), 0);
345
+ if (totalWeight <= 0)
346
+ break;
347
+ const froze = [];
348
+ for (const child of flexible) {
349
+ const natural = naturalOf(child);
350
+ const floor = floorOf.get(child);
351
+ const wanted = natural - (weight(child) / totalWeight) * toAbsorb;
352
+ if (wanted < floor) {
353
+ target.set(child, floor);
354
+ froze.push(child);
355
+ absorbedByFrozen += natural - floor;
332
356
  }
333
357
  else {
334
- const childCross = axis.crossOf(fixedSize.get(child));
335
- const size = child.calculateLayout(axis.measureConstraints(stretch ? crossExtent : crossAvail, mainCapFor(child)), axis.offsetAt(mainPos, crossOrigin + crossOffset(align, crossExtent, childCross)), ctx);
336
- mainExtent = axis.mainOf(size);
337
- placedCross = Math.max(placedCross, axis.crossOf(size));
358
+ target.set(child, wanted);
338
359
  }
339
- mainPos += mainExtent;
340
- if (index < count - 1)
341
- mainPos += betweenSpace;
360
+ }
361
+ if (froze.length === 0)
362
+ break;
363
+ for (const c of froze)
364
+ frozen.add(c);
365
+ flexible = flexible.filter((c) => !frozen.has(c));
366
+ }
367
+ for (const child of shrinkers) {
368
+ const capped = target.get(child);
369
+ if (capped >= naturalOf(child))
370
+ continue;
371
+ if (flexibleOf(child)) {
372
+ // A flex child is measured later, at its basis plus its share; it starts from less.
373
+ line.totalBasis -= naturalOf(child) - capped;
374
+ line.shrunkBasis.set(child, capped);
375
+ continue;
376
+ }
377
+ line.shrunkCap.set(child, capped);
378
+ // Re-measured NOW: a narrower child may wrap to more lines, and the line's cross extent is
379
+ // settled before anything is placed.
380
+ remeasure(line, child, capped);
381
+ }
382
+ }
383
+ /**
384
+ * Step 3: a stack holding a Spacer grows like an `Expanded` of flex 1 whose basis is its content - it
385
+ * shares what is left with the flex children. Handing it the whole line squeezed its siblings; handing
386
+ * it a share before the line was settled could leave it less than its own content. Returns the total
387
+ * weight the leftover is shared by.
388
+ */
389
+ function shareLeftover(line, remaining) {
390
+ const { axis } = line;
391
+ const sharing = line.children.filter((child) => line.percentBase !== Infinity &&
392
+ !flexibleOf(child) &&
393
+ child.relativeSizeFactor(axis.mainHorizontal) === undefined &&
394
+ child.needsBoundedMain(axis.mainHorizontal));
395
+ const weight = line.totalFlex + sharing.length;
396
+ if (remaining > 0) {
397
+ for (const child of sharing) {
398
+ const grown = axis.mainOf(line.sizes.get(child)) + remaining / weight;
399
+ line.sharedCap.set(child, grown);
400
+ remeasure(line, child, grown);
401
+ }
402
+ }
403
+ return weight;
404
+ }
405
+ /** Step 4: the main-axis distribution - only with nothing to flex and bounded, positive space. */
406
+ function mainSpacing(main, gap, count, mainAvail, leftover, weight) {
407
+ if (weight !== 0 || mainAvail === Infinity || !(leftover > 0))
408
+ return { leading: 0, between: gap };
409
+ if (main === "center")
410
+ return { leading: leftover / 2, between: gap };
411
+ if (main === "end")
412
+ return { leading: leftover, between: gap };
413
+ if (main === "between" && count > 1)
414
+ return { leading: 0, between: gap + leftover / (count - 1) };
415
+ if (main === "around") {
416
+ const unit = leftover / count;
417
+ return { leading: unit / 2, between: gap + unit };
418
+ }
419
+ return { leading: 0, between: gap };
420
+ }
421
+ /**
422
+ * Step 5: each flex child's main extent - its basis plus its share of the leftover, held between its
423
+ * floor and its ceiling. CSS resolves that iteratively (Flexbox 9.7): clamp everyone, and by the sign of
424
+ * the total change freeze the ones held at their floor (it grew) or at their ceiling (it shrank); the
425
+ * rest share what is left again. Nothing clamped: the plain share.
426
+ */
427
+ function shareFlex(line, weight, remaining) {
428
+ const items = [];
429
+ for (const child of line.children) {
430
+ const flexible = flexibleOf(child);
431
+ if (flexible)
432
+ items.push([child, flexible]);
433
+ }
434
+ // `totalFlex` is 0 when every flex child asked for `flex: 0` (a pure `flexBasis` slot). The share is
435
+ // then 0/0 - a NaN that becomes the offset of every later sibling. Such a child takes its basis.
436
+ const extentAt = (child, flexible, part, of) => basisOf(line, child, flexible) + (of > 0 ? (flexible.getFlex() / of) * part : 0);
437
+ for (const [child, flexible] of items) {
438
+ line.flexExtent.set(child, extentAt(child, flexible, remaining, weight));
439
+ }
440
+ // The flex children's part of the leftover; a Spacer stack's share (`shareLeftover`) stays its own.
441
+ let pot = weight > 0 ? (remaining * line.totalFlex) / weight : 0;
442
+ let active = items;
443
+ for (;;) {
444
+ let change = 0;
445
+ const clamped = new Map();
446
+ for (const [child] of active) {
447
+ const extent = line.flexExtent.get(child);
448
+ const held = Math.min(Math.max(extent, line.flexFloor.get(child) ?? 0), line.flexCeil.get(child) ?? Infinity);
449
+ if (held !== extent)
450
+ clamped.set(child, held);
451
+ change += held - extent;
452
+ }
453
+ if (clamped.size === 0)
454
+ return;
455
+ const froze = active.filter(([child]) => {
456
+ const held = clamped.get(child);
457
+ return (held !== undefined &&
458
+ (change > 0
459
+ ? held > line.flexExtent.get(child)
460
+ : change < 0
461
+ ? held < line.flexExtent.get(child)
462
+ : true));
342
463
  });
343
- return { mainUsed: mainPos - mainStart, crossUsed: placedCross };
464
+ for (const [child, flexible] of froze) {
465
+ const held = clamped.get(child);
466
+ pot -= held - basisOf(line, child, flexible);
467
+ line.flexExtent.set(child, held);
468
+ }
469
+ active = active.filter((item) => !froze.includes(item));
470
+ const activeFlex = active.reduce((n, [, flexible]) => n + flexible.getFlex(), 0);
471
+ for (const [child, flexible] of active) {
472
+ line.flexExtent.set(child, extentAt(child, flexible, Math.max(0, pot), activeFlex));
473
+ }
344
474
  }
345
475
  }
476
+ /**
477
+ * Step 6: measure the flex children at their extent, so a tall or wrapping flex cell counts toward the
478
+ * line's cross extent before anything is placed.
479
+ */
480
+ function measureFlex(line, cross) {
481
+ for (const [child, mainExtent] of line.flexExtent) {
482
+ // A pure `flexBasis` slot (`flex: 0`) has no share but still has a size across the line.
483
+ const crossMax = (alignSelfOf(child) ?? cross) === "stretch"
484
+ ? line.crossAvail
485
+ : ownCross(line, child, mainExtent);
486
+ const size = measureInto(line, child, line.axis.flexConstraints(mainExtent, crossMax));
487
+ line.crossUsed = Math.max(line.crossUsed, line.axis.crossOf(size));
488
+ }
489
+ }
490
+ /**
491
+ * Across the line, a flex child that is not stretched takes what its content needs and is aligned like
492
+ * any other item. Its content is measured against the line's cross bound - an unbounded one would stop a
493
+ * text in a Column from wrapping.
494
+ */
495
+ function ownCross(line, child, mainExtent) {
496
+ const flexible = flexibleOf(child);
497
+ const content = flexible.layoutChildren()[0];
498
+ if (!content)
499
+ return 0;
500
+ // In the context it is laid out in: a wrapper on the way to the `Expanded` may hand one down.
501
+ let ctx = line.ctx;
502
+ for (let el = child; el !== flexible; el = el.proxied())
503
+ ctx = el.proxiedContext(ctx);
504
+ const constraints = line.axis.flexConstraints(mainExtent, line.crossAvail);
505
+ return line.axis.crossOf(content.measure(constraints, ctx));
506
+ }
507
+ /**
508
+ * Step 7: place each child at the running main position, offset across by its alignment. This is the
509
+ * ONE layout a child gets. Measuring, it only sizes - and reuses a size already measured under the same
510
+ * constraints, or the measuring itself would double at every level.
511
+ *
512
+ * `stretch` caps a child's cross to `crossExtent`, not `crossAvail`, so siblings end up equal across the
513
+ * axis. A child may override the container with its own `alignSelf` (CSS).
514
+ */
515
+ function placeLine(line, cross, crossExtent, crossOrigin, spacing, mode) {
516
+ const { axis, ctx } = line;
517
+ const place = (child, constraints, offset) => {
518
+ if (mode === "layout")
519
+ return child.calculateLayout(constraints, offset, ctx);
520
+ const known = line.under.get(child);
521
+ return known && sameConstraints(known, constraints)
522
+ ? line.sizes.get(child)
523
+ : child.measure(constraints, ctx);
524
+ };
525
+ const last = line.children.length - 1;
526
+ let mainPos = spacing.leading;
527
+ let placedCross = 0;
528
+ line.children.forEach((child, index) => {
529
+ let mainExtent;
530
+ const flexible = flexibleOf(child);
531
+ // A region-sized child (a Flow) takes the height the Row was given, never one its siblings make.
532
+ const regionSized = axis.mainHorizontal && child.needsRegion();
533
+ if (flexible) {
534
+ mainExtent = line.flexExtent.get(child);
535
+ const align = alignSelfOf(child) ?? cross;
536
+ // Stretched, it is capped at the line; otherwise it may take what the line offers, and is
537
+ // aligned by what it takes.
538
+ const cap = align === "stretch" && !regionSized ? crossExtent : line.crossAvail;
539
+ const own = align === "stretch" || regionSized ? cap : ownCross(line, child, mainExtent);
540
+ const size = place(child, axis.flexConstraints(mainExtent, cap), axis.offsetAt(mainPos, crossOrigin + crossOffset(align, crossExtent, own)));
541
+ placedCross = Math.max(placedCross, axis.crossOf(size));
542
+ }
543
+ else {
544
+ const align = alignSelfOf(child) ?? cross;
545
+ const childCross = axis.crossOf(line.sizes.get(child));
546
+ const size = place(child, axis.measureConstraints(align === "stretch" && !regionSized ? crossExtent : line.crossAvail, mainCapFor(line, child)), axis.offsetAt(mainPos, crossOrigin + crossOffset(align, crossExtent, childCross)));
547
+ mainExtent = axis.mainOf(size);
548
+ placedCross = Math.max(placedCross, axis.crossOf(size));
549
+ }
550
+ mainPos += mainExtent;
551
+ if (index < last)
552
+ mainPos += spacing.between;
553
+ });
554
+ return { mainUsed: mainPos, crossUsed: placedCross };
555
+ }