@react-x11/components 0.14.1 → 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.
Files changed (96) hide show
  1. package/dist/code-editor/node.d.ts +13 -0
  2. package/dist/code-editor/node.d.ts.map +1 -1
  3. package/dist/code-editor/node.js +23 -5
  4. package/dist/code-editor/node.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts +171 -26
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +616 -216
  8. package/dist/html/css/cascade.js.map +1 -1
  9. package/dist/html/css/color.d.ts +25 -0
  10. package/dist/html/css/color.d.ts.map +1 -1
  11. package/dist/html/css/color.js +307 -19
  12. package/dist/html/css/color.js.map +1 -1
  13. package/dist/html/css/content.d.ts +11 -6
  14. package/dist/html/css/content.d.ts.map +1 -1
  15. package/dist/html/css/content.js +171 -12
  16. package/dist/html/css/content.js.map +1 -1
  17. package/dist/html/css/parse.d.ts.map +1 -1
  18. package/dist/html/css/parse.js +62 -9
  19. package/dist/html/css/parse.js.map +1 -1
  20. package/dist/html/css/shapes.d.ts +18 -0
  21. package/dist/html/css/shapes.d.ts.map +1 -1
  22. package/dist/html/css/shapes.js +21 -1
  23. package/dist/html/css/shapes.js.map +1 -1
  24. package/dist/html/css/style.d.ts +42 -5
  25. package/dist/html/css/style.d.ts.map +1 -1
  26. package/dist/html/css/style.js +74 -12
  27. package/dist/html/css/style.js.map +1 -1
  28. package/dist/html/css/ua.js +15 -0
  29. package/dist/html/css/ua.js.map +1 -1
  30. package/dist/html/css/values.d.ts +5 -0
  31. package/dist/html/css/values.d.ts.map +1 -1
  32. package/dist/html/css/values.js +18 -9
  33. package/dist/html/css/values.js.map +1 -1
  34. package/dist/html/index.d.ts.map +1 -1
  35. package/dist/html/index.js +7 -0
  36. package/dist/html/index.js.map +1 -1
  37. package/dist/html/layout/block.d.ts +29 -9
  38. package/dist/html/layout/block.d.ts.map +1 -1
  39. package/dist/html/layout/block.js +141 -52
  40. package/dist/html/layout/block.js.map +1 -1
  41. package/dist/html/layout/boxes.d.ts +16 -2
  42. package/dist/html/layout/boxes.d.ts.map +1 -1
  43. package/dist/html/layout/boxes.js +144 -23
  44. package/dist/html/layout/boxes.js.map +1 -1
  45. package/dist/html/layout/css-grid.d.ts.map +1 -1
  46. package/dist/html/layout/css-grid.js +11 -9
  47. package/dist/html/layout/css-grid.js.map +1 -1
  48. package/dist/html/layout/flex.d.ts.map +1 -1
  49. package/dist/html/layout/flex.js +1631 -221
  50. package/dist/html/layout/flex.js.map +1 -1
  51. package/dist/html/layout/inline.d.ts +15 -3
  52. package/dist/html/layout/inline.d.ts.map +1 -1
  53. package/dist/html/layout/inline.js +23 -8
  54. package/dist/html/layout/inline.js.map +1 -1
  55. package/dist/html/layout/table.d.ts +27 -1
  56. package/dist/html/layout/table.d.ts.map +1 -1
  57. package/dist/html/layout/table.js +886 -324
  58. package/dist/html/layout/table.js.map +1 -1
  59. package/dist/html/node.d.ts +35 -4
  60. package/dist/html/node.d.ts.map +1 -1
  61. package/dist/html/node.js +118 -26
  62. package/dist/html/node.js.map +1 -1
  63. package/dist/html/paint.d.ts.map +1 -1
  64. package/dist/html/paint.js +558 -178
  65. package/dist/html/paint.js.map +1 -1
  66. package/dist/html/resources.js +14 -1
  67. package/dist/html/resources.js.map +1 -1
  68. package/dist/html/svg.d.ts +23 -7
  69. package/dist/html/svg.d.ts.map +1 -1
  70. package/dist/html/svg.js +264 -59
  71. package/dist/html/svg.js.map +1 -1
  72. package/dist/html/widgets.d.ts.map +1 -1
  73. package/dist/html/widgets.js +57 -4
  74. package/dist/html/widgets.js.map +1 -1
  75. package/package.json +3 -3
  76. package/src/code-editor/node.ts +24 -5
  77. package/src/html/css/cascade.ts +697 -221
  78. package/src/html/css/color.ts +345 -18
  79. package/src/html/css/content.ts +173 -15
  80. package/src/html/css/parse.ts +60 -8
  81. package/src/html/css/shapes.ts +35 -1
  82. package/src/html/css/style.ts +122 -17
  83. package/src/html/css/ua.ts +15 -0
  84. package/src/html/css/values.ts +23 -9
  85. package/src/html/index.ts +7 -0
  86. package/src/html/layout/block.ts +149 -58
  87. package/src/html/layout/boxes.ts +171 -27
  88. package/src/html/layout/css-grid.ts +12 -9
  89. package/src/html/layout/flex.ts +1843 -212
  90. package/src/html/layout/inline.ts +27 -7
  91. package/src/html/layout/table.ts +961 -332
  92. package/src/html/node.ts +151 -27
  93. package/src/html/paint.ts +692 -234
  94. package/src/html/resources.ts +13 -1
  95. package/src/html/svg.ts +299 -56
  96. package/src/html/widgets.ts +61 -4
