@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.
- package/dist/api/color.d.ts +1 -24
- package/dist/api/color.js +2 -278
- package/dist/api/content.d.ts +10 -5
- package/dist/api/content.js +29 -13
- package/dist/api/descriptor.js +30 -3
- package/dist/api/flow.d.ts +55 -0
- package/dist/api/flow.js +71 -0
- package/dist/api/font-url.d.ts +1 -2
- package/dist/api/font-url.js +8 -74
- package/dist/api/forms.d.ts +3 -0
- package/dist/api/forms.js +3 -0
- package/dist/api/gradient.d.ts +1 -53
- package/dist/api/gradient.js +2 -97
- package/dist/api/index.d.ts +4 -0
- package/dist/api/index.js +3 -0
- package/dist/api/layout.d.ts +6 -0
- package/dist/api/layout.js +9 -3
- package/dist/api/page-builder.d.ts +4 -0
- package/dist/api/page-builder.js +4 -0
- package/dist/api/structure.d.ts +10 -2
- package/dist/api/structure.js +9 -6
- package/dist/api/table.d.ts +5 -3
- package/dist/api/table.js +101 -22
- package/dist/api/text.d.ts +8 -8
- package/dist/api/text.js +6 -0
- package/dist/canvas/painter.d.ts +2 -2
- package/dist/canvas/painter.js +2 -2
- package/dist/common/color-input.d.ts +24 -0
- package/dist/common/color-input.js +278 -0
- package/dist/common/diagnostics.d.ts +61 -0
- package/dist/common/diagnostics.js +77 -0
- package/dist/common/gradient.d.ts +53 -0
- package/dist/common/gradient.js +97 -0
- package/dist/edit/acroform-reader.js +7 -7
- package/dist/edit/appearance.js +3 -3
- package/dist/edit/attachments.d.ts +52 -0
- package/dist/edit/attachments.js +178 -0
- package/dist/edit/document.d.ts +38 -8
- package/dist/edit/document.js +208 -51
- package/dist/edit/fill.js +8 -23
- package/dist/edit/filters.d.ts +15 -0
- package/dist/edit/filters.js +157 -0
- package/dist/edit/index.d.ts +4 -1
- package/dist/edit/index.js +3 -1
- package/dist/edit/objects.d.ts +3 -4
- package/dist/edit/objects.js +5 -18
- package/dist/elements/canvas-element.d.ts +3 -0
- package/dist/elements/canvas-element.js +14 -3
- package/dist/elements/container-element.d.ts +8 -1
- package/dist/elements/container-element.js +49 -17
- package/dist/elements/forms/checkbox-element.d.ts +2 -0
- package/dist/elements/forms/checkbox-element.js +12 -3
- package/dist/elements/forms/choice-element.d.ts +6 -1
- package/dist/elements/forms/choice-element.js +17 -6
- package/dist/elements/forms/field-font.d.ts +8 -0
- package/dist/elements/forms/field-font.js +5 -0
- package/dist/elements/forms/push-button-element.d.ts +6 -1
- package/dist/elements/forms/push-button-element.js +16 -4
- package/dist/elements/forms/radio-element.d.ts +2 -0
- package/dist/elements/forms/radio-element.js +12 -3
- package/dist/elements/forms/signature-element.d.ts +6 -1
- package/dist/elements/forms/signature-element.js +16 -4
- package/dist/elements/forms/text-field-element.d.ts +6 -1
- package/dist/elements/forms/text-field-element.js +16 -4
- package/dist/elements/image-element.d.ts +70 -21
- package/dist/elements/image-element.js +173 -52
- package/dist/elements/index.d.ts +3 -0
- package/dist/elements/index.js +3 -0
- package/dist/elements/layout/anchor-element.d.ts +8 -10
- package/dist/elements/layout/anchor-element.js +10 -18
- package/dist/elements/layout/bookmark-element.d.ts +9 -11
- package/dist/elements/layout/bookmark-element.js +11 -20
- package/dist/elements/layout/column-break-element.d.ts +17 -0
- package/dist/elements/layout/column-break-element.js +26 -0
- package/dist/elements/layout/default-text-style-element.d.ts +11 -13
- package/dist/elements/layout/default-text-style-element.js +14 -31
- package/dist/elements/layout/deferred-element.d.ts +20 -4
- package/dist/elements/layout/deferred-element.js +41 -9
- package/dist/elements/layout/expanded-element.d.ts +11 -0
- package/dist/elements/layout/expanded-element.js +53 -18
- package/dist/elements/layout/flow-element.d.ts +109 -0
- package/dist/elements/layout/flow-element.js +317 -0
- package/dist/elements/layout/keep-together-element.d.ts +8 -18
- package/dist/elements/layout/keep-together-element.js +33 -45
- package/dist/elements/layout/link-element.d.ts +8 -13
- package/dist/elements/layout/link-element.js +9 -19
- package/dist/elements/layout/padding-element.d.ts +18 -1
- package/dist/elements/layout/padding-element.js +63 -28
- package/dist/elements/layout/page-break-element.d.ts +4 -1
- package/dist/elements/layout/page-break-element.js +6 -1
- package/dist/elements/layout/page-builder-element.d.ts +5 -0
- package/dist/elements/layout/page-builder-element.js +13 -2
- package/dist/elements/layout/positioned-element.d.ts +23 -4
- package/dist/elements/layout/positioned-element.js +42 -12
- package/dist/elements/layout/proxy-element.d.ts +47 -0
- package/dist/elements/layout/proxy-element.js +106 -0
- package/dist/elements/layout/region-element.d.ts +53 -0
- package/dist/elements/layout/region-element.js +108 -0
- package/dist/elements/layout/repeating-header-element.d.ts +9 -0
- package/dist/elements/layout/repeating-header-element.js +55 -13
- package/dist/elements/layout/rotated-box-element.d.ts +6 -0
- package/dist/elements/layout/rotated-box-element.js +27 -14
- package/dist/elements/layout/rotated-element.d.ts +9 -0
- package/dist/elements/layout/rotated-element.js +33 -2
- package/dist/elements/layout/struct-group.d.ts +9 -17
- package/dist/elements/layout/struct-group.js +10 -35
- package/dist/elements/line-element.d.ts +1 -0
- package/dist/elements/line-element.js +6 -0
- package/dist/elements/page-element.d.ts +10 -3
- package/dist/elements/page-element.js +39 -31
- package/dist/elements/pdf-document-element.d.ts +3 -0
- package/dist/elements/pdf-document-element.js +7 -0
- package/dist/elements/pdf-element.d.ts +105 -16
- package/dist/elements/pdf-element.js +118 -19
- package/dist/elements/rectangle-element.d.ts +22 -2
- package/dist/elements/rectangle-element.js +119 -55
- package/dist/elements/row-element.d.ts +17 -12
- package/dist/elements/row-element.js +89 -9
- package/dist/elements/svg-element.d.ts +30 -6
- package/dist/elements/svg-element.js +91 -26
- package/dist/elements/text-element.d.ts +48 -13
- package/dist/elements/text-element.js +161 -116
- package/dist/forms/acroform.d.ts +9 -1
- package/dist/forms/acroform.js +83 -34
- package/dist/forms/appearance.d.ts +17 -6
- package/dist/forms/appearance.js +18 -16
- package/dist/ir/display-list.d.ts +18 -3
- package/dist/layout/box-constraints.d.ts +17 -0
- package/dist/layout/box-constraints.js +31 -0
- package/dist/layout/collect-images.d.ts +6 -4
- package/dist/layout/collect-images.js +14 -8
- package/dist/layout/flex-item.d.ts +11 -0
- package/dist/layout/flex-item.js +51 -0
- package/dist/layout/fragmentation.d.ts +23 -9
- package/dist/layout/fragmentation.js +62 -19
- package/dist/layout/layout-cache.d.ts +53 -0
- package/dist/layout/layout-cache.js +110 -0
- package/dist/platform/browser-fs.d.ts +1 -2
- package/dist/platform/browser-fs.js +3 -6
- package/dist/platform/node-fs.d.ts +0 -1
- package/dist/platform/node-fs.js +0 -4
- package/dist/renderer/anchor-renderer.d.ts +2 -1
- package/dist/renderer/anchor-renderer.js +5 -4
- package/dist/renderer/bookmark-renderer.d.ts +2 -1
- package/dist/renderer/bookmark-renderer.js +5 -4
- package/dist/renderer/canvas-renderer.d.ts +2 -1
- package/dist/renderer/canvas-renderer.js +5 -2
- package/dist/renderer/column-break-renderer.d.ts +8 -0
- package/dist/renderer/column-break-renderer.js +15 -0
- package/dist/renderer/container-renderer.d.ts +2 -1
- package/dist/renderer/container-renderer.js +6 -5
- package/dist/renderer/default-text-style-renderer.d.ts +2 -1
- package/dist/renderer/default-text-style-renderer.js +2 -3
- package/dist/renderer/deferred-renderer.d.ts +2 -1
- package/dist/renderer/deferred-renderer.js +2 -3
- package/dist/renderer/emoji-image.js +3 -1
- package/dist/renderer/expanded-renderer.d.ts +2 -1
- package/dist/renderer/expanded-renderer.js +2 -3
- package/dist/renderer/flow-renderer.d.ts +16 -0
- package/dist/renderer/flow-renderer.js +21 -0
- package/dist/renderer/form-field-renderer.d.ts +2 -1
- package/dist/renderer/form-field-renderer.js +16 -3
- package/dist/renderer/image-renderer.d.ts +2 -1
- package/dist/renderer/image-renderer.js +33 -53
- package/dist/renderer/index.d.ts +2 -0
- package/dist/renderer/index.js +2 -0
- package/dist/renderer/keep-together-renderer.d.ts +2 -1
- package/dist/renderer/keep-together-renderer.js +2 -3
- package/dist/renderer/line-renderer.d.ts +2 -1
- package/dist/renderer/line-renderer.js +7 -2
- package/dist/renderer/link-renderer.d.ts +2 -1
- package/dist/renderer/link-renderer.js +36 -5
- package/dist/renderer/padding-renderer.d.ts +2 -1
- package/dist/renderer/padding-renderer.js +5 -5
- package/dist/renderer/page-break-renderer.d.ts +6 -5
- package/dist/renderer/page-break-renderer.js +14 -7
- package/dist/renderer/page-builder-renderer.d.ts +2 -1
- package/dist/renderer/page-builder-renderer.js +2 -3
- package/dist/renderer/page-renderer.js +30 -10
- package/dist/renderer/pdf-backend.d.ts +4 -3
- package/dist/renderer/pdf-backend.js +14 -7
- package/dist/renderer/pdf-document-renderer.d.ts +1 -8
- package/dist/renderer/pdf-document-renderer.js +17 -17
- package/dist/renderer/pdf-renderer.js +20 -11
- package/dist/renderer/positioned-renderer.d.ts +18 -1
- package/dist/renderer/positioned-renderer.js +43 -6
- package/dist/renderer/rectangle-renderer.d.ts +2 -1
- package/dist/renderer/rectangle-renderer.js +18 -8
- package/dist/renderer/repeating-header-renderer.d.ts +2 -1
- package/dist/renderer/repeating-header-renderer.js +6 -5
- package/dist/renderer/rotated-renderer.d.ts +2 -1
- package/dist/renderer/rotated-renderer.js +9 -5
- package/dist/renderer/row-renderer.d.ts +2 -1
- package/dist/renderer/row-renderer.js +6 -5
- package/dist/renderer/struct-group-renderer.d.ts +2 -1
- package/dist/renderer/struct-group-renderer.js +2 -3
- package/dist/renderer/svg-renderer.d.ts +2 -1
- package/dist/renderer/svg-renderer.js +8 -2
- package/dist/renderer/text-renderer.d.ts +4 -6
- package/dist/renderer/text-renderer.js +114 -127
- package/dist/svg/gradients.js +5 -0
- package/dist/svg/index.d.ts +1 -1
- package/dist/svg/index.js +36 -6
- package/dist/svg/style.d.ts +2 -0
- package/dist/svg/style.js +12 -1
- package/dist/text/advance.d.ts +4 -0
- package/dist/text/advance.js +17 -4
- package/dist/text/bidi.d.ts +0 -2
- package/dist/text/bidi.js +0 -10
- package/dist/text/glyph-coverage.d.ts +1 -1
- package/dist/text/glyph-coverage.js +7 -3
- package/dist/text/hyphen-breaks.d.ts +17 -0
- package/dist/text/hyphen-breaks.js +54 -0
- package/dist/text/line-breaker.d.ts +55 -29
- package/dist/text/line-breaker.js +453 -188
- package/dist/text/line-metrics.d.ts +8 -3
- package/dist/text/line-metrics.js +11 -2
- package/dist/text/pdf-text-string.d.ts +21 -0
- package/dist/text/pdf-text-string.js +114 -0
- package/dist/text/text-decoration.d.ts +5 -5
- package/dist/text/text-decoration.js +5 -5
- package/dist/text/word-splitting.d.ts +15 -2
- package/dist/text/word-splitting.js +45 -13
- package/dist/utils/afm-parser.d.ts +2 -0
- package/dist/utils/afm-parser.js +10 -1
- package/dist/utils/dest-registry.d.ts +1 -1
- package/dist/utils/dest-registry.js +3 -2
- package/dist/utils/fetch-bytes.d.ts +19 -0
- package/dist/utils/fetch-bytes.js +79 -0
- package/dist/utils/flex-layout.d.ts +16 -14
- package/dist/utils/flex-layout.js +427 -217
- package/dist/utils/font-metrics.d.ts +4 -3
- package/dist/utils/image-helper.d.ts +59 -11
- package/dist/utils/image-helper.js +209 -81
- package/dist/utils/pdf-object-manager.d.ts +17 -6
- package/dist/utils/pdf-object-manager.js +70 -20
- package/dist/utils/renderer-registry.d.ts +49 -2
- package/dist/utils/renderer-registry.js +40 -0
- package/dist/utils/struct-tree.d.ts +24 -0
- package/dist/utils/struct-tree.js +61 -9
- package/dist/utils/ttf-parser.d.ts +4 -0
- package/dist/utils/ttf-parser.js +30 -6
- package/dist/validators/element-validator.js +2 -1
- package/package.json +1 -1
- package/dist/elements/layout/sized-container-element.d.ts +0 -14
- package/dist/elements/layout/sized-container-element.js +0 -32
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import {
|
|
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
|
|
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
|
|
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
|
|
37
|
-
? [...children].sort((a, b) => a
|
|
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,
|
|
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,
|
|
68
|
+
return FlexLayoutHelper.layoutLine(children, axis, mainAvail, crossAvail, 0, options, ctx, mode);
|
|
62
69
|
}
|
|
63
|
-
return FlexLayoutHelper.layoutWrapped(children, axis, mainAvail, crossAvail,
|
|
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,
|
|
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
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
//
|
|
79
|
-
//
|
|
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
|
-
|
|
86
|
-
|
|
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
|
-
|
|
97
|
-
const main = axis.mainOf(child.
|
|
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
|
|
122
|
-
|
|
123
|
-
|
|
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 =
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
156
|
-
const
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
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
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
-
|
|
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
|
-
|
|
340
|
-
|
|
341
|
-
|
|
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
|
-
|
|
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
|
+
}
|