@react-x11/components 0.12.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 +41 -5
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js +85 -57
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/cascade.d.ts +134 -20
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +545 -126
- 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 +47 -6
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +336 -30
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +142 -15
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +563 -147
- 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 +11 -1
- package/dist/html/css/ua.d.ts.map +1 -1
- package/dist/html/css/ua.js +73 -9
- 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/dom.d.ts.map +1 -1
- package/dist/html/dom.js +3 -1
- package/dist/html/dom.js.map +1 -1
- package/dist/html/fonts.d.ts +78 -4
- package/dist/html/fonts.d.ts.map +1 -1
- package/dist/html/fonts.js +373 -19
- package/dist/html/fonts.js.map +1 -1
- package/dist/html/form.d.ts +200 -0
- package/dist/html/form.d.ts.map +1 -0
- package/dist/html/form.js +801 -0
- package/dist/html/form.js.map +1 -0
- package/dist/html/index.d.ts +12 -0
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +30 -301
- package/dist/html/index.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 +393 -39
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +90 -2
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +74 -10
- 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 +574 -84
- 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 +154 -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 +90 -15
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +771 -186
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +101 -5
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +1502 -160
- 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.d.ts +29 -0
- package/dist/html/widgets.d.ts.map +1 -0
- package/dist/html/widgets.js +700 -0
- package/dist/html/widgets.js.map +1 -0
- 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/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- 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 +2 -2
- package/src/html/controls.ts +114 -51
- package/src/html/css/cascade.ts +614 -140
- package/src/html/css/color.ts +29 -0
- package/src/html/css/parse.ts +388 -33
- package/src/html/css/style.ts +688 -150
- package/src/html/css/transform.ts +459 -0
- package/src/html/css/ua.ts +77 -10
- package/src/html/css/values.ts +4 -0
- package/src/html/dom.ts +3 -1
- package/src/html/fonts.ts +417 -24
- package/src/html/form.ts +962 -0
- package/src/html/index.ts +49 -338
- package/src/html/layout/axes.ts +62 -0
- package/src/html/layout/block.ts +447 -37
- package/src/html/layout/boxes.ts +143 -14
- package/src/html/layout/cache.ts +6 -0
- package/src/html/layout/css-grid.ts +95 -22
- package/src/html/layout/flex.ts +675 -78
- package/src/html/layout/inline.ts +159 -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 +798 -172
- package/src/html/paint.ts +1742 -241
- package/src/html/svg.ts +277 -19
- package/src/html/widgets.ts +821 -0
- package/src/html/woff2.ts +612 -0
- package/src/index.ts +1 -0
- package/src/maps/controller.ts +28 -7
- package/src/richtext/node.ts +13 -2
- package/src/richtext/runs.ts +313 -35
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
// `transform`, and the properties that are one function of it each —
|
|
2
|
+
// `translate`, `rotate` and `scale` (CSS Transforms 1 and 2) — read down to
|
|
3
|
+
// what is drawn: a matrix in the plane.
|
|
4
|
+
//
|
|
5
|
+
// A transform moves nothing but the box it is on and what that holds, so it
|
|
6
|
+
// is no part of layout: the box is laid out where it would be, moved by the
|
|
7
|
+
// matrix's translation (`applyRelativeOffsets`), and painted through the
|
|
8
|
+
// rest of it, about its `transform-origin` (`paintTransformed`). The two
|
|
9
|
+
// halves are one matrix: with `L` its linear part, `t` its translation and
|
|
10
|
+
// `O` the origin, a point goes to `O + L(p - O) + t`, which is `L` about the
|
|
11
|
+
// origin of the box `t` moved. So the translation stays the layout's, where
|
|
12
|
+
// a hit test, a control's rectangle and the ink bounds already find it, and
|
|
13
|
+
// only a box that turns, scales or skews is painted through a matrix.
|
|
14
|
+
//
|
|
15
|
+
// The functions out of the plane — `rotateX()`, `translateZ()`,
|
|
16
|
+
// `perspective()` — are read and are no part of the matrix: a declaration
|
|
17
|
+
// that names one is kept, and drawn as the rest of its list.
|
|
18
|
+
|
|
19
|
+
import { parseMath } from './calc.js';
|
|
20
|
+
import {
|
|
21
|
+
AUTO,
|
|
22
|
+
parseLength,
|
|
23
|
+
parseNumber,
|
|
24
|
+
resolve,
|
|
25
|
+
splitCommas,
|
|
26
|
+
splitValue,
|
|
27
|
+
} from './values.js';
|
|
28
|
+
import type { Len, UnitContext } from './values.js';
|
|
29
|
+
import type { ComputedStyle } from './style.js';
|
|
30
|
+
|
|
31
|
+
/** `matrix(a, b, c, d, e, f)`: the point (x, y) goes to
|
|
32
|
+
* (ax + cy + e, bx + dy + f), as a 2d context's `transform` has it. */
|
|
33
|
+
export type Matrix = readonly [number, number, number, number, number, number];
|
|
34
|
+
|
|
35
|
+
/** One function of a `transform` list: a translation by lengths, a
|
|
36
|
+
* percentage of the box's own width across and its height down, or a
|
|
37
|
+
* matrix — a run of the functions that are numbers alone, multiplied. */
|
|
38
|
+
export type TransformFunction = { by: [Len, Len] } | { matrix: Matrix };
|
|
39
|
+
|
|
40
|
+
export const IDENTITY: Matrix = [1, 0, 0, 1, 0, 0];
|
|
41
|
+
|
|
42
|
+
/** `m` after `n`: the matrix that does `n` and then `m`, which is what two
|
|
43
|
+
* functions of a list come to, the first written being `m`. */
|
|
44
|
+
export function multiply(m: Matrix, n: Matrix): Matrix {
|
|
45
|
+
return [
|
|
46
|
+
m[0] * n[0] + m[2] * n[1],
|
|
47
|
+
m[1] * n[0] + m[3] * n[1],
|
|
48
|
+
m[0] * n[2] + m[2] * n[3],
|
|
49
|
+
m[1] * n[2] + m[3] * n[3],
|
|
50
|
+
m[0] * n[4] + m[2] * n[5] + m[4],
|
|
51
|
+
m[1] * n[4] + m[3] * n[5] + m[5],
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** The matrix that undoes `m`, or null where none does: one that flattens
|
|
56
|
+
* the plane to a line or a point, which draws nothing (CSS Transforms 1,
|
|
57
|
+
* 6). */
|
|
58
|
+
export function invert(m: Matrix): Matrix | null {
|
|
59
|
+
const det = m[0] * m[3] - m[1] * m[2];
|
|
60
|
+
if (!Number.isFinite(det) || Math.abs(det) < 1e-12) return null;
|
|
61
|
+
const a = m[3] / det;
|
|
62
|
+
const b = -m[1] / det;
|
|
63
|
+
const c = -m[2] / det;
|
|
64
|
+
const d = m[0] / det;
|
|
65
|
+
return [a, b, c, d, -(a * m[4] + c * m[5]), -(b * m[4] + d * m[5])];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Where a point goes. */
|
|
69
|
+
export function mapPoint(m: Matrix, x: number, y: number): [number, number] {
|
|
70
|
+
return [m[0] * x + m[2] * y + m[4], m[1] * x + m[3] * y + m[5]];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** The rectangle around where a rectangle goes: its four corners'. */
|
|
74
|
+
export function mapRect(
|
|
75
|
+
m: Matrix,
|
|
76
|
+
x: number,
|
|
77
|
+
y: number,
|
|
78
|
+
width: number,
|
|
79
|
+
height: number,
|
|
80
|
+
): { x: number; y: number; width: number; height: number } {
|
|
81
|
+
let x1 = Infinity;
|
|
82
|
+
let y1 = Infinity;
|
|
83
|
+
let x2 = -Infinity;
|
|
84
|
+
let y2 = -Infinity;
|
|
85
|
+
for (let i = 0; i < 4; i += 1) {
|
|
86
|
+
const px = i & 1 ? x + width : x;
|
|
87
|
+
const py = i & 2 ? y + height : y;
|
|
88
|
+
const qx = m[0] * px + m[2] * py + m[4];
|
|
89
|
+
const qy = m[1] * px + m[3] * py + m[5];
|
|
90
|
+
if (qx < x1) x1 = qx;
|
|
91
|
+
if (qx > x2) x2 = qx;
|
|
92
|
+
if (qy < y1) y1 = qy;
|
|
93
|
+
if (qy > y2) y2 = qy;
|
|
94
|
+
}
|
|
95
|
+
return { x: x1, y: y1, width: x2 - x1, height: y2 - y1 };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** A rotation by degrees, clockwise as the page is seen. A quarter turn is
|
|
99
|
+
* its own numbers exactly: the cosine of ninety degrees computed is 6e-17,
|
|
100
|
+
* and a box turned on its side was a sliver of a pixel off the grid. */
|
|
101
|
+
export function rotation(degrees: number): Matrix {
|
|
102
|
+
const quarters = degrees / 90;
|
|
103
|
+
if (Number.isInteger(quarters)) {
|
|
104
|
+
const at = ((quarters % 4) + 4) % 4;
|
|
105
|
+
const cos = at === 0 ? 1 : at === 2 ? -1 : 0;
|
|
106
|
+
const sin = at === 1 ? 1 : at === 3 ? -1 : 0;
|
|
107
|
+
// no negative zero: it is a zero that compares unequal to one
|
|
108
|
+
return [cos, sin, sin === 0 ? 0 : -sin, cos, 0, 0];
|
|
109
|
+
}
|
|
110
|
+
const angle = (degrees * Math.PI) / 180;
|
|
111
|
+
const cos = Math.cos(angle);
|
|
112
|
+
const sin = Math.sin(angle);
|
|
113
|
+
return [cos, sin, -sin, cos, 0, 0];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const DEGREES: Record<string, number> = {
|
|
117
|
+
deg: 1,
|
|
118
|
+
grad: 0.9,
|
|
119
|
+
rad: 180 / Math.PI,
|
|
120
|
+
turn: 360,
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const ANGLE = /^([+-]?(?:\d*\.\d+|\d+)(?:e[+-]?\d+)?)(deg|grad|rad|turn)$/;
|
|
124
|
+
|
|
125
|
+
/** An angle in degrees: a number with its unit, or a `calc()` of them —
|
|
126
|
+
* and a zero without one, where `bare` lets it be, as a transform
|
|
127
|
+
* function's argument does (CSS Values 4, 7.1). Null for anything else. */
|
|
128
|
+
export function parseAngle(value: string, bare = true): number | null {
|
|
129
|
+
const v = value.trim().toLowerCase();
|
|
130
|
+
const m = ANGLE.exec(v);
|
|
131
|
+
if (m) return Number(m[1]) * DEGREES[m[2]];
|
|
132
|
+
if (bare && /^[+-]?0*\.?0+$/.test(v)) return 0;
|
|
133
|
+
if (!v.includes('(')) return null;
|
|
134
|
+
const sum = parseMath(v, (token) => {
|
|
135
|
+
const unit = ANGLE.exec(token);
|
|
136
|
+
return unit ? Number(unit[1]) * DEGREES[unit[2]] : null;
|
|
137
|
+
});
|
|
138
|
+
return typeof sum === 'number' ? sum : null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** A scale factor: a number, or a percentage of one (CSS Transforms 2, 5),
|
|
142
|
+
* or a `calc()` of either. */
|
|
143
|
+
function parseFactor(value: string): number | null {
|
|
144
|
+
const v = value.trim();
|
|
145
|
+
if (v.endsWith('%')) {
|
|
146
|
+
const n = parseNumber(v.slice(0, -1));
|
|
147
|
+
return n === null ? null : n / 100;
|
|
148
|
+
}
|
|
149
|
+
const n = parseNumber(v);
|
|
150
|
+
if (n !== null) return n;
|
|
151
|
+
if (!v.includes('(')) return null;
|
|
152
|
+
const sum = parseMath(v.toLowerCase(), () => null);
|
|
153
|
+
if (sum === null || sum === AUTO || typeof sum === 'number') return null;
|
|
154
|
+
return sum.of ? null : sum.pct / 100;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function parseOffset(value: string | undefined, ctx: UnitContext): Len | null {
|
|
158
|
+
if (value === undefined) return null;
|
|
159
|
+
const len = parseLength(value, ctx);
|
|
160
|
+
return len === AUTO ? null : len;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* A `transform`: its functions in the order written. Null for `none`, and
|
|
165
|
+
* undefined for what is no transform list, which drops the declaration. A
|
|
166
|
+
* list that comes to nothing in the plane — `translateZ(0)`, which pages
|
|
167
|
+
* write to have a layer made — is a list of none, and a transform still: the
|
|
168
|
+
* box is a containing block and a stacking context as any transformed box
|
|
169
|
+
* is.
|
|
170
|
+
*/
|
|
171
|
+
export function parseTransform(
|
|
172
|
+
value: string,
|
|
173
|
+
ctx: UnitContext,
|
|
174
|
+
): TransformFunction[] | null | undefined {
|
|
175
|
+
if (value.trim().toLowerCase() === 'none') return null;
|
|
176
|
+
const parts = splitValue(value);
|
|
177
|
+
if (!parts.length) return undefined;
|
|
178
|
+
const out: TransformFunction[] = [];
|
|
179
|
+
for (const part of parts) {
|
|
180
|
+
const m = /^([a-z0-9]+)\((.*)\)$/is.exec(part.trim());
|
|
181
|
+
if (!m) return undefined;
|
|
182
|
+
const args = splitCommas(m[2]).map((a) => a.trim());
|
|
183
|
+
const fn = functionOf(m[1].toLowerCase(), args, ctx);
|
|
184
|
+
if (fn === undefined) return undefined;
|
|
185
|
+
if (fn === null) continue;
|
|
186
|
+
const last = out[out.length - 1];
|
|
187
|
+
if ('matrix' in fn && last && 'matrix' in last) {
|
|
188
|
+
out[out.length - 1] = { matrix: multiply(last.matrix, fn.matrix) };
|
|
189
|
+
} else out.push(fn);
|
|
190
|
+
}
|
|
191
|
+
return out;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/** One function: what it is in the plane, null where that is nothing, and
|
|
195
|
+
* undefined where it is no function or its arguments are not its own. */
|
|
196
|
+
function functionOf(
|
|
197
|
+
name: string,
|
|
198
|
+
args: string[],
|
|
199
|
+
ctx: UnitContext,
|
|
200
|
+
): TransformFunction | null | undefined {
|
|
201
|
+
const count = args.length;
|
|
202
|
+
const angles = (): number[] | null => {
|
|
203
|
+
const out = args.map((a) => parseAngle(a));
|
|
204
|
+
return out.some((a) => a === null) ? null : (out as number[]);
|
|
205
|
+
};
|
|
206
|
+
const factors = (): number[] | null => {
|
|
207
|
+
const out = args.map(parseFactor);
|
|
208
|
+
return out.some((n) => n === null) ? null : (out as number[]);
|
|
209
|
+
};
|
|
210
|
+
const tan = (degrees: number): number => Math.tan((degrees * Math.PI) / 180);
|
|
211
|
+
switch (name) {
|
|
212
|
+
case 'translate': {
|
|
213
|
+
if (count < 1 || count > 2) return undefined;
|
|
214
|
+
const x = parseOffset(args[0], ctx);
|
|
215
|
+
const y = count === 2 ? parseOffset(args[1], ctx) : 0;
|
|
216
|
+
return x === null || y === null ? undefined : { by: [x, y] };
|
|
217
|
+
}
|
|
218
|
+
case 'translate3d': {
|
|
219
|
+
if (count !== 3) return undefined;
|
|
220
|
+
const x = parseOffset(args[0], ctx);
|
|
221
|
+
const y = parseOffset(args[1], ctx);
|
|
222
|
+
if (x === null || y === null) return undefined;
|
|
223
|
+
// a depth is a length, and no percentage
|
|
224
|
+
return typeof parseOffset(args[2], ctx) === 'number'
|
|
225
|
+
? { by: [x, y] }
|
|
226
|
+
: undefined;
|
|
227
|
+
}
|
|
228
|
+
case 'translatex':
|
|
229
|
+
case 'translatey': {
|
|
230
|
+
const by = count === 1 ? parseOffset(args[0], ctx) : null;
|
|
231
|
+
if (by === null) return undefined;
|
|
232
|
+
return { by: name === 'translatex' ? [by, 0] : [0, by] };
|
|
233
|
+
}
|
|
234
|
+
case 'translatez':
|
|
235
|
+
return count === 1 && typeof parseOffset(args[0], ctx) === 'number'
|
|
236
|
+
? null
|
|
237
|
+
: undefined;
|
|
238
|
+
case 'scale': {
|
|
239
|
+
const f = count >= 1 && count <= 2 ? factors() : null;
|
|
240
|
+
return f ? { matrix: [f[0], 0, 0, f[1] ?? f[0], 0, 0] } : undefined;
|
|
241
|
+
}
|
|
242
|
+
case 'scale3d': {
|
|
243
|
+
const f = count === 3 ? factors() : null;
|
|
244
|
+
return f ? { matrix: [f[0], 0, 0, f[1], 0, 0] } : undefined;
|
|
245
|
+
}
|
|
246
|
+
case 'scalex':
|
|
247
|
+
case 'scaley': {
|
|
248
|
+
const f = count === 1 ? factors() : null;
|
|
249
|
+
if (!f) return undefined;
|
|
250
|
+
return {
|
|
251
|
+
matrix:
|
|
252
|
+
name === 'scalex' ? [f[0], 0, 0, 1, 0, 0] : [1, 0, 0, f[0], 0, 0],
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
case 'scalez':
|
|
256
|
+
return count === 1 && factors() ? null : undefined;
|
|
257
|
+
case 'rotate':
|
|
258
|
+
case 'rotatez': {
|
|
259
|
+
const a = count === 1 ? angles() : null;
|
|
260
|
+
return a ? { matrix: rotation(a[0]) } : undefined;
|
|
261
|
+
}
|
|
262
|
+
case 'rotatex':
|
|
263
|
+
case 'rotatey':
|
|
264
|
+
return count === 1 && angles() ? null : undefined;
|
|
265
|
+
case 'rotate3d': {
|
|
266
|
+
if (count !== 4) return undefined;
|
|
267
|
+
const axis = args.slice(0, 3).map(parseNumber);
|
|
268
|
+
const angle = parseAngle(args[3]);
|
|
269
|
+
if (angle === null || axis.some((n) => n === null)) return undefined;
|
|
270
|
+
return axisRotation(axis as number[], angle);
|
|
271
|
+
}
|
|
272
|
+
case 'skew': {
|
|
273
|
+
const a = count >= 1 && count <= 2 ? angles() : null;
|
|
274
|
+
return a
|
|
275
|
+
? { matrix: [1, tan(a[1] ?? 0), tan(a[0]), 1, 0, 0] }
|
|
276
|
+
: undefined;
|
|
277
|
+
}
|
|
278
|
+
case 'skewx':
|
|
279
|
+
case 'skewy': {
|
|
280
|
+
const a = count === 1 ? angles() : null;
|
|
281
|
+
if (!a) return undefined;
|
|
282
|
+
return {
|
|
283
|
+
matrix:
|
|
284
|
+
name === 'skewx'
|
|
285
|
+
? [1, 0, tan(a[0]), 1, 0, 0]
|
|
286
|
+
: [1, tan(a[0]), 0, 1, 0, 0],
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
case 'matrix':
|
|
290
|
+
case 'matrix3d': {
|
|
291
|
+
const n = args.map(parseNumber);
|
|
292
|
+
const flat = name === 'matrix';
|
|
293
|
+
if (n.length !== (flat ? 6 : 16) || n.some((v) => v === null)) {
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
296
|
+
const v = n as number[];
|
|
297
|
+
// the plane's part of a 4×4, whose columns are written in turn; a
|
|
298
|
+
// matrix's translation is in CSS pixels, as a length without its unit
|
|
299
|
+
const [a, b, c, d, e, f] = flat
|
|
300
|
+
? v
|
|
301
|
+
: [v[0], v[1], v[4], v[5], v[12], v[13]];
|
|
302
|
+
return { matrix: [a, b, c, d, e * ctx.scale, f * ctx.scale] };
|
|
303
|
+
}
|
|
304
|
+
case 'perspective': {
|
|
305
|
+
if (count !== 1) return undefined;
|
|
306
|
+
if (args[0].toLowerCase() === 'none') return null;
|
|
307
|
+
return typeof parseOffset(args[0], ctx) === 'number' ? null : undefined;
|
|
308
|
+
}
|
|
309
|
+
default:
|
|
310
|
+
return undefined;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** A turn about an axis, where the axis is the one out of the page — the
|
|
315
|
+
* other way where it points into it; about any other it is out of the
|
|
316
|
+
* plane, and nothing here. */
|
|
317
|
+
function axisRotation(axis: number[], angle: number): TransformFunction | null {
|
|
318
|
+
const [x, y, z] = axis;
|
|
319
|
+
if (x !== 0 || y !== 0 || z === 0) return null;
|
|
320
|
+
return { matrix: rotation(z > 0 ? angle : -angle) };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* `rotate` (CSS Transforms 2, 4): an angle in degrees, about the axis out of
|
|
325
|
+
* the page where one is named. Null for `none`, and for a turn about any
|
|
326
|
+
* other axis, which is out of the plane; undefined for what is no value of
|
|
327
|
+
* it.
|
|
328
|
+
*/
|
|
329
|
+
export function parseRotate(value: string): number | null | undefined {
|
|
330
|
+
const parts = splitValue(value.trim().toLowerCase());
|
|
331
|
+
if (parts.length === 1) {
|
|
332
|
+
if (parts[0] === 'none') return null;
|
|
333
|
+
return parseAngle(parts[0], false) ?? undefined;
|
|
334
|
+
}
|
|
335
|
+
// an axis and an angle, in either order; the axis a letter or a vector
|
|
336
|
+
const at = parts.findIndex((p) => parseAngle(p, false) !== null);
|
|
337
|
+
const angle = at < 0 ? null : parseAngle(parts[at], false);
|
|
338
|
+
if (angle === null || (at !== 0 && at !== parts.length - 1)) return undefined;
|
|
339
|
+
const axis = parts.filter((_, i) => i !== at);
|
|
340
|
+
if (axis.length === 1) {
|
|
341
|
+
if (axis[0] === 'z') return angle;
|
|
342
|
+
return axis[0] === 'x' || axis[0] === 'y' ? null : undefined;
|
|
343
|
+
}
|
|
344
|
+
const vector = axis.map(parseNumber);
|
|
345
|
+
if (vector.length !== 3 || vector.some((n) => n === null)) return undefined;
|
|
346
|
+
const turned = axisRotation(vector as number[], 1);
|
|
347
|
+
if (!turned) return null;
|
|
348
|
+
return (vector[2] as number) > 0 ? angle : -angle;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** `scale` (CSS Transforms 2, 5): across and down, the one factor both
|
|
352
|
+
* where one is written, and a depth that is nothing here. Null for
|
|
353
|
+
* `none`. */
|
|
354
|
+
export function parseScale(value: string): [number, number] | null | undefined {
|
|
355
|
+
const parts = splitValue(value.trim());
|
|
356
|
+
if (parts.length === 1 && parts[0].toLowerCase() === 'none') return null;
|
|
357
|
+
if (parts.length < 1 || parts.length > 3) return undefined;
|
|
358
|
+
const factors = parts.map(parseFactor);
|
|
359
|
+
if (factors.some((f) => f === null)) return undefined;
|
|
360
|
+
const [x, y] = factors as number[];
|
|
361
|
+
return [x, y ?? x];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
type Transformed = Pick<
|
|
365
|
+
ComputedStyle,
|
|
366
|
+
'translate' | 'rotate' | 'scale' | 'transform'
|
|
367
|
+
>;
|
|
368
|
+
|
|
369
|
+
/** Whether a style transforms its box: any of the four properties at
|
|
370
|
+
* anything but `none`, whatever it comes to — which makes the box a
|
|
371
|
+
* containing block for all it holds and a stacking context (CSS
|
|
372
|
+
* Transforms 1, 3). */
|
|
373
|
+
export function transformed(style: Transformed): boolean {
|
|
374
|
+
return (
|
|
375
|
+
style.transform !== null ||
|
|
376
|
+
style.translate !== null ||
|
|
377
|
+
style.rotate !== null ||
|
|
378
|
+
style.scale !== null
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* A style's whole transform, for a box `width` by `height`, which its
|
|
384
|
+
* percentages are of: `translate`, then `rotate`, then `scale`, then the
|
|
385
|
+
* `transform` list (CSS Transforms 2, 7) — without its origin, which is
|
|
386
|
+
* where the box is.
|
|
387
|
+
*/
|
|
388
|
+
export function matrixOf(
|
|
389
|
+
style: Transformed,
|
|
390
|
+
width: number,
|
|
391
|
+
height: number,
|
|
392
|
+
): Matrix {
|
|
393
|
+
let m = IDENTITY;
|
|
394
|
+
const moved = style.translate;
|
|
395
|
+
if (moved) {
|
|
396
|
+
m = [1, 0, 0, 1, resolve(moved[0], width, 0), resolve(moved[1], height, 0)];
|
|
397
|
+
}
|
|
398
|
+
if (style.rotate !== null) m = multiply(m, rotation(style.rotate));
|
|
399
|
+
if (style.scale) {
|
|
400
|
+
m = multiply(m, [style.scale[0], 0, 0, style.scale[1], 0, 0]);
|
|
401
|
+
}
|
|
402
|
+
if (style.transform) {
|
|
403
|
+
for (const fn of style.transform) {
|
|
404
|
+
m = multiply(
|
|
405
|
+
m,
|
|
406
|
+
'matrix' in fn
|
|
407
|
+
? fn.matrix
|
|
408
|
+
: [
|
|
409
|
+
1,
|
|
410
|
+
0,
|
|
411
|
+
0,
|
|
412
|
+
1,
|
|
413
|
+
resolve(fn.by[0], width, 0),
|
|
414
|
+
resolve(fn.by[1], height, 0),
|
|
415
|
+
],
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return m;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** The part of a matrix that turns, scales and skews. */
|
|
423
|
+
export type Linear = readonly [number, number, number, number];
|
|
424
|
+
|
|
425
|
+
const LINEAR = new WeakMap<object, Linear | null>();
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* What a style's transform does besides move the box: null where that is
|
|
429
|
+
* nothing, as it is for every box but a few. The same for a box of any
|
|
430
|
+
* size — a translation's percentages are no part of it — so kept by the
|
|
431
|
+
* style.
|
|
432
|
+
*/
|
|
433
|
+
export function linearOf(style: Transformed): Linear | null {
|
|
434
|
+
if (
|
|
435
|
+
style.transform === null &&
|
|
436
|
+
style.rotate === null &&
|
|
437
|
+
style.scale === null
|
|
438
|
+
) {
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
let linear = LINEAR.get(style);
|
|
442
|
+
if (linear === undefined) {
|
|
443
|
+
const m = matrixOf(style, 0, 0);
|
|
444
|
+
const near = (a: number, b: number) => Math.abs(a - b) < 1e-9;
|
|
445
|
+
linear =
|
|
446
|
+
near(m[0], 1) && near(m[1], 0) && near(m[2], 0) && near(m[3], 1)
|
|
447
|
+
? null
|
|
448
|
+
: [m[0], m[1], m[2], m[3]];
|
|
449
|
+
LINEAR.set(style, linear);
|
|
450
|
+
}
|
|
451
|
+
return linear;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/** `linear` about a point: the matrix a box at that origin is drawn
|
|
455
|
+
* through, in the coordinates the point is in. */
|
|
456
|
+
export function about(linear: Linear, x: number, y: number): Matrix {
|
|
457
|
+
const [a, b, c, d] = linear;
|
|
458
|
+
return [a, b, c, d, x - (a * x + c * y), y - (b * x + d * y)];
|
|
459
|
+
}
|
package/src/html/css/ua.ts
CHANGED
|
@@ -8,16 +8,34 @@
|
|
|
8
8
|
// already using, so an unstyled document reads as part of the window. An
|
|
9
9
|
// author stylesheet still overrides all of it — that is what makes it a UA
|
|
10
10
|
// sheet and not a skin.
|
|
11
|
+
//
|
|
12
|
+
// The theme has the colours of one scheme. A document whose root is of the
|
|
13
|
+
// other — `color-scheme: light` under a dark palette — is a browser's in
|
|
14
|
+
// that one: the text and the links a `light-dark()` of the theme's and
|
|
15
|
+
// Chrome's (`inScheme`), over a canvas of that scheme (`paintDocument`).
|
|
16
|
+
import { SCHEME_COLORS } from './color.js';
|
|
11
17
|
import { parseStylesheet } from './parse.js';
|
|
12
|
-
import type { Stylesheet } from './parse.js';
|
|
18
|
+
import type { Declaration, Stylesheet } from './parse.js';
|
|
13
19
|
import type { RootLook } from './style.js';
|
|
14
20
|
|
|
21
|
+
/**
|
|
22
|
+
* The declaration blocks that draw a control as the palette's widget rather
|
|
23
|
+
* than as the web's UA sheet has it. The cascade drops them from an element
|
|
24
|
+
* whose page styles its background or border (`Cascade`'s
|
|
25
|
+
* `dropPaletteChrome`), as Blink drops a control's native appearance
|
|
26
|
+
* (`LayoutTheme::IsControlStyled`), since a page that styles a control
|
|
27
|
+
* builds on the UA values a browser gives it. Keyed by the array, which is
|
|
28
|
+
* what a cascade candidate carries.
|
|
29
|
+
*/
|
|
30
|
+
export const PALETTE_CHROME = new WeakSet<Declaration[]>();
|
|
31
|
+
|
|
15
32
|
/** Cache key → parsed sheet. The text depends only on the look, so two
|
|
16
33
|
* documents in one themed window parse this once between them. */
|
|
17
34
|
const CACHE = new Map<string, Stylesheet>();
|
|
18
35
|
|
|
19
36
|
export function lookKey(look: RootLook): string {
|
|
20
37
|
return [
|
|
38
|
+
look.colorScheme,
|
|
21
39
|
look.color,
|
|
22
40
|
look.fontFamily,
|
|
23
41
|
look.fontSize,
|
|
@@ -30,6 +48,8 @@ export function lookKey(look: RootLook): string {
|
|
|
30
48
|
look.controlPadY,
|
|
31
49
|
look.controlBorder,
|
|
32
50
|
look.controlRadius,
|
|
51
|
+
look.controlFontSize,
|
|
52
|
+
look.controlFontFamily,
|
|
33
53
|
].join('|');
|
|
34
54
|
}
|
|
35
55
|
|
|
@@ -38,6 +58,13 @@ export function uaStylesheet(look: RootLook): Stylesheet {
|
|
|
38
58
|
const hit = CACHE.get(key);
|
|
39
59
|
if (hit) return hit;
|
|
40
60
|
const sheet = parseStylesheet(uaText(look), -1_000_000);
|
|
61
|
+
// after the rest, so its values win over theirs while it applies
|
|
62
|
+
const last = sheet.rules[sheet.rules.length - 1];
|
|
63
|
+
const chrome = parseStylesheet(chromeText(look), last.order + 1);
|
|
64
|
+
for (const rule of chrome.rules) {
|
|
65
|
+
PALETTE_CHROME.add(rule.declarations);
|
|
66
|
+
sheet.rules.push(rule);
|
|
67
|
+
}
|
|
41
68
|
if (CACHE.size > 8) CACHE.clear();
|
|
42
69
|
CACHE.set(key, sheet);
|
|
43
70
|
return sheet;
|
|
@@ -52,6 +79,12 @@ export function uaStylesheet(look: RootLook): Stylesheet {
|
|
|
52
79
|
*/
|
|
53
80
|
function uaText(look: RootLook): string {
|
|
54
81
|
const mono = look.monoFamily;
|
|
82
|
+
// the theme's colour where the element's scheme is the theme's, and a
|
|
83
|
+
// browser's own for the other scheme where it is not
|
|
84
|
+
const inScheme = (own: string, which: 'text' | 'link'): string =>
|
|
85
|
+
look.colorScheme === 'dark'
|
|
86
|
+
? `light-dark(${SCHEME_COLORS.light[which]}, ${own})`
|
|
87
|
+
: `light-dark(${own}, ${SCHEME_COLORS.dark[which]})`;
|
|
55
88
|
return `
|
|
56
89
|
html, body, div, p, h1, h2, h3, h4, h5, h6, ol, ul, li, dl, dt, dd,
|
|
57
90
|
blockquote, pre, hr, table, form, fieldset, figure, figcaption, address,
|
|
@@ -74,7 +107,7 @@ bdo { unicode-bidi: isolate-override; }
|
|
|
74
107
|
/* the theme's colour and font are the root's (\`initialStyle\`), and the
|
|
75
108
|
body inherits them, from an author's \`html\` rule too */
|
|
76
109
|
body { margin: 8px; }
|
|
77
|
-
html { color: ${look.color}; }
|
|
110
|
+
html { color: ${inScheme(look.color, 'text')}; }
|
|
78
111
|
|
|
79
112
|
p { margin: 1em 0; }
|
|
80
113
|
h1 { font-size: 2em; font-weight: bold; margin: 0.67em 0; }
|
|
@@ -139,7 +172,7 @@ q::after { content: close-quote; }
|
|
|
139
172
|
pointer moves, and a rule that changes nothing (the link is already
|
|
140
173
|
underlined) would make every plain document pay that. An 'a' with no
|
|
141
174
|
'href' is an anchor, not a link, and is drawn as the text around it. */
|
|
142
|
-
a[href] { color: ${look.linkColor}; text-decoration: underline; cursor: pointer; }
|
|
175
|
+
a[href] { color: ${inScheme(look.linkColor, 'link')}; text-decoration: underline; cursor: pointer; }
|
|
143
176
|
|
|
144
177
|
hr {
|
|
145
178
|
margin: 0.5em auto;
|
|
@@ -181,11 +214,23 @@ col { display: table-column; }
|
|
|
181
214
|
makes a label and its input share a line. */
|
|
182
215
|
input, button, select, textarea, meter, progress {
|
|
183
216
|
display: inline-block;
|
|
184
|
-
font-family: ${look.fontFamily};
|
|
185
|
-
font-size: 1em;
|
|
186
217
|
vertical-align: middle;
|
|
187
218
|
margin: 3px 2px;
|
|
188
219
|
}
|
|
220
|
+
/* A control's text is a system font's, not its parent's: Chrome gives these
|
|
221
|
+
four \`font: -webkit-small-control\`, Arial at the default size less 2pt
|
|
222
|
+
(13.33px) whatever the text around it is set in, and Gecko \`-moz-field\`
|
|
223
|
+
the same. The system here is the palette, and its family and size are the
|
|
224
|
+
ones core's widgets are set in — so a document's form is its window's,
|
|
225
|
+
and the box a control is measured into is the size the widget mounted in
|
|
226
|
+
it draws. A <textarea> is then \`monospace\` in Chrome, the sheet's code
|
|
227
|
+
face here; a <meter> and a <progress> keep their parent's font, as they
|
|
228
|
+
do in Chrome. */
|
|
229
|
+
input, button, select, textarea {
|
|
230
|
+
font-family: ${look.controlFontFamily ?? look.fontFamily};
|
|
231
|
+
font-size: ${look.controlFontSize ?? look.fontSize}px;
|
|
232
|
+
}
|
|
233
|
+
textarea { font-family: ${mono}; }
|
|
189
234
|
/* Chrome's own UA margins for the checkables, near enough: they are the
|
|
190
235
|
controls that sit hard against their label text otherwise. */
|
|
191
236
|
/* HTML's rendering section (15.3.10): a control's text keeps none of the
|
|
@@ -208,13 +253,16 @@ input[type=hidden] { display: none; }
|
|
|
208
253
|
/* A <button> is drawn rather than mounted: its content is the document's —
|
|
209
254
|
an icon, a label in spans, a pill of the page's own design, which is what
|
|
210
255
|
most buttons on the web are — and the page restyles it as it restyles
|
|
211
|
-
anything.
|
|
212
|
-
|
|
256
|
+
anything. The edges here are Chrome's, which a page that styles its
|
|
257
|
+
buttons builds on: Codex gives Wikipedia's search button its side padding
|
|
258
|
+
and a 32px min-height, and leaves the 1px above and below the label, and
|
|
259
|
+
the border box the minimum holds, to the browser. One the page left alone
|
|
260
|
+
is the palette's control instead, below the rest of this sheet. */
|
|
213
261
|
button {
|
|
214
262
|
vertical-align: baseline;
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
border
|
|
263
|
+
box-sizing: border-box;
|
|
264
|
+
padding: 1px 6px;
|
|
265
|
+
border: 2px outset ${look.borderColor};
|
|
218
266
|
background-color: ${look.surface};
|
|
219
267
|
color: ${look.color};
|
|
220
268
|
text-align: center;
|
|
@@ -235,3 +283,22 @@ details[open] > summary:first-of-type { list-style-type: disclosure-open; }
|
|
|
235
283
|
[hidden] { display: none; }
|
|
236
284
|
`;
|
|
237
285
|
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* The palette's control, for a `<button>` whose page left its background and
|
|
289
|
+
* border alone: the chrome a mounted widget has, so a form in a document is
|
|
290
|
+
* the size of the window's own. The rules land in `PALETTE_CHROME`, and a
|
|
291
|
+
* page that styles either loses all of them — Blink counts every background
|
|
292
|
+
* and border longhand, radius included — along with the native look it
|
|
293
|
+
* would lose in a browser. Nothing stays half the palette's and half the
|
|
294
|
+
* page's.
|
|
295
|
+
*/
|
|
296
|
+
function chromeText(look: RootLook): string {
|
|
297
|
+
return `
|
|
298
|
+
button {
|
|
299
|
+
padding: ${look.controlPadY}px 0.75em;
|
|
300
|
+
border: ${look.controlBorder}px solid ${look.borderColor};
|
|
301
|
+
border-radius: ${look.controlRadius}px;
|
|
302
|
+
}
|
|
303
|
+
`;
|
|
304
|
+
}
|
package/src/html/css/values.ts
CHANGED
|
@@ -117,6 +117,10 @@ export interface UnitContext {
|
|
|
117
117
|
* itself, with `@font-face`, put as the text engine knows them — see
|
|
118
118
|
* `fonts.ts`. Absent, the list is the one the author wrote. */
|
|
119
119
|
families?: (list: string) => string;
|
|
120
|
+
/** The family a `font-family` list with no generic family at its end
|
|
121
|
+
* goes on to: the document's own, for a list an author wrote, and none
|
|
122
|
+
* for the UA sheet's, which names the host's faces. */
|
|
123
|
+
fallbackFamily?: () => string | null;
|
|
120
124
|
/** The element's line height and the root's, for `lh` and `rlh`, asked
|
|
121
125
|
* only for a length in them; 1.2em where they are missing. */
|
|
122
126
|
lh?: () => number;
|
package/src/html/dom.ts
CHANGED
|
@@ -342,7 +342,9 @@ export class HtmlSource {
|
|
|
342
342
|
tag === 'image' ||
|
|
343
343
|
tag === 'object' ||
|
|
344
344
|
tag === 'embed' ||
|
|
345
|
-
tag === 'video'
|
|
345
|
+
tag === 'video' ||
|
|
346
|
+
(tag === 'input' &&
|
|
347
|
+
(attr(el, 'type') ?? '').trim().toLowerCase() === 'image')
|
|
346
348
|
) {
|
|
347
349
|
if (imageUrlOf(el)) facts.resources.push(el);
|
|
348
350
|
} else if (tag === 'title' && facts.title === null) {
|