@@ -13,12 +13,28 @@
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, FLEXED_HEIGHT, MIN_CONTENT_PROBE, STRETCHED_ACROSS, USED_HEIGHT, 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
- /** Lay out a table's insides. Returns the content height. */
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 { rows, captions, cells, columnCount, columnBoxes, columnGroups } = tableGrid(table);
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
@@ -27,33 +43,10 @@ export function layoutTable(table, ctx, contentWidth) {
27
43
  // — and one a grid stretches across its area is as wide as that, as a
28
44
  // table with a width of its own is (`STRETCHED_ACROSS`)
29
45
  const auto = own && !STRETCHED_ACROSS.has(table);
30
- if (!columnCount) {
31
- // With no columns, an auto table is as wide as its widest caption can
32
- // be and its `min-width` asks, as one with columns is at the least
33
- // (17.5.2), rather than as the room on offer: a caption over no cells
34
- // was centred in the width of the page.
35
- if (auto) {
36
- let inner = captions.length
37
- ? captionMinimum(captions, ctx) - table.horizontalExtra
38
- : 0;
39
- const min = resolveOrNull(table.style.minWidth, contentWidth);
40
- if (min !== null) {
41
- inner = Math.max(inner, table.style.boxSizing === 'border-box'
42
- ? min - table.horizontalExtra
43
- : min);
44
- }
45
- table.width = Math.max(0, inner) + table.horizontalExtra;
46
- }
47
- layoutCaptions(captions, ctx, table);
46
+ if (!grid.columnCount) {
47
+ layoutColumnless(table, grid.captions, ctx, contentWidth, auto);
48
48
  return 0;
49
49
  }
50
- const style = table.style;
51
- // between the columns, and between the rows (CSS 2.1 17.6.1)
52
- const apart = style.borderCollapse !== 'collapse';
53
- const spacing = apart ? style.borderSpacing : 0;
54
- const rowSpacing = apart ? style.borderSpacingY : 0;
55
- const gaps = spacing * (columnCount + 1);
56
- const available = Math.max(0, contentWidth - gaps);
57
50
  // While the columns are sized, a percentage in a cell's padding is of
58
51
  // nothing: the width it is of is the one the columns are being sized to
59
52
  // come to (CSS Sizing 3, 5.2.1), and a cell asks of its column what its
@@ -61,21 +54,56 @@ export function layoutTable(table, ctx, contentWidth) {
61
54
  // widths, below. Taken of the table's width here, and of the cell's own
62
55
  // where the cell was laid out, a padded cell's column was wider than
63
56
  // its share and its padding narrower.
64
- for (const cell of cells)
57
+ for (const cell of grid.cells)
65
58
  resolveEdges(cell.box, 0);
66
59
  // a fixed layout needs a width to be fixed to; with `auto` a table is laid
67
60
  // out by its contents after all (CSS 2.1 17.5.2.1), stretched or not
68
- const fixed = style.tableLayout === 'fixed' && !own;
69
- // a percentage of the table's width less its spacing (CSS 2.1 17.5.2.1)
70
- const set = columnWidths(columnBoxes, columnGroups, available);
71
- const widths = fixed
72
- ? fixedColumns(cells, set, columnCount, available, spacing)
73
- : autoColumns(cells, set, columnCount, available, ctx, contentWidth, !auto, spacing);
74
- // --- place ---------------------------------------------------------------
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);
75
102
  // An auto table is at least as wide as its widest caption can be (CSS 2.1
76
- // 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;
77
106
  if (auto) {
78
- let room = 0;
79
107
  if (captions.length) {
80
108
  room = captionMinimum(captions, ctx) - table.horizontalExtra - gaps;
81
109
  }
@@ -84,16 +112,17 @@ export function layoutTable(table, ctx, contentWidth) {
84
112
  const inner = style.boxSizing === 'border-box' ? min - table.horizontalExtra : min;
85
113
  room = Math.max(room, inner - gaps);
86
114
  }
87
- const sum = widths.reduce((a, b) => a + b, 0);
88
- if (room > sum) {
89
- for (let c = 0; c < columnCount; c += 1) {
90
- widths[c] +=
91
- sum > 0
92
- ? (widths[c] / sum) * (room - sum)
93
- : (room - sum) / columnCount;
94
- }
95
- }
96
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 ---------------------------------------------------------------
97
126
  // A column that `visibility: collapse` takes out of the table has been
98
127
  // sized with the rest, and is no room at all where they are placed (CSS
99
128
  // 2.1 17.5.5): no width, and no spacing after it. Its cells are still
@@ -132,45 +161,74 @@ export function layoutTable(table, ctx, contentWidth) {
132
161
  tableContentWidth = used;
133
162
  table.width = used + table.horizontalExtra;
134
163
  }
135
- layoutCaptions(captions, ctx, table);
136
- const top = table.contentY + table.captionTop;
137
- let y = top;
138
- const rowTop = new Array(rows.length);
139
- const rowHeight = new Array(rows.length).fill(0);
140
- y += rowSpacing;
141
- // Size every cell at its column width first, so a row's height is the
142
- // tallest cell in it rather than the first one that was measured. What
143
- // its content came to is kept apart from a height the cell sets: it is
144
- // that content `vertical-align` moves in the cell.
145
- //
146
- // A percentage in a cell's padding is of the width of its row: the
147
- // columns and the spacing between them, without the spacing either side
148
- // and the table's own padding. CSS 2.1 8.4 has it of the containing
149
- // block's width and names none for a cell; the row's is what browsers
150
- // take, and what the table's content box is wherever there is no
151
- // spacing. Every column counts, one `visibility: collapse` takes out
152
- // too, so that taking it out changes no row's height.
153
- let rowWidth = spacing * Math.max(0, columnCount - 1);
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);
154
186
  for (const columnWidth of widths)
155
187
  rowWidth += columnWidth;
156
188
  const natural = new Array(cells.length);
157
189
  for (let i = 0; i < cells.length; i += 1) {
158
190
  const cell = cells[i];
159
191
  const width = spannedWidth(widths, cell, spacing);
160
- ctx.layoutSubtree(cell.box, width, rowWidth);
192
+ layoutSubtree(cell.box, ctx, width, rowWidth);
161
193
  natural[i] = CELL_CONTENT.get(cell.box) ?? cell.box.height;
162
194
  // A height a cell sets is a least one: its content is laid out as a
163
195
  // block's, which keeps a height it sets and lets the content run out of
164
196
  // it, and a cell does not. `<td height="10">` with a line in it is as
165
197
  // tall as the line (CSS 2.1 17.5.3), where it was 10px with the line
166
198
  // hanging out of it.
167
- if (natural[i] > cell.box.height)
168
- cell.box.height = natural[i];
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];
169
208
  const specified = resolveOrNull(cell.box.style.height, NaN);
170
209
  if (specified !== null) {
171
- cell.box.height = Math.max(cell.box.height, specified + cell.box.verticalExtra);
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);
172
215
  }
173
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;
174
232
  // A cell aligned on the baseline — every value but `top`, `middle` and
175
233
  // `bottom` — hangs its first line on its first row's baseline, the lowest
176
234
  // of theirs from the row's top (CSS 2.1 17.5.3), and the row is as tall as
@@ -178,17 +236,23 @@ export function layoutTable(table, ctx, contentWidth) {
178
236
  // needs the lift and its content, and a height it sets is a least one
179
237
  // beside that rather than more room under the lift: two cells set
180
238
  // `height: 80px` with text of two sizes in them are a row 80px tall, where
181
- // the lift on top of the height made it 104px. A cell spanning rows needs
182
- // the same of them, which is where Chrome differs: it sizes a spanning
183
- // cell by its content and height alone and lets the lifted content hang
184
- // out of it, and the rows here "encompass the cell" (17.5.3).
185
- const lift = baselineLifts(cells, rows.length, natural);
186
- const needs = (i) => Math.max(cells[i].box.height, lift[i] + natural[i]);
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]);
187
250
  for (let i = 0; i < cells.length; i += 1) {
188
251
  const cell = cells[i];
189
- if (cell.rowSpan > 1)
190
- continue;
191
- rowHeight[cell.row] = Math.max(rowHeight[cell.row], needs(i));
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;
192
256
  }
