@react-x11/components 0.14.0 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/code-editor/node.d.ts +13 -0
- package/dist/code-editor/node.d.ts.map +1 -1
- package/dist/code-editor/node.js +23 -5
- package/dist/code-editor/node.js.map +1 -1
- package/dist/html/css/cascade.d.ts +209 -23
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +873 -175
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +25 -0
- package/dist/html/css/color.d.ts.map +1 -1
- package/dist/html/css/color.js +307 -19
- package/dist/html/css/color.js.map +1 -1
- package/dist/html/css/content.d.ts +11 -6
- package/dist/html/css/content.d.ts.map +1 -1
- package/dist/html/css/content.js +171 -12
- package/dist/html/css/content.js.map +1 -1
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +62 -9
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/shapes.d.ts +62 -0
- package/dist/html/css/shapes.d.ts.map +1 -0
- package/dist/html/css/shapes.js +214 -0
- package/dist/html/css/shapes.js.map +1 -0
- package/dist/html/css/style.d.ts +42 -5
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +75 -27
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.js +15 -0
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +5 -0
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +18 -9
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +7 -0
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/block.d.ts +38 -7
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +160 -50
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +29 -2
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +159 -20
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/css-grid.d.ts.map +1 -1
- package/dist/html/layout/css-grid.js +69 -19
- package/dist/html/layout/css-grid.js.map +1 -1
- package/dist/html/layout/flex.d.ts.map +1 -1
- package/dist/html/layout/flex.js +1648 -225
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/inline.d.ts +15 -3
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +23 -8
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/layout/table.d.ts +27 -1
- package/dist/html/layout/table.d.ts.map +1 -1
- package/dist/html/layout/table.js +907 -323
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +36 -4
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +174 -37
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +4 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +769 -204
- package/dist/html/paint.js.map +1 -1
- package/dist/html/resources.js +14 -1
- package/dist/html/resources.js.map +1 -1
- package/dist/html/svg.d.ts +50 -17
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +501 -83
- package/dist/html/svg.js.map +1 -1
- package/dist/html/widgets.d.ts.map +1 -1
- package/dist/html/widgets.js +57 -4
- package/dist/html/widgets.js.map +1 -1
- package/package.json +3 -3
- package/src/code-editor/node.ts +24 -5
- package/src/html/css/cascade.ts +987 -176
- package/src/html/css/color.ts +345 -18
- package/src/html/css/content.ts +173 -15
- package/src/html/css/parse.ts +60 -8
- package/src/html/css/shapes.ts +273 -0
- package/src/html/css/style.ts +123 -30
- package/src/html/css/ua.ts +15 -0
- package/src/html/css/values.ts +23 -9
- package/src/html/index.ts +7 -0
- package/src/html/layout/block.ts +169 -56
- package/src/html/layout/boxes.ts +198 -24
- package/src/html/layout/css-grid.ts +89 -22
- package/src/html/layout/flex.ts +1863 -219
- package/src/html/layout/inline.ts +27 -7
- package/src/html/layout/table.ts +989 -333
- package/src/html/node.ts +204 -37
- package/src/html/paint.ts +999 -252
- package/src/html/resources.ts +13 -1
- package/src/html/svg.ts +600 -78
- package/src/html/widgets.ts +61 -4
package/src/html/layout/boxes.ts
CHANGED
|
@@ -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,
|
|
@@ -38,6 +39,7 @@ import type { CollapsedTable } from './collapse.js';
|
|
|
38
39
|
import { quoteAt } from '../css/content.js';
|
|
39
40
|
import type { CounterStyles } from '../css/counter-styles.js';
|
|
40
41
|
import type { ContentItem } from '../css/content.js';
|
|
42
|
+
import type { ShapeStyles } from '../css/shapes.js';
|
|
41
43
|
import {
|
|
42
44
|
CONTAIN_STYLE,
|
|
43
45
|
FIRST_LINE_INHERITED,
|
|
@@ -47,7 +49,7 @@ import {
|
|
|
47
49
|
inherit,
|
|
48
50
|
} from '../css/style.js';
|
|
49
51
|
import { AUTO } from '../css/values.js';
|
|
50
|
-
import { svgIntrinsics } from '../svg.js';
|
|
52
|
+
import { concreteSize, svgIntrinsics } from '../svg.js';
|
|
51
53
|
import type { IntrinsicSize } from '../svg.js';
|
|
52
54
|
import type { ComputedStyle } from '../css/style.js';
|
|
53
55
|
import type { GridLines } from './grid-lines.js';
|
|
@@ -81,15 +83,32 @@ export interface Marker {
|
|
|
81
83
|
/** Its own style where a `::marker` rule gives it one; the item's where
|
|
82
84
|
* none does. */
|
|
83
85
|
style: ComputedStyle | null;
|
|
84
|
-
/** The image it is, `list-style-image`'s,
|
|
85
|
-
*
|
|
86
|
-
|
|
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 };
|
|
87
92
|
/** Its text is its own — a string `list-style-type`, a `::marker`'s
|
|
88
93
|
* `content` — and ends at the content's edge, with no gap a number
|
|
89
94
|
* or a bullet is set apart by. */
|
|
90
95
|
flush?: true;
|
|
91
96
|
}
|
|
92
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
|
+
|
|
93
112
|
/** An out-of-flow box's static position, as an offset from the box whose
|
|
94
113
|
* flow it was taken from, which may yet move. */
|
|
95
114
|
export interface StaticPosition {
|
|
@@ -713,6 +732,15 @@ export const COLUMN_ROWS = new WeakMap<
|
|
|
713
732
|
* that would ask the three maps above of every box ask first. */
|
|
714
733
|
export const columned = { any: false };
|
|
715
734
|
|
|
735
|
+
/** What the document's rules give the elements inside an inline `<svg>`
|
|
736
|
+
* (`Cascade.shapeStyles`), null where they give none. */
|
|
737
|
+
export type ShapeStyler = (box: Box) => ShapeStyles | null;
|
|
738
|
+
|
|
739
|
+
/** What `BoxTree.shapeStyler` answered for each drawing's box it has been
|
|
740
|
+
* asked of, which are the ones painted: a box restyled where it is has
|
|
741
|
+
* its answer put here again (`HtmlViewNode._hoverInPlace`). */
|
|
742
|
+
export const SHAPE_STYLES = new WeakMap<Box, ShapeStyles | null>();
|
|
743
|
+
|
|
716
744
|
/** The element a pseudo-element's box is generated from, which has no
|
|
717
745
|
* element of its own (`Box.pseudo`): whose the images its styles name are,
|
|
718
746
|
* when the host is asked for them. */
|
|
@@ -774,6 +802,16 @@ export interface BoxTree {
|
|
|
774
802
|
* one: worked out as a selection is first painted over it, not as the
|
|
775
803
|
* tree is built, since most builds are never selected in. */
|
|
776
804
|
selectionStyler: SelectionStyler | null;
|
|
805
|
+
/** What the rules give the elements in each inline `<svg>`
|
|
806
|
+
* (`ShapeStyler`), which have no boxes and no styles, where any rule
|
|
807
|
+
* could reach one: worked out as a drawing is first painted, since a
|
|
808
|
+
* page of icons shows few of them at once. */
|
|
809
|
+
shapeStyler: ShapeStyler | null;
|
|
810
|
+
/** What the drawings of this build share in `Cascade.shapeStyles`: the
|
|
811
|
+
* document's elements by their ids, indexed when first asked, where a
|
|
812
|
+
* `<use>` finds the icon a sprite has for it; and what the rules matched
|
|
813
|
+
* in each copy a `<use>` makes. Null where `shapeStyler` is. */
|
|
814
|
+
shapeCopies: ShapeCopies | null;
|
|
777
815
|
/** Whether a float or an out-of-flow box sits in an inline box: where
|
|
778
816
|
* none does, layout looks for them among a block's own children and
|
|
779
817
|
* goes through no inline box to find them. */
|
|
@@ -811,6 +849,10 @@ export interface BuildOptions {
|
|
|
811
849
|
imageSize(el: Element): IntrinsicSize | null;
|
|
812
850
|
/** The same for an image named by url — generated content's. */
|
|
813
851
|
urlSize?(url: string): IntrinsicSize | null;
|
|
852
|
+
/** How far a style's first face reaches above its baseline, in device
|
|
853
|
+
* pixels: what a list marker's image with no size of its own is sized
|
|
854
|
+
* by (`_markerImageSize`). Where it is not known, 0.8em. */
|
|
855
|
+
faceAscent?(style: ComputedStyle): number | undefined;
|
|
814
856
|
/** The size a real widget wants, so the box in the flow is the size the
|
|
815
857
|
* control will be drawn at. */
|
|
816
858
|
controlSize(
|
|
@@ -832,6 +874,28 @@ export function buildBoxes(
|
|
|
832
874
|
return builder.run(root as Element);
|
|
833
875
|
}
|
|
834
876
|
|
|
877
|
+
/** A document's first element with an id: one walk of it, the first time
|
|
878
|
+
* an id is asked for (`ShapeCopies`). */
|
|
879
|
+
function idIndex(root: Element): (id: string) => Element | null {
|
|
880
|
+
let index: Map<string, Element> | null = null;
|
|
881
|
+
return (id) => {
|
|
882
|
+
if (!index) {
|
|
883
|
+
index = new Map();
|
|
884
|
+
const stack: ChildNode[] = [...root.children].reverse();
|
|
885
|
+
for (let node = stack.pop(); node; node = stack.pop()) {
|
|
886
|
+
if (!isElement(node)) continue;
|
|
887
|
+
const own = node.attribs.id;
|
|
888
|
+
if (own && !index.has(own)) index.set(own, node);
|
|
889
|
+
const children = node.children;
|
|
890
|
+
for (let i = children.length - 1; i >= 0; i -= 1) {
|
|
891
|
+
stack.push(children[i]);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
return index.get(id) ?? null;
|
|
896
|
+
};
|
|
897
|
+
}
|
|
898
|
+
|
|
835
899
|
/**
|
|
836
900
|
* How deep the box tree may go. Everything downstream of the builder — the
|
|
837
901
|
* fix-up pass, layout, paint, the accessor walks — recurses on box depth, so
|
|
@@ -839,8 +903,17 @@ export function buildBoxes(
|
|
|
839
903
|
* output, a runaway template) from a stack overflow five phases later.
|
|
840
904
|
* Blink's parser flattens at 512 for the same reason; content past the cap
|
|
841
905
|
* is dropped, which beats the alternative of crashing the application.
|
|
906
|
+
*
|
|
842
907
|
* The parser keeps a document to 256 elements (`dom.ts`), so what reaches
|
|
843
|
-
* this
|
|
908
|
+
* this is the anonymous boxes a table builds round them, which the builder
|
|
909
|
+
* counts as it goes (`wrappersAround`), since the fix-up makes them after
|
|
910
|
+
* it. Counted as elements, a `display: table-cell` box in another was one
|
|
911
|
+
* level where it is four boxes — a table, a row group, a row and the cell
|
|
912
|
+
* — and every pass pays for a box: painting one takes some 800 bytes of
|
|
913
|
+
* the stack. Two hundred and fifty cells deep, a thousand boxes, layout
|
|
914
|
+
* and paint each ran out of the 984 KB V8 gives the main thread, and the
|
|
915
|
+
* document came out blank. 512 boxes paint in under half of it, and are a
|
|
916
|
+
* cell in a cell 128 deep.
|
|
844
917
|
*/
|
|
845
918
|
const MAX_DEPTH = 512;
|
|
846
919
|
|
|
@@ -901,11 +974,15 @@ class Builder {
|
|
|
901
974
|
run(root: Element): BoxTree {
|
|
902
975
|
const cascade = this._options.cascade;
|
|
903
976
|
cascade.beginSharing(this._options.kept ?? null);
|
|
977
|
+
cascade.pageColorScheme = metaColorScheme(root);
|
|
904
978
|
const { style: rootStyle, html: impliedHtml } = cascade.rootStyle(
|
|
905
979
|
hasBody(root),
|
|
906
980
|
hasHtml(root),
|
|
907
981
|
);
|
|
908
982
|
const rootBox = new Box('block', null, rootStyle);
|
|
983
|
+
// the root standing in for a `<body>` has its background clipped to
|
|
984
|
+
// the document's text as the body's would be
|
|
985
|
+
if (rootStyle.backgroundClipText) this._clipText = true;
|
|
909
986
|
// a fragment's root stands in for a `<body>`, counters and all
|
|
910
987
|
this._scopes.open();
|
|
911
988
|
this._counterChanges(rootStyle, {
|
|
@@ -923,6 +1000,7 @@ class Builder {
|
|
|
923
1000
|
const anonymous = anonymousStyles(cascade.initial);
|
|
924
1001
|
fixUp(rootBox, anonymous);
|
|
925
1002
|
assignSubtreeRanges(rootBox);
|
|
1003
|
+
const copies = cascade.stylesShapes ? new ShapeCopies(idIndex(root)) : null;
|
|
926
1004
|
return {
|
|
927
1005
|
root: rootBox,
|
|
928
1006
|
anonymous,
|
|
@@ -939,6 +1017,8 @@ class Builder {
|
|
|
939
1017
|
firstLine: this._firstLine,
|
|
940
1018
|
firstLineStyler: this._firstLine ? this._firstLineStyler() : null,
|
|
941
1019
|
selectionStyler: cascade.hasSelection ? this._selectionStyler() : null,
|
|
1020
|
+
shapeStyler: copies ? this._shapeStyler(copies) : null,
|
|
1021
|
+
shapeCopies: copies,
|
|
942
1022
|
nestedOutOfLine: this._nestedOutOfLine,
|
|
943
1023
|
movedInline: this._movedInline,
|
|
944
1024
|
clipText: this._clipText,
|
|
@@ -971,6 +1051,20 @@ class Builder {
|
|
|
971
1051
|
};
|
|
972
1052
|
}
|
|
973
1053
|
|
|
1054
|
+
/** `BoxTree.shapeStyler`, over this build's cascade: each drawing's
|
|
1055
|
+
* once, from its box's style as it is when first asked. */
|
|
1056
|
+
private _shapeStyler(copies: ShapeCopies): ShapeStyler {
|
|
1057
|
+
const cascade = this._options.cascade;
|
|
1058
|
+
return (box) => {
|
|
1059
|
+
let shapes = SHAPE_STYLES.get(box);
|
|
1060
|
+
if (shapes === undefined) {
|
|
1061
|
+
shapes = box.el ? cascade.shapeStyles(box.el, box.style, copies) : null;
|
|
1062
|
+
SHAPE_STYLES.set(box, shapes);
|
|
1063
|
+
}
|
|
1064
|
+
return shapes;
|
|
1065
|
+
};
|
|
1066
|
+
}
|
|
1067
|
+
|
|
974
1068
|
/** `BoxTree.firstLineStyler`, over this build's cascade. */
|
|
975
1069
|
private _firstLineStyler(): FirstLineStyler {
|
|
976
1070
|
const cascade = this._options.cascade;
|
|
@@ -1160,8 +1254,10 @@ class Builder {
|
|
|
1160
1254
|
return;
|
|
1161
1255
|
}
|
|
1162
1256
|
|
|
1163
|
-
if (this._depth >= MAX_DEPTH) return;
|
|
1164
1257
|
const kind = boxKindFor(style.display);
|
|
1258
|
+
// the box, and the anonymous boxes the fix-up will put round it
|
|
1259
|
+
const depth = 1 + wrappersAround(into.kind, kind, style);
|
|
1260
|
+
if (this._depth + depth > MAX_DEPTH) return;
|
|
1165
1261
|
const box = new Box(kind, el, style);
|
|
1166
1262
|
into.append(box);
|
|
1167
1263
|
if (
|
|
@@ -1191,7 +1287,7 @@ class Builder {
|
|
|
1191
1287
|
let markerStyle: ComputedStyle | null = null;
|
|
1192
1288
|
let ownMarker = false;
|
|
1193
1289
|
let markerImage: { url: string; size: IntrinsicSize } | null = null;
|
|
1194
|
-
let insideImage:
|
|
1290
|
+
let insideImage: NonNullable<Marker['image']> | null = null;
|
|
1195
1291
|
if (style.display === 'list-item' && style.listStyleImage) {
|
|
1196
1292
|
// asked for as generated content's images are, and the tree built
|
|
1197
1293
|
// again when it arrives; until it has, and where it never does, the
|
|
@@ -1226,20 +1322,21 @@ class Builder {
|
|
|
1226
1322
|
flush = true;
|
|
1227
1323
|
}
|
|
1228
1324
|
if (markerImage && !ownMarker) {
|
|
1229
|
-
|
|
1325
|
+
const [width, height] = this._markerImageSize(
|
|
1326
|
+
markerImage.size,
|
|
1327
|
+
markerStyle ?? style,
|
|
1328
|
+
);
|
|
1329
|
+
const gap = MARKER_IMAGE_GAP * (this._options.scale ?? 1);
|
|
1330
|
+
const image = { url: markerImage.url, width, height, gap };
|
|
1331
|
+
if (style.listStylePosition === 'inside') insideImage = image;
|
|
1230
1332
|
else {
|
|
1231
|
-
const scale = this._options.scale ?? 1;
|
|
1232
1333
|
box.marker = {
|
|
1233
1334
|
text: '',
|
|
1234
1335
|
layout: null,
|
|
1235
1336
|
x: 0,
|
|
1236
1337
|
y: 0,
|
|
1237
1338
|
style: markerStyle,
|
|
1238
|
-
image
|
|
1239
|
-
url: markerImage.url,
|
|
1240
|
-
width: (markerImage.size.width ?? 0) * scale,
|
|
1241
|
-
height: (markerImage.size.height ?? 0) * scale,
|
|
1242
|
-
},
|
|
1339
|
+
image,
|
|
1243
1340
|
};
|
|
1244
1341
|
}
|
|
1245
1342
|
} else if (text && style.listStylePosition === 'inside') {
|
|
@@ -1272,19 +1369,23 @@ class Builder {
|
|
|
1272
1369
|
: null;
|
|
1273
1370
|
const ownLetter = ownRules ? { rules: ownRules, punctuation: [] } : null;
|
|
1274
1371
|
if (ownLetter) this._firstLetter = ownLetter;
|
|
1275
|
-
this._depth +=
|
|
1372
|
+
this._depth += depth;
|
|
1276
1373
|
if (insideImage) {
|
|
1277
1374
|
// an inline image at the start of the first line, as a generated
|
|
1278
|
-
// image is, and the
|
|
1279
|
-
|
|
1280
|
-
|
|
1375
|
+
// image is, and the room after it Blink gives it rather than a
|
|
1376
|
+
// space: the content's own white space after it is kept, as it is
|
|
1377
|
+
// after an image (`MARKER_IMAGE_GAP`)
|
|
1378
|
+
const { url, gap } = insideImage;
|
|
1379
|
+
const image = this._contentImage(url, box, style, el, insideImage);
|
|
1380
|
+
if (style.direction === 'rtl') image.style.marginLeft = gap;
|
|
1381
|
+
else image.style.marginRight = gap;
|
|
1281
1382
|
} else if (insideMarker) {
|
|
1282
1383
|
this._insideMarker(insideMarker, style, markerStyle, box, el);
|
|
1283
1384
|
}
|
|
1284
1385
|
this._pseudo(el, 'before', style, box);
|
|
1285
1386
|
this._children(el, box, style, childInFlex, el, key);
|
|
1286
1387
|
this._pseudo(el, 'after', style, box);
|
|
1287
|
-
this._depth -=
|
|
1388
|
+
this._depth -= depth;
|
|
1288
1389
|
this._letterAfter(flow, skipped, outerLetter, ownLetter);
|
|
1289
1390
|
if (flow !== 'inline') this._endLine();
|
|
1290
1391
|
this._ws = after(flow, this._ws, around);
|
|
@@ -1724,14 +1825,14 @@ class Builder {
|
|
|
1724
1825
|
.join(item.separator);
|
|
1725
1826
|
break;
|
|
1726
1827
|
case 'open-quote':
|
|
1727
|
-
text += quoteAt(style.quotes, this._quoteDepth, 0);
|
|
1828
|
+
text += quoteAt(style.quotes, this._quoteDepth, 0, languageOf(el));
|
|
1728
1829
|
this._quoteDepth += 1;
|
|
1729
1830
|
break;
|
|
1730
1831
|
case 'close-quote':
|
|
1731
1832
|
// a close with nothing open writes nothing and closes nothing
|
|
1732
1833
|
if (this._quoteDepth > 0) {
|
|
1733
1834
|
this._quoteDepth -= 1;
|
|
1734
|
-
text += quoteAt(style.quotes, this._quoteDepth, 1);
|
|
1835
|
+
text += quoteAt(style.quotes, this._quoteDepth, 1, languageOf(el));
|
|
1735
1836
|
}
|
|
1736
1837
|
break;
|
|
1737
1838
|
case 'no-open-quote':
|
|
@@ -1757,7 +1858,10 @@ class Builder {
|
|
|
1757
1858
|
into: Box,
|
|
1758
1859
|
style: ComputedStyle,
|
|
1759
1860
|
owner: Element,
|
|
1760
|
-
|
|
1861
|
+
/** Its size where it is a list marker's, which has one whatever the
|
|
1862
|
+
* image says (`_markerImageSize`). */
|
|
1863
|
+
marker?: { width: number; height: number },
|
|
1864
|
+
): Box {
|
|
1761
1865
|
const box = new Box('replaced', null, {
|
|
1762
1866
|
...inherit(style, this._options.cascade.initial),
|
|
1763
1867
|
display: 'inline',
|
|
@@ -1767,11 +1871,40 @@ class Builder {
|
|
|
1767
1871
|
CONTENT_IMAGES.set(box, url);
|
|
1768
1872
|
const size = this._options.urlSize?.(url) ?? null;
|
|
1769
1873
|
this._contentImages.push({ url, element: owner, sized: !!size });
|
|
1770
|
-
if (
|
|
1874
|
+
if (marker) {
|
|
1875
|
+
const { width, height } = marker;
|
|
1876
|
+
const ratio = width > 0 && height > 0 ? width / height : 0;
|
|
1877
|
+
box.intrinsic = { width, height, missing: 0, ratio };
|
|
1878
|
+
} else if (size) setIntrinsics(box, size, this._options.scale ?? 1);
|
|
1771
1879
|
else box.intrinsic = { width: 0, height: 0, missing: 0, ratio: 0 };
|
|
1772
1880
|
this._ws = after('atomic', this._ws, this._ws);
|
|
1773
1881
|
this._lettered = false;
|
|
1774
1882
|
this._abandonLetter();
|
|
1883
|
+
return box;
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
/**
|
|
1887
|
+
* A list marker image's size, in device pixels: its own where it has one,
|
|
1888
|
+
* and where it lacks some — an SVG with only a `viewBox`, or with no size
|
|
1889
|
+
* and no ratio — CSS Images' default sizing in a square half its face's
|
|
1890
|
+
* ascent across. CSS Lists 3 (3.3) makes the square 1em, which both
|
|
1891
|
+
* engines find too large (w3c/csswg-drafts#4207): Blink's is half the
|
|
1892
|
+
* ascent, rounded to a whole pixel first
|
|
1893
|
+
* (`LayoutListMarkerImage::DefaultSize`), Gecko's 0.4em, and this is
|
|
1894
|
+
* Blink's. A `viewBox` alone came to 0 by 0 and drew no marker at all,
|
|
1895
|
+
* and inside the item to the 300 by 150 a generated image defaults to.
|
|
1896
|
+
*/
|
|
1897
|
+
private _markerImageSize(
|
|
1898
|
+
size: IntrinsicSize,
|
|
1899
|
+
style: ComputedStyle,
|
|
1900
|
+
): [number, number] {
|
|
1901
|
+
let side = 0;
|
|
1902
|
+
// CoreText reads a size of 0 as its default twelve points
|
|
1903
|
+
if ((size.width === null || size.height === null) && style.fontSize > 0) {
|
|
1904
|
+
const ascent = this._options.faceAscent?.(style) ?? style.fontSize * 0.8;
|
|
1905
|
+
side = Math.round(ascent) / 2;
|
|
1906
|
+
}
|
|
1907
|
+
return concreteSize(size, side, side, this._options.scale ?? 1);
|
|
1775
1908
|
}
|
|
1776
1909
|
|
|
1777
1910
|
/** A text node, whitespace-processed per the inherited `white-space`. */
|
|
@@ -2437,6 +2570,47 @@ function after(
|
|
|
2437
2570
|
}
|
|
2438
2571
|
}
|
|
2439
2572
|
|
|
2573
|
+
/**
|
|
2574
|
+
* How many anonymous boxes the fix-up will put between a box of `kind` and
|
|
2575
|
+
* the box it is appended to, `parent` (`fixUpTable`, `wrapOrphans`,
|
|
2576
|
+
* `wrapTableParts`): a table part outside a table is given what it is
|
|
2577
|
+
* missing of one, and what is in a table and no part of it is given a
|
|
2578
|
+
* cell, a row and a row group. A cell in a block is in a table, a row group
|
|
2579
|
+
* and a row of its own, and a block in a row is in a cell. Counted against
|
|
2580
|
+
* `MAX_DEPTH`, which bounds the boxes and not the elements.
|
|
2581
|
+
*/
|
|
2582
|
+
function wrappersAround(
|
|
2583
|
+
parent: BoxKind,
|
|
2584
|
+
kind: BoxKind,
|
|
2585
|
+
style: ComputedStyle,
|
|
2586
|
+
): number {
|
|
2587
|
+
const column =
|
|
2588
|
+
style.display === 'table-column' || style.display === 'table-column-group';
|
|
2589
|
+
switch (parent) {
|
|
2590
|
+
case 'table':
|
|
2591
|
+
if (kind === 'table-row-group' || kind === 'table-caption' || column) {
|
|
2592
|
+
return 0;
|
|
2593
|
+
}
|
|
2594
|
+
return kind === 'table-row' ? 1 : kind === 'table-cell' ? 2 : 3;
|
|
2595
|
+
case 'table-row-group':
|
|
2596
|
+
return kind === 'table-row' ? 0 : kind === 'table-cell' ? 1 : 2;
|
|
2597
|
+
case 'table-row':
|
|
2598
|
+
return kind === 'table-cell' ? 0 : 1;
|
|
2599
|
+
}
|
|
2600
|
+
// a part out of flow is no part (`isTablePart`)
|
|
2601
|
+
if (
|
|
2602
|
+
style.position === 'absolute' ||
|
|
2603
|
+
style.position === 'fixed' ||
|
|
2604
|
+
style.float !== 'none'
|
|
2605
|
+
) {
|
|
2606
|
+
return 0;
|
|
2607
|
+
}
|
|
2608
|
+
if (kind === 'table-row-group' || kind === 'table-caption' || column) {
|
|
2609
|
+
return 1;
|
|
2610
|
+
}
|
|
2611
|
+
return kind === 'table-row' ? 2 : kind === 'table-cell' ? 3 : 0;
|
|
2612
|
+
}
|
|
2613
|
+
|
|
2440
2614
|
function boxKindFor(display: ComputedStyle['display']): BoxKind {
|
|
2441
2615
|
switch (display) {
|
|
2442
2616
|
case 'inline':
|
|
@@ -31,6 +31,7 @@ import { Box, GRID_TRACKS, isBlank } from './boxes.js';
|
|
|
31
31
|
import {
|
|
32
32
|
FLEXED_HEIGHT,
|
|
33
33
|
MIN_CONTENT_PROBE,
|
|
34
|
+
STRETCHED_ACROSS,
|
|
34
35
|
USED_HEIGHT,
|
|
35
36
|
centreButton,
|
|
36
37
|
clampHeight,
|
|
@@ -60,6 +61,9 @@ interface Item {
|
|
|
60
61
|
/** Min-content and max-content widths, margin box, taken when asked. */
|
|
61
62
|
min: number;
|
|
62
63
|
max: number;
|
|
64
|
+
/** Whether it is a table stretched across its area (`STRETCHED_ACROSS`),
|
|
65
|
+
* which `layoutItem` decides. */
|
|
66
|
+
across: boolean;
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
/**
|
|
@@ -203,6 +207,11 @@ export function layoutGrid(
|
|
|
203
207
|
const extra = style.boxSizing === 'border-box' ? 0 : box.horizontalExtra;
|
|
204
208
|
widest = narrowest = Math.max(style.width + extra, box.horizontalExtra);
|
|
205
209
|
} else {
|
|
210
|
+
// A percentage is of an area the columns are still being sized for,
|
|
211
|
+
// and what it comes to depends on what the item makes them: it is
|
|
212
|
+
// `auto` for that (CSS Sizing 3, 5.2.1), and the item's content says
|
|
213
|
+
// how wide it is. Measured at the width a percentage made of the
|
|
214
|
+
// probe's, an item `width: 100%` made its `auto` column nothing.
|
|
206
215
|
const fresh = box.intrinsicMaxContent < 0;
|
|
207
216
|
widest = maxContentOf(box, ctx);
|
|
208
217
|
if (box.intrinsicMinContent < 0) {
|
|
@@ -210,8 +219,7 @@ export function layoutGrid(
|
|
|
210
219
|
// which is the max-content one only if it was made just now
|
|
211
220
|
const exact =
|
|
212
221
|
fresh && ctx.fonts ? exactMinContent(box, ctx.fonts) : null;
|
|
213
|
-
box.intrinsicMinContent =
|
|
214
|
-
exact ?? measureIntrinsicWidth(box, ctx, MIN_CONTENT_PROBE);
|
|
222
|
+
box.intrinsicMinContent = exact ?? minContentOf(box, ctx);
|
|
215
223
|
}
|
|
216
224
|
narrowest = box.intrinsicMinContent;
|
|
217
225
|
}
|
|
@@ -366,18 +374,19 @@ export function layoutGrid(
|
|
|
366
374
|
} else if (align === 'center') dy = (tall - outerHeight) / 2;
|
|
367
375
|
else if (align === 'flex-end') dy = tall - outerHeight;
|
|
368
376
|
else if (
|
|
369
|
-
(align === 'stretch' || align === 'baseline') &&
|
|
377
|
+
(align === 'stretch' || align === 'normal' || align === 'baseline') &&
|
|
370
378
|
child.style.height === AUTO &&
|
|
371
379
|
!child.style.heightKeyword &&
|
|
372
|
-
(
|
|
380
|
+
(align === 'stretch' ||
|
|
373
381
|
(child.kind !== 'replaced' && !child.style.aspectRatio))
|
|
374
382
|
) {
|
|
375
383
|
// a stretched item is its area's height, within its own least and
|
|
376
384
|
// greatest (10.3): taller than what it holds, or shorter where its
|
|
377
385
|
// tracks let it be, what it holds then running past it. An image, a
|
|
378
|
-
// control or a box with an `aspect-ratio` keeps its own height
|
|
379
|
-
// `normal
|
|
380
|
-
//
|
|
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).
|
|
381
390
|
const stretched = tall - child.marginTop - child.marginBottom;
|
|
382
391
|
const height = clampHeight(
|
|
383
392
|
child,
|
|
@@ -401,21 +410,27 @@ export function layoutGrid(
|
|
|
401
410
|
// purpose"): a card's `margin-top: auto` takes what its row is taller
|
|
402
411
|
// than the card by, its `flex: 1` grows into it (CSS Flexbox 8.1,
|
|
403
412
|
// 9.7). Its content's own height is the same layout, and kept.
|
|
413
|
+
// A table shares out among its rows what the area is taller than
|
|
414
|
+
// they are (`layoutTable`), and is no shorter than they are however
|
|
415
|
+
// short its area: its rows are the least it can be, as they are
|
|
416
|
+
// where it has a height of its own (CSS 2.1 17.5.3).
|
|
404
417
|
if (
|
|
405
418
|
child.kind !== 'replaced' &&
|
|
406
419
|
(wide !== null ||
|
|
407
420
|
percentHeightsIn(child) ||
|
|
408
|
-
(child.kind === 'flex' && height !== child.height)
|
|
421
|
+
(child.kind === 'flex' && height !== child.height) ||
|
|
422
|
+
(child.kind === 'table' && height > child.height + 0.01))
|
|
409
423
|
) {
|
|
410
424
|
FLEXED_HEIGHT.set(child, Math.max(0, height - child.verticalExtra));
|
|
411
425
|
try {
|
|
412
|
-
|
|
426
|
+
layoutAcross(item, width, area, ctx);
|
|
413
427
|
} finally {
|
|
414
428
|
FLEXED_HEIGHT.delete(child);
|
|
415
429
|
}
|
|
416
430
|
}
|
|
417
431
|
child.width = width;
|
|
418
|
-
child.height =
|
|
432
|
+
child.height =
|
|
433
|
+
child.kind === 'table' ? Math.max(height, child.height) : height;
|
|
419
434
|
centreButton(child);
|
|
420
435
|
}
|
|
421
436
|
moveTo(
|
|
@@ -596,6 +611,7 @@ function place(
|
|
|
596
611
|
cols: p.bs,
|
|
597
612
|
min: -1,
|
|
598
613
|
max: -1,
|
|
614
|
+
across: false,
|
|
599
615
|
}
|
|
600
616
|
: {
|
|
601
617
|
box: p.box,
|
|
@@ -605,6 +621,7 @@ function place(
|
|
|
605
621
|
cols: p.as,
|
|
606
622
|
min: -1,
|
|
607
623
|
max: -1,
|
|
624
|
+
across: false,
|
|
608
625
|
},
|
|
609
626
|
);
|
|
610
627
|
}
|
|
@@ -641,14 +658,37 @@ function extent(
|
|
|
641
658
|
}
|
|
642
659
|
|
|
643
660
|
/** An item's max-content width, its border box's: measured once in the
|
|
644
|
-
* box's life, as a table cell's is (`Box.intrinsicMaxContent`).
|
|
661
|
+
* box's life, as a table cell's is (`Box.intrinsicMaxContent`). Its
|
|
662
|
+
* content's, whatever width it has of its own (`minContentOf`). */
|
|
645
663
|
function maxContentOf(box: Box, ctx: LayoutContext): number {
|
|
646
664
|
if (box.intrinsicMaxContent < 0) {
|
|
647
|
-
box.intrinsicMaxContent = measureIntrinsicWidth(
|
|
665
|
+
box.intrinsicMaxContent = measureIntrinsicWidth(
|
|
666
|
+
box,
|
|
667
|
+
ctx,
|
|
668
|
+
Infinity,
|
|
669
|
+
box.kind !== 'replaced',
|
|
670
|
+
);
|
|
648
671
|
}
|
|
649
672
|
return box.intrinsicMaxContent;
|
|
650
673
|
}
|
|
651
674
|
|
|
675
|
+
/**
|
|
676
|
+
* An item's min-content width, its border box's. What it is measured for is
|
|
677
|
+
* what it contributes to its columns, and the width a percentage makes of
|
|
678
|
+
* them is not known then: a box that is not replaced is as wide as its
|
|
679
|
+
* content for that, whatever its own `width` says, and a replaced element
|
|
680
|
+
* takes its percentage of nothing (CSS Sizing 3, 5.2.1). A width that is a
|
|
681
|
+
* length is weighed apart (`measure` in `layoutGrid`).
|
|
682
|
+
*/
|
|
683
|
+
function minContentOf(box: Box, ctx: LayoutContext): number {
|
|
684
|
+
return measureIntrinsicWidth(
|
|
685
|
+
box,
|
|
686
|
+
ctx,
|
|
687
|
+
MIN_CONTENT_PROBE,
|
|
688
|
+
box.kind !== 'replaced',
|
|
689
|
+
);
|
|
690
|
+
}
|
|
691
|
+
|
|
652
692
|
/**
|
|
653
693
|
* Lay an item out in its area: stretched across it where its width is
|
|
654
694
|
* `auto` and it is stretched, or at its content's width, no wider than
|
|
@@ -693,11 +733,7 @@ function layoutItem(
|
|
|
693
733
|
probed = true;
|
|
694
734
|
}
|
|
695
735
|
if (child.intrinsicMinContent < 0) {
|
|
696
|
-
child.intrinsicMinContent =
|
|
697
|
-
child,
|
|
698
|
-
ctx,
|
|
699
|
-
MIN_CONTENT_PROBE,
|
|
700
|
-
);
|
|
736
|
+
child.intrinsicMinContent = minContentOf(child, ctx);
|
|
701
737
|
probed = true;
|
|
702
738
|
}
|
|
703
739
|
if (probed) resolveEdges(child, area);
|
|
@@ -757,7 +793,15 @@ function layoutItem(
|
|
|
757
793
|
width = Math.min(widest, Math.max(narrowest, room));
|
|
758
794
|
}
|
|
759
795
|
if (child.kind !== 'replaced') {
|
|
760
|
-
|
|
796
|
+
// a table stretched across is as wide as its area, which in a block's
|
|
797
|
+
// flow it would shrink from to its columns (`STRETCHED_ACROSS`)
|
|
798
|
+
item.across =
|
|
799
|
+
across &&
|
|
800
|
+
child.kind === 'table' &&
|
|
801
|
+
style.width === AUTO &&
|
|
802
|
+
!keyword &&
|
|
803
|
+
through === null;
|
|
804
|
+
layoutAcross(item, width, area, ctx);
|
|
761
805
|
return;
|
|
762
806
|
}
|
|
763
807
|
// a replaced element sizes itself from its style: then it is given the
|
|
@@ -776,6 +820,27 @@ function layoutItem(
|
|
|
776
820
|
}
|
|
777
821
|
}
|
|
778
822
|
|
|
823
|
+
/** Lay an item out at a width: a table stretched across its area at that
|
|
824
|
+
* width as its own (`STRETCHED_ACROSS`), for this layout alone. */
|
|
825
|
+
function layoutAcross(
|
|
826
|
+
item: Item,
|
|
827
|
+
width: number,
|
|
828
|
+
area: number,
|
|
829
|
+
ctx: LayoutContext,
|
|
830
|
+
): void {
|
|
831
|
+
const child = item.box;
|
|
832
|
+
if (!item.across) {
|
|
833
|
+
ctx.layoutSubtree(child, width, area);
|
|
834
|
+
return;
|
|
835
|
+
}
|
|
836
|
+
STRETCHED_ACROSS.add(child);
|
|
837
|
+
try {
|
|
838
|
+
ctx.layoutSubtree(child, width, area);
|
|
839
|
+
} finally {
|
|
840
|
+
STRETCHED_ACROSS.delete(child);
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
779
844
|
/** What the percentages in a box's padding come to across it, its edges
|
|
780
845
|
* resolved: what a width of its content's, measured with them of nothing,
|
|
781
846
|
* is short of its border box by. */
|
|
@@ -875,9 +940,9 @@ function percentOwn(box: Box): boolean {
|
|
|
875
940
|
/**
|
|
876
941
|
* An item's border-box height before the rows are sized, where it has one
|
|
877
942
|
* then: a height of its own — a percentage of the rows it spans where they
|
|
878
|
-
* have a length for a size — or the rows' where
|
|
879
|
-
*
|
|
880
|
-
* 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.
|
|
881
946
|
*/
|
|
882
947
|
function heightBeforeRows(box: Box, rows: number): number | null {
|
|
883
948
|
const style = box.style;
|
|
@@ -890,10 +955,12 @@ function heightBeforeRows(box: Box, rows: number): number | null {
|
|
|
890
955
|
: own + box.verticalExtra,
|
|
891
956
|
);
|
|
892
957
|
}
|
|
958
|
+
const align =
|
|
959
|
+
style.alignSelf === AUTO ? box.parent?.style.alignItems : style.alignSelf;
|
|
893
960
|
if (
|
|
894
961
|
style.height === AUTO &&
|
|
895
962
|
!style.heightKeyword &&
|
|
896
|
-
|
|
963
|
+
align === 'stretch' &&
|
|
897
964
|
Number.isFinite(rows) &&
|
|
898
965
|
!autoMargins(box, 'y').some(Boolean)
|
|
899
966
|
) {
|