@react-x11/components 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/html/controls.d.ts +29 -2
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js +70 -2
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +26 -4
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +18 -0
- package/dist/html/css/color.d.ts.map +1 -1
- package/dist/html/css/color.js +27 -0
- package/dist/html/css/color.js.map +1 -1
- package/dist/html/css/parse.d.ts +18 -4
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +66 -28
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +128 -15
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +561 -144
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/transform.d.ts +85 -0
- package/dist/html/css/transform.d.ts.map +1 -0
- package/dist/html/css/transform.js +425 -0
- package/dist/html/css/transform.js.map +1 -0
- package/dist/html/css/ua.d.ts.map +1 -1
- package/dist/html/css/ua.js +14 -2
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +4 -0
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/fonts.d.ts +55 -5
- package/dist/html/fonts.d.ts.map +1 -1
- package/dist/html/fonts.js +284 -23
- package/dist/html/fonts.js.map +1 -1
- package/dist/html/layout/axes.d.ts +14 -0
- package/dist/html/layout/axes.d.ts.map +1 -0
- package/dist/html/layout/axes.js +28 -0
- package/dist/html/layout/axes.js.map +1 -0
- package/dist/html/layout/block.d.ts +93 -12
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +389 -39
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +59 -1
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +22 -2
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/cache.d.ts +1 -1
- package/dist/html/layout/cache.d.ts.map +1 -1
- package/dist/html/layout/cache.js +6 -0
- package/dist/html/layout/cache.js.map +1 -1
- package/dist/html/layout/css-grid.d.ts.map +1 -1
- package/dist/html/layout/css-grid.js +90 -18
- package/dist/html/layout/css-grid.js.map +1 -1
- package/dist/html/layout/flex.d.ts.map +1 -1
- package/dist/html/layout/flex.js +570 -83
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/inline.d.ts +3 -0
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +144 -54
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/layout/multicol.d.ts +42 -0
- package/dist/html/layout/multicol.d.ts.map +1 -0
- package/dist/html/layout/multicol.js +897 -0
- package/dist/html/layout/multicol.js.map +1 -0
- package/dist/html/layout/shaping.d.ts +7 -0
- package/dist/html/layout/shaping.d.ts.map +1 -1
- package/dist/html/layout/shaping.js +25 -20
- package/dist/html/layout/shaping.js.map +1 -1
- package/dist/html/layout/table.d.ts.map +1 -1
- package/dist/html/layout/table.js +62 -18
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +3 -0
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +263 -36
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +79 -4
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +1213 -141
- package/dist/html/paint.js.map +1 -1
- package/dist/html/svg.d.ts +15 -2
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +249 -17
- package/dist/html/svg.js.map +1 -1
- package/dist/html/widgets.js +87 -7
- package/dist/html/widgets.js.map +1 -1
- package/dist/html/woff2.d.ts +14 -0
- package/dist/html/woff2.d.ts.map +1 -0
- package/dist/html/woff2.js +608 -0
- package/dist/html/woff2.js.map +1 -0
- package/dist/maps/controller.d.ts +17 -1
- package/dist/maps/controller.d.ts.map +1 -1
- package/dist/maps/controller.js +27 -7
- package/dist/maps/controller.js.map +1 -1
- package/dist/richtext/node.d.ts +8 -1
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +5 -1
- package/dist/richtext/node.js.map +1 -1
- package/dist/richtext/runs.d.ts +25 -6
- package/dist/richtext/runs.d.ts.map +1 -1
- package/dist/richtext/runs.js +240 -24
- package/dist/richtext/runs.js.map +1 -1
- package/package.json +1 -1
- package/src/html/controls.ts +85 -2
- package/src/html/css/cascade.ts +23 -0
- package/src/html/css/color.ts +29 -0
- package/src/html/css/parse.ts +71 -31
- package/src/html/css/style.ts +664 -147
- package/src/html/css/transform.ts +459 -0
- package/src/html/css/ua.ts +15 -2
- package/src/html/css/values.ts +4 -0
- package/src/html/fonts.ts +312 -29
- package/src/html/layout/axes.ts +62 -0
- package/src/html/layout/block.ts +444 -37
- package/src/html/layout/boxes.ts +65 -3
- package/src/html/layout/cache.ts +6 -0
- package/src/html/layout/css-grid.ts +95 -22
- package/src/html/layout/flex.ts +671 -77
- package/src/html/layout/inline.ts +149 -55
- package/src/html/layout/multicol.ts +977 -0
- package/src/html/layout/shaping.ts +27 -18
- package/src/html/layout/table.ts +62 -19
- package/src/html/node.ts +300 -37
- package/src/html/paint.ts +1406 -206
- package/src/html/svg.ts +277 -19
- package/src/html/widgets.ts +95 -9
- package/src/html/woff2.ts +612 -0
- package/src/maps/controller.ts +28 -7
- package/src/richtext/node.ts +13 -2
- package/src/richtext/runs.ts +313 -35
package/src/html/css/style.ts
CHANGED
|
@@ -40,6 +40,8 @@ import {
|
|
|
40
40
|
} from './content.js';
|
|
41
41
|
import type { ContentItem, CounterChange } from './content.js';
|
|
42
42
|
import type { CustomProps } from './vars.js';
|
|
43
|
+
import { parseRotate, parseScale, parseTransform } from './transform.js';
|
|
44
|
+
import type { TransformFunction } from './transform.js';
|
|
43
45
|
import { LIGHT_DARK, lightDark, usedColorScheme } from './color.js';
|
|
44
46
|
|
|
45
47
|
export type Display =
|
|
@@ -108,15 +110,43 @@ export interface BoxShadow {
|
|
|
108
110
|
inset: boolean;
|
|
109
111
|
}
|
|
110
112
|
|
|
113
|
+
/** A gradient's colour stop: its colour, at a position along the gradient
|
|
114
|
+
* or, with none, where its neighbours put it. */
|
|
115
|
+
export interface GradientStop {
|
|
116
|
+
color: string;
|
|
117
|
+
at: Len | null;
|
|
118
|
+
}
|
|
119
|
+
|
|
111
120
|
/** A `linear-gradient()` (CSS Images 3, 3.1): its direction, an angle
|
|
112
121
|
* clockwise from up or a corner the box's shape turns into one, and its
|
|
113
|
-
* colour stops
|
|
122
|
+
* colour stops. */
|
|
114
123
|
export interface LinearGradient {
|
|
124
|
+
kind: 'linear';
|
|
115
125
|
angle: number;
|
|
116
126
|
corner: 'top left' | 'top right' | 'bottom left' | 'bottom right' | null;
|
|
117
|
-
stops:
|
|
127
|
+
stops: GradientStop[];
|
|
118
128
|
}
|
|
119
129
|
|
|
130
|
+
/** A `radial-gradient()` (CSS Images 3, 3.2): its ending shape, a circle or
|
|
131
|
+
* an ellipse; that shape's size, a keyword for the side or the corner of
|
|
132
|
+
* the box it reaches or the radii it was given, one for a circle and two
|
|
133
|
+
* for an ellipse; its centre, a position in the box; and its colour stops,
|
|
134
|
+
* along the ray from the centre to the shape's edge. */
|
|
135
|
+
export interface RadialGradient {
|
|
136
|
+
kind: 'radial';
|
|
137
|
+
circle: boolean;
|
|
138
|
+
extent: RadialExtent | null;
|
|
139
|
+
radii: [Len, Len] | null;
|
|
140
|
+
at: [Len, Len];
|
|
141
|
+
stops: GradientStop[];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export type RadialExtent =
|
|
145
|
+
'closest-side' | 'closest-corner' | 'farthest-side' | 'farthest-corner';
|
|
146
|
+
|
|
147
|
+
/** An image a gradient function draws. */
|
|
148
|
+
export type Gradient = LinearGradient | RadialGradient;
|
|
149
|
+
|
|
120
150
|
/** Where a grid item starts or ends (CSS Grid 1, 8.3): a line, counted
|
|
121
151
|
* among the lines of a name where it has one; a span, to a line of a name
|
|
122
152
|
* where it has one; a name alone, an area's edge or a line's; or auto. */
|
|
@@ -174,6 +204,121 @@ function parseClip(
|
|
|
174
204
|
return { top: edges[0], right: edges[1], bottom: edges[2], left: edges[3] };
|
|
175
205
|
}
|
|
176
206
|
|
|
207
|
+
/** The box a `clip-path`'s shape is measured in, a `<geometry-box>` (CSS
|
|
208
|
+
* Masking 1, 5.1). */
|
|
209
|
+
export type ClipBox =
|
|
210
|
+
'margin-box' | 'border-box' | 'padding-box' | 'content-box';
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* `clip-path` (CSS Masking 1, 5.1): a rectangle in a box of the element's,
|
|
214
|
+
* its corners rounded or not — a `<basic-shape-rect>` (CSS Shapes 1, 3.1),
|
|
215
|
+
* kept as the function it was written as, since each measures its four
|
|
216
|
+
* lengths its own way and a percentage among them has no pixels before
|
|
217
|
+
* layout. The element shows through it, and everything in it does.
|
|
218
|
+
*/
|
|
219
|
+
export interface ClipPath {
|
|
220
|
+
box: ClipBox;
|
|
221
|
+
/** What the four lengths are: `inset()`'s, how far in from the box's
|
|
222
|
+
* top, right, bottom and left; `rect()`'s, those four edges from its top
|
|
223
|
+
* and its left, `auto` the box's own; `xywh()`'s, the rectangle's left
|
|
224
|
+
* and top from the box's, and its width and height. Null for a box
|
|
225
|
+
* named alone, which is the shape: the whole of it, rounded as the
|
|
226
|
+
* element's corners are. */
|
|
227
|
+
shape: 'inset' | 'rect' | 'xywh' | null;
|
|
228
|
+
lengths: readonly [Len, Len, Len, Len];
|
|
229
|
+
/** `round`: the corners' radii across, and down where they differ, as
|
|
230
|
+
* `border-radius` has them — a percentage is of the box, and not of the
|
|
231
|
+
* rectangle in it. Null for square corners. */
|
|
232
|
+
radii: readonly [Len, Len, Len, Len] | null;
|
|
233
|
+
radiiY: readonly [Len, Len, Len, Len] | null;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** The boxes `clip-path` names; an SVG one is the CSS box that stands for
|
|
237
|
+
* it on an element with a CSS layout box (5.1). */
|
|
238
|
+
const CLIP_BOXES: Record<string, ClipBox> = {
|
|
239
|
+
'margin-box': 'margin-box',
|
|
240
|
+
'border-box': 'border-box',
|
|
241
|
+
'padding-box': 'padding-box',
|
|
242
|
+
'content-box': 'content-box',
|
|
243
|
+
'fill-box': 'content-box',
|
|
244
|
+
'stroke-box': 'border-box',
|
|
245
|
+
'view-box': 'border-box',
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
/** The shapes that are no rectangle, and a reference to a `<clipPath>`
|
|
249
|
+
* element: a value, which sets the property, and none this draws. */
|
|
250
|
+
const UNDRAWN_CLIPS = /^(circle|ellipse|polygon|path|shape|url)\(/i;
|
|
251
|
+
|
|
252
|
+
const NO_LENGTHS = [0, 0, 0, 0] as const;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* `none`; a rectangle's function, a box, or the two in either order; or a
|
|
256
|
+
* shape that is no rectangle, which is a value and clips nothing here.
|
|
257
|
+
* Undefined for anything else, which leaves the value as it was.
|
|
258
|
+
*/
|
|
259
|
+
function parseClipPath(
|
|
260
|
+
value: string,
|
|
261
|
+
ctx: UnitContext,
|
|
262
|
+
): ClipPath | null | undefined {
|
|
263
|
+
const v = value.trim();
|
|
264
|
+
if (/^none$/i.test(v)) return null;
|
|
265
|
+
let box: ClipBox | null = null;
|
|
266
|
+
let shape: Omit<ClipPath, 'box'> | null | undefined;
|
|
267
|
+
for (const part of splitValue(v)) {
|
|
268
|
+
const named = CLIP_BOXES[part.toLowerCase()];
|
|
269
|
+
if (named) {
|
|
270
|
+
if (box) return undefined;
|
|
271
|
+
box = named;
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
if (shape !== undefined) return undefined;
|
|
275
|
+
if (UNDRAWN_CLIPS.test(part) && part.endsWith(')')) shape = null;
|
|
276
|
+
else shape = rectShape(part, ctx);
|
|
277
|
+
if (shape === undefined) return undefined;
|
|
278
|
+
}
|
|
279
|
+
if (shape === null) return null;
|
|
280
|
+
if (shape) return { box: box ?? 'border-box', ...shape };
|
|
281
|
+
if (!box) return undefined;
|
|
282
|
+
return { box, shape: null, lengths: NO_LENGTHS, radii: null, radiiY: null };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/** `inset()`, `rect()` or `xywh()`, with the corners it rounds; undefined
|
|
286
|
+
* for anything else. */
|
|
287
|
+
function rectShape(
|
|
288
|
+
text: string,
|
|
289
|
+
ctx: UnitContext,
|
|
290
|
+
): Omit<ClipPath, 'box'> | undefined {
|
|
291
|
+
const m = /^(inset|rect|xywh)\((.*)\)$/is.exec(text);
|
|
292
|
+
if (!m) return undefined;
|
|
293
|
+
const shape = m[1].toLowerCase() as 'inset' | 'rect' | 'xywh';
|
|
294
|
+
const parts = splitValue(m[2].trim());
|
|
295
|
+
const round = parts.findIndex((p) => p.toLowerCase() === 'round');
|
|
296
|
+
const sides = round < 0 ? parts : parts.slice(0, round);
|
|
297
|
+
let radii: ClipPath['radii'] = null;
|
|
298
|
+
let radiiY: ClipPath['radiiY'] = null;
|
|
299
|
+
if (round >= 0) {
|
|
300
|
+
const corners = parseRadii(parts.slice(round + 1).join(' '), ctx);
|
|
301
|
+
if (!corners) return undefined;
|
|
302
|
+
[radii, radiiY] = corners;
|
|
303
|
+
}
|
|
304
|
+
// one to four insets, as a margin's sides are written; the other two
|
|
305
|
+
// take all four
|
|
306
|
+
if (
|
|
307
|
+
shape === 'inset' ? !sides.length || sides.length > 4 : sides.length !== 4
|
|
308
|
+
)
|
|
309
|
+
return undefined;
|
|
310
|
+
const lengths: Len[] = [];
|
|
311
|
+
for (let i = 0; i < sides.length; i += 1) {
|
|
312
|
+
const len = parseLength(sides[i], ctx);
|
|
313
|
+
if (len === null) return undefined;
|
|
314
|
+
// only `rect()` has an edge that is `auto`, and a width is not negative
|
|
315
|
+
if (len === AUTO && shape !== 'rect') return undefined;
|
|
316
|
+
if (shape === 'xywh' && i > 1 && !notNegative(len)) return undefined;
|
|
317
|
+
lengths.push(len);
|
|
318
|
+
}
|
|
319
|
+
return { shape, lengths: fourSides(lengths), radii, radiiY };
|
|
320
|
+
}
|
|
321
|
+
|
|
177
322
|
export type BorderStyle =
|
|
178
323
|
| 'none'
|
|
179
324
|
| 'hidden'
|
|
@@ -187,7 +332,7 @@ export type BorderStyle =
|
|
|
187
332
|
| 'outset';
|
|
188
333
|
|
|
189
334
|
/** A background layer's image: a url, a gradient, or none. */
|
|
190
|
-
export type BackgroundImage = string |
|
|
335
|
+
export type BackgroundImage = string | Gradient | null;
|
|
191
336
|
|
|
192
337
|
/** An intrinsic size: `min-content`, `max-content` or `fit-content`, and
|
|
193
338
|
* `fit-content()`, whose argument stands in for the room (`fit`). */
|
|
@@ -290,6 +435,15 @@ export interface ComputedStyle {
|
|
|
290
435
|
* in place of its `list-style-type`'s; null for `none`. */
|
|
291
436
|
listStyleImage: string | null;
|
|
292
437
|
cursor: string | null;
|
|
438
|
+
/** `pointer-events`: `none` for an element the pointer passes through,
|
|
439
|
+
* to what is under it (CSS UI 4, 5.2). Its SVG values are `auto`. */
|
|
440
|
+
pointerEvents: 'auto' | 'none';
|
|
441
|
+
/** `fill` and `stroke`, what an `<svg>` in the document is painted with
|
|
442
|
+
* (SVG 2, 13.2): a colour, `currentColor`, `none`, or a `url()` of a
|
|
443
|
+
* paint server in the drawing — and null where nothing has set one,
|
|
444
|
+
* which leaves the drawing its own. */
|
|
445
|
+
fill: string | null;
|
|
446
|
+
stroke: string | null;
|
|
293
447
|
borderCollapse: 'separate' | 'collapse';
|
|
294
448
|
/** `border-spacing`: between columns, and between rows. */
|
|
295
449
|
borderSpacing: number;
|
|
@@ -369,6 +523,26 @@ export interface ComputedStyle {
|
|
|
369
523
|
* multicol container, which this lays out as one column and which
|
|
370
524
|
* `line-clamp` does not clamp (CSS Overflow 4, 5.2). */
|
|
371
525
|
columns: number;
|
|
526
|
+
/** `column-count` where it is a number, and `column-width` where it is
|
|
527
|
+
* a length; null for `auto` (CSS Multi-column 1, 2 and 3). */
|
|
528
|
+
columnCount: number | null;
|
|
529
|
+
columnWidth: number | null;
|
|
530
|
+
/** Whether `column-gap` is `normal`: nothing in a flex box or a grid,
|
|
531
|
+
* and an em between the columns of a multicol container (4.1). */
|
|
532
|
+
columnGapNormal: boolean;
|
|
533
|
+
/** The fewest lines of a block a column may end on, and the fewest it
|
|
534
|
+
* may start with (CSS Fragmentation 3, 4.3). Inherited. */
|
|
535
|
+
orphans: number;
|
|
536
|
+
widows: number;
|
|
537
|
+
/** `break-inside`: whether a column break may fall inside the box;
|
|
538
|
+
* `avoid-column` and `avoid-page` are read as `avoid`. */
|
|
539
|
+
breakInside: 'auto' | 'avoid';
|
|
540
|
+
/** `column-fill`: whether the columns are balanced, or each filled
|
|
541
|
+
* before the next is started; `balance-all` is `balance`. */
|
|
542
|
+
columnFill: 'balance' | 'auto';
|
|
543
|
+
/** `column-span: all`: whether the box is set across all the columns of
|
|
544
|
+
* the multicol container it is in (CSS Multi-column 1, 6). */
|
|
545
|
+
columnSpan: boolean;
|
|
372
546
|
/** How a line cut by `overflow` ends: `clip`, or with an ellipsis. */
|
|
373
547
|
textOverflow: 'clip' | 'ellipsis';
|
|
374
548
|
/** `aspect-ratio`: a box's width over its height, where its height is
|
|
@@ -388,16 +562,28 @@ export interface ComputedStyle {
|
|
|
388
562
|
* its edges measured from the border box's top left, a null edge the
|
|
389
563
|
* border box's own (CSS 2.1 11.1.2). Null for `auto`. */
|
|
390
564
|
clip: ClipRect | null;
|
|
565
|
+
/** `clip-path`: the rectangle the element and all it holds show through
|
|
566
|
+
* (CSS Masking 1, 5.1); null for `none`, and for a shape this does not
|
|
567
|
+
* draw. */
|
|
568
|
+
clipPath: ClipPath | null;
|
|
391
569
|
opacity: number;
|
|
392
570
|
/** Where `translate` moves the box after layout (CSS Transforms 2): a
|
|
393
571
|
* length or a percentage of its own border box across and down; null
|
|
394
572
|
* for `none`. */
|
|
395
573
|
translate: [Len, Len] | null;
|
|
396
|
-
/**
|
|
397
|
-
*
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
574
|
+
/** `rotate`: a turn in the plane of the page, in degrees; null for
|
|
575
|
+
* `none`, and for a turn about an axis in the page. */
|
|
576
|
+
rotate: number | null;
|
|
577
|
+
/** `scale`: across and down; null for `none`. */
|
|
578
|
+
scale: [number, number] | null;
|
|
579
|
+
/** `transform`: its functions in the order written, the ones out of the
|
|
580
|
+
* plane left out (`css/transform.ts`); null for `none`. Any of the four
|
|
581
|
+
* makes the box a containing block and a stacking context, and paints it
|
|
582
|
+
* with the positioned boxes (CSS Transforms 1, 3; `transformed`). */
|
|
583
|
+
transform: TransformFunction[] | null;
|
|
584
|
+
/** `transform-origin`: the point a transform turns and scales about,
|
|
585
|
+
* from the border box's top left, a percentage of its width and height. */
|
|
586
|
+
transformOrigin: [Len, Len];
|
|
401
587
|
zIndex: number | 'auto';
|
|
402
588
|
/** A keyword, a length to raise the box by, or a percentage of its own
|
|
403
589
|
* line height. */
|
|
@@ -484,7 +670,7 @@ export interface ComputedStyle {
|
|
|
484
670
|
backgroundImage: string | null;
|
|
485
671
|
/** A background drawn rather than fetched: the first layer's linear
|
|
486
672
|
* gradient, where it is one. */
|
|
487
|
-
backgroundGradient:
|
|
673
|
+
backgroundGradient: Gradient | null;
|
|
488
674
|
/** How the first layer repeats across and down (CSS Backgrounds 3, 3.4):
|
|
489
675
|
* one of a few pairs made once, so a style copies no array. */
|
|
490
676
|
backgroundRepeat: BackgroundRepeat;
|
|
@@ -543,10 +729,17 @@ export interface ComputedStyle {
|
|
|
543
729
|
* set it (`decorate`). No property: the cascade works them out. */
|
|
544
730
|
underline: string | null;
|
|
545
731
|
underlineStyle: 'solid' | 'double' | 'dotted' | 'dashed' | 'wavy';
|
|
546
|
-
/** And the underline's thickness and offset, the box's that set it
|
|
732
|
+
/** And the underline's thickness and offset, the box's that set it: the
|
|
733
|
+
* thickness the one used, which `auto` takes from that box's font size
|
|
734
|
+
* (`usedThickness`), and the offset null where it is `auto`. */
|
|
547
735
|
underlineThickness: number | null;
|
|
548
736
|
underlineOffset: number | null;
|
|
549
737
|
lineThrough: string | null;
|
|
738
|
+
/** And the line through's style and thickness, the box's that set it, as
|
|
739
|
+
* the underline's are. It has no offset to set: it is drawn where each
|
|
740
|
+
* font it crosses puts it (`paintRunRules`). */
|
|
741
|
+
lineThroughStyle: 'solid' | 'double' | 'dotted' | 'dashed' | 'wavy';
|
|
742
|
+
lineThroughThickness: number | null;
|
|
550
743
|
|
|
551
744
|
// flex — handed to yoga rather than interpreted here
|
|
552
745
|
flexDirection: 'row' | 'row-reverse' | 'column' | 'column-reverse';
|
|
@@ -653,6 +846,8 @@ export const INHERITED = [
|
|
|
653
846
|
'fontFeatureSettings',
|
|
654
847
|
'tabSize',
|
|
655
848
|
'tabSizeIsLength',
|
|
849
|
+
'orphans',
|
|
850
|
+
'widows',
|
|
656
851
|
'whiteSpace',
|
|
657
852
|
'overflowWrap',
|
|
658
853
|
'wordBreak',
|
|
@@ -664,6 +859,9 @@ export const INHERITED = [
|
|
|
664
859
|
'listStylePosition',
|
|
665
860
|
'listStyleImage',
|
|
666
861
|
'cursor',
|
|
862
|
+
'pointerEvents',
|
|
863
|
+
'fill',
|
|
864
|
+
'stroke',
|
|
667
865
|
'borderCollapse',
|
|
668
866
|
'borderSpacing',
|
|
669
867
|
'borderSpacingY',
|
|
@@ -758,6 +956,9 @@ export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
|
|
|
758
956
|
listStylePosition: 'outside',
|
|
759
957
|
listStyleImage: null,
|
|
760
958
|
cursor: null,
|
|
959
|
+
pointerEvents: 'auto',
|
|
960
|
+
fill: null,
|
|
961
|
+
stroke: null,
|
|
761
962
|
borderCollapse: 'separate',
|
|
762
963
|
// CSS's initial value; a `<table>` gets its 2px from the UA sheet, and
|
|
763
964
|
// an anonymous table, which no sheet names, has none
|
|
@@ -793,6 +994,14 @@ export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
|
|
|
793
994
|
webkitBoxAlign: null,
|
|
794
995
|
webkitBoxFlex: 0,
|
|
795
996
|
columns: 0,
|
|
997
|
+
columnCount: null,
|
|
998
|
+
columnWidth: null,
|
|
999
|
+
columnGapNormal: true,
|
|
1000
|
+
orphans: 2,
|
|
1001
|
+
widows: 2,
|
|
1002
|
+
breakInside: 'auto',
|
|
1003
|
+
columnFill: 'balance',
|
|
1004
|
+
columnSpan: false,
|
|
796
1005
|
textOverflow: 'clip',
|
|
797
1006
|
aspectRatio: null,
|
|
798
1007
|
objectFit: 'fill',
|
|
@@ -802,9 +1011,13 @@ export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
|
|
|
802
1011
|
overflowX: 'visible',
|
|
803
1012
|
overflowY: 'visible',
|
|
804
1013
|
clip: null,
|
|
1014
|
+
clipPath: null,
|
|
805
1015
|
opacity: 1,
|
|
806
1016
|
translate: null,
|
|
807
|
-
|
|
1017
|
+
rotate: null,
|
|
1018
|
+
scale: null,
|
|
1019
|
+
transform: null,
|
|
1020
|
+
transformOrigin: [{ pct: 50 }, { pct: 50 }],
|
|
808
1021
|
zIndex: AUTO,
|
|
809
1022
|
verticalAlign: 'baseline',
|
|
810
1023
|
|
|
@@ -894,6 +1107,8 @@ export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
|
|
|
894
1107
|
underlineThickness: null,
|
|
895
1108
|
underlineOffset: null,
|
|
896
1109
|
lineThrough: null,
|
|
1110
|
+
lineThroughStyle: 'solid',
|
|
1111
|
+
lineThroughThickness: null,
|
|
897
1112
|
|
|
898
1113
|
flexDirection: 'row',
|
|
899
1114
|
flexWrap: 'nowrap',
|
|
@@ -1003,6 +1218,8 @@ export function inherit(
|
|
|
1003
1218
|
out.fontFeatureSettings = parent.fontFeatureSettings;
|
|
1004
1219
|
out.tabSize = parent.tabSize;
|
|
1005
1220
|
out.tabSizeIsLength = parent.tabSizeIsLength;
|
|
1221
|
+
out.orphans = parent.orphans;
|
|
1222
|
+
out.widows = parent.widows;
|
|
1006
1223
|
out.whiteSpace = parent.whiteSpace;
|
|
1007
1224
|
out.overflowWrap = parent.overflowWrap;
|
|
1008
1225
|
out.wordBreak = parent.wordBreak;
|
|
@@ -1014,6 +1231,9 @@ export function inherit(
|
|
|
1014
1231
|
out.listStylePosition = parent.listStylePosition;
|
|
1015
1232
|
out.listStyleImage = parent.listStyleImage;
|
|
1016
1233
|
out.cursor = parent.cursor;
|
|
1234
|
+
out.pointerEvents = parent.pointerEvents;
|
|
1235
|
+
out.fill = parent.fill;
|
|
1236
|
+
out.stroke = parent.stroke;
|
|
1017
1237
|
out.borderCollapse = parent.borderCollapse;
|
|
1018
1238
|
out.borderSpacing = parent.borderSpacing;
|
|
1019
1239
|
out.borderSpacingY = parent.borderSpacingY;
|
|
@@ -1033,6 +1253,8 @@ export function inherit(
|
|
|
1033
1253
|
out.underlineThickness = parent.underlineThickness;
|
|
1034
1254
|
out.underlineOffset = parent.underlineOffset;
|
|
1035
1255
|
out.lineThrough = parent.lineThrough;
|
|
1256
|
+
out.lineThroughStyle = parent.lineThroughStyle;
|
|
1257
|
+
out.lineThroughThickness = parent.lineThroughThickness;
|
|
1036
1258
|
return out;
|
|
1037
1259
|
}
|
|
1038
1260
|
|
|
@@ -1067,6 +1289,8 @@ export const FIRST_LINE_INHERITED = [
|
|
|
1067
1289
|
'underlineThickness',
|
|
1068
1290
|
'underlineOffset',
|
|
1069
1291
|
'lineThrough',
|
|
1292
|
+
'lineThroughStyle',
|
|
1293
|
+
'lineThroughThickness',
|
|
1070
1294
|
] as const satisfies readonly (keyof ComputedStyle)[];
|
|
1071
1295
|
|
|
1072
1296
|
/**
|
|
@@ -1142,6 +1366,15 @@ const BORDER_WIDTH_KEYWORDS: Record<string, number> = {
|
|
|
1142
1366
|
thick: 5,
|
|
1143
1367
|
};
|
|
1144
1368
|
|
|
1369
|
+
/** `-webkit-` names that are the multicol properties' own. */
|
|
1370
|
+
const MULTICOL_ALIASES: Record<string, string> = {
|
|
1371
|
+
'-webkit-columns': 'columns',
|
|
1372
|
+
'-webkit-column-count': 'column-count',
|
|
1373
|
+
'-webkit-column-width': 'column-width',
|
|
1374
|
+
'-webkit-column-gap': 'column-gap',
|
|
1375
|
+
'-webkit-column-break-inside': 'break-inside',
|
|
1376
|
+
};
|
|
1377
|
+
|
|
1145
1378
|
/**
|
|
1146
1379
|
* Apply one declaration to a style, in place.
|
|
1147
1380
|
*
|
|
@@ -1157,7 +1390,12 @@ export function applyDeclaration(
|
|
|
1157
1390
|
rawValue: string,
|
|
1158
1391
|
ctx: UnitContext,
|
|
1159
1392
|
): void {
|
|
1160
|
-
const
|
|
1393
|
+
const written = prop.toLowerCase();
|
|
1394
|
+
// the multicol properties under the names WebKit had them by, which
|
|
1395
|
+
// Blink still reads, each an alias of the property (`-webkit-column-
|
|
1396
|
+
// count` in its `css_properties.json5`): a page written for it alone
|
|
1397
|
+
// names no other
|
|
1398
|
+
const name = MULTICOL_ALIASES[written] ?? written;
|
|
1161
1399
|
let value = rawValue.trim();
|
|
1162
1400
|
if (!value) return;
|
|
1163
1401
|
// a logical property is the physical one it stands for, the CSS-wide
|
|
@@ -1285,17 +1523,63 @@ export function applyDeclaration(
|
|
|
1285
1523
|
case 'column-count':
|
|
1286
1524
|
case 'column-width':
|
|
1287
1525
|
case 'columns': {
|
|
1288
|
-
// `auto`, or a count, a width or both of them
|
|
1526
|
+
// `auto`, or a count, a width or both of them, in either order; what
|
|
1527
|
+
// the shorthand leaves out is `auto`
|
|
1289
1528
|
const bit = name === 'column-count' ? 1 : name === 'column-width' ? 2 : 3;
|
|
1290
|
-
let
|
|
1529
|
+
let count: number | null = null;
|
|
1530
|
+
let width: number | null = null;
|
|
1291
1531
|
for (const part of splitValue(value)) {
|
|
1292
1532
|
const v = part.toLowerCase();
|
|
1293
1533
|
if (v === 'auto') continue;
|
|
1294
|
-
if (/^\d+$/.test(v) && Number(v) >= 1)
|
|
1295
|
-
|
|
1296
|
-
|
|
1534
|
+
if (/^\d+$/.test(v) && Number(v) >= 1) {
|
|
1535
|
+
if (count !== null || !(bit & 1)) return;
|
|
1536
|
+
count = Number(v);
|
|
1537
|
+
continue;
|
|
1538
|
+
}
|
|
1539
|
+
const len = parseLength(part, ctx);
|
|
1540
|
+
if (typeof len !== 'number' || !(len > 0)) return;
|
|
1541
|
+
if (width !== null || !(bit & 2)) return;
|
|
1542
|
+
width = len;
|
|
1543
|
+
}
|
|
1544
|
+
if (bit & 1) style.columnCount = count;
|
|
1545
|
+
if (bit & 2) style.columnWidth = width;
|
|
1546
|
+
style.columns =
|
|
1547
|
+
(style.columnCount !== null ? 1 : 0) |
|
|
1548
|
+
(style.columnWidth !== null ? 2 : 0);
|
|
1549
|
+
return;
|
|
1550
|
+
}
|
|
1551
|
+
case 'orphans':
|
|
1552
|
+
case 'widows': {
|
|
1553
|
+
// a whole number of lines, one or more
|
|
1554
|
+
const v = value.trim();
|
|
1555
|
+
if (!/^\+?\d+$/.test(v) || Number(v) < 1) return;
|
|
1556
|
+
style[name] = Number(v);
|
|
1557
|
+
return;
|
|
1558
|
+
}
|
|
1559
|
+
case '-webkit-column-span':
|
|
1560
|
+
case 'column-span': {
|
|
1561
|
+
const v = value.trim().toLowerCase();
|
|
1562
|
+
if (v === 'all') style.columnSpan = true;
|
|
1563
|
+
else if (v === 'none') style.columnSpan = false;
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
case 'column-fill': {
|
|
1567
|
+
const v = value.trim().toLowerCase();
|
|
1568
|
+
if (v === 'auto') style.columnFill = 'auto';
|
|
1569
|
+
else if (v === 'balance' || v === 'balance-all') {
|
|
1570
|
+
style.columnFill = 'balance';
|
|
1571
|
+
}
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1574
|
+
case 'break-inside':
|
|
1575
|
+
case 'page-break-inside': {
|
|
1576
|
+
const v = value.trim().toLowerCase();
|
|
1577
|
+
if (v === 'auto') style.breakInside = 'auto';
|
|
1578
|
+
else if (/^avoid(?:-page|-column|-region)?$/.test(v)) {
|
|
1579
|
+
// the legacy property knows `avoid` alone
|
|
1580
|
+
if (name === 'page-break-inside' && v !== 'avoid') return;
|
|
1581
|
+
style.breakInside = 'avoid';
|
|
1297
1582
|
}
|
|
1298
|
-
style.columns = (style.columns & ~bit) | (set & bit);
|
|
1299
1583
|
return;
|
|
1300
1584
|
}
|
|
1301
1585
|
case '-webkit-box-flex': {
|
|
@@ -1534,6 +1818,12 @@ export function applyDeclaration(
|
|
|
1534
1818
|
if (clip !== undefined) style.clip = clip;
|
|
1535
1819
|
return;
|
|
1536
1820
|
}
|
|
1821
|
+
case 'clip-path':
|
|
1822
|
+
case '-webkit-clip-path': {
|
|
1823
|
+
const path = parseClipPath(value, ctx);
|
|
1824
|
+
if (path !== undefined) style.clipPath = path;
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1537
1827
|
case 'opacity': {
|
|
1538
1828
|
const a = parseAlpha(value);
|
|
1539
1829
|
if (a !== null) style.opacity = a;
|
|
@@ -1553,9 +1843,31 @@ export function applyDeclaration(
|
|
|
1553
1843
|
style.translate = [x, y ?? 0];
|
|
1554
1844
|
return;
|
|
1555
1845
|
}
|
|
1556
|
-
case '
|
|
1557
|
-
const
|
|
1558
|
-
if (
|
|
1846
|
+
case 'rotate': {
|
|
1847
|
+
const turn = parseRotate(value);
|
|
1848
|
+
if (turn !== undefined) style.rotate = turn;
|
|
1849
|
+
return;
|
|
1850
|
+
}
|
|
1851
|
+
case 'scale': {
|
|
1852
|
+
const factors = parseScale(value);
|
|
1853
|
+
if (factors !== undefined) style.scale = factors;
|
|
1854
|
+
return;
|
|
1855
|
+
}
|
|
1856
|
+
case 'transform':
|
|
1857
|
+
case '-webkit-transform': {
|
|
1858
|
+
const list = parseTransform(value, ctx);
|
|
1859
|
+
if (list !== undefined) style.transform = list;
|
|
1860
|
+
return;
|
|
1861
|
+
}
|
|
1862
|
+
case 'transform-origin':
|
|
1863
|
+
case '-webkit-transform-origin': {
|
|
1864
|
+
// across and down, as a position is, and a depth nothing here has
|
|
1865
|
+
const parts = splitValue(value);
|
|
1866
|
+
if (parts.length === 3) {
|
|
1867
|
+
if (typeof parseLength(parts.pop()!, ctx) !== 'number') return;
|
|
1868
|
+
} else if (parts.length > 2) return;
|
|
1869
|
+
const origin = positionPair(parts, ctx);
|
|
1870
|
+
if (origin) style.transformOrigin = origin;
|
|
1559
1871
|
return;
|
|
1560
1872
|
}
|
|
1561
1873
|
case 'visibility': {
|
|
@@ -1818,19 +2130,8 @@ export function applyDeclaration(
|
|
|
1818
2130
|
return;
|
|
1819
2131
|
}
|
|
1820
2132
|
case 'border-radius': {
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
const halves = value.split('/');
|
|
1824
|
-
if (halves.length > 2) return;
|
|
1825
|
-
const [horizontal, vertical] = halves.map((half) =>
|
|
1826
|
-
splitValue(half).map((p) => radiusOf(p, ctx)),
|
|
1827
|
-
);
|
|
1828
|
-
for (const parts of vertical ? [horizontal, vertical] : [horizontal]) {
|
|
1829
|
-
if (!parts.length || parts.length > 4 || parts.includes(null)) return;
|
|
1830
|
-
}
|
|
1831
|
-
style.borderRadius = fourSides(horizontal as Len[]);
|
|
1832
|
-
const y = vertical ? fourSides(vertical as Len[]) : null;
|
|
1833
|
-
style.borderRadiusY = y && !sameRadii(style.borderRadius, y) ? y : null;
|
|
2133
|
+
const radii = parseRadii(value, ctx);
|
|
2134
|
+
if (radii) [style.borderRadius, style.borderRadiusY] = radii;
|
|
1834
2135
|
return;
|
|
1835
2136
|
}
|
|
1836
2137
|
|
|
@@ -2073,7 +2374,20 @@ export function applyDeclaration(
|
|
|
2073
2374
|
)
|
|
2074
2375
|
.filter(Boolean)
|
|
2075
2376
|
.join(', ');
|
|
2076
|
-
|
|
2377
|
+
// A list that ends in no generic family ends in the document's own
|
|
2378
|
+
// font: where no face in the list matches, the text is set in the
|
|
2379
|
+
// user agent's default (CSS Fonts 4, 5.1), which for a browser is its
|
|
2380
|
+
// standard font and here is the one the document is set in. The text
|
|
2381
|
+
// engine's own answer for a name nothing has is its platform's pick,
|
|
2382
|
+
// Verdana under fontconfig: the Zen Garden's 216 sets its summary in
|
|
2383
|
+
// Montserrat alone, which no machine here has, and Chrome sets it in
|
|
2384
|
+
// Times where ours was Verdana, a fifth wider
|
|
2385
|
+
const last = names[names.length - 1].trim();
|
|
2386
|
+
const generic =
|
|
2387
|
+
!/^['"]/.test(last) && GENERIC_FAMILY.has(last.toLowerCase());
|
|
2388
|
+
const fallback = generic ? null : (ctx.fallbackFamily?.() ?? null);
|
|
2389
|
+
const full = fallback ? `${list}, ${fallback}` : list;
|
|
2390
|
+
style.fontFamily = ctx.families ? ctx.families(full) : full;
|
|
2077
2391
|
return;
|
|
2078
2392
|
}
|
|
2079
2393
|
case 'font-size': {
|
|
@@ -2479,6 +2793,20 @@ export function applyDeclaration(
|
|
|
2479
2793
|
style.cursor = splitCommas(value)[0]?.trim().toLowerCase() || null;
|
|
2480
2794
|
return;
|
|
2481
2795
|
}
|
|
2796
|
+
case 'pointer-events': {
|
|
2797
|
+
// `none`, or one of the values SVG gives it, which are `auto` on
|
|
2798
|
+
// anything that is not SVG's to draw
|
|
2799
|
+
const v = value.trim().toLowerCase();
|
|
2800
|
+
if (v === 'none') style.pointerEvents = 'none';
|
|
2801
|
+
else if (POINTER_EVENTS.has(v)) style.pointerEvents = 'auto';
|
|
2802
|
+
return;
|
|
2803
|
+
}
|
|
2804
|
+
case 'fill':
|
|
2805
|
+
case 'stroke': {
|
|
2806
|
+
const paint = svgPaint(value);
|
|
2807
|
+
if (paint !== null) style[name] = paint;
|
|
2808
|
+
return;
|
|
2809
|
+
}
|
|
2482
2810
|
|
|
2483
2811
|
// --- lists --------------------------------------------------------------
|
|
2484
2812
|
case 'list-style': {
|
|
@@ -2651,7 +2979,11 @@ export function applyDeclaration(
|
|
|
2651
2979
|
const col = parts.length > 1 ? gap(parts[1]) : row;
|
|
2652
2980
|
if (row === null || col === null) return;
|
|
2653
2981
|
if (which !== 'column-gap') style.rowGap = row;
|
|
2654
|
-
if (which !== 'row-gap')
|
|
2982
|
+
if (which !== 'row-gap') {
|
|
2983
|
+
style.columnGap = col;
|
|
2984
|
+
const word = parts[parts.length - 1].toLowerCase();
|
|
2985
|
+
style.columnGapNormal = word === 'normal';
|
|
2986
|
+
}
|
|
2655
2987
|
return;
|
|
2656
2988
|
}
|
|
2657
2989
|
case 'grid-template-columns':
|
|
@@ -3128,6 +3460,12 @@ function alignKeyword(value: string): string | null {
|
|
|
3128
3460
|
return 'flex-end';
|
|
3129
3461
|
case 'first baseline':
|
|
3130
3462
|
return 'baseline';
|
|
3463
|
+
// By last baselines, which nothing here aligns: set where that falls
|
|
3464
|
+
// back to, the end (CSS Box Alignment 3, 4.2). Dropped, a row that asks
|
|
3465
|
+
// for it stretched its items, and a flex box among them was as tall as
|
|
3466
|
+
// the row, its lines spread down it.
|
|
3467
|
+
case 'last baseline':
|
|
3468
|
+
return 'flex-end';
|
|
3131
3469
|
case 'center':
|
|
3132
3470
|
return 'center';
|
|
3133
3471
|
case 'stretch':
|
|
@@ -3799,7 +4137,7 @@ function backgroundImageOf(
|
|
|
3799
4137
|
ctx: UnitContext,
|
|
3800
4138
|
): BackgroundImage | undefined {
|
|
3801
4139
|
const v = text.trim();
|
|
3802
|
-
if (IMAGE_FUNCTION.test(v.toLowerCase())) return
|
|
4140
|
+
if (IMAGE_FUNCTION.test(v.toLowerCase())) return parseGradient(v, ctx);
|
|
3803
4141
|
return parseUrl(v);
|
|
3804
4142
|
}
|
|
3805
4143
|
|
|
@@ -3821,7 +4159,7 @@ function layerValues<T>(
|
|
|
3821
4159
|
interface BackgroundLayer {
|
|
3822
4160
|
color: string | null;
|
|
3823
4161
|
image: string | null;
|
|
3824
|
-
gradient:
|
|
4162
|
+
gradient: Gradient | null;
|
|
3825
4163
|
repeat: ComputedStyle['backgroundRepeat'];
|
|
3826
4164
|
size: ComputedStyle['backgroundSize'];
|
|
3827
4165
|
attachment: ComputedStyle['backgroundAttachment'];
|
|
@@ -3837,7 +4175,7 @@ interface BackgroundLayer {
|
|
|
3837
4175
|
* is dropped rather than resetting the background it meant to replace
|
|
3838
4176
|
* (CSS 2.1 4.2). What CSS3 adds is read too, so that a declaration a
|
|
3839
4177
|
* browser keeps is kept: `/ cover` after the position, `space` and `round`,
|
|
3840
|
-
* a gradient
|
|
4178
|
+
* and a gradient, over the layer's colour.
|
|
3841
4179
|
*/
|
|
3842
4180
|
function readBackgroundLayer(
|
|
3843
4181
|
text: string,
|
|
@@ -3872,7 +4210,7 @@ function readBackgroundLayer(
|
|
|
3872
4210
|
const url = parseUrl(part);
|
|
3873
4211
|
if (url === undefined) return null;
|
|
3874
4212
|
layer.image = url;
|
|
3875
|
-
} else if (v !== 'none') layer.gradient =
|
|
4213
|
+
} else if (v !== 'none') layer.gradient = parseGradient(part, ctx);
|
|
3876
4214
|
i += 1;
|
|
3877
4215
|
} else if (v === 'repeat-x' || v === 'repeat-y' || REPEATS.has(v)) {
|
|
3878
4216
|
if (!once(2)) return null;
|
|
@@ -3936,10 +4274,8 @@ function readBackgroundLayer(
|
|
|
3936
4274
|
|
|
3937
4275
|
/**
|
|
3938
4276
|
* A `linear-gradient()`, or null for any other image function and for one
|
|
3939
|
-
* that is no gradient: a direction by angle, by side or by corner,
|
|
3940
|
-
* colour
|
|
3941
|
-
* not honoured, and at least two stops, each with no position, one, or two;
|
|
3942
|
-
* a bare percentage between stops, a hint, is passed over.
|
|
4277
|
+
* that is no gradient: a direction by angle, by side or by corner, and its
|
|
4278
|
+
* colour stops.
|
|
3943
4279
|
*/
|
|
3944
4280
|
function parseLinearGradient(
|
|
3945
4281
|
text: string,
|
|
@@ -3948,14 +4284,13 @@ function parseLinearGradient(
|
|
|
3948
4284
|
const m = /^linear-gradient\((.*)\)$/is.exec(text.trim());
|
|
3949
4285
|
if (!m) return null;
|
|
3950
4286
|
const args = splitCommas(m[1]).map((a) => a.trim());
|
|
3951
|
-
const out: LinearGradient = {
|
|
3952
|
-
|
|
3953
|
-
.
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
.trim();
|
|
4287
|
+
const out: LinearGradient = {
|
|
4288
|
+
kind: 'linear',
|
|
4289
|
+
angle: Math.PI,
|
|
4290
|
+
corner: null,
|
|
4291
|
+
stops: [],
|
|
4292
|
+
};
|
|
4293
|
+
const first = withoutInterpolation(args[0] ?? '');
|
|
3959
4294
|
let from = 0;
|
|
3960
4295
|
if (!first || first.startsWith('to ')) {
|
|
3961
4296
|
from = 1;
|
|
@@ -3991,7 +4326,120 @@ function parseLinearGradient(
|
|
|
3991
4326
|
from = 1;
|
|
3992
4327
|
}
|
|
3993
4328
|
}
|
|
3994
|
-
|
|
4329
|
+
const stops = parseStops(args.slice(from), ctx);
|
|
4330
|
+
if (!stops) return null;
|
|
4331
|
+
out.stops = stops;
|
|
4332
|
+
return out;
|
|
4333
|
+
}
|
|
4334
|
+
|
|
4335
|
+
/**
|
|
4336
|
+
* A `radial-gradient()` (CSS Images 3, 3.2.1), or null for anything else:
|
|
4337
|
+
* an ending shape, `circle` or `ellipse`, and its size, in either order —
|
|
4338
|
+
* a keyword for the side or corner of the box it reaches, one length for a
|
|
4339
|
+
* circle's radius, or two lengths or percentages for an ellipse's — then
|
|
4340
|
+
* `at` and its centre, a position as `background-position` writes one;
|
|
4341
|
+
* each of the three may be left out, for an ellipse through the farthest
|
|
4342
|
+
* corner from the middle of the box. A colour interpolation method is read
|
|
4343
|
+
* and not honoured, and the stops are a linear gradient's, spaced along
|
|
4344
|
+
* the ray from the centre to the shape's edge.
|
|
4345
|
+
*/
|
|
4346
|
+
function parseRadialGradient(
|
|
4347
|
+
text: string,
|
|
4348
|
+
ctx: UnitContext,
|
|
4349
|
+
): RadialGradient | null {
|
|
4350
|
+
const m = /^radial-gradient\((.*)\)$/is.exec(text.trim());
|
|
4351
|
+
if (!m) return null;
|
|
4352
|
+
const args = splitCommas(m[1]).map((a) => a.trim());
|
|
4353
|
+
const out: RadialGradient = {
|
|
4354
|
+
kind: 'radial',
|
|
4355
|
+
circle: false,
|
|
4356
|
+
extent: null,
|
|
4357
|
+
radii: null,
|
|
4358
|
+
at: [{ pct: 50 }, { pct: 50 }],
|
|
4359
|
+
stops: [],
|
|
4360
|
+
};
|
|
4361
|
+
const first = withoutInterpolation(args[0] ?? '');
|
|
4362
|
+
let from = 0;
|
|
4363
|
+
if (!first) from = 1;
|
|
4364
|
+
else if (parseColor(splitValue(first)[0]) === null) {
|
|
4365
|
+
from = 1;
|
|
4366
|
+
const words = splitValue(first);
|
|
4367
|
+
const at = words.indexOf('at');
|
|
4368
|
+
if (at >= 0) {
|
|
4369
|
+
const position = positionPair(words.slice(at + 1), ctx);
|
|
4370
|
+
if (!position) return null;
|
|
4371
|
+
out.at = position;
|
|
4372
|
+
}
|
|
4373
|
+
let shape: string | null = null;
|
|
4374
|
+
const sizes: Len[] = [];
|
|
4375
|
+
// the size is one run of words, the shape's keyword before or after it
|
|
4376
|
+
let sized = false;
|
|
4377
|
+
for (const word of at >= 0 ? words.slice(0, at) : words) {
|
|
4378
|
+
if (word === 'circle' || word === 'ellipse') {
|
|
4379
|
+
if (shape) return null;
|
|
4380
|
+
shape = word;
|
|
4381
|
+
if (sizes.length || out.extent) sized = true;
|
|
4382
|
+
continue;
|
|
4383
|
+
}
|
|
4384
|
+
if (sized || out.extent) return null;
|
|
4385
|
+
if (RADIAL_EXTENTS.has(word)) {
|
|
4386
|
+
if (sizes.length) return null;
|
|
4387
|
+
out.extent = word as RadialExtent;
|
|
4388
|
+
continue;
|
|
4389
|
+
}
|
|
4390
|
+
const len = parseLength(word, ctx);
|
|
4391
|
+
if (len === null || len === AUTO || !notNegative(len)) return null;
|
|
4392
|
+
sizes.push(len);
|
|
4393
|
+
}
|
|
4394
|
+
if (sizes.length === 1) {
|
|
4395
|
+
// a circle's radius is a length: a percentage of what, the spec asks
|
|
4396
|
+
const [r] = sizes;
|
|
4397
|
+
if (shape === 'ellipse' || typeof r !== 'number') {
|
|
4398
|
+
return null;
|
|
4399
|
+
}
|
|
4400
|
+
out.circle = true;
|
|
4401
|
+
out.radii = [r, r];
|
|
4402
|
+
} else if (sizes.length === 2) {
|
|
4403
|
+
if (shape === 'circle') return null;
|
|
4404
|
+
out.radii = [sizes[0], sizes[1]];
|
|
4405
|
+
} else if (sizes.length) return null;
|
|
4406
|
+
else {
|
|
4407
|
+
out.circle = shape === 'circle';
|
|
4408
|
+
out.extent ??= 'farthest-corner';
|
|
4409
|
+
}
|
|
4410
|
+
}
|
|
4411
|
+
const stops = parseStops(args.slice(from), ctx);
|
|
4412
|
+
if (!stops) return null;
|
|
4413
|
+
out.stops = stops;
|
|
4414
|
+
return out;
|
|
4415
|
+
}
|
|
4416
|
+
|
|
4417
|
+
const RADIAL_EXTENTS = new Set([
|
|
4418
|
+
'closest-side',
|
|
4419
|
+
'closest-corner',
|
|
4420
|
+
'farthest-side',
|
|
4421
|
+
'farthest-corner',
|
|
4422
|
+
]);
|
|
4423
|
+
|
|
4424
|
+
/** A gradient's first argument, lower-cased, without the colour
|
|
4425
|
+
* interpolation method CSS Images 4 adds to it (`in oklab`, which
|
|
4426
|
+
* Tailwind 4 writes): read, and not honoured. */
|
|
4427
|
+
function withoutInterpolation(arg: string): string {
|
|
4428
|
+
return arg
|
|
4429
|
+
.toLowerCase()
|
|
4430
|
+
.replace(
|
|
4431
|
+
/\bin\s+[a-z-]+(?:\s+(?:shorter|longer|increasing|decreasing)\s+hue)?/,
|
|
4432
|
+
'',
|
|
4433
|
+
)
|
|
4434
|
+
.trim();
|
|
4435
|
+
}
|
|
4436
|
+
|
|
4437
|
+
/** A gradient's colour stops, at least two, each with no position, one or
|
|
4438
|
+
* two; a bare length between two stops, a hint, is passed over. Null
|
|
4439
|
+
* where an argument is none of those. */
|
|
4440
|
+
function parseStops(args: string[], ctx: UnitContext): GradientStop[] | null {
|
|
4441
|
+
const stops: GradientStop[] = [];
|
|
4442
|
+
for (const arg of args) {
|
|
3995
4443
|
const parts = splitValue(arg);
|
|
3996
4444
|
const color = parseColor(parts[0] ?? '');
|
|
3997
4445
|
if (color === null) {
|
|
@@ -4002,14 +4450,19 @@ function parseLinearGradient(
|
|
|
4002
4450
|
if (parts.length > 3) return null;
|
|
4003
4451
|
const at = parts.slice(1).map((p) => parseLength(p, ctx));
|
|
4004
4452
|
if (at.some((p) => p === null || p === AUTO)) return null;
|
|
4005
|
-
if (!at.length)
|
|
4006
|
-
for (const p of at)
|
|
4453
|
+
if (!at.length) stops.push({ color, at: null });
|
|
4454
|
+
for (const p of at) stops.push({ color, at: p as Len });
|
|
4007
4455
|
}
|
|
4008
|
-
return
|
|
4456
|
+
return stops.length >= 2 ? stops : null;
|
|
4457
|
+
}
|
|
4458
|
+
|
|
4459
|
+
/** A gradient function's image, where it is one this draws. */
|
|
4460
|
+
function parseGradient(text: string, ctx: UnitContext): Gradient | null {
|
|
4461
|
+
return parseLinearGradient(text, ctx) ?? parseRadialGradient(text, ctx);
|
|
4009
4462
|
}
|
|
4010
4463
|
|
|
4011
4464
|
/** The images CSS3 has beyond `url()`, which a layer may name: a linear
|
|
4012
|
-
* gradient
|
|
4465
|
+
* and a radial gradient are drawn, and the rest draw as nothing. */
|
|
4013
4466
|
const IMAGE_FUNCTION =
|
|
4014
4467
|
/^(?:-(?:webkit|moz|o|ms)-)?(?:(?:repeating-)?(?:linear|radial|conic)-gradient|gradient|image-set|cross-fade|element|paint)\(/;
|
|
4015
4468
|
|
|
@@ -4050,87 +4503,6 @@ function whiteSpaceOf(collapse: Collapse, wrap: boolean): WhiteSpace {
|
|
|
4050
4503
|
return 'pre-line';
|
|
4051
4504
|
}
|
|
4052
4505
|
|
|
4053
|
-
/**
|
|
4054
|
-
* The translation a `transform` makes: the sum of its translate functions'
|
|
4055
|
-
* and its matrices' — `translate(-50%, -50%)`, Tailwind 3's `translate(
|
|
4056
|
-
* var(--tw-translate-x), var(--tw-translate-y)) rotate(…) skewX(…) …` —
|
|
4057
|
-
* with the functions that rotate, scale, skew or add perspective read and
|
|
4058
|
-
* not drawn. Null for `none`, and undefined for what is no transform list,
|
|
4059
|
-
* which drops the declaration.
|
|
4060
|
-
*/
|
|
4061
|
-
function transformTranslation(
|
|
4062
|
-
value: string,
|
|
4063
|
-
ctx: UnitContext,
|
|
4064
|
-
): [Len, Len] | null | undefined {
|
|
4065
|
-
if (value.trim().toLowerCase() === 'none') return null;
|
|
4066
|
-
let x: Len = 0;
|
|
4067
|
-
let y: Len = 0;
|
|
4068
|
-
const parts = splitValue(value);
|
|
4069
|
-
if (!parts.length) return undefined;
|
|
4070
|
-
for (const part of parts) {
|
|
4071
|
-
const m = /^([a-z0-9]+)\((.*)\)$/is.exec(part.trim());
|
|
4072
|
-
if (!m) return undefined;
|
|
4073
|
-
const name = m[1].toLowerCase();
|
|
4074
|
-
const args = splitCommas(m[2]).map((a) => a.trim());
|
|
4075
|
-
let dx: Len | null = 0;
|
|
4076
|
-
let dy: Len | null = 0;
|
|
4077
|
-
if (name === 'translate' || name === 'translate3d') {
|
|
4078
|
-
dx = parseLength(args[0] ?? '', ctx);
|
|
4079
|
-
dy = args[1] === undefined ? 0 : parseLength(args[1], ctx);
|
|
4080
|
-
} else if (name === 'translatex') dx = parseLength(args[0] ?? '', ctx);
|
|
4081
|
-
else if (name === 'translatey') dy = parseLength(args[0] ?? '', ctx);
|
|
4082
|
-
else if (name === 'matrix' || name === 'matrix3d') {
|
|
4083
|
-
// the translation is the last column's
|
|
4084
|
-
const n = args.map(Number);
|
|
4085
|
-
if (n.length !== (name === 'matrix' ? 6 : 16) || n.some(isNaN)) {
|
|
4086
|
-
return undefined;
|
|
4087
|
-
}
|
|
4088
|
-
[dx, dy] = name === 'matrix' ? [n[4], n[5]] : [n[12], n[13]];
|
|
4089
|
-
} else if (!TRANSFORMS.has(name)) return undefined;
|
|
4090
|
-
if (dx === null || dx === AUTO || dy === null || dy === AUTO) {
|
|
4091
|
-
return undefined;
|
|
4092
|
-
}
|
|
4093
|
-
const sx = addLen(x, dx);
|
|
4094
|
-
const sy = addLen(y, dy);
|
|
4095
|
-
if (sx === null || sy === null) return undefined;
|
|
4096
|
-
x = sx;
|
|
4097
|
-
y = sy;
|
|
4098
|
-
}
|
|
4099
|
-
return [x, y];
|
|
4100
|
-
}
|
|
4101
|
-
|
|
4102
|
-
/** The transform functions read and not drawn. */
|
|
4103
|
-
const TRANSFORMS = new Set([
|
|
4104
|
-
'translatez',
|
|
4105
|
-
'rotate',
|
|
4106
|
-
'rotatex',
|
|
4107
|
-
'rotatey',
|
|
4108
|
-
'rotatez',
|
|
4109
|
-
'rotate3d',
|
|
4110
|
-
'scale',
|
|
4111
|
-
'scalex',
|
|
4112
|
-
'scaley',
|
|
4113
|
-
'scalez',
|
|
4114
|
-
'scale3d',
|
|
4115
|
-
'skew',
|
|
4116
|
-
'skewx',
|
|
4117
|
-
'skewy',
|
|
4118
|
-
'perspective',
|
|
4119
|
-
]);
|
|
4120
|
-
|
|
4121
|
-
/** Two lengths added, a percentage and a length kept apart; null where
|
|
4122
|
-
* one is a `min()` or a `max()`, which a sum cannot be kept of. */
|
|
4123
|
-
function addLen(a: Len, b: Len): Len | null {
|
|
4124
|
-
if (a === AUTO || b === AUTO) return null;
|
|
4125
|
-
if (typeof a === 'number' && typeof b === 'number') return a + b;
|
|
4126
|
-
if (a === 0) return b;
|
|
4127
|
-
if (b === 0) return a;
|
|
4128
|
-
const pa: Pct = typeof a === 'number' ? { pct: 0, px: a } : a;
|
|
4129
|
-
const pb: Pct = typeof b === 'number' ? { pct: 0, px: b } : b;
|
|
4130
|
-
if (pa.of || pb.of) return null;
|
|
4131
|
-
return { pct: pa.pct + pb.pct, px: (pa.px ?? 0) + (pb.px ?? 0) };
|
|
4132
|
-
}
|
|
4133
|
-
|
|
4134
4506
|
/** An intrinsic size keyword, with the prefixes browsers still read, or
|
|
4135
4507
|
* `fit-content()` of a length or a percentage (CSS Sizing 3, 3.1) — a
|
|
4136
4508
|
* width's, where a height's is its content height as the keyword is;
|
|
@@ -4769,6 +5141,33 @@ function splitTopLevelSlash(value: string): string[] {
|
|
|
4769
5141
|
|
|
4770
5142
|
// --- logical properties ----------------------------------------------------
|
|
4771
5143
|
|
|
5144
|
+
/** `pointer-events`' values besides `none` (CSS UI 4, SVG 2 15.6). */
|
|
5145
|
+
const POINTER_EVENTS = new Set([
|
|
5146
|
+
'auto',
|
|
5147
|
+
'bounding-box',
|
|
5148
|
+
'visiblepainted',
|
|
5149
|
+
'visiblefill',
|
|
5150
|
+
'visiblestroke',
|
|
5151
|
+
'visible',
|
|
5152
|
+
'painted',
|
|
5153
|
+
'fill',
|
|
5154
|
+
'stroke',
|
|
5155
|
+
'all',
|
|
5156
|
+
]);
|
|
5157
|
+
/**
|
|
5158
|
+
* A `fill` or a `stroke` (SVG 2, 13.2): `none`, a colour, or a `url()`
|
|
5159
|
+
* naming a gradient in the drawing, with what follows it, its fallback,
|
|
5160
|
+
* left off. Null for anything else, `context-fill` and `context-stroke`
|
|
5161
|
+
* among it, which only a marker or a `<use>` has a context for.
|
|
5162
|
+
*/
|
|
5163
|
+
function svgPaint(value: string): string | null {
|
|
5164
|
+
const v = value.trim();
|
|
5165
|
+
if (v.toLowerCase() === 'none') return 'none';
|
|
5166
|
+
const url = /^url\(\s*(['"]?)(#[^'")\s]+)\1\s*\)/i.exec(v);
|
|
5167
|
+
if (url) return `url(${url[2]})`;
|
|
5168
|
+
return parseColor(v);
|
|
5169
|
+
}
|
|
5170
|
+
|
|
4772
5171
|
const OBJECT_FITS = new Set(['fill', 'contain', 'cover', 'none', 'scale-down']);
|
|
4773
5172
|
|
|
4774
5173
|
/**
|
|
@@ -4860,6 +5259,29 @@ function radiusOf(text: string, ctx: UnitContext): Len | null {
|
|
|
4860
5259
|
return len.pct < 0 && !len.px && !len.of ? null : len;
|
|
4861
5260
|
}
|
|
4862
5261
|
|
|
5262
|
+
/**
|
|
5263
|
+
* `border-radius`'s value: one to four radii, and after a `/` one to four
|
|
5264
|
+
* more for the vertical radii where they differ, elliptical corners — the
|
|
5265
|
+
* radii across, and the ones down, null where they are the same values.
|
|
5266
|
+
* Null for anything else.
|
|
5267
|
+
*/
|
|
5268
|
+
function parseRadii(
|
|
5269
|
+
value: string,
|
|
5270
|
+
ctx: UnitContext,
|
|
5271
|
+
): [[Len, Len, Len, Len], [Len, Len, Len, Len] | null] | null {
|
|
5272
|
+
const halves = value.split('/');
|
|
5273
|
+
if (halves.length > 2) return null;
|
|
5274
|
+
const [horizontal, vertical] = halves.map((half) =>
|
|
5275
|
+
splitValue(half).map((p) => radiusOf(p, ctx)),
|
|
5276
|
+
);
|
|
5277
|
+
for (const parts of vertical ? [horizontal, vertical] : [horizontal]) {
|
|
5278
|
+
if (!parts.length || parts.length > 4 || parts.includes(null)) return null;
|
|
5279
|
+
}
|
|
5280
|
+
const across = fourSides(horizontal as Len[]);
|
|
5281
|
+
const down = vertical ? fourSides(vertical as Len[]) : null;
|
|
5282
|
+
return [across, down && !sameRadii(across, down) ? down : null];
|
|
5283
|
+
}
|
|
5284
|
+
|
|
4863
5285
|
/** Whether two corners' radii are the same values. */
|
|
4864
5286
|
function sameRadii(a: readonly Len[], b: readonly Len[]): boolean {
|
|
4865
5287
|
for (let i = 0; i < 4; i += 1) {
|
|
@@ -5102,10 +5524,15 @@ const INHERITED_NAMES = new Set<string>([
|
|
|
5102
5524
|
'list-style-position',
|
|
5103
5525
|
'list-style-image',
|
|
5104
5526
|
'cursor',
|
|
5527
|
+
'pointer-events',
|
|
5528
|
+
'fill',
|
|
5529
|
+
'stroke',
|
|
5105
5530
|
'border-collapse',
|
|
5106
5531
|
'border-spacing',
|
|
5107
5532
|
'empty-cells',
|
|
5108
5533
|
'quotes',
|
|
5534
|
+
'orphans',
|
|
5535
|
+
'widows',
|
|
5109
5536
|
]);
|
|
5110
5537
|
|
|
5111
5538
|
export function isInherited(name: string): boolean {
|
|
@@ -5260,6 +5687,9 @@ const INHERIT_TARGETS: Record<string, readonly (keyof ComputedStyle)[]> = {
|
|
|
5260
5687
|
'list-style-position': ['listStylePosition'],
|
|
5261
5688
|
'list-style-image': ['listStyleImage'],
|
|
5262
5689
|
cursor: ['cursor'],
|
|
5690
|
+
'pointer-events': ['pointerEvents'],
|
|
5691
|
+
fill: ['fill'],
|
|
5692
|
+
stroke: ['stroke'],
|
|
5263
5693
|
'border-collapse': ['borderCollapse'],
|
|
5264
5694
|
'caption-side': ['captionSide'],
|
|
5265
5695
|
'empty-cells': ['emptyCells'],
|
|
@@ -5328,9 +5758,16 @@ const INHERIT_TARGETS: Record<string, readonly (keyof ComputedStyle)[]> = {
|
|
|
5328
5758
|
'-webkit-box-pack': ['webkitBoxPack'],
|
|
5329
5759
|
'-webkit-box-align': ['webkitBoxAlign'],
|
|
5330
5760
|
'-webkit-box-flex': ['webkitBoxFlex'],
|
|
5331
|
-
columns: ['columns'],
|
|
5332
|
-
'column-count': ['columns'],
|
|
5333
|
-
'column-width': ['columns'],
|
|
5761
|
+
columns: ['columns', 'columnCount', 'columnWidth'],
|
|
5762
|
+
'column-count': ['columns', 'columnCount'],
|
|
5763
|
+
'column-width': ['columns', 'columnWidth'],
|
|
5764
|
+
orphans: ['orphans'],
|
|
5765
|
+
widows: ['widows'],
|
|
5766
|
+
'break-inside': ['breakInside'],
|
|
5767
|
+
'column-fill': ['columnFill'],
|
|
5768
|
+
'column-span': ['columnSpan'],
|
|
5769
|
+
'-webkit-column-span': ['columnSpan'],
|
|
5770
|
+
'page-break-inside': ['breakInside'],
|
|
5334
5771
|
'text-overflow': ['textOverflow'],
|
|
5335
5772
|
// a flex box's and its items', and a grid's
|
|
5336
5773
|
'flex-direction': ['flexDirection'],
|
|
@@ -5345,12 +5782,12 @@ const INHERIT_TARGETS: Record<string, readonly (keyof ComputedStyle)[]> = {
|
|
|
5345
5782
|
'flex-shrink': ['flexShrink'],
|
|
5346
5783
|
'flex-basis': ['flexBasis'],
|
|
5347
5784
|
order: ['order'],
|
|
5348
|
-
gap: ['rowGap', 'columnGap'],
|
|
5785
|
+
gap: ['rowGap', 'columnGap', 'columnGapNormal'],
|
|
5349
5786
|
'row-gap': ['rowGap'],
|
|
5350
|
-
'column-gap': ['columnGap'],
|
|
5351
|
-
'grid-gap': ['rowGap', 'columnGap'],
|
|
5787
|
+
'column-gap': ['columnGap', 'columnGapNormal'],
|
|
5788
|
+
'grid-gap': ['rowGap', 'columnGap', 'columnGapNormal'],
|
|
5352
5789
|
'grid-row-gap': ['rowGap'],
|
|
5353
|
-
'grid-column-gap': ['columnGap'],
|
|
5790
|
+
'grid-column-gap': ['columnGap', 'columnGapNormal'],
|
|
5354
5791
|
'grid-template-columns': ['gridColumns'],
|
|
5355
5792
|
'grid-template-rows': ['gridRows'],
|
|
5356
5793
|
'grid-auto-rows': ['gridAutoRows'],
|
|
@@ -5445,11 +5882,18 @@ const INHERIT_TARGETS: Record<string, readonly (keyof ComputedStyle)[]> = {
|
|
|
5445
5882
|
clear: ['clear'],
|
|
5446
5883
|
overflow: ['overflowX', 'overflowY'],
|
|
5447
5884
|
clip: ['clip'],
|
|
5885
|
+
'clip-path': ['clipPath'],
|
|
5886
|
+
'-webkit-clip-path': ['clipPath'],
|
|
5448
5887
|
'overflow-x': ['overflowX'],
|
|
5449
5888
|
'overflow-y': ['overflowY'],
|
|
5450
5889
|
opacity: ['opacity'],
|
|
5451
5890
|
translate: ['translate'],
|
|
5452
|
-
|
|
5891
|
+
rotate: ['rotate'],
|
|
5892
|
+
scale: ['scale'],
|
|
5893
|
+
transform: ['transform'],
|
|
5894
|
+
'-webkit-transform': ['transform'],
|
|
5895
|
+
'transform-origin': ['transformOrigin'],
|
|
5896
|
+
'-webkit-transform-origin': ['transformOrigin'],
|
|
5453
5897
|
'z-index': ['zIndex'],
|
|
5454
5898
|
'vertical-align': ['verticalAlign'],
|
|
5455
5899
|
'text-decoration': [
|
|
@@ -5512,7 +5956,7 @@ export function decorate(style: ComputedStyle): void {
|
|
|
5512
5956
|
style.color,
|
|
5513
5957
|
);
|
|
5514
5958
|
style.underlineStyle = style.textDecorationStyle;
|
|
5515
|
-
style.underlineThickness = style
|
|
5959
|
+
style.underlineThickness = usedThickness(style);
|
|
5516
5960
|
style.underlineOffset = style.textUnderlineOffset;
|
|
5517
5961
|
}
|
|
5518
5962
|
if (lines.includes('line-through')) {
|
|
@@ -5520,9 +5964,30 @@ export function decorate(style: ComputedStyle): void {
|
|
|
5520
5964
|
style.textDecorationColor ?? 'currentColor',
|
|
5521
5965
|
style.color,
|
|
5522
5966
|
);
|
|
5967
|
+
style.lineThroughStyle = style.textDecorationStyle;
|
|
5968
|
+
style.lineThroughThickness = usedThickness(style);
|
|
5523
5969
|
}
|
|
5524
5970
|
}
|
|
5525
5971
|
|
|
5972
|
+
/**
|
|
5973
|
+
* How thick the lines a box decorates its text with are. `auto` leaves it
|
|
5974
|
+
* to the user agent, which is to take it from the font where the font says
|
|
5975
|
+
* (CSS Text Decoration 4, 2.4.1); neither text engine here reports a face's
|
|
5976
|
+
* underline, so it is a part of the font size, Blink's tenth — of the size
|
|
5977
|
+
* of the box that set the decoration, which draws one line of one thickness
|
|
5978
|
+
* through everything inside it (2.9). A pixel for every size drew the dots
|
|
5979
|
+
* under the `<abbr>` of a 140px title as a hairline. `from-font` is `auto`
|
|
5980
|
+
* for the same want of a metric, a length is itself on whole pixels, and
|
|
5981
|
+
* no line is thinner than one.
|
|
5982
|
+
*/
|
|
5983
|
+
function usedThickness(style: ComputedStyle): number {
|
|
5984
|
+
const length = style.textDecorationThickness;
|
|
5985
|
+
return Math.max(
|
|
5986
|
+
1,
|
|
5987
|
+
length === null ? style.fontSize / 10 : Math.round(length),
|
|
5988
|
+
);
|
|
5989
|
+
}
|
|
5990
|
+
|
|
5526
5991
|
/**
|
|
5527
5992
|
* The absolutely positioned styles that were inline-level before they were
|
|
5528
5993
|
* made blocks: where such a box would have been in flow is on its line,
|
|
@@ -5638,3 +6103,55 @@ export function blockify(style: ComputedStyle, inFlexContainer: boolean): void {
|
|
|
5638
6103
|
return;
|
|
5639
6104
|
}
|
|
5640
6105
|
}
|
|
6106
|
+
|
|
6107
|
+
/**
|
|
6108
|
+
* The `display` a `<button>` is laid out with, which is not always the one
|
|
6109
|
+
* it was given (HTML's rendering section, button layout, 15.5.3): a flex
|
|
6110
|
+
* box or a grid is that, and of the rest, "a value such that the outer
|
|
6111
|
+
* display type is 'inline'" behaves as `inline-block` and anything else as
|
|
6112
|
+
* `flow-root`. Blink computes them so — `inline-block` for `inline` and
|
|
6113
|
+
* `inline-table`, `block` for `table` and `list-item` — and makes the one
|
|
6114
|
+
* block flow of each; the formatting context is the layout's to give every
|
|
6115
|
+
* button that is a block (`establishesBFC`).
|
|
6116
|
+
*
|
|
6117
|
+
* Left an inline box, a button around a block was broken in two around it,
|
|
6118
|
+
* as any inline box is: as wide as the line, its `height` applied to
|
|
6119
|
+
* nothing, and its border and padding drawn above and below the line it
|
|
6120
|
+
* opened. A table's part is left what it is, where the two disagree: a
|
|
6121
|
+
* block to the text, an inline-block in Blink. After `blockify`, since a
|
|
6122
|
+
* float, an absolute box and a flex item are blocks first.
|
|
6123
|
+
*/
|
|
6124
|
+
export function settleButton(style: ComputedStyle): void {
|
|
6125
|
+
switch (style.display) {
|
|
6126
|
+
case 'inline':
|
|
6127
|
+
case 'inline-table':
|
|
6128
|
+
style.display = 'inline-block';
|
|
6129
|
+
return;
|
|
6130
|
+
case 'list-item':
|
|
6131
|
+
case 'table':
|
|
6132
|
+
style.display = 'block';
|
|
6133
|
+
return;
|
|
6134
|
+
default:
|
|
6135
|
+
return;
|
|
6136
|
+
}
|
|
6137
|
+
}
|
|
6138
|
+
|
|
6139
|
+
/** The generic font families, which a list ending in one falls back
|
|
6140
|
+
* through already, in the text engine (CSS Fonts 4, 4.2). */
|
|
6141
|
+
const GENERIC_FAMILY = new Set([
|
|
6142
|
+
'serif',
|
|
6143
|
+
'sans-serif',
|
|
6144
|
+
'monospace',
|
|
6145
|
+
'cursive',
|
|
6146
|
+
'fantasy',
|
|
6147
|
+
'system-ui',
|
|
6148
|
+
'ui-serif',
|
|
6149
|
+
'ui-sans-serif',
|
|
6150
|
+
'ui-monospace',
|
|
6151
|
+
'ui-rounded',
|
|
6152
|
+
'math',
|
|
6153
|
+
'emoji',
|
|
6154
|
+
'fangsong',
|
|
6155
|
+
'-apple-system',
|
|
6156
|
+
'blinkmacsystemfont',
|
|
6157
|
+
]);
|