193
257
  for (let r = 0; r < rows.length; r += 1) {
194
258
  const specified = resolveOrNull(rows[r].style.height, NaN);
@@ -197,20 +261,59 @@ export function layoutTable(table, ctx, contentWidth) {
197
261
  }
198
262
  // A row-spanning cell only forces height when the rows it covers do not
199
263
  // already provide it — otherwise a `rowspan="3"` cell would make each of
200
- // 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);
201
271
  for (let i = 0; i < cells.length; i += 1) {
202
- const cell = cells[i];
203
- if (cell.rowSpan <= 1)
204
- continue;
205
- const last = Math.min(rows.length - 1, cell.row + cell.rowSpan - 1);
206
- let covered = 0;
207
- for (let r = cell.row; r <= last; r += 1)
208
- covered += rowHeight[r];
209
- covered += rowSpacing * (last - cell.row);
210
- const missing = needs(i) - covered;
211
- if (missing > 0)
212
- rowHeight[last] += missing;
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
+ }
213
315
  }
316
+ TABLE_CONTENT.set(table, rowsOwn + table.captionTop + table.captionBottom + table.verticalExtra);
214
317
  // A table's own height, within its least and greatest, is a least
215
318
  // height: what its rows come short of it goes to them (CSS 2.1 17.5.3,
