@react-x11/components 0.14.0 → 0.16.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/code-editor/node.d.ts +13 -0
- package/dist/code-editor/node.d.ts.map +1 -1
- package/dist/code-editor/node.js +23 -5
- package/dist/code-editor/node.js.map +1 -1
- package/dist/html/css/cascade.d.ts +209 -23
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +873 -175
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +25 -0
- package/dist/html/css/color.d.ts.map +1 -1
- package/dist/html/css/color.js +307 -19
- package/dist/html/css/color.js.map +1 -1
- package/dist/html/css/content.d.ts +11 -6
- package/dist/html/css/content.d.ts.map +1 -1
- package/dist/html/css/content.js +171 -12
- package/dist/html/css/content.js.map +1 -1
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +62 -9
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/shapes.d.ts +62 -0
- package/dist/html/css/shapes.d.ts.map +1 -0
- package/dist/html/css/shapes.js +214 -0
- package/dist/html/css/shapes.js.map +1 -0
- package/dist/html/css/style.d.ts +42 -5
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +75 -27
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.js +15 -0
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +5 -0
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +18 -9
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +7 -0
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/block.d.ts +38 -7
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +160 -50
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +29 -2
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +159 -20
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/css-grid.d.ts.map +1 -1
- package/dist/html/layout/css-grid.js +69 -19
- 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 +1648 -225
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/inline.d.ts +15 -3
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +23 -8
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/layout/table.d.ts +27 -1
- package/dist/html/layout/table.d.ts.map +1 -1
- package/dist/html/layout/table.js +907 -323
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +36 -4
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +174 -37
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +4 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +769 -204
- package/dist/html/paint.js.map +1 -1
- package/dist/html/resources.js +14 -1
- package/dist/html/resources.js.map +1 -1
- package/dist/html/svg.d.ts +50 -17
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +501 -83
- package/dist/html/svg.js.map +1 -1
- package/dist/html/widgets.d.ts.map +1 -1
- package/dist/html/widgets.js +57 -4
- package/dist/html/widgets.js.map +1 -1
- package/package.json +3 -3
- package/src/code-editor/node.ts +24 -5
- package/src/html/css/cascade.ts +987 -176
- package/src/html/css/color.ts +345 -18
- package/src/html/css/content.ts +173 -15
- package/src/html/css/parse.ts +60 -8
- package/src/html/css/shapes.ts +273 -0
- package/src/html/css/style.ts +123 -30
- package/src/html/css/ua.ts +15 -0
- package/src/html/css/values.ts +23 -9
- package/src/html/index.ts +7 -0
- package/src/html/layout/block.ts +169 -56
- package/src/html/layout/boxes.ts +198 -24
- package/src/html/layout/css-grid.ts +89 -22
- package/src/html/layout/flex.ts +1863 -219
- package/src/html/layout/inline.ts +27 -7
- package/src/html/layout/table.ts +989 -333
- package/src/html/node.ts +204 -37
- package/src/html/paint.ts +999 -252
- package/src/html/resources.ts +13 -1
- package/src/html/svg.ts +600 -78
- package/src/html/widgets.ts +61 -4
|
@@ -13,44 +13,40 @@
|
|
|
13
13
|
// algorithm is too slow — which, on a table with a thousand rows, it is.
|
|
14
14
|
import { AUTO, isPct, resolveOrNull } from '../css/values.js';
|
|
15
15
|
import { Box, CLIPPED_CELLS, COLLAPSED_CELLS, isBlank } from './boxes.js';
|
|
16
|
-
import { CELL_CONTENT, MIN_CONTENT_PROBE, clampHeight, cyclicWidth, exactMinContent, layoutBlockIn, measureIntrinsicWidth, moveContent, moveTo, resolveEdges, } from './block.js';
|
|
16
|
+
import { CELL_CONTENT, FLEXED_HEIGHT, MIN_CONTENT_PROBE, STRETCHED_ACROSS, USED_HEIGHT, clampHeight, cyclicWidth, exactMinContent, layoutBlockIn, layoutSubtree, measureIntrinsicWidth, moveContent, moveTo, resolveEdges, } from './block.js';
|
|
17
17
|
import { firstBaselineIn } from './inline.js';
|
|
18
18
|
import { tableGrid } from './grid.js';
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Lay out a table's insides. Returns the content height.
|
|
21
|
+
*
|
|
22
|
+
* A table in a cell is measured and laid out from inside the layout of the
|
|
23
|
+
* table around it, so nested tables hold every level's frames on the stack
|
|
24
|
+
* at once. So this is the phases and nothing else, and what waits on the
|
|
25
|
+
* stack while a cell's content is measured (`measureCell`) and laid out
|
|
26
|
+
* (`layoutCells`) is the few values passed between them; the columns'
|
|
27
|
+
* arithmetic (`sizeColumns`, `autoColumns`) and the rows' (`placeRows`)
|
|
28
|
+
* are done before or after, in frames of their own. As one function, every
|
|
29
|
+
* local of every phase in the one frame the interpreter keeps whole, a
|
|
30
|
+
* level of nested tables took 4 KB of the stack where it takes 2.8, and
|
|
31
|
+
* `display: table-cell` boxes 254 deep, as deep as the parser lets a
|
|
32
|
+
* document go, ran out of the 984 KB V8 gives the main thread: the
|
|
33
|
+
* document came out blank. (The box builder stops them at 128 now, for
|
|
34
|
+
* paint's sake, `MAX_DEPTH`.)
|
|
35
|
+
*/
|
|
20
36
|
export function layoutTable(table, ctx, contentWidth) {
|
|
21
|
-
const
|
|
37
|
+
const grid = tableGrid(table);
|
|
22
38
|
table.captionTop = 0;
|
|
23
39
|
table.captionBottom = 0;
|
|
24
40
|
// a percentage of a width that is not known is `auto`: the table is as
|
|
25
41
|
// wide as its columns where its content is measured (`cyclicWidth`)
|
|
26
|
-
const
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
if (auto) {
|
|
33
|
-
let inner = captions.length
|
|
34
|
-
? captionMinimum(captions, ctx) - table.horizontalExtra
|
|
35
|
-
: 0;
|
|
36
|
-
const min = resolveOrNull(table.style.minWidth, contentWidth);
|
|
37
|
-
if (min !== null) {
|
|
38
|
-
inner = Math.max(inner, table.style.boxSizing === 'border-box'
|
|
39
|
-
? min - table.horizontalExtra
|
|
40
|
-
: min);
|
|
41
|
-
}
|
|
42
|
-
table.width = Math.max(0, inner) + table.horizontalExtra;
|
|
43
|
-
}
|
|
44
|
-
layoutCaptions(captions, ctx, table);
|
|
42
|
+
const own = table.style.width === AUTO || cyclicWidth(table.style, contentWidth);
|
|
43
|
+
// — and one a grid stretches across its area is as wide as that, as a
|
|
44
|
+
// table with a width of its own is (`STRETCHED_ACROSS`)
|
|
45
|
+
const auto = own && !STRETCHED_ACROSS.has(table);
|
|
46
|
+
if (!grid.columnCount) {
|
|
47
|
+
layoutColumnless(table, grid.captions, ctx, contentWidth, auto);
|
|
45
48
|
return 0;
|
|
46
49
|
}
|
|
47
|
-
const style = table.style;
|
|
48
|
-
// between the columns, and between the rows (CSS 2.1 17.6.1)
|
|
49
|
-
const apart = style.borderCollapse !== 'collapse';
|
|
50
|
-
const spacing = apart ? style.borderSpacing : 0;
|
|
51
|
-
const rowSpacing = apart ? style.borderSpacingY : 0;
|
|
52
|
-
const gaps = spacing * (columnCount + 1);
|
|
53
|
-
const available = Math.max(0, contentWidth - gaps);
|
|
54
50
|
// While the columns are sized, a percentage in a cell's padding is of
|
|
55
51
|
// nothing: the width it is of is the one the columns are being sized to
|
|
56
52
|
// come to (CSS Sizing 3, 5.2.1), and a cell asks of its column what its
|
|
@@ -58,21 +54,56 @@ export function layoutTable(table, ctx, contentWidth) {
|
|
|
58
54
|
// widths, below. Taken of the table's width here, and of the cell's own
|
|
59
55
|
// where the cell was laid out, a padded cell's column was wider than
|
|
60
56
|
// its share and its padding narrower.
|
|
61
|
-
for (const cell of cells)
|
|
57
|
+
for (const cell of grid.cells)
|
|
62
58
|
resolveEdges(cell.box, 0);
|
|
63
59
|
// a fixed layout needs a width to be fixed to; with `auto` a table is laid
|
|
64
|
-
// out by its contents after all (CSS 2.1 17.5.2.1)
|
|
65
|
-
const fixed = style.tableLayout === 'fixed' && !
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
60
|
+
// out by its contents after all (CSS 2.1 17.5.2.1), stretched or not
|
|
61
|
+
const fixed = table.style.tableLayout === 'fixed' && !own;
|
|
62
|
+
if (!fixed)
|
|
63
|
+
for (const cell of grid.cells)
|
|
64
|
+
measureCell(cell.box, ctx);
|
|
65
|
+
const columns = sizeColumns(table, grid, contentWidth, fixed, auto, ctx);
|
|
66
|
+
layoutCaptions(grid.captions, ctx, table);
|
|
67
|
+
const natural = layoutCells(grid.cells, columns, ctx);
|
|
68
|
+
return placeRows(table, grid, columns, natural);
|
|
69
|
+
}
|
|
70
|
+
/** A table with no columns, and its captions. */
|
|
71
|
+
function layoutColumnless(table, captions, ctx, contentWidth, auto) {
|
|
72
|
+
// With no columns, an auto table is as wide as its widest caption can
|
|
73
|
+
// be and its `min-width` asks, as one with columns is at the least
|
|
74
|
+
// (17.5.2), rather than as the room on offer: a caption over no cells
|
|
75
|
+
// was centred in the width of the page.
|
|
76
|
+
if (auto) {
|
|
77
|
+
let inner = captions.length
|
|
78
|
+
? captionMinimum(captions, ctx) - table.horizontalExtra
|
|
79
|
+
: 0;
|
|
80
|
+
const min = resolveOrNull(table.style.minWidth, contentWidth);
|
|
81
|
+
if (min !== null) {
|
|
82
|
+
inner = Math.max(inner, table.style.boxSizing === 'border-box'
|
|
83
|
+
? min - table.horizontalExtra
|
|
84
|
+
: min);
|
|
85
|
+
}
|
|
86
|
+
table.width = Math.max(0, inner) + table.horizontalExtra;
|
|
87
|
+
}
|
|
88
|
+
layoutCaptions(captions, ctx, table);
|
|
89
|
+
TABLE_CONTENT.set(table, table.captionTop + table.captionBottom + table.verticalExtra);
|
|
90
|
+
}
|
|
91
|
+
/** A table's columns sized and placed, and the table as wide as they
|
|
92
|
+
* make it. Its cells are measured already (`measureCell`). */
|
|
93
|
+
function sizeColumns(table, grid, contentWidth, fixed, auto, ctx) {
|
|
94
|
+
const { cells, captions, columnCount, columnBoxes, columnGroups } = grid;
|
|
95
|
+
const style = table.style;
|
|
96
|
+
// between the columns, and between the rows (CSS 2.1 17.6.1)
|
|
97
|
+
const apart = style.borderCollapse !== 'collapse';
|
|
98
|
+
const spacing = apart ? style.borderSpacing : 0;
|
|
99
|
+
const rowSpacing = apart ? style.borderSpacingY : 0;
|
|
100
|
+
const gaps = spacing * (columnCount + 1);
|
|
101
|
+
const available = Math.max(0, contentWidth - gaps);
|
|
72
102
|
// An auto table is at least as wide as its widest caption can be (CSS 2.1
|
|
73
|
-
// 17.5.2), and as its `min-width` asks; the columns share what that adds
|
|
103
|
+
// 17.5.2), and as its `min-width` asks; the columns share what that adds
|
|
104
|
+
// as they share a width of the table's own (`autoColumns`).
|
|
105
|
+
let room = 0;
|
|
74
106
|
if (auto) {
|
|
75
|
-
let room = 0;
|
|
76
107
|
if (captions.length) {
|
|
77
108
|
room = captionMinimum(captions, ctx) - table.horizontalExtra - gaps;
|
|
78
109
|
}
|
|
@@ -81,16 +112,17 @@ export function layoutTable(table, ctx, contentWidth) {
|
|
|
81
112
|
const inner = style.boxSizing === 'border-box' ? min - table.horizontalExtra : min;
|
|
82
113
|
room = Math.max(room, inner - gaps);
|
|
83
114
|
}
|
|
84
|
-
const sum = widths.reduce((a, b) => a + b, 0);
|
|
85
|
-
if (room > sum) {
|
|
86
|
-
for (let c = 0; c < columnCount; c += 1) {
|
|
87
|
-
widths[c] +=
|
|
88
|
-
sum > 0
|
|
89
|
-
? (widths[c] / sum) * (room - sum)
|
|
90
|
-
: (room - sum) / columnCount;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
115
|
}
|
|
116
|
+
// A percentage, a column's or a cell's, is of the table's width less
|
|
117
|
+
// its spacing: CSS Tables 3's assignable table width, which Blink takes
|
|
118
|
+
// one of in either layout, so a 50% cell in a table of 300 spaced 2 is
|
|
119
|
+
// 147. In an auto table it is of the width the columns come to, and
|
|
120
|
+
// helps decide it (`autoColumns`).
|
|
121
|
+
const set = columnWidths(columnBoxes, columnGroups, available, fixed);
|
|
122
|
+
const widths = fixed
|
|
123
|
+
? fixedColumns(cells, set, columnPercents(columnBoxes, columnGroups), columnLeast(columnBoxes, columnGroups), columnCount, available, spacing)
|
|
124
|
+
: autoColumns(cells, set, columnLeast(columnBoxes, columnGroups), columnPercents(columnBoxes, columnGroups), columnCount, available, !auto, spacing, room, percentagesWiden(table, contentWidth));
|
|
125
|
+
// --- place ---------------------------------------------------------------
|
|
94
126
|
// A column that `visibility: collapse` takes out of the table has been
|
|
95
127
|
// sized with the rest, and is no room at all where they are placed (CSS
|
|
96
128
|
// 2.1 17.5.5): no width, and no spacing after it. Its cells are still
|
|
@@ -129,55 +161,98 @@ export function layoutTable(table, ctx, contentWidth) {
|
|
|
129
161
|
tableContentWidth = used;
|
|
130
162
|
table.width = used + table.horizontalExtra;
|
|
131
163
|
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
164
|
+
return { widths, columnX, gone, anyGone, end: x, spacing, rowSpacing };
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Every cell laid out at its columns' width, all of them before a row is
|
|
168
|
+
* sized, so a row's height is the tallest cell in it rather than the first
|
|
169
|
+
* one that was measured. What its content came to is kept apart from a
|
|
170
|
+
* height the cell sets: it is that content `vertical-align` moves in the
|
|
171
|
+
* cell.
|
|
172
|
+
*
|
|
173
|
+
* A percentage in a cell's padding is of the width of its row: the
|
|
174
|
+
* columns and the spacing between them, without the spacing either side
|
|
175
|
+
* and the table's own padding. CSS 2.1 8.4 has it of the containing
|
|
176
|
+
* block's width and names none for a cell; the row's is what browsers
|
|
177
|
+
* take, and what the table's content box is wherever there is no
|
|
178
|
+
* spacing. Every column counts, one `visibility: collapse` takes out
|
|
179
|
+
* too, so that taking it out changes no row's height.
|
|
180
|
+
*
|
|
181
|
+
* Returns what each cell's content came to.
|
|
182
|
+
*/
|
|
183
|
+
function layoutCells(cells, columns, ctx) {
|
|
184
|
+
const { widths, spacing } = columns;
|
|
185
|
+
let rowWidth = spacing * Math.max(0, widths.length - 1);
|
|
151
186
|
for (const columnWidth of widths)
|
|
152
187
|
rowWidth += columnWidth;
|
|
153
188
|
const natural = new Array(cells.length);
|
|
154
189
|
for (let i = 0; i < cells.length; i += 1) {
|
|
155
190
|
const cell = cells[i];
|
|
156
191
|
const width = spannedWidth(widths, cell, spacing);
|
|
157
|
-
|
|
192
|
+
layoutSubtree(cell.box, ctx, width, rowWidth);
|
|
158
193
|
natural[i] = CELL_CONTENT.get(cell.box) ?? cell.box.height;
|
|
159
194
|
// A height a cell sets is a least one: its content is laid out as a
|
|
160
195
|
// block's, which keeps a height it sets and lets the content run out of
|
|
161
196
|
// it, and a cell does not. `<td height="10">` with a line in it is as
|
|
162
197
|
// tall as the line (CSS 2.1 17.5.3), where it was 10px with the line
|
|
163
198
|
// hanging out of it.
|
|
164
|
-
|
|
165
|
-
|
|
199
|
+
//
|
|
200
|
+
// The height is of the box `box-sizing` names, and a border-box one
|
|
201
|
+
// holds at least the padding and border; a cell's `min-height` and
|
|
202
|
+
// `max-height` count for nothing here, as in Blink (`ComputeRowData`),
|
|
203
|
+
// so the height its own layout clamped to them is not kept. The padding
|
|
204
|
+
// went on a border-box height a second time, and a cell set 80px tall
|
|
205
|
+
// was 100px. (Quirks mode takes every cell's height as a border box;
|
|
206
|
+
// `<Html>` has no quirks mode.)
|
|
207
|
+
cell.box.height = natural[i];
|
|
166
208
|
const specified = resolveOrNull(cell.box.style.height, NaN);
|
|
167
209
|
if (specified !== null) {
|
|
168
|
-
|
|
210
|
+
const extra = cell.box.verticalExtra;
|
|
211
|
+
const set = Math.max(0, specified);
|
|
212
|
+
cell.box.height = Math.max(cell.box.height, cell.box.style.boxSizing === 'border-box'
|
|
213
|
+
? Math.max(set, extra)
|
|
214
|
+
: set + extra);
|
|
169
215
|
}
|
|
170
216
|
}
|
|
217
|
+
return natural;
|
|
218
|
+
}
|
|
219
|
+
/** The rows' heights, and the rows and the cells placed, once every cell
|
|
220
|
+
* is laid out at its columns' width (`layoutCells`) and its content came
|
|
221
|
+
* to `natural`. Returns the rows' height. */
|
|
222
|
+
function placeRows(table, grid, columns, natural) {
|
|
223
|
+
const { rows, cells } = grid;
|
|
224
|
+
const { widths, columnX, gone, anyGone, spacing, rowSpacing } = columns;
|
|
225
|
+
const columnCount = widths.length;
|
|
226
|
+
const style = table.style;
|
|
227
|
+
const top = table.contentY + table.captionTop;
|
|
228
|
+
let y = top;
|
|
229
|
+
const rowTop = new Array(rows.length);
|
|
230
|
+
const rowHeight = new Array(rows.length).fill(0);
|
|
231
|
+
y += rowSpacing;
|
|
171
232
|
// A cell aligned on the baseline — every value but `top`, `middle` and
|
|
172
233
|
// `bottom` — hangs its first line on its first row's baseline, the lowest
|
|
173
234
|
// of theirs from the row's top (CSS 2.1 17.5.3), and the row is as tall as
|
|
174
|
-
// the cells hung from it need.
|
|
175
|
-
|
|
235
|
+
// the cells hung from it need. What moves down is the content, so a cell
|
|
236
|
+
// needs the lift and its content, and a height it sets is a least one
|
|
237
|
+
// beside that rather than more room under the lift: two cells set
|
|
238
|
+
// `height: 80px` with text of two sizes in them are a row 80px tall, where
|
|
239
|
+
// the lift on top of the height made it 104px.
|
|
240
|
+
//
|
|
241
|
+
// A cell spanning rows asks its first row for its baseline and nothing
|
|
242
|
+
// under it, and the rows it spans for its content and height alone, as
|
|
243
|
+
// Chrome sizes it (Blink's `RowBaselineTabulator` gives it an ascent and
|
|
244
|
+
// no descent): the lifted content hangs out of the cell. 17.5.3 asks only
|
|
245
|
+
// that the rows encompass the cell, whose box is as tall as its content.
|
|
246
|
+
const { lift, own: ascent } = baselineLifts(cells, rows.length, natural);
|
|
247
|
+
const needs = (i) => cells[i].rowSpan > 1
|
|
248
|
+
? cells[i].box.height
|
|
249
|
+
: Math.max(cells[i].box.height, lift[i] + natural[i]);
|
|
176
250
|
for (let i = 0; i < cells.length; i += 1) {
|
|
177
251
|
const cell = cells[i];
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
252
|
+
const need = cell.rowSpan > 1 ? lift[i] + ascent[i] : needs(i);
|
|
253
|
+
// NaN for a spanning cell with no baseline, which Math.max would keep
|
|
254
|
+
if (need > rowHeight[cell.row])
|
|
255
|
+
rowHeight[cell.row] = need;
|
|
181
256
|
}
|
|
182
257
|
for (let r = 0; r < rows.length; r += 1) {
|
|
183
258
|
const specified = resolveOrNull(rows[r].style.height, NaN);
|
|
@@ -186,24 +261,73 @@ export function layoutTable(table, ctx, contentWidth) {
|
|
|
186
261
|
}
|
|
187
262
|
// A row-spanning cell only forces height when the rows it covers do not
|
|
188
263
|
// already provide it — otherwise a `rowspan="3"` cell would make each of
|
|
189
|
-
// its three rows as tall as all of it
|
|
264
|
+
// its three rows as tall as all of it — and what they come short of it
|
|
265
|
+
// is shared out over them (`growRows`), where it all went to the last.
|
|
266
|
+
// Blink takes the cells in an order of its own: one inside another's
|
|
267
|
+
// rows first, so that the outer one is given what the inner left it; of
|
|
268
|
+
// two over the same rows, the taller; and else from the top down.
|
|
269
|
+
const spanning = [];
|
|
270
|
+
const lastRow = (cell) => Math.min(rows.length - 1, cell.row + cell.rowSpan - 1);
|
|
190
271
|
for (let i = 0; i < cells.length; i += 1) {
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
for (
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
272
|
+
if (cells[i].rowSpan > 1)
|
|
273
|
+
spanning.push(i);
|
|
274
|
+
}
|
|
275
|
+
if (spanning.length) {
|
|
276
|
+
const starts = new Array(rows.length).fill(false);
|
|
277
|
+
for (const i of spanning) {
|
|
278
|
+
if (lastRow(cells[i]) > cells[i].row)
|
|
279
|
+
starts[cells[i].row] = true;
|
|
280
|
+
}
|
|
281
|
+
spanning.sort((a, b) => {
|
|
282
|
+
const p = cells[a];
|
|
283
|
+
const q = cells[b];
|
|
284
|
+
const pEnd = lastRow(p);
|
|
285
|
+
const qEnd = lastRow(q);
|
|
286
|
+
if (p.row === q.row && pEnd === qEnd)
|
|
287
|
+
return needs(b) - needs(a);
|
|
288
|
+
if (p.row >= q.row && pEnd <= qEnd)
|
|
289
|
+
return -1;
|
|
290
|
+
if (q.row >= p.row && qEnd <= pEnd)
|
|
291
|
+
return 1;
|
|
292
|
+
return p.row - q.row;
|
|
293
|
+
});
|
|
294
|
+
const sizing = rowSizing(rows, cells);
|
|
295
|
+
for (const i of spanning) {
|
|
296
|
+
const cell = cells[i];
|
|
297
|
+
const last = lastRow(cell);
|
|
298
|
+
let covered = 0;
|
|
299
|
+
for (let r = cell.row; r <= last; r += 1)
|
|
300
|
+
covered += rowHeight[r];
|
|
301
|
+
covered += rowSpacing * (last - cell.row);
|
|
302
|
+
const missing = needs(i) - covered;
|
|
303
|
+
if (missing > 0) {
|
|
304
|
+
growRows(rowHeight, sizing, missing, null, cell.row, last, starts);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
// What the rows come to before any height is shared out to them, the
|
|
309
|
+
// spacing above, below and between them in (`TABLE_CONTENT`)
|
|
310
|
+
let rowsOwn = rowSpacing;
|
|
311
|
+
for (let r = 0; r < rows.length; r += 1) {
|
|
312
|
+
if (rows[r].style.visibility !== 'collapse') {
|
|
313
|
+
rowsOwn += rowHeight[r] + rowSpacing;
|
|
314
|
+
}
|
|
202
315
|
}
|
|
316
|
+
TABLE_CONTENT.set(table, rowsOwn + table.captionTop + table.captionBottom + table.verticalExtra);
|
|
203
317
|
// A table's own height, within its least and greatest, is a least
|
|
204
318
|
// height: what its rows come short of it goes to them (CSS 2.1 17.5.3,
|
|
205
|
-
// which leaves how to its user agents: `growRows`)
|
|
319
|
+
// which leaves how to its user agents: `growRows`). So is the height a
|
|
320
|
+
// flex or grid layout gave it, stretched or flexed (`FLEXED_HEIGHT`,
|
|
321
|
+
// `USED_HEIGHT`), which is its wrapper box's, captions and all (CSS
|
|
322
|
+
// Flexbox 1, 4): taken as the table's box alone, a table stretched down
|
|
323
|
+
// its line kept its rows at their content's height, and a cell had
|
|
324
|
+
// nothing for `vertical-align` to centre its content in. That one is its
|
|
325
|
+
// height in place of its own, which the flex layout started from: flexed
|
|
326
|
+
// down a column shorter than a height of its own, it is as short as that,
|
|
327
|
+
// where its rows let it be (Blink: "A table interprets forced block-size
|
|
328
|
+
// as the block-size of its rows", `flex_layout_algorithm.cc`).
|
|
206
329
|
const wanted = resolveOrNull(style.height, table.percentHeightBase);
|
|
330
|
+
const given = givenHeight(table);
|
|
207
331
|
if (rows.length) {
|
|
208
332
|
const extraBox = table.verticalExtra;
|
|
209
333
|
const set = wanted === null
|
|
@@ -211,7 +335,9 @@ export function layoutTable(table, ctx, contentWidth) {
|
|
|
211
335
|
: style.boxSizing === 'border-box'
|
|
212
336
|
? wanted
|
|
213
337
|
: wanted + extraBox;
|
|
214
|
-
const inner =
|
|
338
|
+
const inner = Number.isFinite(given)
|
|
339
|
+
? given - table.captionTop - table.captionBottom
|
|
340
|
+
: clampHeight(table, set) - extraBox;
|
|
215
341
|
let total = 0;
|
|
216
342
|
for (const h of rowHeight)
|
|
217
343
|
total += h;
|
|
@@ -329,7 +455,7 @@ export function layoutTable(table, ctx, contentWidth) {
|
|
|
329
455
|
// left edge to the last one's right, the spacing around them outside it
|
|
330
456
|
// (CSS 2.1 17.5.1), as it is above and below.
|
|
331
457
|
const gridX = table.contentX + spacing;
|
|
332
|
-
const gridWidth = Math.max(0,
|
|
458
|
+
const gridWidth = Math.max(0, columns.end - spacing - gridX);
|
|
333
459
|
for (let r = 0; r < rows.length; r += 1) {
|
|
334
460
|
const row = rows[r];
|
|
335
461
|
row.x = gridX;
|
|
@@ -353,6 +479,18 @@ export function layoutTable(table, ctx, contentWidth) {
|
|
|
353
479
|
}
|
|
354
480
|
return y - top;
|
|
355
481
|
}
|
|
482
|
+
/**
|
|
483
|
+
* A table's height as its rows and captions came to in its last layout,
|
|
484
|
+
* border box, apart from a height it sets or is given: the least it can be
|
|
485
|
+
* at the width it was laid out at. What a flex layout holds a table item to
|
|
486
|
+
* down a column (`flex.ts`'s `autoMinimums`).
|
|
487
|
+
*/
|
|
488
|
+
export const TABLE_CONTENT = new WeakMap();
|
|
489
|
+
/** The content height a flex or grid layout gave a table, its captions in
|
|
490
|
+
* (`FLEXED_HEIGHT`, `USED_HEIGHT`), and else NaN. */
|
|
491
|
+
export function givenHeight(table) {
|
|
492
|
+
return FLEXED_HEIGHT.get(table) ?? USED_HEIGHT.get(table) ?? NaN;
|
|
493
|
+
}
|
|
356
494
|
/** Whether `visibility: collapse` takes a column out: its own, or the
|
|
357
495
|
* column group's that holds it with no column box of its own. */
|
|
358
496
|
function collapsedColumn(column, group) {
|
|
@@ -429,124 +567,279 @@ function spannedWidth(widths, cell, spacing) {
|
|
|
429
567
|
/**
|
|
430
568
|
* `table-layout: fixed`: the columns and the first row decide, and the rest
|
|
431
569
|
* of the table is not measured at all, which is the whole point of it (CSS
|
|
432
|
-
* 2.1 17.5.2.1).
|
|
433
|
-
*
|
|
434
|
-
*
|
|
435
|
-
*
|
|
436
|
-
*
|
|
570
|
+
* 2.1 17.5.2.1). Each column is set to a length, to a percentage of the
|
|
571
|
+
* table's width less its spacing, or to neither. A `<col>` set to a length
|
|
572
|
+
* has it whatever its cells say; otherwise a cell of the first row sets
|
|
573
|
+
* the column it is in, its percentage over the `<col>`'s where it is the
|
|
574
|
+
* larger. A cell spanning columns gives each that has no length an even
|
|
575
|
+
* share of its border box less the spacing between them, and each that
|
|
576
|
+
* has neither an even share of its percentage: `<td colspan=2
|
|
577
|
+
* style="width:60%">` sets two columns to 30%, and one 200 wide over a
|
|
578
|
+
* `<col>` of 50 and a column of nothing makes that 99, where it was the
|
|
579
|
+
* 148 the `<col>` left. All of this is Blink's, which CSS 2.1 leaves to
|
|
580
|
+
* the browser.
|
|
581
|
+
*
|
|
582
|
+
* The table's width is shared out as Blink shares it too
|
|
583
|
+
* (`SynchronizeAssignableTableInlineSizeAndColumnsFixed`): the lengths
|
|
584
|
+
* first, then the percentages, then what is left to the columns set to
|
|
585
|
+
* neither. Percentages that come to more than 100% are scaled until they
|
|
586
|
+
* come to that, and percentages that come to more than the lengths leave
|
|
587
|
+
* are scaled down to it. Where no column is set to neither, what is left
|
|
588
|
+
* goes to the lengths, or failing those to the percentages. Only a column's
|
|
589
|
+
* least widens the table: its length, or a `<col>`'s `min-width`. Taken
|
|
590
|
+
* as lengths, two cells of 60% made a table of 300 one of 359, where each
|
|
591
|
+
* is 147 and the table 300; and a length of 100 beside a 60% column
|
|
592
|
+
* shared what the two left with it, 106.4 and 187.6, where the length
|
|
593
|
+
* takes it all, 117.6 and 176.4.
|
|
437
594
|
*/
|
|
438
|
-
function fixedColumns(cells, columnWidths, columnCount, available, spacing) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
continue;
|
|
445
|
-
widths[c] = px;
|
|
446
|
-
set[c] = true;
|
|
447
|
-
}
|
|
595
|
+
function fixedColumns(cells, columnWidths, columnPercents, columnLeast, columnCount, available, spacing) {
|
|
596
|
+
// each column's length, its percentage, and what a cell with the
|
|
597
|
+
// percentage adds to it where its padding and borders are not in it
|
|
598
|
+
const length = columnWidths.map((px, c) => columnPercents[c] === null ? px : null);
|
|
599
|
+
const percent = columnPercents.slice();
|
|
600
|
+
const added = new Array(columnCount).fill(0);
|
|
448
601
|
for (const cell of cells) {
|
|
449
602
|
if (cell.row > 0)
|
|
450
603
|
break;
|
|
451
|
-
const px = ownWidth(cell.box.style, available);
|
|
452
|
-
if (px === null)
|
|
453
|
-
continue;
|
|
454
604
|
const box = cell.box;
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
const
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
605
|
+
const first = cell.column;
|
|
606
|
+
const last = Math.min(columnCount, first + cell.colSpan);
|
|
607
|
+
const span = last - first;
|
|
608
|
+
const share = ownPercent(box.style);
|
|
609
|
+
if (share !== null) {
|
|
610
|
+
for (let c = first; c < last; c += 1) {
|
|
611
|
+
if (length[c] !== null)
|
|
612
|
+
continue;
|
|
613
|
+
if (span > 1) {
|
|
614
|
+
if (percent[c] === null)
|
|
615
|
+
percent[c] = share / span;
|
|
616
|
+
}
|
|
617
|
+
else if (percent[c] === null || share > percent[c]) {
|
|
618
|
+
percent[c] = share;
|
|
619
|
+
added[c] =
|
|
620
|
+
box.style.boxSizing === 'border-box' ? 0 : box.horizontalExtra;
|
|
621
|
+
}
|
|
622
|
+
}
|
|
623
|
+
continue;
|
|
466
624
|
}
|
|
467
|
-
|
|
625
|
+
const px = ownWidth(box.style, available);
|
|
626
|
+
if (px === null)
|
|
468
627
|
continue;
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
628
|
+
// A length within a length `max-width`, which a `min-width` raises
|
|
629
|
+
// and does nothing else to: a fixed table does not measure its cells'
|
|
630
|
+
// content, so a least width has nothing to hold up (CSS Tables 3,
|
|
631
|
+
// 3.8.2 and 3.8.3, as Blink reads them). Unheld, a cell `width: 300px;
|
|
632
|
+
// max-width: 100px` took 300 of the table.
|
|
633
|
+
const outer = Math.min(outerWidth(box, px), cellLimits(box).cap);
|
|
634
|
+
const each = Math.max(0, outer - spacing * (span - 1)) / span;
|
|
635
|
+
for (let c = first; c < last; c += 1) {
|
|
636
|
+
if (length[c] === null)
|
|
637
|
+
length[c] = each;
|
|
475
638
|
}
|
|
476
639
|
}
|
|
477
|
-
|
|
478
|
-
|
|
640
|
+
// The table is as wide as the least its columns come to, which is what
|
|
641
|
+
// widens it past its own width: the lengths, and the `min-width` of each
|
|
642
|
+
// `<col>` set to anything else.
|
|
643
|
+
let least = 0;
|
|
644
|
+
let total = 0;
|
|
479
645
|
for (let c = 0; c < columnCount; c += 1) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
646
|
+
const floor = columnLeast[c] ?? 0;
|
|
647
|
+
const px = length[c];
|
|
648
|
+
if (percent[c] === null && px !== null) {
|
|
649
|
+
length[c] = Math.max(px, floor);
|
|
650
|
+
least += length[c];
|
|
651
|
+
}
|
|
652
|
+
else {
|
|
653
|
+
least += floor;
|
|
654
|
+
}
|
|
655
|
+
if (percent[c] !== null)
|
|
656
|
+
total += percent[c];
|
|
657
|
+
}
|
|
658
|
+
const target = Math.max(available, least);
|
|
659
|
+
const scale = total > 100 ? 100 / total : 1;
|
|
660
|
+
const ofTarget = new Array(columnCount).fill(0);
|
|
661
|
+
let lengths = 0;
|
|
662
|
+
let percents = 0;
|
|
663
|
+
let percentCount = 0;
|
|
664
|
+
let autoCount = 0;
|
|
665
|
+
let zeroCount = 0;
|
|
666
|
+
for (let c = 0; c < columnCount; c += 1) {
|
|
667
|
+
const share = percent[c];
|
|
668
|
+
const px = length[c];
|
|
669
|
+
if (share !== null) {
|
|
670
|
+
ofTarget[c] = Math.max(columnLeast[c] ?? 0, ((share * scale) / 100) * target + added[c]);
|
|
671
|
+
percents += ofTarget[c];
|
|
672
|
+
percentCount += 1;
|
|
673
|
+
}
|
|
674
|
+
else if (px === null) {
|
|
675
|
+
autoCount += 1;
|
|
676
|
+
}
|
|
677
|
+
else if (px > 0) {
|
|
678
|
+
lengths += px;
|
|
679
|
+
}
|
|
680
|
+
else {
|
|
681
|
+
zeroCount += 1;
|
|
682
|
+
}
|
|
484
683
|
}
|
|
485
|
-
const
|
|
486
|
-
|
|
684
|
+
const widths = new Array(columnCount).fill(0);
|
|
685
|
+
let assigned = 0;
|
|
686
|
+
// the lengths fit, since they widen the table; they grow into what the
|
|
687
|
+
// percentages leave where no column is set to neither
|
|
688
|
+
if (lengths > 0) {
|
|
689
|
+
const room = Math.max(0, target - percents);
|
|
690
|
+
const grow = !autoCount && lengths < room ? room / lengths : 1;
|
|
487
691
|
for (let c = 0; c < columnCount; c += 1) {
|
|
488
|
-
|
|
489
|
-
|
|
692
|
+
const px = length[c];
|
|
693
|
+
if (percent[c] !== null || px === null || !(px > 0))
|
|
694
|
+
continue;
|
|
695
|
+
widths[c] = px * grow;
|
|
696
|
+
assigned += widths[c];
|
|
490
697
|
}
|
|
491
698
|
}
|
|
492
|
-
|
|
699
|
+
if (assigned >= target)
|
|
700
|
+
return widths;
|
|
701
|
+
// the percentages are scaled down to what the lengths leave, and up to
|
|
702
|
+
// it where no column is set to neither
|
|
703
|
+
if (percentCount) {
|
|
704
|
+
const room = target - assigned;
|
|
705
|
+
const toRoom = percents > room || (!autoCount && percents < room);
|
|
493
706
|
for (let c = 0; c < columnCount; c += 1) {
|
|
494
|
-
|
|
495
|
-
|
|
707
|
+
if (percent[c] === null)
|
|
708
|
+
continue;
|
|
709
|
+
widths[c] = !toRoom
|
|
710
|
+
? ofTarget[c]
|
|
711
|
+
: percents > 0
|
|
712
|
+
? (ofTarget[c] * room) / percents
|
|
713
|
+
: room / percentCount;
|
|
714
|
+
assigned += widths[c];
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
// The columns set to neither share what is left. One set to 0 is set to
|
|
718
|
+
// a length that gets nothing, unless every column is set to 0.
|
|
719
|
+
const rest = target - assigned;
|
|
720
|
+
const all = zeroCount === columnCount;
|
|
721
|
+
if (!all && !autoCount)
|
|
722
|
+
return widths;
|
|
723
|
+
for (let c = 0; c < columnCount; c += 1) {
|
|
724
|
+
if (percent[c] !== null)
|
|
725
|
+
continue;
|
|
726
|
+
if (all || length[c] === null) {
|
|
727
|
+
widths[c] = rest / (all ? zeroCount : autoCount);
|
|
496
728
|
}
|
|
497
729
|
}
|
|
498
730
|
return widths;
|
|
499
731
|
}
|
|
732
|
+
/**
|
|
733
|
+
* A cell's max-content and min-content widths, for `autoColumns`: two probe
|
|
734
|
+
* layouts, unconstrained for max-content, and at no width for min-content
|
|
735
|
+
* (`MIN_CONTENT_PROBE`) — which the line breaker answers by breaking at
|
|
736
|
+
* every opportunity, so the widest line is the longest unbreakable word.
|
|
737
|
+
*
|
|
738
|
+
* Cached on the box, because intrinsic widths are width-independent by
|
|
739
|
+
* definition — measuring them per layout pass made a *resize* of a 500-row
|
|
740
|
+
* table cost as much as its first layout (223ms of re-probing measured
|
|
741
|
+
* against 10ms with the cache). The cache's invalidation rule is the box's
|
|
742
|
+
* lifetime: any DOM or style change rebuilds the tree.
|
|
743
|
+
*
|
|
744
|
+
* Every cell is measured before the columns are sized, and not in the loop
|
|
745
|
+
* that sizes them, so that the frame a table in the cell is measured under
|
|
746
|
+
* is this small one and not `autoColumns`'s (`layoutTable`).
|
|
747
|
+
*/
|
|
748
|
+
function measureCell(box, ctx) {
|
|
749
|
+
if (box.intrinsicMaxContent >= 0)
|
|
750
|
+
return;
|
|
751
|
+
box.intrinsicMaxContent = measureIntrinsicWidth(box, ctx, Infinity);
|
|
752
|
+
// where its words say it exactly, read from the layout just made
|
|
753
|
+
box.intrinsicMinContent =
|
|
754
|
+
(ctx.fonts && exactMinContent(box, ctx.fonts)) ??
|
|
755
|
+
measureIntrinsicWidth(box, ctx, MIN_CONTENT_PROBE, true);
|
|
756
|
+
}
|
|
500
757
|
/**
|
|
501
758
|
* The auto algorithm: measure every cell's max-content and min-content
|
|
502
|
-
* width,
|
|
759
|
+
* width, gather them into columns, and share the table's width out over
|
|
760
|
+
* them, as CSS Tables 3 has it (3.9.3, "width distribution") and Blink
|
|
761
|
+
* implements it (`DistributeInlineSizeToComputedInlineSizeAuto`).
|
|
503
762
|
*
|
|
504
|
-
* The distribution is the part worth stating.
|
|
763
|
+
* The distribution is the part worth stating. A column is set to a
|
|
764
|
+
* percentage, set to a length, or neither, and the table's width is shared
|
|
765
|
+
* out in steps, each tried only where the one before falls short: every
|
|
766
|
+
* column at its least; then the percentage columns grown towards their
|
|
767
|
+
* percentage of the table; then the columns set to a length grown towards
|
|
768
|
+
* it; then the rest towards their max-content. When the max-content widths
|
|
505
769
|
* fit, they are used — a small table is exactly as wide as its contents.
|
|
506
|
-
* When they do not, the surplus over each column's minimum is scaled down
|
|
507
|
-
* proportion, so a column of long prose gives up more than a column of
|
|
508
|
-
* dates.
|
|
770
|
+
* When they do not, the surplus over each column's minimum is scaled down
|
|
771
|
+
* in proportion, so a column of long prose gives up more than a column of
|
|
772
|
+
* dates. What a table has over all of that goes to the columns set to
|
|
773
|
+
* nothing, or else to those set to a length, or else to the percentages.
|
|
774
|
+
*
|
|
775
|
+
* What a cell asks is its content within its length limits (CSS Tables 3,
|
|
776
|
+
* 3.8.2): its outer min-content width is `max(min-width, min-content)`
|
|
777
|
+
* and its outer max-content width `max(min-width, min-content,
|
|
778
|
+
* min(max-width, max-content))`. A `max-width` caps the min-content too,
|
|
779
|
+
* as Blink has it where the draft does not (`CreateCellInlineConstraint`),
|
|
780
|
+
* so that `td { max-width: 50px }` over a long word, or a line that does
|
|
781
|
+
* not wrap and ends in an ellipsis, makes a column 50 wide and not the
|
|
782
|
+
* word's. Neither limit sets the column's width, which only a `width`
|
|
783
|
+
* does: a cell `min-width: 100px` over 10px of content in a table of 600
|
|
784
|
+
* takes its share of the rest, as one 100 wide would.
|
|
785
|
+
*
|
|
786
|
+
* A percentage is kept a percentage until the table's width is known, and
|
|
787
|
+
* is of that, less its spacing: resolved against the room on offer and
|
|
788
|
+
* held as a length, `<col style="width:20%">` beside a column of 100 made
|
|
789
|
+
* a table of 180 in 400 of room, where it is 125 — the 100 is the other
|
|
790
|
+
* 80% of it. That is how a table's percentages make it wider than its
|
|
791
|
+
* content (`gridMax`), and what `widen` says it may (`percentagesWiden`).
|
|
792
|
+
* `room` is what its captions and its `min-width` hold an auto table to,
|
|
793
|
+
* and is shared out with the rest: added on in proportion afterwards, a
|
|
794
|
+
* caption 200 wide over a column of 50 and one set to 50 made them 100
|
|
795
|
+
* each, where the column set to nothing takes it all.
|
|
509
796
|
*/
|
|
510
|
-
function autoColumns(cells, columnWidths, columnCount, available,
|
|
797
|
+
function autoColumns(cells, columnWidths, columnLeast, columnPercents, columnCount, available, fill, spacing, room, widen) {
|
|
511
798
|
const max = new Array(columnCount).fill(0);
|
|
512
799
|
const min = new Array(columnCount).fill(0);
|
|
513
800
|
const explicit = new Array(columnCount).fill(null);
|
|
801
|
+
// each column's percentage: its own, or the most a cell of it asks
|
|
802
|
+
const percent = columnPercents.slice();
|
|
514
803
|
const spanning = [];
|
|
804
|
+
// each cell's outer min-content and max-content, within its limits
|
|
805
|
+
const asks = new Map();
|
|
515
806
|
for (const cell of cells) {
|
|
516
|
-
//
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
// measured against 10ms with the cache). The cache's invalidation rule
|
|
525
|
-
// is the box's lifetime: any DOM or style change rebuilds the tree.
|
|
526
|
-
if (cell.box.intrinsicMaxContent < 0) {
|
|
527
|
-
cell.box.intrinsicMaxContent = measureIntrinsicWidth(cell.box, ctx, Infinity);
|
|
528
|
-
// where its words say it exactly, read from the layout just made
|
|
529
|
-
cell.box.intrinsicMinContent =
|
|
530
|
-
(ctx.fonts && exactMinContent(cell.box, ctx.fonts)) ??
|
|
531
|
-
measureIntrinsicWidth(cell.box, ctx, MIN_CONTENT_PROBE, true);
|
|
532
|
-
}
|
|
807
|
+
// what its content asks, measured before (`measureCell`)
|
|
808
|
+
const { floor, cap } = cellLimits(cell.box);
|
|
809
|
+
const low = Math.min(Math.max(cell.box.intrinsicMinContent, floor), cap);
|
|
810
|
+
const ask = {
|
|
811
|
+
min: low,
|
|
812
|
+
max: Math.max(low, Math.min(cell.box.intrinsicMaxContent, cap)),
|
|
813
|
+
};
|
|
814
|
+
asks.set(cell, ask);
|
|
533
815
|
if (cell.colSpan > 1) {
|
|
534
816
|
spanning.push(cell);
|
|
535
817
|
continue;
|
|
536
818
|
}
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
819
|
+
const c = cell.column;
|
|
820
|
+
max[c] = Math.max(max[c], ask.max);
|
|
821
|
+
min[c] = Math.max(min[c], ask.min);
|
|
822
|
+
// a percentage is the column's, 0% among them, as Blink keeps it
|
|
823
|
+
const share = ownPercent(cell.box.style);
|
|
824
|
+
if (share !== null)
|
|
825
|
+
percent[c] = Math.max(percent[c] ?? 0, share);
|
|
826
|
+
const width = cellWidth(cell);
|
|
827
|
+
if (width !== null)
|
|
828
|
+
explicit[c] = Math.max(explicit[c] ?? 0, width);
|
|
543
829
|
}
|
|
544
830
|
// a column's own width counts as its cells' do: at least that, and its
|
|
545
|
-
// content's max where that is wider (CSS 2.1 17.5.2.2, step 2)
|
|
831
|
+
// content's max where that is wider (CSS 2.1 17.5.2.2, step 2); and its
|
|
832
|
+
// `min-width` alone is the least it and its content are, as a cell's
|
|
833
|
+
// is, and sets no width (CSS Tables 3, 3.8.2)
|
|
546
834
|
for (let c = 0; c < columnCount; c += 1) {
|
|
547
835
|
const own = columnWidths[c];
|
|
548
836
|
if (own !== null)
|
|
549
837
|
explicit[c] = Math.max(explicit[c] ?? 0, own);
|
|
838
|
+
const floor = columnLeast[c];
|
|
839
|
+
if (floor !== null) {
|
|
840
|
+
min[c] = Math.max(min[c], floor);
|
|
841
|
+
max[c] = Math.max(max[c], floor);
|
|
842
|
+
}
|
|
550
843
|
}
|
|
551
844
|
// A spanning cell comes after the cells of one column, the narrower
|
|
552
845
|
// spans first, and adds to its columns only what they come short of it
|
|
@@ -556,174 +849,441 @@ function autoColumns(cells, columnWidths, columnCount, available, ctx, containin
|
|
|
556
849
|
// they have none. Taken in document order and spread evenly, a spanning
|
|
557
850
|
// cell in a first row was shared out before the cells of one column
|
|
558
851
|
// under it were seen, and a column one of them set to 5px took half.
|
|
852
|
+
//
|
|
853
|
+
// A column with a percentage is first that much of what the cell asks,
|
|
854
|
+
// less the spacing, as Blink's distribution over the span resolves it;
|
|
855
|
+
// what is left goes to the columns set to nothing, or else to those set
|
|
856
|
+
// to a length, or else to the percentages, by them: `<td colspan=2>` 100
|
|
857
|
+
// wide over a 50% column and one set to nothing, each of 10, makes them
|
|
858
|
+
// 50 each. A spanning cell's own percentage goes first to the columns it
|
|
859
|
+
// spans that have none, as much more as the ones that have come short
|
|
860
|
+
// of it, in proportion to their content (Blink's
|
|
861
|
+
// `DistributeColspanCellToColumnsAuto`): `<td colspan=2 style="width:
|
|
862
|
+
// 50%">` over two columns makes each 25%.
|
|
559
863
|
spanning.sort((a, b) => a.colSpan - b.colSpan);
|
|
560
864
|
const grow = (into, want, cell) => {
|
|
561
|
-
const
|
|
562
|
-
|
|
563
|
-
|
|
865
|
+
const first = cell.column;
|
|
866
|
+
const last = Math.min(columnCount - 1, first + cell.colSpan - 1);
|
|
867
|
+
const between = spacing * (last - first);
|
|
868
|
+
let have = between;
|
|
869
|
+
for (let c = first; c <= last; c += 1)
|
|
564
870
|
have += into[c];
|
|
565
871
|
if (!(want > have))
|
|
566
872
|
return;
|
|
567
|
-
let
|
|
568
|
-
|
|
569
|
-
|
|
873
|
+
for (let c = first; c <= last; c += 1) {
|
|
874
|
+
const share = percent[c];
|
|
875
|
+
if (share === null)
|
|
876
|
+
continue;
|
|
877
|
+
const size = (share / 100) * (want - between);
|
|
878
|
+
if (size > into[c]) {
|
|
879
|
+
have += size - into[c];
|
|
880
|
+
into[c] = size;
|
|
881
|
+
}
|
|
570
882
|
}
|
|
883
|
+
if (!(want > have))
|
|
884
|
+
return;
|
|
885
|
+
let takes = (c) => explicit[c] === null && percent[c] === null;
|
|
886
|
+
let any = false;
|
|
887
|
+
for (let c = first; c <= last && !any; c += 1)
|
|
888
|
+
any = takes(c);
|
|
889
|
+
if (!any)
|
|
890
|
+
takes = (c) => percent[c] === null;
|
|
891
|
+
for (let c = first; c <= last && !any; c += 1)
|
|
892
|
+
any = takes(c);
|
|
893
|
+
if (!any)
|
|
894
|
+
takes = () => true;
|
|
895
|
+
const byPercent = !any;
|
|
896
|
+
const weightOf = (c) => byPercent ? percent[c] : max[c];
|
|
571
897
|
let weight = 0;
|
|
572
898
|
let count = 0;
|
|
573
|
-
for (let c =
|
|
574
|
-
if (
|
|
899
|
+
for (let c = first; c <= last; c += 1) {
|
|
900
|
+
if (!takes(c))
|
|
575
901
|
continue;
|
|
576
|
-
weight +=
|
|
902
|
+
weight += weightOf(c);
|
|
577
903
|
count += 1;
|
|
578
904
|
}
|
|
579
|
-
|
|
580
|
-
|
|
905
|
+
const extra = want - have;
|
|
906
|
+
for (let c = first; c <= last; c += 1) {
|
|
907
|
+
if (!takes(c))
|
|
581
908
|
continue;
|
|
582
|
-
into[c] +=
|
|
583
|
-
weight > 0 ? ((want - have) * max[c]) / weight : (want - have) / count;
|
|
909
|
+
into[c] += weight > 0 ? (extra * weightOf(c)) / weight : extra / count;
|
|
584
910
|
}
|
|
585
911
|
};
|
|
912
|
+
for (const cell of spanning) {
|
|
913
|
+
const share = ownPercent(cell.box.style);
|
|
914
|
+
if (share !== null)
|
|
915
|
+
spreadPercent(percent, max, share, cell, columnCount);
|
|
916
|
+
}
|
|
586
917
|
// what the columns' content alone asks, before their set widths: the
|
|
587
918
|
// least a column gives way to when the table has less room than its
|
|
588
919
|
// cells' widths want, as beside a float
|
|
589
920
|
const least = min.slice();
|
|
590
921
|
for (const cell of spanning)
|
|
591
|
-
grow(least, cell.
|
|
922
|
+
grow(least, asks.get(cell).min, cell);
|
|
923
|
+
// A column set to a length is at least that wide, where there is room;
|
|
924
|
+
// one with a percentage too is that, and its length is only the most
|
|
925
|
+
// its content asks, which is what its percentage holds the table to
|
|
592
926
|
for (let c = 0; c < columnCount; c += 1) {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
927
|
+
const own = explicit[c];
|
|
928
|
+
if (own === null)
|
|
929
|
+
continue;
|
|
930
|
+
max[c] = Math.max(min[c], own);
|
|
931
|
+
if (percent[c] === null)
|
|
932
|
+
min[c] = Math.max(min[c], Math.min(own, max[c]));
|
|
597
933
|
}
|
|
598
934
|
// and with them: a spanning cell's own width is the least it is, as a
|
|
599
935
|
// single cell's is (step 1), so a cell over three columns set to 100px,
|
|
600
936
|
// with 20px between them, has 60 to share, where it had none
|
|
601
937
|
for (const cell of spanning) {
|
|
602
|
-
const own = cellWidth(cell
|
|
603
|
-
|
|
604
|
-
grow(
|
|
938
|
+
const own = cellWidth(cell) ?? 0;
|
|
939
|
+
const ask = asks.get(cell);
|
|
940
|
+
grow(min, Math.max(ask.min, own), cell);
|
|
941
|
+
grow(max, Math.max(ask.max, own), cell);
|
|
605
942
|
}
|
|
606
943
|
for (let c = 0; c < columnCount; c += 1)
|
|
607
944
|
min[c] = Math.min(min[c], max[c]);
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
945
|
+
// The percentages come to no more than 100, the columns' in order:
|
|
946
|
+
// where they would, a column has what is left, which may be none
|
|
947
|
+
let total = 0;
|
|
948
|
+
for (let c = 0; c < columnCount; c += 1) {
|
|
949
|
+
const share = percent[c];
|
|
950
|
+
if (share === null)
|
|
951
|
+
continue;
|
|
952
|
+
percent[c] = share + total > 100 ? 100 - total : share;
|
|
953
|
+
total += percent[c];
|
|
954
|
+
}
|
|
955
|
+
// How wide the table is: the room on offer where it has a width of its
|
|
956
|
+
// own, and where it has not, as wide as its columns ask, up to the room
|
|
957
|
+
// (CSS Tables 3, 3.9.2) — wide enough for its percentages too
|
|
958
|
+
const most = gridMax(max, percent, total, widen);
|
|
959
|
+
let target = fill ? available : Math.min(available, most);
|
|
960
|
+
target = Math.max(target, room);
|
|
961
|
+
if (!Number.isFinite(target))
|
|
962
|
+
target = Math.max(most, room);
|
|
963
|
+
// The guesses, each a sum over the columns (3.9.3): every column at its
|
|
964
|
+
// least; the percentages at theirs; the lengths at theirs; and every
|
|
965
|
+
// column at its max-content, the percentages still at theirs
|
|
966
|
+
const sized = new Array(columnCount).fill(0);
|
|
967
|
+
let atLeast = 0;
|
|
968
|
+
let atPercent = 0;
|
|
969
|
+
let atSet = 0;
|
|
970
|
+
let atMax = 0;
|
|
971
|
+
for (let c = 0; c < columnCount; c += 1) {
|
|
972
|
+
atLeast += least[c];
|
|
973
|
+
const share = percent[c];
|
|
974
|
+
if (share !== null) {
|
|
975
|
+
// a percentage of the border box, so never narrower than its content
|
|
976
|
+
sized[c] = Math.max(min[c], (share / 100) * target);
|
|
977
|
+
atPercent += sized[c];
|
|
978
|
+
atSet += sized[c];
|
|
979
|
+
atMax += sized[c];
|
|
980
|
+
}
|
|
981
|
+
else {
|
|
982
|
+
atPercent += least[c];
|
|
983
|
+
atSet += min[c];
|
|
984
|
+
atMax += max[c];
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
if (target <= atLeast) {
|
|
988
|
+
// Less room than the cells' content needs: a table never goes narrower
|
|
989
|
+
// than its words (CSS 2.1 17.5.2.2), which it overflows.
|
|
990
|
+
return least;
|
|
991
|
+
}
|
|
992
|
+
if (target <= atPercent) {
|
|
993
|
+
// The percentages grow from their least towards their size, each by as
|
|
994
|
+
// much as it has to go, and the rest stay at theirs
|
|
995
|
+
const deficit = target - atLeast;
|
|
996
|
+
let increase = 0;
|
|
619
997
|
let count = 0;
|
|
620
998
|
for (let c = 0; c < columnCount; c += 1) {
|
|
621
|
-
if (
|
|
999
|
+
if (percent[c] === null)
|
|
622
1000
|
continue;
|
|
623
|
-
|
|
1001
|
+
increase += sized[c] - least[c];
|
|
624
1002
|
count += 1;
|
|
625
1003
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
1004
|
+
return least.map((l, c) => percent[c] === null
|
|
1005
|
+
? l
|
|
1006
|
+
: l +
|
|
1007
|
+
(increase > 0
|
|
1008
|
+
? (deficit * (sized[c] - l)) / increase
|
|
1009
|
+
: deficit / count));
|
|
630
1010
|
}
|
|
631
|
-
|
|
632
|
-
if (totalMin >= available) {
|
|
1011
|
+
if (target <= atSet) {
|
|
633
1012
|
// Less room than the cells' widths want: a width a cell was set to
|
|
634
|
-
// gives way first, down to what its content asks
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
1013
|
+
// gives way first, down to what its content asks.
|
|
1014
|
+
const give = atSet - atPercent;
|
|
1015
|
+
return min.map((m, c) => percent[c] !== null
|
|
1016
|
+
? sized[c]
|
|
1017
|
+
: m - ((m - least[c]) / give) * (atSet - target));
|
|
1018
|
+
}
|
|
1019
|
+
if (target < atMax) {
|
|
1020
|
+
const slack = target - atSet;
|
|
1021
|
+
const surplus = atMax - atSet;
|
|
1022
|
+
return max.map((m, c) => percent[c] !== null
|
|
1023
|
+
? sized[c]
|
|
1024
|
+
: min[c] + ((m - min[c]) / surplus) * slack);
|
|
1025
|
+
}
|
|
1026
|
+
// Everything fits. A table with a width of its own still fills it (CSS
|
|
1027
|
+
// 2.1 17.5.2.2), and an auto table its captions and `min-width`: the
|
|
1028
|
+
// columns set to nothing take the rest in proportion to what they asked
|
|
1029
|
+
// for; where there are none, the columns set to a length; and where
|
|
1030
|
+
// every column has a percentage, those, by it. Returned as asked,
|
|
1031
|
+
// `<table width="600">` drew its cells at their content's width and left
|
|
1032
|
+
// the rest of its 600 empty.
|
|
1033
|
+
let takes = (c) => percent[c] === null && explicit[c] === null;
|
|
1034
|
+
let any = false;
|
|
1035
|
+
for (let c = 0; c < columnCount && !any; c += 1)
|
|
1036
|
+
any = takes(c);
|
|
1037
|
+
if (!any)
|
|
1038
|
+
takes = (c) => percent[c] === null;
|
|
1039
|
+
for (let c = 0; c < columnCount && !any; c += 1)
|
|
1040
|
+
any = takes(c);
|
|
1041
|
+
const byPercent = !any;
|
|
1042
|
+
if (byPercent)
|
|
1043
|
+
takes = () => true;
|
|
1044
|
+
const weightOf = (c) => byPercent ? percent[c] : max[c];
|
|
1045
|
+
let weight = 0;
|
|
1046
|
+
let count = 0;
|
|
1047
|
+
for (let c = 0; c < columnCount; c += 1) {
|
|
1048
|
+
if (!takes(c))
|
|
1049
|
+
continue;
|
|
1050
|
+
weight += weightOf(c);
|
|
1051
|
+
count += 1;
|
|
1052
|
+
}
|
|
1053
|
+
const extra = target - atMax;
|
|
1054
|
+
return max.map((m, c) => {
|
|
1055
|
+
const base = percent[c] !== null ? sized[c] : m;
|
|
1056
|
+
if (!takes(c))
|
|
1057
|
+
return base;
|
|
1058
|
+
return base + (weight > 0 ? (extra * weightOf(c)) / weight : extra / count);
|
|
1059
|
+
});
|
|
645
1060
|
}
|
|
1061
|
+
/** What Blink makes the most of a table's percentages can make it,
|
|
1062
|
+
* kTableMaxInlineSize, where the columns with one come to 100% and there
|
|
1063
|
+
* are others with content: no width is wide enough. */
|
|
1064
|
+
const PERCENT_TABLE_MOST = 1_000_000;
|
|
646
1065
|
/**
|
|
647
|
-
* The
|
|
648
|
-
*
|
|
649
|
-
*
|
|
650
|
-
*
|
|
651
|
-
* `
|
|
652
|
-
*
|
|
1066
|
+
* The widest an auto table's columns make it, less its spacing (CSS
|
|
1067
|
+
* Tables 3, 3.9.2, GRIDMAX): what they ask, and what their percentages
|
|
1068
|
+
* need where they may count (`widen`). The draft says that a percentage
|
|
1069
|
+
* is a constraint the table should try to satisfy and no more; Blink's
|
|
1070
|
+
* `ComputeGridInlineMinMax` widens the table until it can: a column's
|
|
1071
|
+
* content is no more than its percentage of the table, and the columns
|
|
1072
|
+
* with none are the rest of it. Chrome's: a 20% column over 10 of content
|
|
1073
|
+
* beside one of 100 is a table of 125, and two 30% columns of 10 one of
|
|
1074
|
+
* 33.33.
|
|
653
1075
|
*/
|
|
654
|
-
function
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
if (have < width) {
|
|
671
|
-
const each = (width - have) / (c - start);
|
|
672
|
-
for (let i = start; i < c; i += 1) {
|
|
673
|
-
widths[i] = (widths[i] ?? 0) + each;
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
}
|
|
1076
|
+
function gridMax(max, percent, total, widen) {
|
|
1077
|
+
let most = 0;
|
|
1078
|
+
for (const m of max)
|
|
1079
|
+
most += m;
|
|
1080
|
+
if (!widen || !(total > 0))
|
|
1081
|
+
return most;
|
|
1082
|
+
let estimate = 0;
|
|
1083
|
+
let rest = 0;
|
|
1084
|
+
for (let c = 0; c < max.length; c += 1) {
|
|
1085
|
+
const share = percent[c];
|
|
1086
|
+
if (share !== null && share > 0) {
|
|
1087
|
+
if (max[c] > 0)
|
|
1088
|
+
estimate = Math.max(estimate, (max[c] * 100) / share);
|
|
1089
|
+
}
|
|
1090
|
+
else {
|
|
1091
|
+
rest += max[c];
|
|
677
1092
|
}
|
|
678
|
-
group = c < columnGroups.length ? columnGroups[c] : null;
|
|
679
1093
|
}
|
|
680
|
-
|
|
1094
|
+
const others = rest > 0
|
|
1095
|
+
? total >= 100 - 1e-6
|
|
1096
|
+
? PERCENT_TABLE_MOST
|
|
1097
|
+
: (rest * 100) / (100 - total)
|
|
1098
|
+
: 0;
|
|
1099
|
+
return Math.max(most, others, estimate);
|
|
1100
|
+
}
|
|
1101
|
+
/** A spanning cell's percentage, over the columns it spans: what it has
|
|
1102
|
+
* more than theirs goes to the ones with none, in proportion to their
|
|
1103
|
+
* content, or evenly where they have none. */
|
|
1104
|
+
function spreadPercent(percent, max, share, cell, columnCount) {
|
|
1105
|
+
const last = Math.min(columnCount - 1, cell.column + cell.colSpan - 1);
|
|
1106
|
+
let taken = 0;
|
|
1107
|
+
let weight = 0;
|
|
1108
|
+
let count = 0;
|
|
1109
|
+
for (let c = cell.column; c <= last; c += 1) {
|
|
1110
|
+
const own = percent[c];
|
|
1111
|
+
if (own !== null)
|
|
1112
|
+
taken += own;
|
|
1113
|
+
else {
|
|
1114
|
+
weight += max[c];
|
|
1115
|
+
count += 1;
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
const surplus = share - taken;
|
|
1119
|
+
if (!(surplus > 0) || !count)
|
|
1120
|
+
return;
|
|
1121
|
+
for (let c = cell.column; c <= last; c += 1) {
|
|
1122
|
+
if (percent[c] !== null)
|
|
1123
|
+
continue;
|
|
1124
|
+
percent[c] = weight > 0 ? (surplus * max[c]) / weight : surplus / count;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Whether an auto table's percentages may make it wider than its content
|
|
1129
|
+
* (`gridMax`), as Blink's `AllowColumnPercentages` has it: not where its
|
|
1130
|
+
* width is `max-content`, and where it is measured for what it asks of a
|
|
1131
|
+
* table cell, a flex box or a grid around it, not either — which is the
|
|
1132
|
+
* width it is measured at being no width at all (`Infinity`). Laid out,
|
|
1133
|
+
* a table always may; and measured for a float or an inline block, which
|
|
1134
|
+
* are as wide as it asks, it may too. Chrome's: a table of a 20% column
|
|
1135
|
+
* and a 100px one asks 110 of a cell around it and 125 of a float.
|
|
1136
|
+
*/
|
|
1137
|
+
function percentagesWiden(table, contentWidth) {
|
|
1138
|
+
if (table.style.widthKeyword === 'max-content')
|
|
1139
|
+
return false;
|
|
1140
|
+
if (Number.isFinite(contentWidth))
|
|
1141
|
+
return true;
|
|
1142
|
+
for (let box = table.parent; box; box = box.parent) {
|
|
1143
|
+
if (box.kind === 'table-cell' || box.kind === 'flex')
|
|
1144
|
+
return false;
|
|
1145
|
+
}
|
|
1146
|
+
return true;
|
|
681
1147
|
}
|
|
682
|
-
/**
|
|
683
|
-
*
|
|
684
|
-
*
|
|
1148
|
+
/**
|
|
1149
|
+
* The width each column is set to, or null, from the `<col>` and the
|
|
1150
|
+
* `<colgroup>` it is in. A group is its columns' default and no more: the
|
|
1151
|
+
* width specified for a column is its column's, or else its group's (CSS
|
|
1152
|
+
* Tables 3, 3.8.3), so a `<col>` with a width of its own keeps it however
|
|
1153
|
+
* wide the group is, and one with none takes the group's whole, not a
|
|
1154
|
+
* share of it. A group with no `<col>` stands for each column it spans
|
|
1155
|
+
* (`<colgroup span>`), and each is as wide as the group says. That is
|
|
1156
|
+
* HTML 4's "a default width for each column" and Blink's
|
|
1157
|
+
* (`ColumnConstraintsBuilder`), where CSS 2.1 17.5.2.2 spread a group's
|
|
1158
|
+
* width over its columns wherever theirs came to less: a group of 100
|
|
1159
|
+
* over two columns of 20 made each 50, and over two of none made each 50,
|
|
1160
|
+
* where Chrome has 20 and 100.
|
|
1161
|
+
*
|
|
1162
|
+
* Only a length is a default, raised to the group's `min-width` and the
|
|
1163
|
+
* `<col>`'s, and only in an auto table, as Blink has it: a fixed table
|
|
1164
|
+
* takes no group's width for a `<col>`, and its columns share the table as
|
|
1165
|
+
* though the group set none. A `<col>` with a percentage keeps it in a
|
|
1166
|
+
* fixed table, of the table's width; in an auto table a percentage is no
|
|
1167
|
+
* length (`columnPercents`), and a `<col>` with one takes its group's
|
|
1168
|
+
* length beside it, as Blink's `CreateColumn` does — `<colgroup
|
|
1169
|
+
* style="width:100px"><col style="width:20%">` is a column that needs 100
|
|
1170
|
+
* and is a fifth of the table, which makes the table 500.
|
|
1171
|
+
*/
|
|
1172
|
+
function columnWidths(columnBoxes, columnGroups, base, fixed) {
|
|
1173
|
+
const read = (box) => fixed || !isPct(box.style.width) ? partWidth(box, base) : null;
|
|
1174
|
+
return columnBoxes.map((column, c) => {
|
|
1175
|
+
const group = columnGroups[c];
|
|
1176
|
+
if (!column)
|
|
1177
|
+
return group ? read(group) : null;
|
|
1178
|
+
const own = read(column);
|
|
1179
|
+
if (own !== null || fixed || !group || isPct(group.style.width))
|
|
1180
|
+
return own;
|
|
1181
|
+
const width = read(group);
|
|
1182
|
+
return width === null ? null : Math.max(width, partLeast(column) ?? 0);
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
/** The percentage each column is set to, or null: its `<col>`'s, or its
|
|
1186
|
+
* group's where the group stands for it with no `<col>`. A group's
|
|
1187
|
+
* percentage is no default for the `<col>`s in it, and a column set to
|
|
1188
|
+
* 0% has none, as Blink has both (`ColumnConstraintsBuilder`,
|
|
1189
|
+
* `CreateColumn`). */
|
|
1190
|
+
function columnPercents(columnBoxes, columnGroups) {
|
|
1191
|
+
return columnBoxes.map((column, c) => {
|
|
1192
|
+
const part = column ?? columnGroups[c];
|
|
1193
|
+
return (part && ownPercent(part.style)) || null;
|
|
1194
|
+
});
|
|
1195
|
+
}
|
|
1196
|
+
/** The least each column is, where a `min-width` says (`partLeast`): its
|
|
1197
|
+
* `<col>`'s, or, where it has none, the group it is in. A group's
|
|
1198
|
+
* `min-width` does not reach the `<col>`s in it, as in Blink, which makes
|
|
1199
|
+
* a group's limits a column's only where the group stands for it:
|
|
1200
|
+
* `<colgroup style="min-width:100px">` over two `<col>`s of 10px content
|
|
1201
|
+
* is 20 wide, where the 100 was spread over them. */
|
|
1202
|
+
function columnLeast(columnBoxes, columnGroups) {
|
|
1203
|
+
return columnBoxes.map((column, c) => {
|
|
1204
|
+
const part = column ?? columnGroups[c];
|
|
1205
|
+
return part ? partLeast(part) : null;
|
|
1206
|
+
});
|
|
1207
|
+
}
|
|
1208
|
+
/** A column's or a column group's width, or null where it sets none: its
|
|
1209
|
+
* `width`, raised to a length `min-width`. A `max-width` does nothing to
|
|
1210
|
+
* it. CSS Tables 3, 3.8.2 makes a column's outer min-content width
|
|
1211
|
+
* `max(min-width, width)`, which is at least as wide as any `max-width`
|
|
1212
|
+
* could hold it to, and Blink reads no `max-width` on a column at all
|
|
1213
|
+
* (`CreateColumn`): `<col style="width:100px; max-width:50px">` is 100
|
|
1214
|
+
* wide, where it was 50. A percentage `max-width` holds a percentage
|
|
1215
|
+
* `width` (`ownWidth`), and a percentage `min-width` is ignored. */
|
|
685
1216
|
function partWidth(box, base) {
|
|
686
|
-
const
|
|
687
|
-
|
|
688
|
-
const min = typeof style.minWidth === 'number' ? style.minWidth : 0;
|
|
689
|
-
if (width === null && !(min > 0))
|
|
1217
|
+
const width = ownWidth(box.style, base);
|
|
1218
|
+
if (width === null)
|
|
690
1219
|
return null;
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
*
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
*
|
|
705
|
-
*
|
|
706
|
-
*
|
|
707
|
-
* percentage
|
|
708
|
-
*
|
|
709
|
-
*
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
const width =
|
|
715
|
-
|
|
716
|
-
if (width === null && !(min > 0))
|
|
1220
|
+
return Math.max(0, width, partLeast(box) ?? 0);
|
|
1221
|
+
}
|
|
1222
|
+
/** The least a column or a column group is, where a length `min-width`
|
|
1223
|
+
* says: what its cells ask is held up to it, and it sets no width of its
|
|
1224
|
+
* own. A fixed table's columns ask nothing, so there it is only the least
|
|
1225
|
+
* the table is (`fixedColumns`). Read as a width, `<col
|
|
1226
|
+
* style="min-width:100px">` held a column of prose to 100 in an auto
|
|
1227
|
+
* table, where it is as wide as the prose is. */
|
|
1228
|
+
function partLeast(box) {
|
|
1229
|
+
const min = box.style.minWidth;
|
|
1230
|
+
return typeof min === 'number' && min > 0 ? min : null;
|
|
1231
|
+
}
|
|
1232
|
+
/** A cell's own length `width`, its padding and borders in, or null. A
|
|
1233
|
+
* length is held within a length `max-width`, which CSS 2.1 leaves
|
|
1234
|
+
* undefined and every browser does: a cell set `width: 3in; max-width:
|
|
1235
|
+
* 1in` is an inch wide, and one `width: 100px; max-width: 10%` 100, a
|
|
1236
|
+
* percentage limit holding a percentage only (CSS Tables 3, 3.8.2). A
|
|
1237
|
+
* `min-width` sets no width, and holds up what the cell asks instead. A
|
|
1238
|
+
* percentage is its column's (`ownPercent`). */
|
|
1239
|
+
function cellWidth(cell) {
|
|
1240
|
+
const box = cell.box;
|
|
1241
|
+
if (isPct(box.style.width))
|
|
1242
|
+
return null;
|
|
1243
|
+
const width = lengthAgainst(box.style.width, NaN);
|
|
1244
|
+
if (width === null)
|
|
717
1245
|
return null;
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
1246
|
+
return Math.min(outerWidth(box, width), cellLimits(box).cap);
|
|
1247
|
+
}
|
|
1248
|
+
/** A cell's, a column's or a column group's percentage `width`, or null
|
|
1249
|
+
* where it is none: no more than a percentage `max-width` (CSS Tables 3,
|
|
1250
|
+
* 3.8.2, as `ownWidth`), and not a `calc()` that adds a length to it,
|
|
1251
|
+
* which is `auto` (`tableWidth`). In an auto table it is a share of the
|
|
1252
|
+
* table that the cell's padding and borders are part of, as browsers
|
|
1253
|
+
* read it: added on, a 90% cell and a 10% one came to more than the
|
|
1254
|
+
* table, which then took it back from both. In a fixed one a
|
|
1255
|
+
* `content-box` cell's are added to it, as Blink's
|
|
1256
|
+
* `percent_border_padding` has them (`fixedColumns`). A length
|
|
1257
|
+
* `max-width` does not hold it: `width: 50%; max-width: 100px` is half
|
|
1258
|
+
* the table, as in Blink, which keeps a percentage apart from the lengths
|
|
1259
|
+
* that cap what a cell's content asks. */
|
|
1260
|
+
function ownPercent(style) {
|
|
1261
|
+
const width = style.width;
|
|
1262
|
+
if (!isPct(width) || width.px || width.of)
|
|
1263
|
+
return null;
|
|
1264
|
+
const limit = style.maxWidth;
|
|
1265
|
+
if (limit === 'none' || !isPct(limit) || limit.px || limit.of) {
|
|
1266
|
+
return width.pct;
|
|
725
1267
|
}
|
|
726
|
-
return
|
|
1268
|
+
return Math.min(width.pct, limit.pct);
|
|
1269
|
+
}
|
|
1270
|
+
/** A cell's length `min-width` and `max-width` as border-box widths, the
|
|
1271
|
+
* limit raised to the least, or 0 and Infinity where it has none: what
|
|
1272
|
+
* its column sizes its content within (`autoColumns`), and a fixed table
|
|
1273
|
+
* its width (`fixedColumns`). As Blink's `InlineSizesFromStyle`. */
|
|
1274
|
+
function cellLimits(box) {
|
|
1275
|
+
const { minWidth, maxWidth } = box.style;
|
|
1276
|
+
const floor = typeof minWidth === 'number' ? outerWidth(box, minWidth) : 0;
|
|
1277
|
+
const cap = typeof maxWidth === 'number'
|
|
1278
|
+
? Math.max(outerWidth(box, maxWidth), floor)
|
|
1279
|
+
: Infinity;
|
|
1280
|
+
return { floor, cap };
|
|
1281
|
+
}
|
|
1282
|
+
/** A width a cell's style gives, as its border box. */
|
|
1283
|
+
function outerWidth(box, width) {
|
|
1284
|
+
return box.style.boxSizing === 'border-box'
|
|
1285
|
+
? Math.max(width, box.horizontalExtra)
|
|
1286
|
+
: width + box.horizontalExtra;
|
|
727
1287
|
}
|
|
728
1288
|
/** A cell's, a column's or a column group's `width` as the columns read
|
|
729
1289
|
* it, or null: a percentage no more than a percentage its `max-width` is
|
|
@@ -757,13 +1317,17 @@ function lengthAgainst(len, base) {
|
|
|
757
1317
|
* row's: 0 for a cell aligned `top`, `middle` or `bottom`, which is placed
|
|
758
1318
|
* once the row's height is known. A cell's baseline is its first line box's,
|
|
759
1319
|
* at any depth, or the bottom of its content where it has none (CSS 2.1
|
|
760
|
-
* 17.5.3); it is taken as laid out, from the cell's top.
|
|
761
|
-
*
|
|
762
|
-
*
|
|
763
|
-
*
|
|
1320
|
+
* 17.5.3); it is taken as laid out, from the cell's top. That bottom is of
|
|
1321
|
+
* the content (`natural`) and not of a height the cell sets, which "does not
|
|
1322
|
+
* increase the height of the cell box": taken from the set height, a cell
|
|
1323
|
+
* holding one block of 30px in 80px hung the text beside it from 80px. An
|
|
1324
|
+
* empty cell has nothing to align and says nothing about the row's, as in a
|
|
1325
|
+
* browser. `own` is each cell's baseline from its top, NaN where it has none
|
|
1326
|
+
* to give.
|
|
764
1327
|
*/
|
|
765
|
-
function baselineLifts(cells, rowCount) {
|
|
1328
|
+
function baselineLifts(cells, rowCount, natural) {
|
|
766
1329
|
const lift = new Array(cells.length).fill(0);
|
|
1330
|
+
const own = new Array(cells.length).fill(NaN);
|
|
767
1331
|
let any = false;
|
|
768
1332
|
for (const cell of cells) {
|
|
769
1333
|
const va = cell.box.style.verticalAlign;
|
|
@@ -771,8 +1335,7 @@ function baselineLifts(cells, rowCount) {
|
|
|
771
1335
|
any = true;
|
|
772
1336
|
}
|
|
773
1337
|
if (!any)
|
|
774
|
-
return lift;
|
|
775
|
-
const own = new Array(cells.length).fill(NaN);
|
|
1338
|
+
return { lift, own };
|
|
776
1339
|
const row = new Array(rowCount).fill(-Infinity);
|
|
777
1340
|
for (let i = 0; i < cells.length; i += 1) {
|
|
778
1341
|
const box = cells[i].box;
|
|
@@ -785,14 +1348,14 @@ function baselineLifts(cells, rowCount) {
|
|
|
785
1348
|
own[i] =
|
|
786
1349
|
first !== null
|
|
787
1350
|
? first - box.y
|
|
788
|
-
:
|
|
1351
|
+
: natural[i] - box.padBottom - box.borderBottom;
|
|
789
1352
|
row[cells[i].row] = Math.max(row[cells[i].row], own[i]);
|
|
790
1353
|
}
|
|
791
1354
|
for (let i = 0; i < cells.length; i += 1) {
|
|
792
1355
|
if (own[i] === own[i])
|
|
793
1356
|
lift[i] = row[cells[i].row] - own[i];
|
|
794
1357
|
}
|
|
795
|
-
return lift;
|
|
1358
|
+
return { lift, own };
|
|
796
1359
|
}
|
|
797
1360
|
/** Whether a cell has anything in flow in it. */
|
|
798
1361
|
function holdsFlow(box) {
|
|
@@ -828,27 +1391,40 @@ function rowSizing(rows, cells) {
|
|
|
828
1391
|
}
|
|
829
1392
|
/**
|
|
830
1393
|
* The height a table has past its rows' given out among them, as browsers
|
|
831
|
-
* give it (the CSS Tables 3 draft leaves it open, csswg-drafts#4418
|
|
832
|
-
*
|
|
833
|
-
*
|
|
834
|
-
*
|
|
835
|
-
*
|
|
836
|
-
*
|
|
1394
|
+
* give it (the CSS Tables 3 draft leaves it open, csswg-drafts#4418; this is
|
|
1395
|
+
* Blink's `DistributeExcessBlockSizeToRows`): first to the rows a percentage
|
|
1396
|
+
* of `base` sets, up to it; then to the rows nothing sets that have
|
|
1397
|
+
* content, in proportion to their heights; then, where every row with
|
|
1398
|
+
* content is set, to the empty rows, those nothing sets first, evenly; and
|
|
1399
|
+
* else to every row with content, in proportion. A row whose cells set its
|
|
1400
|
+
* height keeps it while an empty row beside it takes the rest.
|
|
1401
|
+
*
|
|
1402
|
+
* What a cell spanning the rows `first` to `last` needs past them goes out
|
|
1403
|
+
* the same way, with three differences, and `starts` says which rows a
|
|
1404
|
+
* spanning cell starts in. A percentage is of nothing yet (`base` is null),
|
|
1405
|
+
* so a row one sets is a row nothing sets. Before anything else, the rows
|
|
1406
|
+
* after the first that another spanning cell starts in take it all,
|
|
1407
|
+
* evenly. And empty rows take none of it unless every row is empty, when
|
|
1408
|
+
* the last takes it.
|
|
837
1409
|
*/
|
|
838
|
-
function growRows(heights, sizing, extra, base) {
|
|
839
|
-
const
|
|
840
|
-
const deficit = heights.map((h, r) => {
|
|
841
|
-
const pct = sizing[r].percent;
|
|
842
|
-
return pct ? Math.max(0, (pct / 100) * base - h) : 0;
|
|
843
|
-
});
|
|
1410
|
+
function growRows(heights, sizing, extra, base, first = 0, last = heights.length - 1, starts = null) {
|
|
1411
|
+
const deficit = [];
|
|
844
1412
|
const empty = [];
|
|
845
1413
|
const nothingSetEmpty = [];
|
|
846
1414
|
const full = [];
|
|
847
1415
|
const nothingSetFull = [];
|
|
1416
|
+
const spanned = [];
|
|
848
1417
|
let setFull = 0;
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
1418
|
+
let owed = 0;
|
|
1419
|
+
for (let r = first; r <= last; r += 1) {
|
|
1420
|
+
const pct = sizing[r].percent;
|
|
1421
|
+
const short = pct && base !== null ? Math.max(0, (pct / 100) * base - heights[r]) : 0;
|
|
1422
|
+
deficit.push(short);
|
|
1423
|
+
owed += short;
|
|
1424
|
+
const set = pct !== null ? base !== null : sizing[r].constrained;
|
|
1425
|
+
if (starts?.[r] && r > first)
|
|
1426
|
+
spanned.push(r);
|
|
1427
|
+
if (heights[r] === 0 && short === 0) {
|
|
852
1428
|
empty.push(r);
|
|
853
1429
|
if (!set)
|
|
854
1430
|
nothingSetEmpty.push(r);
|
|
@@ -861,11 +1437,11 @@ function growRows(heights, sizing, extra, base) {
|
|
|
861
1437
|
nothingSetFull.push(r);
|
|
862
1438
|
}
|
|
863
1439
|
}
|
|
864
|
-
const owed = deficit.reduce((a, b) => a + b, 0);
|
|
865
1440
|
if (owed > 0) {
|
|
866
1441
|
const give = Math.min(owed, extra);
|
|
867
|
-
for (let r =
|
|
868
|
-
heights[r] += (give * deficit[r]) / owed;
|
|
1442
|
+
for (let r = first; r <= last; r += 1) {
|
|
1443
|
+
heights[r] += (give * deficit[r - first]) / owed;
|
|
1444
|
+
}
|
|
869
1445
|
extra -= give;
|
|
870
1446
|
if (!(extra > 0))
|
|
871
1447
|
return;
|
|
@@ -879,9 +1455,17 @@ function growRows(heights, sizing, extra, base) {
|
|
|
879
1455
|
even || !(sum > 0) ? extra / which.length : (extra * heights[r]) / sum;
|
|
880
1456
|
}
|
|
881
1457
|
};
|
|
882
|
-
if (
|
|
1458
|
+
if (spanned.length)
|
|
1459
|
+
grow(spanned, true);
|
|
1460
|
+
else if (nothingSetFull.length)
|
|
883
1461
|
grow(nothingSetFull, false);
|
|
884
|
-
else if (
|
|
1462
|
+
else if (starts) {
|
|
1463
|
+
if (full.length)
|
|
1464
|
+
grow(full, false);
|
|
1465
|
+
else
|
|
1466
|
+
heights[last] += extra;
|
|
1467
|
+
}
|
|
1468
|
+
else if (empty.length && empty.length + setFull === last - first + 1) {
|
|
885
1469
|
grow(nothingSetEmpty.length ? nothingSetEmpty : empty, true);
|
|
886
1470
|
}
|
|
887
1471
|
else
|