@react-x11/components 0.12.0 → 0.14.0
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/html/controls.d.ts +41 -5
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js +85 -57
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/cascade.d.ts +134 -20
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +545 -126
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +18 -0
- package/dist/html/css/color.d.ts.map +1 -1
- package/dist/html/css/color.js +27 -0
- package/dist/html/css/color.js.map +1 -1
- package/dist/html/css/parse.d.ts +47 -6
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +336 -30
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +142 -15
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +563 -147
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/transform.d.ts +85 -0
- package/dist/html/css/transform.d.ts.map +1 -0
- package/dist/html/css/transform.js +425 -0
- package/dist/html/css/transform.js.map +1 -0
- package/dist/html/css/ua.d.ts +11 -1
- package/dist/html/css/ua.d.ts.map +1 -1
- package/dist/html/css/ua.js +73 -9
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +4 -0
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/dom.d.ts.map +1 -1
- package/dist/html/dom.js +3 -1
- package/dist/html/dom.js.map +1 -1
- package/dist/html/fonts.d.ts +78 -4
- package/dist/html/fonts.d.ts.map +1 -1
- package/dist/html/fonts.js +373 -19
- package/dist/html/fonts.js.map +1 -1
- package/dist/html/form.d.ts +200 -0
- package/dist/html/form.d.ts.map +1 -0
- package/dist/html/form.js +801 -0
- package/dist/html/form.js.map +1 -0
- package/dist/html/index.d.ts +12 -0
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +30 -301
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/axes.d.ts +14 -0
- package/dist/html/layout/axes.d.ts.map +1 -0
- package/dist/html/layout/axes.js +28 -0
- package/dist/html/layout/axes.js.map +1 -0
- package/dist/html/layout/block.d.ts +93 -12
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +393 -39
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +90 -2
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +74 -10
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/cache.d.ts +1 -1
- package/dist/html/layout/cache.d.ts.map +1 -1
- package/dist/html/layout/cache.js +6 -0
- package/dist/html/layout/cache.js.map +1 -1
- package/dist/html/layout/css-grid.d.ts.map +1 -1
- package/dist/html/layout/css-grid.js +90 -18
- package/dist/html/layout/css-grid.js.map +1 -1
- package/dist/html/layout/flex.d.ts.map +1 -1
- package/dist/html/layout/flex.js +574 -84
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/inline.d.ts +3 -0
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +154 -54
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/layout/multicol.d.ts +42 -0
- package/dist/html/layout/multicol.d.ts.map +1 -0
- package/dist/html/layout/multicol.js +897 -0
- package/dist/html/layout/multicol.js.map +1 -0
- package/dist/html/layout/shaping.d.ts +7 -0
- package/dist/html/layout/shaping.d.ts.map +1 -1
- package/dist/html/layout/shaping.js +25 -20
- package/dist/html/layout/shaping.js.map +1 -1
- package/dist/html/layout/table.d.ts.map +1 -1
- package/dist/html/layout/table.js +62 -18
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +90 -15
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +771 -186
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +101 -5
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +1502 -160
- package/dist/html/paint.js.map +1 -1
- package/dist/html/svg.d.ts +15 -2
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +249 -17
- package/dist/html/svg.js.map +1 -1
- package/dist/html/widgets.d.ts +29 -0
- package/dist/html/widgets.d.ts.map +1 -0
- package/dist/html/widgets.js +700 -0
- package/dist/html/widgets.js.map +1 -0
- package/dist/html/woff2.d.ts +14 -0
- package/dist/html/woff2.d.ts.map +1 -0
- package/dist/html/woff2.js +608 -0
- package/dist/html/woff2.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/maps/controller.d.ts +17 -1
- package/dist/maps/controller.d.ts.map +1 -1
- package/dist/maps/controller.js +27 -7
- package/dist/maps/controller.js.map +1 -1
- package/dist/richtext/node.d.ts +8 -1
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +5 -1
- package/dist/richtext/node.js.map +1 -1
- package/dist/richtext/runs.d.ts +25 -6
- package/dist/richtext/runs.d.ts.map +1 -1
- package/dist/richtext/runs.js +240 -24
- package/dist/richtext/runs.js.map +1 -1
- package/package.json +2 -2
- package/src/html/controls.ts +114 -51
- package/src/html/css/cascade.ts +614 -140
- package/src/html/css/color.ts +29 -0
- package/src/html/css/parse.ts +388 -33
- package/src/html/css/style.ts +688 -150
- package/src/html/css/transform.ts +459 -0
- package/src/html/css/ua.ts +77 -10
- package/src/html/css/values.ts +4 -0
- package/src/html/dom.ts +3 -1
- package/src/html/fonts.ts +417 -24
- package/src/html/form.ts +962 -0
- package/src/html/index.ts +49 -338
- package/src/html/layout/axes.ts +62 -0
- package/src/html/layout/block.ts +447 -37
- package/src/html/layout/boxes.ts +143 -14
- package/src/html/layout/cache.ts +6 -0
- package/src/html/layout/css-grid.ts +95 -22
- package/src/html/layout/flex.ts +675 -78
- package/src/html/layout/inline.ts +159 -55
- package/src/html/layout/multicol.ts +977 -0
- package/src/html/layout/shaping.ts +27 -18
- package/src/html/layout/table.ts +62 -19
- package/src/html/node.ts +798 -172
- package/src/html/paint.ts +1742 -241
- package/src/html/svg.ts +277 -19
- package/src/html/widgets.ts +821 -0
- package/src/html/woff2.ts +612 -0
- package/src/index.ts +1 -0
- package/src/maps/controller.ts +28 -7
- package/src/richtext/node.ts +13 -2
- package/src/richtext/runs.ts +313 -35
|
@@ -0,0 +1,897 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Multi-column layout (CSS Multi-column 1), over the layout the block pass
|
|
3
|
+
* makes of a container's content as one column: a strip a column wide and
|
|
4
|
+
* as tall as it all comes to. What is here decides where the strip breaks,
|
|
5
|
+
* and moves each piece of it to its column.
|
|
6
|
+
*
|
|
7
|
+
* Moved rather than laid out again, because a break changes nothing about a
|
|
8
|
+
* box but where it is: every column is the same width, so a paragraph's
|
|
9
|
+
* lines are the ones the strip gave it, and a box that a break misses is the
|
|
10
|
+
* same box in another place. A box a break falls inside keeps one rect,
|
|
11
|
+
* which takes in all its pieces as a browser's bounding rect of it does, and
|
|
12
|
+
* its pieces beside it (`COLUMN_PIECES`); the lines of a paragraph go each to
|
|
13
|
+
* its column (`COLUMN_LINES`).
|
|
14
|
+
*
|
|
15
|
+
* Where the breaks go is CSS Fragmentation 3's: between the lines of a block
|
|
16
|
+
* and between sibling boxes, never inside a line, a replaced element, a
|
|
17
|
+
* table, a flex box or a grid, a box that clips or scrolls, or one that
|
|
18
|
+
* says `break-inside: avoid` (4.1); `orphans` and `widows` lines of a block
|
|
19
|
+
* kept together at either side of one (4.3), and a margin next to one
|
|
20
|
+
* dropped where the break was not forced (5.2). A box with a height and
|
|
21
|
+
* nothing in it has no line or box for a break to fall inside, and is cut
|
|
22
|
+
* wherever a column ends; so is the room clearance leaves above a box or a
|
|
23
|
+
* line, which is no margin. How tall the columns are is left to the user
|
|
24
|
+
* agent where they balance (Multi-column 1, 7), and this does as Blink
|
|
25
|
+
* does: the content's height over the number of columns, no less than the
|
|
26
|
+
* tallest thing that cannot break, and then, while that many columns do not
|
|
27
|
+
* hold it, taller by the least amount that lets one more thing into a
|
|
28
|
+
* column — the smallest "space shortage" any break left
|
|
29
|
+
* (`NGColumnLayoutAlgorithm`, `StretchColumnBlockSize`).
|
|
30
|
+
*
|
|
31
|
+
* What this cannot do for having laid the content out once: a float that
|
|
32
|
+
* cannot be cut stays with the lines that were set beside it, where a
|
|
33
|
+
* browser takes it to the next column and sets those lines again, wider;
|
|
34
|
+
* and a table, a flex box and a grid go to a column whole, where a browser
|
|
35
|
+
* breaks them between their rows.
|
|
36
|
+
*/
|
|
37
|
+
import { CONTAIN_SIZE, scrolls } from '../css/style.js';
|
|
38
|
+
import { gapOf } from '../css/values.js';
|
|
39
|
+
import { contained, establishesBFC, moveTo, transformed } from './block.js';
|
|
40
|
+
import { CLEARED_FROM, COLUMN_LINES, COLUMN_PIECES, COLUMN_ROWS, columned, } from './boxes.js';
|
|
41
|
+
/** Slack for sums of fractions of a pixel that should have come out even. */
|
|
42
|
+
const EPS = 1 / 128;
|
|
43
|
+
/** How far past the rows of its column a text's layout is left to draw at
|
|
44
|
+
* an end no other column's rows are beyond: a glyph taller than its line. */
|
|
45
|
+
const REACH = 2048;
|
|
46
|
+
/**
|
|
47
|
+
* The columns a multicol container's content box of a width is set in (CSS
|
|
48
|
+
* Multi-column 1, 3.4): as many as `column-count` says, or as many
|
|
49
|
+
* `column-width` wide as fit, or the fewer of the two, and each as wide as
|
|
50
|
+
* leaves a gap between them. Null for a box that is none, and for one laid
|
|
51
|
+
* out at no width limit, whose content is one column as wide as it likes.
|
|
52
|
+
*/
|
|
53
|
+
export function columnsOf(box, available) {
|
|
54
|
+
const style = box.style;
|
|
55
|
+
if (!style.columns || box.kind !== 'block')
|
|
56
|
+
return null;
|
|
57
|
+
if (!Number.isFinite(available))
|
|
58
|
+
return null;
|
|
59
|
+
// `normal` is an em between columns (4.1)
|
|
60
|
+
const gap = style.columnGapNormal
|
|
61
|
+
? style.fontSize
|
|
62
|
+
: gapOf(style.columnGap, available);
|
|
63
|
+
let count;
|
|
64
|
+
if (style.columnWidth === null)
|
|
65
|
+
count = style.columnCount ?? 1;
|
|
66
|
+
else {
|
|
67
|
+
const fit = Math.max(1, Math.floor((available + gap) / (style.columnWidth + gap)));
|
|
68
|
+
count = style.columnCount === null ? fit : Math.min(style.columnCount, fit);
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
count,
|
|
72
|
+
gap,
|
|
73
|
+
width: Math.max(0, (available - (count - 1) * gap) / count),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const NO_GROUP = {
|
|
77
|
+
top: 0,
|
|
78
|
+
bottom: 0,
|
|
79
|
+
block: null,
|
|
80
|
+
sliced: false,
|
|
81
|
+
empty: true,
|
|
82
|
+
end: 0,
|
|
83
|
+
column: 0,
|
|
84
|
+
last: 0,
|
|
85
|
+
};
|
|
86
|
+
/** Whether no break may fall inside a box, whatever it holds: it is
|
|
87
|
+
* monolithic, or says so. It goes to a column with all that is in it. */
|
|
88
|
+
function whole(box) {
|
|
89
|
+
if (box.kind !== 'block')
|
|
90
|
+
return true;
|
|
91
|
+
if (box.style.breakInside === 'avoid')
|
|
92
|
+
return true;
|
|
93
|
+
return establishesBFC(box) || transformed(box.style);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Whether a box that holds nothing to break between may be cut anywhere: a
|
|
97
|
+
* block with a height and nothing in it, floated or not, which a browser
|
|
98
|
+
* carries on from one column to the next (CSS Fragmentation 3, 4.1 makes
|
|
99
|
+
* only what cannot be cut monolithic: replaced content, and a box that
|
|
100
|
+
* scrolls or clips).
|
|
101
|
+
*/
|
|
102
|
+
function sliceable(box) {
|
|
103
|
+
if (box.kind !== 'block' || box.marker)
|
|
104
|
+
return false;
|
|
105
|
+
const style = box.style;
|
|
106
|
+
if (style.breakInside === 'avoid' || scrolls(style))
|
|
107
|
+
return false;
|
|
108
|
+
if (transformed(style) || style.columns)
|
|
109
|
+
return false;
|
|
110
|
+
// size containment makes a box monolithic (CSS Containment 2, 3.1)
|
|
111
|
+
if (contained(box, CONTAIN_SIZE))
|
|
112
|
+
return false;
|
|
113
|
+
if (box.lines?.length)
|
|
114
|
+
return false;
|
|
115
|
+
for (const child of box.children) {
|
|
116
|
+
if (flows(child))
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
/** Whether a block's child is a box in its flow. */
|
|
122
|
+
function flows(child) {
|
|
123
|
+
switch (child.kind) {
|
|
124
|
+
case 'text':
|
|
125
|
+
case 'break':
|
|
126
|
+
case 'inline':
|
|
127
|
+
case 'marker':
|
|
128
|
+
return false;
|
|
129
|
+
default:
|
|
130
|
+
return !child.outOfFlow && !child.isFloat;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/** The floats and the out-of-flow boxes in a block's inline content, at
|
|
134
|
+
* any depth of inline box. */
|
|
135
|
+
function eachAside(box, visit) {
|
|
136
|
+
for (const child of box.children) {
|
|
137
|
+
if (child.isFloat || child.outOfFlow)
|
|
138
|
+
visit(child);
|
|
139
|
+
else if (child.kind === 'inline')
|
|
140
|
+
eachAside(child, visit);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/** How far down a box that goes whole reaches: its own bottom, and the
|
|
144
|
+
* floats that hang out of it where it does not hold them. */
|
|
145
|
+
function reachOf(box) {
|
|
146
|
+
let bottom = box.y + box.height;
|
|
147
|
+
if (box.kind !== 'block' || establishesBFC(box))
|
|
148
|
+
return bottom;
|
|
149
|
+
const walk = (node) => {
|
|
150
|
+
for (const child of node.children) {
|
|
151
|
+
if (child.outOfFlow)
|
|
152
|
+
continue;
|
|
153
|
+
if (child.isFloat) {
|
|
154
|
+
bottom = Math.max(bottom, child.y + child.height + Math.max(0, child.marginBottom));
|
|
155
|
+
}
|
|
156
|
+
else if (child.kind === 'inline' || child.kind === 'block')
|
|
157
|
+
walk(child);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
walk(box);
|
|
161
|
+
return bottom;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* A float that goes whole, as a unit with the lines and the boxes beside
|
|
165
|
+
* it, which it is set in a column with. One that can be cut anywhere is
|
|
166
|
+
* none: it stands beside the flow, which breaks as it would without it,
|
|
167
|
+
* and is cut where the columns end (`cutFloat`).
|
|
168
|
+
*/
|
|
169
|
+
function floatUnit(float, from, into) {
|
|
170
|
+
if (columned.any)
|
|
171
|
+
COLUMN_PIECES.delete(float);
|
|
172
|
+
into.loose.add(from);
|
|
173
|
+
if (sliceable(float))
|
|
174
|
+
return;
|
|
175
|
+
const unit = {
|
|
176
|
+
top: float.y - Math.max(0, float.marginTop),
|
|
177
|
+
bottom: float.y + float.height + Math.max(0, float.marginBottom),
|
|
178
|
+
block: null,
|
|
179
|
+
sliced: false,
|
|
180
|
+
size: 0,
|
|
181
|
+
end: 0,
|
|
182
|
+
group: NO_GROUP,
|
|
183
|
+
};
|
|
184
|
+
into.units.push(made(unit));
|
|
185
|
+
into.boxes.set(float, unit);
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Room in the flow that nothing stands in and no margin makes: what
|
|
189
|
+
* clearance moved a box or a line down by. A break drops a margin beside
|
|
190
|
+
* it (CSS Fragmentation 3, 5.2) and not this, which the columns take as
|
|
191
|
+
* they would a box with nothing in it, cut wherever one ends.
|
|
192
|
+
*/
|
|
193
|
+
function space(top, bottom,
|
|
194
|
+
/** The block whose lines it is between. */
|
|
195
|
+
block, into) {
|
|
196
|
+
const unit = {
|
|
197
|
+
top,
|
|
198
|
+
bottom,
|
|
199
|
+
block,
|
|
200
|
+
sliced: true,
|
|
201
|
+
size: 0,
|
|
202
|
+
end: 0,
|
|
203
|
+
group: NO_GROUP,
|
|
204
|
+
};
|
|
205
|
+
into.units.push(made(unit));
|
|
206
|
+
return unit;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* The units of a box's content, in the strip, and which box or line each
|
|
210
|
+
* is: a line for each line, a box for each box that goes whole, and the
|
|
211
|
+
* content of any other block in its turn — whose own top edge goes with its
|
|
212
|
+
* first and whose bottom with its last, since no break comes between a box's
|
|
213
|
+
* edge and what it holds. Answers the first and the last in the flow.
|
|
214
|
+
*/
|
|
215
|
+
function gather(box, into) {
|
|
216
|
+
let first = null;
|
|
217
|
+
let last = null;
|
|
218
|
+
const lines = box.lines;
|
|
219
|
+
if (lines?.length) {
|
|
220
|
+
let under = box.contentY;
|
|
221
|
+
for (const line of lines) {
|
|
222
|
+
// room a line was set below — a `<br clear>`, a line that did not
|
|
223
|
+
// fit beside a float — is no margin for a break to drop
|
|
224
|
+
if (line.y - under > EPS) {
|
|
225
|
+
const room = space(under, line.y, box, into);
|
|
226
|
+
first ??= room;
|
|
227
|
+
}
|
|
228
|
+
under = line.y + line.height;
|
|
229
|
+
const unit = {
|
|
230
|
+
top: line.y,
|
|
231
|
+
bottom: line.y + line.height,
|
|
232
|
+
block: box,
|
|
233
|
+
sliced: false,
|
|
234
|
+
size: 0,
|
|
235
|
+
end: 0,
|
|
236
|
+
group: NO_GROUP,
|
|
237
|
+
};
|
|
238
|
+
into.units.push(made(unit));
|
|
239
|
+
into.lines.set(line, unit);
|
|
240
|
+
first ??= unit;
|
|
241
|
+
last = unit;
|
|
242
|
+
}
|
|
243
|
+
eachAside(box, (aside) => {
|
|
244
|
+
if (aside.isFloat)
|
|
245
|
+
floatUnit(aside, box, into);
|
|
246
|
+
});
|
|
247
|
+
return first && last ? [first, last] : null;
|
|
248
|
+
}
|
|
249
|
+
for (const child of box.children) {
|
|
250
|
+
if (child.isFloat && !child.outOfFlow) {
|
|
251
|
+
floatUnit(child, box, into);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
if (!flows(child))
|
|
255
|
+
continue;
|
|
256
|
+
if (columned.any)
|
|
257
|
+
COLUMN_PIECES.delete(child);
|
|
258
|
+
// the room clearance set it down by, which is none of its margin
|
|
259
|
+
const clear = CLEARED_FROM.get(child);
|
|
260
|
+
if (clear !== undefined && child.y - clear > EPS) {
|
|
261
|
+
const room = space(clear, child.y, null, into);
|
|
262
|
+
first ??= room;
|
|
263
|
+
}
|
|
264
|
+
if (whole(child)) {
|
|
265
|
+
const unit = {
|
|
266
|
+
top: child.y,
|
|
267
|
+
bottom: reachOf(child),
|
|
268
|
+
block: null,
|
|
269
|
+
sliced: sliceable(child),
|
|
270
|
+
size: 0,
|
|
271
|
+
end: 0,
|
|
272
|
+
group: NO_GROUP,
|
|
273
|
+
};
|
|
274
|
+
into.units.push(made(unit));
|
|
275
|
+
into.boxes.set(child, unit);
|
|
276
|
+
first ??= unit;
|
|
277
|
+
last = unit;
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
const span = gather(child, into);
|
|
281
|
+
if (into.loose.has(child))
|
|
282
|
+
into.loose.add(box);
|
|
283
|
+
if (!span) {
|
|
284
|
+
// nothing in its flow to break between: it goes whole, or is cut
|
|
285
|
+
const unit = {
|
|
286
|
+
top: child.y,
|
|
287
|
+
bottom: child.y + child.height,
|
|
288
|
+
block: null,
|
|
289
|
+
sliced: sliceable(child),
|
|
290
|
+
size: 0,
|
|
291
|
+
end: 0,
|
|
292
|
+
group: NO_GROUP,
|
|
293
|
+
};
|
|
294
|
+
into.units.push(made(unit));
|
|
295
|
+
into.boxes.set(child, unit);
|
|
296
|
+
first ??= unit;
|
|
297
|
+
last = unit;
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
span[0].top = Math.min(span[0].top, child.y);
|
|
301
|
+
span[1].bottom = Math.max(span[1].bottom, child.y + child.height);
|
|
302
|
+
into.spans.set(child, span);
|
|
303
|
+
first ??= span[0];
|
|
304
|
+
last = span[1];
|
|
305
|
+
}
|
|
306
|
+
return first && last ? [first, last] : null;
|
|
307
|
+
}
|
|
308
|
+
/** The units in groups, down the strip: each group what a break may come
|
|
309
|
+
* before. Units that overlap are one group, which no break divides. */
|
|
310
|
+
/** Note a unit's own extent, as it is made. */
|
|
311
|
+
function made(unit) {
|
|
312
|
+
unit.size = unit.bottom - unit.top;
|
|
313
|
+
unit.end = unit.bottom;
|
|
314
|
+
return unit;
|
|
315
|
+
}
|
|
316
|
+
function groupsOf(units) {
|
|
317
|
+
// in the order of their tops, which is the document's but for a float
|
|
318
|
+
const sorted = units.slice().sort((a, b) => a.top - b.top);
|
|
319
|
+
const groups = [];
|
|
320
|
+
let group = null;
|
|
321
|
+
for (const unit of sorted) {
|
|
322
|
+
if (group && unit.top < group.bottom - EPS) {
|
|
323
|
+
group.bottom = Math.max(group.bottom, unit.bottom);
|
|
324
|
+
group.block = null;
|
|
325
|
+
group.sliced = false;
|
|
326
|
+
group.empty &&= unit.size <= EPS;
|
|
327
|
+
group.end = Math.max(group.end, unit.end);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
group = {
|
|
331
|
+
top: unit.top,
|
|
332
|
+
bottom: unit.bottom,
|
|
333
|
+
block: unit.block,
|
|
334
|
+
sliced: unit.sliced,
|
|
335
|
+
empty: unit.size <= EPS,
|
|
336
|
+
end: unit.end,
|
|
337
|
+
column: 0,
|
|
338
|
+
last: 0,
|
|
339
|
+
};
|
|
340
|
+
groups.push(group);
|
|
341
|
+
}
|
|
342
|
+
unit.group = group;
|
|
343
|
+
}
|
|
344
|
+
return groups;
|
|
345
|
+
}
|
|
346
|
+
/** The most columns content is set in: past it, the rest stays in the
|
|
347
|
+
* last. A column a fraction of a pixel tall would make one for every such
|
|
348
|
+
* fraction of a box that can be cut anywhere. */
|
|
349
|
+
const MOST_COLUMNS = 2000;
|
|
350
|
+
/**
|
|
351
|
+
* The groups set in columns of a height: each column filled until the next
|
|
352
|
+
* group does not fit, and broken there — or before there, where that would
|
|
353
|
+
* leave fewer lines of a block than `orphans` at the foot of the column, or
|
|
354
|
+
* fewer than `widows` at the head of the next: `orphans` first, and then as
|
|
355
|
+
* many of `widows` as that leaves, which is how Blink weighs the two (a
|
|
356
|
+
* block of four lines with `widows: 3` breaks two and two; CSS says only
|
|
357
|
+
* that both are to be honoured, 4.3). What stands
|
|
358
|
+
* first in a column stays there however tall, and the first `orphans` lines
|
|
359
|
+
* of a block with it. A box that can be cut anywhere is cut at the foot of
|
|
360
|
+
* the column, and goes on at the head of the next.
|
|
361
|
+
*/
|
|
362
|
+
function fill(groups, top, height,
|
|
363
|
+
/** Whether the columns can be no taller: the container's own height, or
|
|
364
|
+
* its `max-height`, is what they are. */
|
|
365
|
+
fixed) {
|
|
366
|
+
const starts = [0];
|
|
367
|
+
const tops = [top];
|
|
368
|
+
const cut = [false];
|
|
369
|
+
let shortage = Infinity;
|
|
370
|
+
let over = false;
|
|
371
|
+
let head = 0;
|
|
372
|
+
let columnTop = top;
|
|
373
|
+
/** Whether the group at the column's head is one the column before
|
|
374
|
+
* cut: it goes on from the column's top. */
|
|
375
|
+
let goesOn = false;
|
|
376
|
+
let i = 0;
|
|
377
|
+
const missed = (index) => {
|
|
378
|
+
const by = groups[index].bottom - columnTop - height;
|
|
379
|
+
if (by > EPS && by < shortage)
|
|
380
|
+
shortage = by;
|
|
381
|
+
};
|
|
382
|
+
/** Whether nothing with a height has been set in the column before a
|
|
383
|
+
* group: the top edges of the boxes it starts, at most — or, `blank`,
|
|
384
|
+
* not even those. */
|
|
385
|
+
const bare = (index, blank) => {
|
|
386
|
+
for (let k = head; k < index; k += 1) {
|
|
387
|
+
const before = groups[k];
|
|
388
|
+
if (blank ? before.bottom - before.top > EPS : !before.empty) {
|
|
389
|
+
return false;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
return !goesOn;
|
|
393
|
+
};
|
|
394
|
+
const next = (at, from, inside) => {
|
|
395
|
+
starts.push(at);
|
|
396
|
+
tops.push(from);
|
|
397
|
+
cut.push(inside);
|
|
398
|
+
head = at;
|
|
399
|
+
columnTop = from;
|
|
400
|
+
goesOn = inside;
|
|
401
|
+
i = at;
|
|
402
|
+
};
|
|
403
|
+
while (i < groups.length) {
|
|
404
|
+
const group = groups[i];
|
|
405
|
+
if (group.bottom - columnTop <= height + EPS ||
|
|
406
|
+
// nothing at all, which takes no room wherever it is
|
|
407
|
+
group.bottom - group.top <= EPS ||
|
|
408
|
+
starts.length >= MOST_COLUMNS) {
|
|
409
|
+
i += 1;
|
|
410
|
+
goesOn = false;
|
|
411
|
+
continue;
|
|
412
|
+
}
|
|
413
|
+
missed(i);
|
|
414
|
+
if (group.sliced && height >= 1) {
|
|
415
|
+
// what fits of it stays, and the rest goes on; none of it fits where
|
|
416
|
+
// it starts at the column's foot, and then all of it goes on
|
|
417
|
+
const from = goesOn ? columnTop : group.top;
|
|
418
|
+
const foot = columnTop + height;
|
|
419
|
+
if (foot - from > EPS)
|
|
420
|
+
next(i, foot, true);
|
|
421
|
+
else
|
|
422
|
+
next(i, group.top, false);
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
if (i === head ||
|
|
426
|
+
bare(i, true) ||
|
|
427
|
+
(group.end - columnTop <= height + EPS && bare(i, false))) {
|
|
428
|
+
// first in its column, or after nothing that takes any room, where
|
|
429
|
+
// it stays however tall; or all of it fits but the bottom edge of a
|
|
430
|
+
// box it ends, and nothing with a height has been set in the column
|
|
431
|
+
// to break before it: the edge hangs out (where Blink breaks before
|
|
432
|
+
// the edge, as a last resort)
|
|
433
|
+
over = true;
|
|
434
|
+
i += 1;
|
|
435
|
+
goesOn = false;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
let at = i;
|
|
439
|
+
const block = group.block;
|
|
440
|
+
if (block) {
|
|
441
|
+
// the lines of its block this one is among, and which of them it is
|
|
442
|
+
const run = linesAbout(groups, i);
|
|
443
|
+
const k = run.indexOf(i);
|
|
444
|
+
const count = run.length;
|
|
445
|
+
const orphans = Math.min(block.style.orphans, count);
|
|
446
|
+
const widows = Math.min(block.style.widows, count);
|
|
447
|
+
const a = run[0];
|
|
448
|
+
if (a >= head && k > 0 && k < orphans) {
|
|
449
|
+
if (a === head && !fixed) {
|
|
450
|
+
// at the head of the column already: they stay, and hang out,
|
|
451
|
+
// for a taller column to take them in
|
|
452
|
+
missed(run[orphans - 1]);
|
|
453
|
+
over = true;
|
|
454
|
+
i = run[orphans - 1] + 1;
|
|
455
|
+
goesOn = false;
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
// too few would stay: the block goes to the next column whole —
|
|
459
|
+
// or, at the head of a column that can be no taller, breaks where
|
|
460
|
+
// it must, with fewer
|
|
461
|
+
missed(run[orphans - 1]);
|
|
462
|
+
if (a !== head)
|
|
463
|
+
at = a;
|
|
464
|
+
}
|
|
465
|
+
else if (k > 0 && count - k < widows) {
|
|
466
|
+
// Too few would go: more go with them, as many as leave `orphans`
|
|
467
|
+
// behind, which may be fewer than `widows` asks for. Of the lines
|
|
468
|
+
// set from this column on, where the block goes on from a column
|
|
469
|
+
// before — and there, where not even all of them are enough, it
|
|
470
|
+
// breaks where it would (as Blink breaks each: seven lines, three
|
|
471
|
+
// to a column, under `widows: 5` are two, three and two).
|
|
472
|
+
const lo = a >= head ? 0 : run.findIndex((line) => line >= head);
|
|
473
|
+
const keep = count - lo - Math.min(block.style.widows, count - lo);
|
|
474
|
+
if (lo === 0 || keep > 0) {
|
|
475
|
+
const stay = Math.max(orphans, keep);
|
|
476
|
+
if (stay < k - lo)
|
|
477
|
+
at = run[lo + stay];
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
next(at, groups[at].top, false);
|
|
482
|
+
}
|
|
483
|
+
return { starts, tops, cut, shortage, over };
|
|
484
|
+
}
|
|
485
|
+
/** The lines of a block that a line is among, in order: the groups about
|
|
486
|
+
* it that are lines of its block, past the room between them. */
|
|
487
|
+
function linesAbout(groups, i) {
|
|
488
|
+
const block = groups[i].block;
|
|
489
|
+
const run = [i];
|
|
490
|
+
for (let k = i - 1; k >= 0 && groups[k].block === block; k -= 1) {
|
|
491
|
+
if (!groups[k].sliced)
|
|
492
|
+
run.unshift(k);
|
|
493
|
+
}
|
|
494
|
+
for (let k = i + 1; k < groups.length && groups[k].block === block; k += 1) {
|
|
495
|
+
if (!groups[k].sliced)
|
|
496
|
+
run.push(k);
|
|
497
|
+
}
|
|
498
|
+
return run;
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* The tallest thing in the strip no break may fall inside, which a column
|
|
502
|
+
* is no shorter than: a box that goes whole, a line, and the lines of a
|
|
503
|
+
* block that `orphans` keeps together at its start and `widows` at its end
|
|
504
|
+
* (as Blink's initial balancing pass takes them,
|
|
505
|
+
* `PropagateTallestUnbreakableBlockSize`).
|
|
506
|
+
*/
|
|
507
|
+
function tallestOf(groups) {
|
|
508
|
+
let tallest = 0;
|
|
509
|
+
for (let i = 0; i < groups.length; i += 1) {
|
|
510
|
+
const group = groups[i];
|
|
511
|
+
const block = group.block;
|
|
512
|
+
if (group.sliced)
|
|
513
|
+
continue;
|
|
514
|
+
if (!block) {
|
|
515
|
+
tallest = Math.max(tallest, group.bottom - group.top);
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
const run = linesAbout(groups, i);
|
|
519
|
+
const count = run.length;
|
|
520
|
+
const orphans = Math.min(block.style.orphans, count);
|
|
521
|
+
const widows = Math.min(block.style.widows, count);
|
|
522
|
+
tallest = Math.max(tallest, groups[run[orphans - 1]].bottom - group.top, groups[run[count - 1]].bottom - groups[run[count - widows]].top);
|
|
523
|
+
i = run[count - 1];
|
|
524
|
+
}
|
|
525
|
+
return tallest;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Set a multicol container's content, laid out as one strip at its content
|
|
529
|
+
* box's top, in its columns, and answer their height — the container's
|
|
530
|
+
* content height, where it has none of its own.
|
|
531
|
+
*
|
|
532
|
+
* `strip` is the strip's height, its first and last margins in it, and
|
|
533
|
+
* `limit` the tallest a column may be: the container's own height, or its
|
|
534
|
+
* `max-height`. Past it the content goes on in columns beyond the
|
|
535
|
+
* container's edge (8.2). Under `column-fill: auto` the columns are that
|
|
536
|
+
* tall, each filled before the next is started, and where nothing limits
|
|
537
|
+
* them the first holds everything (7.1).
|
|
538
|
+
*/
|
|
539
|
+
export function layoutColumns(box, columns, strip, limit,
|
|
540
|
+
/** Where the strip starts: the content box's top, or under a box that
|
|
541
|
+
* spans the columns (`spansColumns`). */
|
|
542
|
+
top = box.contentY) {
|
|
543
|
+
const gathered = {
|
|
544
|
+
units: [],
|
|
545
|
+
lines: new Map(),
|
|
546
|
+
boxes: new Map(),
|
|
547
|
+
spans: new Map(),
|
|
548
|
+
loose: new Set(),
|
|
549
|
+
};
|
|
550
|
+
gather(box, gathered);
|
|
551
|
+
const groups = groupsOf(gathered.units);
|
|
552
|
+
// as far down as the content goes, past a box with a height it overflows:
|
|
553
|
+
// what stands out of a box takes room in a column as what is in it does
|
|
554
|
+
for (const group of groups)
|
|
555
|
+
strip = Math.max(strip, group.bottom - top);
|
|
556
|
+
let height;
|
|
557
|
+
let filled;
|
|
558
|
+
if (box.style.columnFill === 'auto') {
|
|
559
|
+
height = Number.isFinite(limit) ? limit : strip;
|
|
560
|
+
filled = fill(groups, top, height, height >= limit);
|
|
561
|
+
// a container with a `max-height` and less than a column of content
|
|
562
|
+
// is as tall as its content
|
|
563
|
+
if (filled.starts.length === 1)
|
|
564
|
+
height = Math.min(height, strip);
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
// balanced: the strip shared out, and no shorter than the tallest
|
|
568
|
+
// thing that cannot break; then as much taller as lets the next thing
|
|
569
|
+
// in, until the columns there are hold it all
|
|
570
|
+
height = Math.min(limit, Math.max(strip / columns.count, tallestOf(groups)));
|
|
571
|
+
filled = fill(groups, top, height, height >= limit);
|
|
572
|
+
for (let pass = 0; pass < 4096; pass += 1) {
|
|
573
|
+
if (filled.starts.length <= columns.count && !filled.over)
|
|
574
|
+
break;
|
|
575
|
+
if (height >= limit || !Number.isFinite(filled.shortage))
|
|
576
|
+
break;
|
|
577
|
+
height = Math.min(limit, height + filled.shortage);
|
|
578
|
+
filled = fill(groups, top, height, height >= limit);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// all in one column where it started, and nothing to move — but a float
|
|
582
|
+
// that is cut where the column ends, which may reach past it
|
|
583
|
+
if (!gathered.loose.size && (!groups.length || filled.starts.length === 1)) {
|
|
584
|
+
return height;
|
|
585
|
+
}
|
|
586
|
+
// the column each group starts in, and the one it ends in
|
|
587
|
+
const { starts, tops, cut } = filled;
|
|
588
|
+
let c = 0;
|
|
589
|
+
for (let i = 0; i < groups.length; i += 1) {
|
|
590
|
+
while (c + 1 < starts.length &&
|
|
591
|
+
starts[c + 1] <= i &&
|
|
592
|
+
!(starts[c + 1] === i && cut[c + 1])) {
|
|
593
|
+
c += 1;
|
|
594
|
+
}
|
|
595
|
+
groups[i].column = c;
|
|
596
|
+
while (c + 1 < starts.length && starts[c + 1] === i && cut[c + 1])
|
|
597
|
+
c += 1;
|
|
598
|
+
groups[i].last = c;
|
|
599
|
+
}
|
|
600
|
+
const step = columns.width + columns.gap;
|
|
601
|
+
// the first column at the start of the line: the right, right to left
|
|
602
|
+
const rtl = box.style.direction === 'rtl';
|
|
603
|
+
const across = (c) => (rtl ? -c * step : c * step);
|
|
604
|
+
const origin = rtl ? box.contentWidth - columns.width : 0;
|
|
605
|
+
place(box, null, {
|
|
606
|
+
gathered,
|
|
607
|
+
tops,
|
|
608
|
+
height,
|
|
609
|
+
dx: (c) => origin + across(c),
|
|
610
|
+
dy: (c) => top - tops[c],
|
|
611
|
+
});
|
|
612
|
+
return height;
|
|
613
|
+
}
|
|
614
|
+
function shift(box, column, to) {
|
|
615
|
+
moveTo(box, box.x + to.dx(column), box.y + to.dy(column));
|
|
616
|
+
}
|
|
617
|
+
function shiftLine(line, dx, dy) {
|
|
618
|
+
line.x += dx;
|
|
619
|
+
line.y += dy;
|
|
620
|
+
for (const text of line.texts) {
|
|
621
|
+
text.drawX += dx;
|
|
622
|
+
text.drawY += dy;
|
|
623
|
+
}
|
|
624
|
+
for (const placed of line.atomics) {
|
|
625
|
+
placed.x += dx;
|
|
626
|
+
placed.y += dy;
|
|
627
|
+
moveTo(placed.box, placed.box.x + dx, placed.box.y + dy);
|
|
628
|
+
}
|
|
629
|
+
for (const edge of line.edges ?? [])
|
|
630
|
+
edge.x += dx;
|
|
631
|
+
}
|
|
632
|
+
/** The column a point down the strip is in, between boxes: at a break it
|
|
633
|
+
* is in the column the break ends, which is where Blink leaves what is out
|
|
634
|
+
* of the flow and stood before the box the next column starts with. */
|
|
635
|
+
function columnAt(y, tops) {
|
|
636
|
+
let c = 0;
|
|
637
|
+
while (c + 1 < tops.length && tops[c + 1] < y - EPS)
|
|
638
|
+
c += 1;
|
|
639
|
+
return c;
|
|
640
|
+
}
|
|
641
|
+
/**
|
|
642
|
+
* Move what a box holds to its columns: the container's content, with no
|
|
643
|
+
* `span`, or that of a box a break falls inside, which is then its pieces.
|
|
644
|
+
*/
|
|
645
|
+
function place(box, span, to) {
|
|
646
|
+
const { gathered } = to;
|
|
647
|
+
const first = span ? span[0].group.column : 0;
|
|
648
|
+
const last = span ? span[1].group.last : to.tops.length - 1;
|
|
649
|
+
if (span && first === last && !gathered.loose.has(box)) {
|
|
650
|
+
shift(box, first, to);
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
const was = { x: box.x, y: box.y, width: box.width, height: box.height };
|
|
654
|
+
const lines = box.lines;
|
|
655
|
+
// what is out of the flow goes from where it would have stood, which is
|
|
656
|
+
// kept from this box's corner: in the column of the line it would have
|
|
657
|
+
// been on, or of the boxes it would have been between
|
|
658
|
+
const asides = [];
|
|
659
|
+
const aside = (child) => {
|
|
660
|
+
const at = child.staticPosition;
|
|
661
|
+
if (!child.outOfFlow || !at || at.from !== box)
|
|
662
|
+
return;
|
|
663
|
+
const y = was.y + at.y;
|
|
664
|
+
let c = columnAt(y, to.tops);
|
|
665
|
+
if (lines?.length) {
|
|
666
|
+
let on = lines[0];
|
|
667
|
+
for (const line of lines) {
|
|
668
|
+
if (line.y > y + EPS)
|
|
669
|
+
break;
|
|
670
|
+
on = line;
|
|
671
|
+
}
|
|
672
|
+
c = gathered.lines.get(on).group.column;
|
|
673
|
+
}
|
|
674
|
+
asides.push([child, Math.min(last, Math.max(first, c))]);
|
|
675
|
+
};
|
|
676
|
+
if (lines?.length)
|
|
677
|
+
eachAside(box, aside);
|
|
678
|
+
else
|
|
679
|
+
for (const child of box.children)
|
|
680
|
+
aside(child);
|
|
681
|
+
if (lines?.length) {
|
|
682
|
+
for (const line of lines) {
|
|
683
|
+
const c = gathered.lines.get(line).group.column;
|
|
684
|
+
shiftLine(line, to.dx(c), to.dy(c));
|
|
685
|
+
}
|
|
686
|
+
if (first !== last) {
|
|
687
|
+
COLUMN_LINES.add(lines);
|
|
688
|
+
rowsOf(lines, gathered);
|
|
689
|
+
columned.any = true;
|
|
690
|
+
}
|
|
691
|
+
eachAside(box, (aside) => {
|
|
692
|
+
const unit = gathered.boxes.get(aside);
|
|
693
|
+
if (unit)
|
|
694
|
+
place(aside, [unit, unit], to);
|
|
695
|
+
else if (aside.isFloat && !aside.outOfFlow)
|
|
696
|
+
cutFloat(aside, to);
|
|
697
|
+
});
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
for (const child of box.children) {
|
|
701
|
+
// a box that goes whole, or is cut where it holds nothing
|
|
702
|
+
const unit = gathered.boxes.get(child);
|
|
703
|
+
const inner = unit ? [unit, unit] : undefined;
|
|
704
|
+
const range = inner ?? gathered.spans.get(child);
|
|
705
|
+
if (range)
|
|
706
|
+
place(child, range, to);
|
|
707
|
+
else if (child.isFloat && !child.outOfFlow)
|
|
708
|
+
cutFloat(child, to);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
if (span && first === last) {
|
|
712
|
+
// in one column, with a float in it that went its own way: the box
|
|
713
|
+
// itself, which what it holds was moved without
|
|
714
|
+
box.x += to.dx(first);
|
|
715
|
+
box.y += to.dy(first);
|
|
716
|
+
if (box.marker) {
|
|
717
|
+
box.marker.x += to.dx(first);
|
|
718
|
+
box.marker.y += to.dy(first);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
else if (span) {
|
|
722
|
+
const pieces = [];
|
|
723
|
+
let x1 = Infinity;
|
|
724
|
+
let y1 = Infinity;
|
|
725
|
+
let x2 = -Infinity;
|
|
726
|
+
let y2 = -Infinity;
|
|
727
|
+
for (let c = first; c <= last; c += 1) {
|
|
728
|
+
const from = c === first ? was.y : to.tops[c];
|
|
729
|
+
const until = c === last ? was.y + was.height : to.tops[c] + to.height;
|
|
730
|
+
const piece = {
|
|
731
|
+
x: was.x + to.dx(c),
|
|
732
|
+
y: from + to.dy(c),
|
|
733
|
+
width: was.width,
|
|
734
|
+
height: Math.max(0, until - from),
|
|
735
|
+
wholeY: was.y + to.dy(c),
|
|
736
|
+
wholeHeight: was.height,
|
|
737
|
+
};
|
|
738
|
+
pieces.push(piece);
|
|
739
|
+
x1 = Math.min(x1, piece.x);
|
|
740
|
+
y1 = Math.min(y1, piece.y);
|
|
741
|
+
x2 = Math.max(x2, piece.x + piece.width);
|
|
742
|
+
y2 = Math.max(y2, piece.y + piece.height);
|
|
743
|
+
}
|
|
744
|
+
COLUMN_PIECES.set(box, pieces);
|
|
745
|
+
columned.any = true;
|
|
746
|
+
if (box.marker) {
|
|
747
|
+
box.marker.x += to.dx(first);
|
|
748
|
+
box.marker.y += to.dy(first);
|
|
749
|
+
}
|
|
750
|
+
box.x = x1;
|
|
751
|
+
box.y = y1;
|
|
752
|
+
box.width = x2 - x1;
|
|
753
|
+
box.height = y2 - y1;
|
|
754
|
+
}
|
|
755
|
+
for (const [child, c] of asides) {
|
|
756
|
+
const at = child.staticPosition;
|
|
757
|
+
const width = at.right - at.x;
|
|
758
|
+
at.x = was.x + at.x + to.dx(c) - box.x;
|
|
759
|
+
at.right = at.x + width;
|
|
760
|
+
at.y = was.y + at.y + to.dy(c) - box.y;
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* A float that can be cut anywhere, set in the columns it reaches: from
|
|
765
|
+
* where it starts to the foot of that column, and then down each column
|
|
766
|
+
* after it from the top, as much of it as is left. By how much of it each
|
|
767
|
+
* column has taken, and not by where the strip has it: the flow beside it
|
|
768
|
+
* drops a margin at a break, and the float has none there to drop.
|
|
769
|
+
*/
|
|
770
|
+
function cutFloat(float, to) {
|
|
771
|
+
const { tops, height } = to;
|
|
772
|
+
let c = 0;
|
|
773
|
+
while (c + 1 < tops.length && tops[c + 1] <= float.y + EPS)
|
|
774
|
+
c += 1;
|
|
775
|
+
// past the foot of its column, in the margin a break dropped: at the
|
|
776
|
+
// head of the next
|
|
777
|
+
let at = float.y - tops[c];
|
|
778
|
+
if (at >= height - EPS && c + 1 < tops.length) {
|
|
779
|
+
c += 1;
|
|
780
|
+
at = 0;
|
|
781
|
+
}
|
|
782
|
+
const was = {
|
|
783
|
+
x: float.x,
|
|
784
|
+
y: float.y,
|
|
785
|
+
width: float.width,
|
|
786
|
+
height: float.height,
|
|
787
|
+
};
|
|
788
|
+
if (at + was.height <= height + EPS || !(height >= 1)) {
|
|
789
|
+
moveTo(float, was.x + to.dx(c), tops[0] + at);
|
|
790
|
+
return;
|
|
791
|
+
}
|
|
792
|
+
const pieces = [];
|
|
793
|
+
let taken = 0;
|
|
794
|
+
let x1 = Infinity;
|
|
795
|
+
let x2 = -Infinity;
|
|
796
|
+
let y2 = -Infinity;
|
|
797
|
+
const head = tops[0];
|
|
798
|
+
for (let k = c; taken < was.height - EPS; k += 1) {
|
|
799
|
+
const from = k === c ? at : 0;
|
|
800
|
+
const part = Math.min(was.height - taken, height - from);
|
|
801
|
+
const x = was.x + to.dx(k);
|
|
802
|
+
pieces.push({
|
|
803
|
+
x,
|
|
804
|
+
y: head + from,
|
|
805
|
+
width: was.width,
|
|
806
|
+
height: part,
|
|
807
|
+
wholeY: head + from - taken,
|
|
808
|
+
wholeHeight: was.height,
|
|
809
|
+
});
|
|
810
|
+
x1 = Math.min(x1, x);
|
|
811
|
+
x2 = Math.max(x2, x + was.width);
|
|
812
|
+
y2 = Math.max(y2, head + from + part);
|
|
813
|
+
taken += part;
|
|
814
|
+
if (pieces.length >= MOST_COLUMNS)
|
|
815
|
+
break;
|
|
816
|
+
}
|
|
817
|
+
COLUMN_PIECES.set(float, pieces);
|
|
818
|
+
columned.any = true;
|
|
819
|
+
float.x = x1;
|
|
820
|
+
float.y = Math.min(head + at, head);
|
|
821
|
+
float.width = x2 - x1;
|
|
822
|
+
float.height = y2 - float.y;
|
|
823
|
+
}
|
|
824
|
+
/**
|
|
825
|
+
* The rows each text's layout has in each column, for the layouts whose
|
|
826
|
+
* lines columns took apart (`COLUMN_ROWS`).
|
|
827
|
+
*/
|
|
828
|
+
function rowsOf(lines, gathered) {
|
|
829
|
+
const byLayout = new Map();
|
|
830
|
+
for (const line of lines) {
|
|
831
|
+
const c = gathered.lines.get(line).group.column;
|
|
832
|
+
for (const text of line.texts) {
|
|
833
|
+
let columns = byLayout.get(text.layout);
|
|
834
|
+
if (!columns)
|
|
835
|
+
byLayout.set(text.layout, (columns = new Map()));
|
|
836
|
+
const texts = columns.get(c);
|
|
837
|
+
if (texts)
|
|
838
|
+
texts.push(text);
|
|
839
|
+
else
|
|
840
|
+
columns.set(c, [text]);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
for (const [layout, columns] of byLayout) {
|
|
844
|
+
if (columns.size < 2)
|
|
845
|
+
continue;
|
|
846
|
+
const end = layout.lines.length - 1;
|
|
847
|
+
for (const texts of columns.values()) {
|
|
848
|
+
let top = Infinity;
|
|
849
|
+
let bottom = -Infinity;
|
|
850
|
+
let open = 0;
|
|
851
|
+
for (const text of texts) {
|
|
852
|
+
const natural = layout.lines[text.layoutLine];
|
|
853
|
+
if (!natural)
|
|
854
|
+
continue;
|
|
855
|
+
top = Math.min(top, natural.y);
|
|
856
|
+
bottom = Math.max(bottom, natural.y + natural.height);
|
|
857
|
+
if (text.layoutLine === 0)
|
|
858
|
+
open |= 1;
|
|
859
|
+
if (text.layoutLine === end)
|
|
860
|
+
open |= 2;
|
|
861
|
+
}
|
|
862
|
+
if (top === Infinity)
|
|
863
|
+
continue;
|
|
864
|
+
const rows = {
|
|
865
|
+
top: open & 1 ? top - REACH : top,
|
|
866
|
+
bottom: open & 2 ? bottom + REACH : bottom,
|
|
867
|
+
};
|
|
868
|
+
for (const text of texts)
|
|
869
|
+
COLUMN_ROWS.set(text, rows);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Whether a box is set across all the columns of the multicol container it
|
|
875
|
+
* is a child of (`column-span: all`, CSS Multi-column 1, 6): a block-level
|
|
876
|
+
* box in its flow. One further in, in a box of the container's, is set in its column
|
|
877
|
+
* as it would be without: the box around it is not broken about it here.
|
|
878
|
+
*/
|
|
879
|
+
export function spansColumns(box) {
|
|
880
|
+
return (box.style.columnSpan &&
|
|
881
|
+
flows(box) &&
|
|
882
|
+
// block-level: on an inline-block it does nothing
|
|
883
|
+
!box.style.display.startsWith('inline'));
|
|
884
|
+
}
|
|
885
|
+
/** Forget the pieces an earlier layout broke a box's content in, where
|
|
886
|
+
* this one sets it in one column. */
|
|
887
|
+
export function forgetColumns(box) {
|
|
888
|
+
if (!columned.any)
|
|
889
|
+
return;
|
|
890
|
+
for (const child of box.children) {
|
|
891
|
+
if (!flows(child) || child.kind !== 'block')
|
|
892
|
+
continue;
|
|
893
|
+
if (COLUMN_PIECES.delete(child) || !whole(child))
|
|
894
|
+
forgetColumns(child);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
//# sourceMappingURL=multicol.js.map
|