216
319
  // which leaves how to its user agents: `growRows`). So is the height a
@@ -218,9 +321,13 @@ export function layoutTable(table, ctx, contentWidth) {
218
321
  // `USED_HEIGHT`), which is its wrapper box's, captions and all (CSS
219
322
  // Flexbox 1, 4): taken as the table's box alone, a table stretched down
220
323
  // its line kept its rows at their content's height, and a cell had
221
- // nothing for `vertical-align` to centre its content in.
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`).
222
329
  const wanted = resolveOrNull(style.height, table.percentHeightBase);
223
- const given = FLEXED_HEIGHT.get(table) ?? USED_HEIGHT.get(table) ?? NaN;
330
+ const given = givenHeight(table);
224
331
  if (rows.length) {
225
332
  const extraBox = table.verticalExtra;
226
333
  const set = wanted === null
@@ -228,10 +335,9 @@ export function layoutTable(table, ctx, contentWidth) {
228
335
  : style.boxSizing === 'border-box'
229
336
  ? wanted
230
337
  : wanted + extraBox;
231
- let inner = clampHeight(table, set) - extraBox;
232
- if (Number.isFinite(given)) {
233
- inner = Math.max(inner, given - table.captionTop - table.captionBottom);
234
- }
338
+ const inner = Number.isFinite(given)
339
+ ? given - table.captionTop - table.captionBottom
340
+ : clampHeight(table, set) - extraBox;
235
341
  let total = 0;
236
342
  for (const h of rowHeight)
237
343
  total += h;
@@ -349,7 +455,7 @@ export function layoutTable(table, ctx, contentWidth) {
349
455
  // left edge to the last one's right, the spacing around them outside it
350
456
  // (CSS 2.1 17.5.1), as it is above and below.
351
457
  const gridX = table.contentX + spacing;
352
- const gridWidth = Math.max(0, x - spacing - gridX);
458
+ const gridWidth = Math.max(0, columns.end - spacing - gridX);
353
459
  for (let r = 0; r < rows.length; r += 1) {
354
460
  const row = rows[r];
355
461
  row.x = gridX;
@@ -373,6 +479,18 @@ export function layoutTable(table, ctx, contentWidth) {
373
479
  }
374
480
  return y - top;
375
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
+ }
376
494
  /** Whether `visibility: collapse` takes a column out: its own, or the
377
495
  * column group's that holds it with no column box of its own. */
378
496
  function collapsedColumn(column, group) {
@@ -449,124 +567,279 @@ function spannedWidth(widths, cell, spacing) {
449
567
  /**
450
568
  * `table-layout: fixed`: the columns and the first row decide, and the rest
451
569
  * of the table is not measured at all, which is the whole point of it (CSS
452
- * 2.1 17.5.2.1). A column with a `width` sets its own; failing that, a cell
453
- * of the first row with one sets the columns it spans, by its border box and
454
- * less the spacing between them; the columns left share what remains, and
455
- * where nothing remains, what is left of a table wider than its columns is
456
- * shared among all of them.
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.
457
594
  */
458
- function fixedColumns(cells, columnWidths, columnCount, available, spacing) {
459
- const widths = new Array(columnCount).fill(0);
460
- const set = new Array(columnCount).fill(false);
461
- for (let c = 0; c < columnCount; c += 1) {
462
- const px = columnWidths[c];
463
- if (px === null)
464
- continue;
465
- widths[c] = px;
466
- set[c] = true;
467
- }
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);
468
601
  for (const cell of cells) {
469
602
  if (cell.row > 0)
470
603
  break;
471
- const px = ownWidth(cell.box.style, available);
472
- if (px === null)
473
- continue;
474
604
  const box = cell.box;
475
- const outer = box.style.boxSizing === 'border-box'
476
- ? Math.max(px, box.horizontalExtra)
477
- : px + box.horizontalExtra;
478
- const last = Math.min(columnCount, cell.column + cell.colSpan);
479
- let taken = spacing * (last - cell.column - 1);
480
- let open = 0;
481
- for (let c = cell.column; c < last; c += 1) {
482
- if (set[c])
483
- taken += widths[c];
484
- else
485
- open += 1;
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;
486
624
  }
487
- if (!open)
625
+ const px = ownWidth(box.style, available);
626
+ if (px === null)
488
627
  continue;
489
- const each = Math.max(0, outer - taken) / open;
490
- for (let c = cell.column; c < last; c += 1) {
491
- if (set[c])
492
- continue;
493
- widths[c] = each;
494
- set[c] = true;
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;
495
638
  }
496
639
  }
497
- let used = 0;
498
- let unset = 0;
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;
499
645
  for (let c = 0; c < columnCount; c += 1) {
500
- if (set[c])
501
- used += widths[c];
502
- else
503
- unset += 1;
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
+ }
504
683
  }
505
- const remaining = Math.max(0, available - used);
506
- if (unset) {
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;
507
691
  for (let c = 0; c < columnCount; c += 1) {
508
- if (!set[c])
509
- widths[c] = remaining / unset;
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];
510
697
  }
511
698
  }
512
- else if (remaining > 0 && columnCount) {
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);
513
706
  for (let c = 0; c < columnCount; c += 1) {
514
- widths[c] +=
515
- used > 0 ? (widths[c] / used) * remaining : remaining / columnCount;
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);
516
728
  }
517
729
  }
518
730
  return widths;
519
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
+ }
520
757
  /**
521
758
  * The auto algorithm: measure every cell's max-content and min-content
522
- * width, then distribute.
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`).
523
762
  *
