@react-x11/components 0.14.1 → 0.16.1

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 +27 -8
  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 +29 -2
  42. package/dist/html/layout/boxes.d.ts.map +1 -1
  43. package/dist/html/layout/boxes.js +172 -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 +1759 -238
  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 +64 -19
  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 +122 -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 +27 -8
  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 +211 -27
  88. package/src/html/layout/css-grid.ts +12 -9
  89. package/src/html/layout/flex.ts +1975 -231
  90. package/src/html/layout/inline.ts +70 -16
  91. package/src/html/layout/table.ts +961 -332
  92. package/src/html/node.ts +155 -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
@@ -16,7 +16,7 @@
16
16
  // reason richtext's node gives for answering from what it draws: an index
17
17
  // built by a second traversal can disagree with the boxes, and a selection
18
18
  // that disagrees with the glyphs is worse than no selection.
19
- import type { Element } from 'domhandler';
19
+ import type { ChildNode, Element } from 'domhandler';
20
20
 
21
21
  import type { TextRun } from '../../richtext/index.js';
22
22
  import {
@@ -28,6 +28,7 @@ import {
28
28
  NON_RENDERED,
29
29
  tagOf,
30
30
  } from '../dom.js';
31
+ import { ShapeCopies, languageOf, metaColorScheme } from '../css/cascade.js';
31
32
  import type {
32
33
  Cascade,
33
34
  FirstLetterRules,
@@ -48,7 +49,7 @@ import {
48
49
  inherit,
49
50
  } from '../css/style.js';
50
51
  import { AUTO } from '../css/values.js';
51
- import { svgIntrinsics } from '../svg.js';
52
+ import { concreteSize, svgIntrinsics } from '../svg.js';
52
53
  import type { IntrinsicSize } from '../svg.js';
53
54
  import type { ComputedStyle } from '../css/style.js';
54
55
  import type { GridLines } from './grid-lines.js';
@@ -82,15 +83,32 @@ export interface Marker {
82
83
  /** Its own style where a `::marker` rule gives it one; the item's where
83
84
  * none does. */
84
85
  style: ComputedStyle | null;
85
- /** The image it is, `list-style-image`'s, at its size in device pixels,
86
- * where that has arrived; its text is then not drawn. */
87
- image?: { url: string; width: number; height: number };
86
+ /** The image it is, `list-style-image`'s, where that has arrived, at its
87
+ * size in device pixels — its own, or the one a marker image with none
88
+ * is given (`Builder._markerImageSize`); its text is then not drawn.
89
+ * `gap` is the room between it and the content, in device pixels
90
+ * (`MARKER_IMAGE_GAP`). */
91
+ image?: { url: string; width: number; height: number; gap: number };
88
92
  /** Its text is its own — a string `list-style-type`, a `::marker`'s
89
93
  * `content` — and ends at the content's edge, with no gap a number
90
94
  * or a bullet is set apart by. */
91
95
  flush?: true;
92
96
  }
93
97
 
98
+ /**
99
+ * The room between a list marker's image and the item's content, in CSS
100
+ * pixels: between the image and the content's edge outside the item, and
101
+ * after the image inside it. CSS 2.1 (12.5.1) leaves where a marker goes
102
+ * to the user agent, and this is Blink's `kCMarkerPaddingPx`
103
+ * (`ListMarker::InlineMarginsForOutside` and `…ForInside`), which WebKit
104
+ * has too. Outside, ours was the gap a bullet has, 0.4em — 16 pixels at
105
+ * 40px text where Chrome has 7 — and inside the space a marker's text ends
106
+ * in, a space's width. Blink writes it into the marker's margins unzoomed,
107
+ * so at a device scale of 2 Chrome's is 7 device pixels; here it is 7 CSS
108
+ * pixels at any scale, so that a page at 2x is the page at 1x doubled.
109
+ */
110
+ const MARKER_IMAGE_GAP = 7;
111
+
94
112
  /** An out-of-flow box's static position, as an offset from the box whose
95
113
  * flow it was taken from, which may yet move. */
96
114
  export interface StaticPosition {
@@ -544,6 +562,40 @@ export type ReplacedKind =
544
562
  /** An inline `<svg>`: a drawing, sized by what it says of its size. */
545
563
  | 'svg';
546
564
 
565
+ /**
566
+ * Where a control sits on the line, as Chrome sets it: a field, a select
567
+ * or an input button on the baseline of the text it shows, its line in its
568
+ * face centred in its content box — that face's ascent and descent — and a
569
+ * checkbox, a radio button or a range on its border box's bottom, its
570
+ * bottom margin under the line. A `<textarea>` is none of these, and sits
571
+ * on its bottom margin edge as a box that scrolls does.
572
+ */
573
+ export const CONTROL_BASELINES = new WeakMap<
574
+ Box,
575
+ InlineDecoration | 'border'
576
+ >();
577
+
578
+ function controlBaseline(
579
+ el: Element,
580
+ kind: ReplacedKind,
581
+ style: ComputedStyle,
582
+ options: BuildOptions,
583
+ ): InlineDecoration | 'border' | null {
584
+ if (kind === 'checkbox' || kind === 'radio') return 'border';
585
+ if (kind === 'input' && (attr(el, 'type') ?? '').toLowerCase() === 'range') {
586
+ return 'border';
587
+ }
588
+ if (kind !== 'input' && kind !== 'select' && kind !== 'button') return null;
589
+ // a list box shows rows, and sits on its bottom edge
590
+ if (kind === 'select' && Number(attr(el, 'size') ?? 1) > 1) return null;
591
+ return (
592
+ options.faceExtent?.(style) ?? {
593
+ ascent: style.fontSize * 0.8,
594
+ descent: style.fontSize * 0.2,
595
+ }
596
+ );
597
+ }
598
+
547
599
  /** A replaced box's intrinsic dimensions, in device pixels. */
548
600
  export interface Intrinsic {
549
601
  /** Its size — or, on an axis `missing` names, the default object size of
@@ -789,6 +841,11 @@ export interface BoxTree {
789
841
  * could reach one: worked out as a drawing is first painted, since a
790
842
  * page of icons shows few of them at once. */
791
843
  shapeStyler: ShapeStyler | null;
844
+ /** What the drawings of this build share in `Cascade.shapeStyles`: the
845
+ * document's elements by their ids, indexed when first asked, where a
846
+ * `<use>` finds the icon a sprite has for it; and what the rules matched
847
+ * in each copy a `<use>` makes. Null where `shapeStyler` is. */
848
+ shapeCopies: ShapeCopies | null;
792
849
  /** Whether a float or an out-of-flow box sits in an inline box: where
793
850
  * none does, layout looks for them among a block's own children and
794
851
  * goes through no inline box to find them. */
@@ -826,6 +883,14 @@ export interface BuildOptions {
826
883
  imageSize(el: Element): IntrinsicSize | null;
827
884
  /** The same for an image named by url — generated content's. */
828
885
  urlSize?(url: string): IntrinsicSize | null;
886
+ /** How far a style's first face reaches above its baseline, in device
887
+ * pixels: what a list marker's image with no size of its own is sized
888
+ * by (`_markerImageSize`). Where it is not known, 0.8em. */
889
+ faceAscent?(style: ComputedStyle): number | undefined;
890
+ /** How far a style's first face reaches above and below its baseline,
891
+ * in device pixels: where a control sets its text, which is where its
892
+ * baseline is (`CONTROL_BASELINES`). */
893
+ faceExtent?(style: ComputedStyle): InlineDecoration | undefined;
829
894
  /** The size a real widget wants, so the box in the flow is the size the
830
895
  * control will be drawn at. */
831
896
  controlSize(
@@ -847,6 +912,28 @@ export function buildBoxes(
847
912
  return builder.run(root as Element);
848
913
  }
849
914
 
915
+ /** A document's first element with an id: one walk of it, the first time
916
+ * an id is asked for (`ShapeCopies`). */
917
+ function idIndex(root: Element): (id: string) => Element | null {
918
+ let index: Map<string, Element> | null = null;
919
+ return (id) => {
920
+ if (!index) {
921
+ index = new Map();
922
+ const stack: ChildNode[] = [...root.children].reverse();
923
+ for (let node = stack.pop(); node; node = stack.pop()) {
924
+ if (!isElement(node)) continue;
925
+ const own = node.attribs.id;
926
+ if (own && !index.has(own)) index.set(own, node);
927
+ const children = node.children;
928
+ for (let i = children.length - 1; i >= 0; i -= 1) {
929
+ stack.push(children[i]);
930
+ }
931
+ }
932
+ }
933
+ return index.get(id) ?? null;
934
+ };
935
+ }
936
+
850
937
  /**
851
938
  * How deep the box tree may go. Everything downstream of the builder — the
852
939
  * fix-up pass, layout, paint, the accessor walks — recurses on box depth, so
@@ -854,8 +941,17 @@ export function buildBoxes(
854
941
  * output, a runaway template) from a stack overflow five phases later.
855
942
  * Blink's parser flattens at 512 for the same reason; content past the cap
856
943
  * is dropped, which beats the alternative of crashing the application.
944
+ *
857
945
  * The parser keeps a document to 256 elements (`dom.ts`), so what reaches
858
- * this now is the anonymous boxes a table builds round each of them.
946
+ * this is the anonymous boxes a table builds round them, which the builder
947
+ * counts as it goes (`wrappersAround`), since the fix-up makes them after
948
+ * it. Counted as elements, a `display: table-cell` box in another was one
949
+ * level where it is four boxes — a table, a row group, a row and the cell
950
+ * — and every pass pays for a box: painting one takes some 800 bytes of
951
+ * the stack. Two hundred and fifty cells deep, a thousand boxes, layout
952
+ * and paint each ran out of the 984 KB V8 gives the main thread, and the
953
+ * document came out blank. 512 boxes paint in under half of it, and are a
954
+ * cell in a cell 128 deep.
859
955
  */
860
956
  const MAX_DEPTH = 512;
861
957
 
@@ -916,11 +1012,15 @@ class Builder {
916
1012
  run(root: Element): BoxTree {
917
1013
  const cascade = this._options.cascade;
918
1014
  cascade.beginSharing(this._options.kept ?? null);
1015
+ cascade.pageColorScheme = metaColorScheme(root);
919
1016
  const { style: rootStyle, html: impliedHtml } = cascade.rootStyle(
920
1017
  hasBody(root),
921
1018
  hasHtml(root),
922
1019
  );
923
1020
  const rootBox = new Box('block', null, rootStyle);
1021
+ // the root standing in for a `<body>` has its background clipped to
1022
+ // the document's text as the body's would be
1023
+ if (rootStyle.backgroundClipText) this._clipText = true;
924
1024
  // a fragment's root stands in for a `<body>`, counters and all
925
1025
  this._scopes.open();
926
1026
  this._counterChanges(rootStyle, {
@@ -938,6 +1038,7 @@ class Builder {
938
1038
  const anonymous = anonymousStyles(cascade.initial);
939
1039
  fixUp(rootBox, anonymous);
940
1040
  assignSubtreeRanges(rootBox);
1041
+ const copies = cascade.stylesShapes ? new ShapeCopies(idIndex(root)) : null;
941
1042
  return {
942
1043
  root: rootBox,
943
1044
  anonymous,
@@ -954,7 +1055,8 @@ class Builder {
954
1055
  firstLine: this._firstLine,
955
1056
  firstLineStyler: this._firstLine ? this._firstLineStyler() : null,
956
1057
  selectionStyler: cascade.hasSelection ? this._selectionStyler() : null,
957
- shapeStyler: cascade.stylesShapes ? this._shapeStyler() : null,
1058
+ shapeStyler: copies ? this._shapeStyler(copies) : null,
1059
+ shapeCopies: copies,
958
1060
  nestedOutOfLine: this._nestedOutOfLine,
959
1061
  movedInline: this._movedInline,
960
1062
  clipText: this._clipText,
@@ -989,12 +1091,12 @@ class Builder {
989
1091
 
990
1092
  /** `BoxTree.shapeStyler`, over this build's cascade: each drawing's
991
1093
  * once, from its box's style as it is when first asked. */
992
- private _shapeStyler(): ShapeStyler {
1094
+ private _shapeStyler(copies: ShapeCopies): ShapeStyler {
993
1095
  const cascade = this._options.cascade;
994
1096
  return (box) => {
995
1097
  let shapes = SHAPE_STYLES.get(box);
996
1098
  if (shapes === undefined) {
997
- shapes = box.el ? cascade.shapeStyles(box.el, box.style) : null;
1099
+ shapes = box.el ? cascade.shapeStyles(box.el, box.style, copies) : null;
998
1100
  SHAPE_STYLES.set(box, shapes);
999
1101
  }
1000
1102
  return shapes;
@@ -1190,8 +1292,10 @@ class Builder {
1190
1292
  return;
1191
1293
  }
1192
1294
 
1193
- if (this._depth >= MAX_DEPTH) return;
1194
1295
  const kind = boxKindFor(style.display);
1296
+ // the box, and the anonymous boxes the fix-up will put round it
1297
+ const depth = 1 + wrappersAround(into.kind, kind, style);
1298
+ if (this._depth + depth > MAX_DEPTH) return;
1195
1299
  const box = new Box(kind, el, style);
1196
1300
  into.append(box);
1197
1301
  if (
@@ -1221,7 +1325,7 @@ class Builder {
1221
1325
  let markerStyle: ComputedStyle | null = null;
1222
1326
  let ownMarker = false;
1223
1327
  let markerImage: { url: string; size: IntrinsicSize } | null = null;
1224
- let insideImage: string | null = null;
1328
+ let insideImage: NonNullable<Marker['image']> | null = null;
1225
1329
  if (style.display === 'list-item' && style.listStyleImage) {
1226
1330
  // asked for as generated content's images are, and the tree built
1227
1331
  // again when it arrives; until it has, and where it never does, the
@@ -1256,20 +1360,21 @@ class Builder {
1256
1360
  flush = true;
1257
1361
  }
1258
1362
  if (markerImage && !ownMarker) {
1259
- if (style.listStylePosition === 'inside') insideImage = markerImage.url;
1363
+ const [width, height] = this._markerImageSize(
1364
+ markerImage.size,
1365
+ markerStyle ?? style,
1366
+ );
1367
+ const gap = MARKER_IMAGE_GAP * (this._options.scale ?? 1);
1368
+ const image = { url: markerImage.url, width, height, gap };
1369
+ if (style.listStylePosition === 'inside') insideImage = image;
1260
1370
  else {
1261
- const scale = this._options.scale ?? 1;
1262
1371
  box.marker = {
1263
1372
  text: '',
1264
1373
  layout: null,
1265
1374
  x: 0,
1266
1375
  y: 0,
1267
1376
  style: markerStyle,
1268
- image: {
1269
- url: markerImage.url,
1270
- width: (markerImage.size.width ?? 0) * scale,
1271
- height: (markerImage.size.height ?? 0) * scale,
1272
- },
1377
+ image,
1273
1378
  };
1274
1379
  }
1275
1380
  } else if (text && style.listStylePosition === 'inside') {
@@ -1302,19 +1407,23 @@ class Builder {
1302
1407
  : null;
1303
1408
  const ownLetter = ownRules ? { rules: ownRules, punctuation: [] } : null;
1304
1409
  if (ownLetter) this._firstLetter = ownLetter;
1305
- this._depth += 1;
1410
+ this._depth += depth;
1306
1411
  if (insideImage) {
1307
1412
  // an inline image at the start of the first line, as a generated
1308
- // image is, and the space a marker's text ends in
1309
- this._contentImage(insideImage, box, style, el);
1310
- this._textNode(' ', box, style, el);
1413
+ // image is, and the room after it Blink gives it rather than a
1414
+ // space: the content's own white space after it is kept, as it is
1415
+ // after an image (`MARKER_IMAGE_GAP`)
1416
+ const { url, gap } = insideImage;
1417
+ const image = this._contentImage(url, box, style, el, insideImage);
1418
+ if (style.direction === 'rtl') image.style.marginLeft = gap;
1419
+ else image.style.marginRight = gap;
1311
1420
  } else if (insideMarker) {
1312
1421
  this._insideMarker(insideMarker, style, markerStyle, box, el);
1313
1422
  }
1314
1423
  this._pseudo(el, 'before', style, box);
1315
1424
  this._children(el, box, style, childInFlex, el, key);
1316
1425
  this._pseudo(el, 'after', style, box);
1317
- this._depth -= 1;
1426
+ this._depth -= depth;
1318
1427
  this._letterAfter(flow, skipped, outerLetter, ownLetter);
1319
1428
  if (flow !== 'inline') this._endLine();
1320
1429
  this._ws = after(flow, this._ws, around);
@@ -1415,6 +1524,8 @@ class Builder {
1415
1524
  ratio: 0,
1416
1525
  };
1417
1526
  this._controls.push(box);
1527
+ const baseline = controlBaseline(el, replaced, style, this._options);
1528
+ if (baseline) CONTROL_BASELINES.set(box, baseline);
1418
1529
  // A control's value is the widget's, not the document's: putting it in
1419
1530
  // the selection index would make Ctrl+A copy the contents of every text
1420
1531
  // field, which no document viewer does.
@@ -1754,14 +1865,14 @@ class Builder {
1754
1865
  .join(item.separator);
1755
1866
  break;
1756
1867
  case 'open-quote':
1757
- text += quoteAt(style.quotes, this._quoteDepth, 0);
1868
+ text += quoteAt(style.quotes, this._quoteDepth, 0, languageOf(el));
1758
1869
  this._quoteDepth += 1;
1759
1870
  break;
1760
1871
  case 'close-quote':
1761
1872
  // a close with nothing open writes nothing and closes nothing
1762
1873
  if (this._quoteDepth > 0) {
1763
1874
  this._quoteDepth -= 1;
1764
- text += quoteAt(style.quotes, this._quoteDepth, 1);
1875
+ text += quoteAt(style.quotes, this._quoteDepth, 1, languageOf(el));
1765
1876
  }
1766
1877
  break;
1767
1878
  case 'no-open-quote':
@@ -1787,7 +1898,10 @@ class Builder {
1787
1898
  into: Box,
1788
1899
  style: ComputedStyle,
1789
1900
  owner: Element,
1790
- ): void {
1901
+ /** Its size where it is a list marker's, which has one whatever the
1902
+ * image says (`_markerImageSize`). */
1903
+ marker?: { width: number; height: number },
1904
+ ): Box {
1791
1905
  const box = new Box('replaced', null, {
1792
1906
  ...inherit(style, this._options.cascade.initial),
1793
1907
  display: 'inline',
@@ -1797,11 +1911,40 @@ class Builder {
1797
1911
  CONTENT_IMAGES.set(box, url);
1798
1912
  const size = this._options.urlSize?.(url) ?? null;
1799
1913
  this._contentImages.push({ url, element: owner, sized: !!size });
1800
- if (size) setIntrinsics(box, size, this._options.scale ?? 1);
1914
+ if (marker) {
1915
+ const { width, height } = marker;
1916
+ const ratio = width > 0 && height > 0 ? width / height : 0;
1917
+ box.intrinsic = { width, height, missing: 0, ratio };
1918
+ } else if (size) setIntrinsics(box, size, this._options.scale ?? 1);
1801
1919
  else box.intrinsic = { width: 0, height: 0, missing: 0, ratio: 0 };
1802
1920
  this._ws = after('atomic', this._ws, this._ws);
1803
1921
  this._lettered = false;
1804
1922
  this._abandonLetter();
1923
+ return box;
1924
+ }
1925
+
1926
+ /**
1927
+ * A list marker image's size, in device pixels: its own where it has one,
1928
+ * and where it lacks some — an SVG with only a `viewBox`, or with no size
1929
+ * and no ratio — CSS Images' default sizing in a square half its face's
1930
+ * ascent across. CSS Lists 3 (3.3) makes the square 1em, which both
1931
+ * engines find too large (w3c/csswg-drafts#4207): Blink's is half the
1932
+ * ascent, rounded to a whole pixel first
1933
+ * (`LayoutListMarkerImage::DefaultSize`), Gecko's 0.4em, and this is
1934
+ * Blink's. A `viewBox` alone came to 0 by 0 and drew no marker at all,
1935
+ * and inside the item to the 300 by 150 a generated image defaults to.
1936
+ */
1937
+ private _markerImageSize(
1938
+ size: IntrinsicSize,
1939
+ style: ComputedStyle,
1940
+ ): [number, number] {
1941
+ let side = 0;
1942
+ // CoreText reads a size of 0 as its default twelve points
1943
+ if ((size.width === null || size.height === null) && style.fontSize > 0) {
1944
+ const ascent = this._options.faceAscent?.(style) ?? style.fontSize * 0.8;
1945
+ side = Math.round(ascent) / 2;
1946
+ }
1947
+ return concreteSize(size, side, side, this._options.scale ?? 1);
1805
1948
  }
1806
1949
 
1807
1950
  /** A text node, whitespace-processed per the inherited `white-space`. */
@@ -2467,6 +2610,47 @@ function after(
2467
2610
  }
2468
2611
  }
2469
2612
 
2613
+ /**
2614
+ * How many anonymous boxes the fix-up will put between a box of `kind` and
2615
+ * the box it is appended to, `parent` (`fixUpTable`, `wrapOrphans`,
2616
+ * `wrapTableParts`): a table part outside a table is given what it is
2617
+ * missing of one, and what is in a table and no part of it is given a
2618
+ * cell, a row and a row group. A cell in a block is in a table, a row group
2619
+ * and a row of its own, and a block in a row is in a cell. Counted against
2620
+ * `MAX_DEPTH`, which bounds the boxes and not the elements.
2621
+ */
2622
+ function wrappersAround(
2623
+ parent: BoxKind,
2624
+ kind: BoxKind,
2625
+ style: ComputedStyle,
2626
+ ): number {
2627
+ const column =
2628
+ style.display === 'table-column' || style.display === 'table-column-group';
2629
+ switch (parent) {
2630
+ case 'table':
2631
+ if (kind === 'table-row-group' || kind === 'table-caption' || column) {
2632
+ return 0;
2633
+ }
2634
+ return kind === 'table-row' ? 1 : kind === 'table-cell' ? 2 : 3;
2635
+ case 'table-row-group':
2636
+ return kind === 'table-row' ? 0 : kind === 'table-cell' ? 1 : 2;
2637
+ case 'table-row':
2638
+ return kind === 'table-cell' ? 0 : 1;
2639
+ }
2640
+ // a part out of flow is no part (`isTablePart`)
2641
+ if (
2642
+ style.position === 'absolute' ||
2643
+ style.position === 'fixed' ||
2644
+ style.float !== 'none'
2645
+ ) {
2646
+ return 0;
2647
+ }
2648
+ if (kind === 'table-row-group' || kind === 'table-caption' || column) {
2649
+ return 1;
2650
+ }
2651
+ return kind === 'table-row' ? 2 : kind === 'table-cell' ? 3 : 0;
2652
+ }
2653
+
2470
2654
  function boxKindFor(display: ComputedStyle['display']): BoxKind {
2471
2655
  switch (display) {
2472
2656
  case 'inline':
@@ -374,18 +374,19 @@ export function layoutGrid(
374
374
  } else if (align === 'center') dy = (tall - outerHeight) / 2;
375
375
  else if (align === 'flex-end') dy = tall - outerHeight;
376
376
  else if (
377
- (align === 'stretch' || align === 'baseline') &&
377
+ (align === 'stretch' || align === 'normal' || align === 'baseline') &&
378
378
  child.style.height === AUTO &&
379
379
  !child.style.heightKeyword &&
380
- (child.style.alignSelf === 'stretch' ||
380
+ (align === 'stretch' ||
381
381
  (child.kind !== 'replaced' && !child.style.aspectRatio))
382
382
  ) {
383
383
  // a stretched item is its area's height, within its own least and
384
384
  // greatest (10.3): taller than what it holds, or shorter where its
385
385
  // tracks let it be, what it holds then running past it. An image, a
386
- // control or a box with an `aspect-ratio` keeps its own height, as
387
- // `normal` has one with a natural size or a ratio — which is what
388
- // the grid's `stretch` may be, and the item's own may not.
386
+ // control or a box with an `aspect-ratio` keeps its own height under
387
+ // `normal`, which stretches only an item with neither a natural size
388
+ // nor a ratio, and is stretched by `stretch`, the grid's or its own
389
+ // (6.2, and CSS Box Alignment 3, 6.1).
389
390
  const stretched = tall - child.marginTop - child.marginBottom;
390
391
  const height = clampHeight(
391
392
  child,
@@ -939,9 +940,9 @@ function percentOwn(box: Box): boolean {
939
940
  /**
940
941
  * An item's border-box height before the rows are sized, where it has one
941
942
  * then: a height of its own — a percentage of the rows it spans where they
942
- * have a length for a size — or the rows' where it is stretched down them
943
- * by its own `align-self`. Null where it has none, which is where its
944
- * height is its content's.
943
+ * have a length for a size — or the rows' where `stretch` stretches it
944
+ * down them, its own `align-self` or the grid's `align-items`. Null where
945
+ * it has none, which is where its height is its content's.
945
946
  */
946
947
  function heightBeforeRows(box: Box, rows: number): number | null {
947
948
  const style = box.style;
@@ -954,10 +955,12 @@ function heightBeforeRows(box: Box, rows: number): number | null {
954
955
  : own + box.verticalExtra,
955
956
  );
956
957
  }
958
+ const align =
959
+ style.alignSelf === AUTO ? box.parent?.style.alignItems : style.alignSelf;
957
960
  if (
958
961
  style.height === AUTO &&
959
962
  !style.heightKeyword &&
960
- style.alignSelf === 'stretch' &&
963
+ align === 'stretch' &&
961
964
  Number.isFinite(rows) &&
962
965
  !autoMargins(box, 'y').some(Boolean)
963
966
  ) {