524
- * The distribution is the part worth stating. When the max-content widths
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
525
769
  * fit, they are used — a small table is exactly as wide as its contents.
526
- * When they do not, the surplus over each column's minimum is scaled down in
527
- * proportion, so a column of long prose gives up more than a column of
528
- * dates. A column with an explicit `width` is honoured before either.
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.
529
796
  */
530
- function autoColumns(cells, columnWidths, columnCount, available, ctx, containingWidth, fill, spacing) {
797
+ function autoColumns(cells, columnWidths, columnLeast, columnPercents, columnCount, available, fill, spacing, room, widen) {
531
798
  const max = new Array(columnCount).fill(0);
532
799
  const min = new Array(columnCount).fill(0);
533
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();
534
803
  const spanning = [];
804
+ // each cell's outer min-content and max-content, within its limits
805
+ const asks = new Map();
535
806
  for (const cell of cells) {
536
- // Two probe layouts per cell: unconstrained for max-content, and at no
537
- // width for min-content (`MIN_CONTENT_PROBE`) — which the line breaker
538
- // answers by breaking at every opportunity, so the widest line is the
539
- // longest unbreakable word.
540
- //
541
- // Cached on the box, because intrinsic widths are width-independent by
542
- // definition — measuring them per layout pass made a *resize* of a
543
- // 500-row table cost as much as its first layout (223ms of re-probing
544
- // measured against 10ms with the cache). The cache's invalidation rule
545
- // is the box's lifetime: any DOM or style change rebuilds the tree.
546
- if (cell.box.intrinsicMaxContent < 0) {
547
- cell.box.intrinsicMaxContent = measureIntrinsicWidth(cell.box, ctx, Infinity);
548
- // where its words say it exactly, read from the layout just made
549
- cell.box.intrinsicMinContent =
550
- (ctx.fonts && exactMinContent(cell.box, ctx.fonts)) ??
551
- measureIntrinsicWidth(cell.box, ctx, MIN_CONTENT_PROBE, true);
552
- }
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);
553
815
  if (cell.colSpan > 1) {
554
816
  spanning.push(cell);
555
817
  continue;
556
818
  }
557
- max[cell.column] = Math.max(max[cell.column], cell.box.intrinsicMaxContent);
558
- min[cell.column] = Math.max(min[cell.column], cell.box.intrinsicMinContent);
559
- const width = cellWidth(cell, containingWidth);
560
- if (width !== null) {
561
- explicit[cell.column] = Math.max(explicit[cell.column] ?? 0, width);
562
- }
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);
563
829
  }
564
830
  // a column's own width counts as its cells' do: at least that, and its
565
- // 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)
566
834
  for (let c = 0; c < columnCount; c += 1) {
567
835
  const own = columnWidths[c];
568
836
  if (own !== null)
569
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
+ }
570
843
  }
571
844
  // A spanning cell comes after the cells of one column, the narrower
572
845
  // spans first, and adds to its columns only what they come short of it
@@ -576,174 +849,441 @@ function autoColumns(cells, columnWidths, columnCount, available, ctx, containin
576
849
  // they have none. Taken in document order and spread evenly, a spanning
577
850
  // cell in a first row was shared out before the cells of one column
578
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%.
579
863
  spanning.sort((a, b) => a.colSpan - b.colSpan);
580
864
  const grow = (into, want, cell) => {
581
- const last = Math.min(columnCount - 1, cell.column + cell.colSpan - 1);
582
- let have = spacing * (last - cell.column);
583
- for (let c = cell.column; c <= last; c += 1)
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)
584
870
  have += into[c];
585
871
  if (!(want > have))
586
872
  return;
587
- let free = false;
588
- for (let c = cell.column; c <= last && !free; c += 1) {
589
- free = explicit[c] === null;
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
+ }
590
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];
591
897
  let weight = 0;
592
898
  let count = 0;
593
- for (let c = cell.column; c <= last; c += 1) {
594
- if (free && explicit[c] !== null)
899
+ for (let c = first; c <= last; c += 1) {
900
+ if (!takes(c))
595
901
  continue;
596
- weight += max[c];
902
+ weight += weightOf(c);
597
903
  count += 1;
598
904
  }
599
- for (let c = cell.column; c <= last; c += 1) {
600
- if (free && explicit[c] !== null)
905
+ const extra = want - have;
906
+ for (let c = first; c <= last; c += 1) {
907
+ if (!takes(c))
601
908
  continue;
602
- into[c] +=
603
- weight > 0 ? ((want - have) * max[c]) / weight : (want - have) / count;
909
+ into[c] += weight > 0 ? (extra * weightOf(c)) / weight : extra / count;
604
910
  }
605
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
+ }
606
917
  // what the columns' content alone asks, before their set widths: the
607
918
  // least a column gives way to when the table has less room than its
608
919
  // cells' widths want, as beside a float
609
920
  const least = min.slice();
610
921
  for (const cell of spanning)
611
- grow(least, cell.box.intrinsicMinContent, 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
612
926
  for (let c = 0; c < columnCount; c += 1) {
613
- if (explicit[c] !== null) {
614
- max[c] = Math.max(min[c], explicit[c]);
615
- min[c] = Math.max(min[c], Math.min(explicit[c], max[c]));
616
- }
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]));
617
933
  }
618
934
  // and with them: a spanning cell's own width is the least it is, as a
619
935
  // single cell's is (step 1), so a cell over three columns set to 100px,
620
936
  // with 20px between them, has 60 to share, where it had none
621
937
  for (const cell of spanning) {
622
- const own = cellWidth(cell, containingWidth) ?? 0;
623
- grow(min, Math.max(cell.box.intrinsicMinContent, own), cell);
624
- grow(max, Math.max(cell.box.intrinsicMaxContent, own), cell);
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);
625
942
  }
626
943
  for (let c = 0; c < columnCount; c += 1)
627
944
  min[c] = Math.min(min[c], max[c]);
628
- const totalMax = max.reduce((a, b) => a + b, 0);
629
- if (totalMax <= available) {
630
- // Everything fits. A table with a width of its own still fills it (CSS
631
- // 2.1 17.5.2.2): the columns not set to a width take the rest in
632
- // proportion to what they asked for, or every column does where all are
633
- // set. Returned as asked, `<table width="600">` drew its cells at their
634
- // content's width and left the rest of its 600 empty.
635
- if (!fill || available <= totalMax)
636
- return max;
637
- const set = explicit.every((w) => w !== null);
638
- let weight = 0;
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;
639
997
  let count = 0;
640
998
  for (let c = 0; c < columnCount; c += 1) {
641
- if (!set && explicit[c] !== null)
999
+ if (percent[c] === null)
642
1000
  continue;
643
- weight += max[c];
1001
+ increase += sized[c] - least[c];
644
1002
  count += 1;
645
1003
  }
646
- const extra = available - totalMax;
647
- return max.map((m, c) => !set && explicit[c] !== null
648
- ? m
649
- : m + (weight > 0 ? (extra * m) / weight : extra / count));
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));
650
1010
  }
651
- const totalMin = min.reduce((a, b) => a + b, 0);
652
- if (totalMin >= available) {
1011
+ if (target <= atSet) {
653
1012
  // Less room than the cells' widths want: a width a cell was set to
654
- // gives way first, down to what its content asks, and a table never
655
- // goes narrower than its words (CSS 2.1 17.5.2.2), which it overflows.
656
- const totalLeast = least.reduce((a, b) => a + b, 0);
657
- if (totalLeast >= available)
658
- return least;
659
- const give = totalMin - totalLeast;
660
- return min.map((m, c) => m - ((m - least[c]) / give) * (totalMin - available));
661
- }
662
- const slack = available - totalMin;
663
- const surplus = totalMax - totalMin;
664
- return max.map((m, c) => min[c] + ((m - min[c]) / surplus) * slack);
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
+ });
665
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;
666
1065
  /**
667
- * The width each column is set to, or null: a column's own `width`, or its
668
- * group's shared among the columns of a group that has none of its own
669
- * (HTML's `<colgroup span>`); and a group's `width` spread over its columns
670
- * where theirs come to less (CSS 2.1 17.5.2.2, step 4). Each within its
671
- * `min-width` and `max-width`, which apply to columns and column groups as
672
- * to any block (10.4), so `min-width` alone sets one.
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.
673
1075
  */
674
- function columnWidths(columnBoxes, columnGroups, base) {
675
- const widths = columnBoxes.map((column) => column ? partWidth(column, base) : null);
676
- let group = null;
677
- for (let c = 0; c <= columnGroups.length; c += 1) {
678
- if (c < columnGroups.length && columnGroups[c] === group)
679
- continue;
680
- // the group that ends here, over its columns from `start`
681
- if (group) {
682
- const width = partWidth(group, base);
683
- let start = c - 1;
684
- while (start > 0 && columnGroups[start - 1] === group)
685
- start -= 1;
686
- if (width !== null) {
687
- let have = 0;
688
- for (let i = start; i < c; i += 1)
689
- have += widths[i] ?? 0;
690
- if (have < width) {
691
- const each = (width - have) / (c - start);
692
- for (let i = start; i < c; i += 1) {
693
- widths[i] = (widths[i] ?? 0) + each;
694
- }
695
- }
696
- }
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];
697
1092
  }
698
- group = c < columnGroups.length ? columnGroups[c] : null;
699
1093
  }
700
- return widths;
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;
1147
+ }
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
+ });
701
1207
  }
702
- /** A column's or a column group's width, within its limits, or null where
703
- * it sets none. The limits are lengths: a percentage in them does what it
704
- * does in a cell's (`cellWidth`). */
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. */
705
1216
  function partWidth(box, base) {
706
- const style = box.style;
707
- const width = ownWidth(style, base);
708
- const min = typeof style.minWidth === 'number' ? style.minWidth : 0;
709
- if (width === null && !(min > 0))
1217
+ const width = ownWidth(box.style, base);
1218
+ if (width === null)
710
1219
  return null;
711
- const max = typeof style.maxWidth === 'number' ? style.maxWidth : null;
712
- let out = width ?? 0;
713
- if (max !== null)
714
- out = Math.min(out, max);
715
- return Math.max(0, out, min);
716
- }
717
- /** A cell's own `width`, its padding and borders in, or null. A
718
- * percentage is a share of the table its padding and borders are part
719
- * of, as browsers read it: added on, a 90% cell and a 10% one came to
720
- * more than the table, which then took it back from both. Held within its
721
- * `min-width` and `max-width` as a column's is (`partWidth`), which CSS
722
- * 2.1 leaves undefined and every browser does: a cell set `width: 3in;
723
- * max-width: 1in` is an inch wide, and one with `min-width` alone is as
724
- * wide as that.
725
- *
726
- * Where they are lengths. A percentage `min-width` is ignored, and a
727
- * percentage `max-width` holds a percentage `width` and nothing else
728
- * (CSS Tables 3, 3.8.2, and `ownWidth`): a cell `width: 100px;
729
- * max-width: 10%` in a table of 600 is 100 wide, where it was 60, and
730
- * one set `min-width: 150%` takes no more than its content asks. */
731
- function cellWidth(cell, base) {
732
- const style = cell.box.style;
733
- const len = style.width;
734
- const width = ownWidth(style, base);
735
- const min = typeof style.minWidth === 'number' ? style.minWidth : 0;
736
- 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)
1245
+ return null;
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)
737
1263
  return null;
738
- const max = typeof style.maxWidth === 'number' ? style.maxWidth : null;
739
- let own = width ?? 0;
740
- if (max !== null)
741
- own = Math.min(own, max);
742
- own = Math.max(0, own, min);
743
- if (isPct(len) && own === width) {
744
- return Math.max(width, cell.box.horizontalExtra);
1264
+ const limit = style.maxWidth;
1265
+ if (limit === 'none' || !isPct(limit) || limit.px || limit.of) {
1266
+ return width.pct;
745
1267
  }
746
- return own + cell.box.horizontalExtra;
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;
747
1287
  }
748
1288
  /** A cell's, a column's or a column group's `width` as the columns read
749
1289
  * it, or null: a percentage no more than a percentage its `max-width` is
@@ -782,10 +1322,12 @@ function lengthAgainst(len, base) {
782
1322
  * increase the height of the cell box": taken from the set height, a cell
783
1323
  * holding one block of 30px in 80px hung the text beside it from 80px. An
784
1324
  * empty cell has nothing to align and says nothing about the row's, as in a
785
- * browser.
1325
+ * browser. `own` is each cell's baseline from its top, NaN where it has none
1326
+ * to give.
786
1327
  */
787
1328
  function baselineLifts(cells, rowCount, natural) {
788
1329
  const lift = new Array(cells.length).fill(0);
1330
+ const own = new Array(cells.length).fill(NaN);
789
1331
  let any = false;
790
1332
  for (const cell of cells) {
791
1333
  const va = cell.box.style.verticalAlign;
@@ -793,8 +1335,7 @@ function baselineLifts(cells, rowCount, natural) {
793
1335
  any = true;
794
1336
  }
795
1337
  if (!any)
796
- return lift;
797
- const own = new Array(cells.length).fill(NaN);
1338
+ return { lift, own };
798
1339
  const row = new Array(rowCount).fill(-Infinity);
799
1340
  for (let i = 0; i < cells.length; i += 1) {
800
1341
  const box = cells[i].box;
@@ -814,7 +1355,7 @@ function baselineLifts(cells, rowCount, natural) {
814
1355
  if (own[i] === own[i])
815
1356
  lift[i] = row[cells[i].row] - own[i];
816
1357
  }
817
- return lift;
1358
+ return { lift, own };
818
1359
  }
819
1360
  /** Whether a cell has anything in flow in it. */
820
1361
  function holdsFlow(box) {
@@ -850,27 +1391,40 @@ function rowSizing(rows, cells) {
850
1391
  }
851
1392
  /**
852
1393
  * The height a table has past its rows' given out among them, as browsers
853
- * give it (the CSS Tables 3 draft leaves it open, csswg-drafts#4418): first
854
- * to the rows a percentage sets, up to it; then to the rows nothing sets
855
- * that have content, in proportion to their heights; then, where every row
856
- * with content is set, to the empty rows, those nothing sets first, evenly;
857
- * and else to every row with content, in proportion. A row whose cells set
858
- * its height keeps it while an empty row beside it takes the rest.
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.
859
1409
  */
860
- function growRows(heights, sizing, extra, base) {
861
- const n = heights.length;
862
- const deficit = heights.map((h, r) => {
863
- const pct = sizing[r].percent;
864
- return pct ? Math.max(0, (pct / 100) * base - h) : 0;
865
- });
1410
+ function growRows(heights, sizing, extra, base, first = 0, last = heights.length - 1, starts = null) {
1411
+ const deficit = [];
866
1412
  const empty = [];
867
1413
  const nothingSetEmpty = [];
868
1414
  const full = [];
869
1415
  const nothingSetFull = [];
1416
+ const spanned = [];
870
1417
  let setFull = 0;
871
- for (let r = 0; r < n; r += 1) {
872
- const set = sizing[r].constrained || sizing[r].percent !== null;
873
- if (heights[r] === 0 && deficit[r] === 0) {
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) {
874
1428
  empty.push(r);
875
1429
  if (!set)
876
1430
  nothingSetEmpty.push(r);
@@ -883,11 +1437,11 @@ function growRows(heights, sizing, extra, base) {
883
1437
  nothingSetFull.push(r);
884
1438
  }
885
1439
  }
886
- const owed = deficit.reduce((a, b) => a + b, 0);
887
1440
  if (owed > 0) {
888
1441
  const give = Math.min(owed, extra);
889
- for (let r = 0; r < n; r += 1)
890
- heights[r] += (give * deficit[r]) / owed;
1442
+ for (let r = first; r <= last; r += 1) {
1443
+ heights[r] += (give * deficit[r - first]) / owed;
1444
+ }
891
1445
  extra -= give;
892
1446
  if (!(extra > 0))
893
1447
  return;
@@ -901,9 +1455,17 @@ function growRows(heights, sizing, extra, base) {
901
1455
  even || !(sum > 0) ? extra / which.length : (extra * heights[r]) / sum;
902
1456
  }
903
1457
  };
904
- if (nothingSetFull.length)
1458
+ if (spanned.length)
1459
+ grow(spanned, true);
1460
+ else if (nothingSetFull.length)
905
1461
  grow(nothingSetFull, false);
906
- else if (empty.length && empty.length + setFull === n) {
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) {
907
1469
  grow(nothingSetEmpty.length ? nothingSetEmpty : empty, true);
908
1470
  }
909
1471
  else