@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.
Files changed (128) hide show
  1. package/dist/html/controls.d.ts +29 -2
  2. package/dist/html/controls.d.ts.map +1 -1
  3. package/dist/html/controls.js +70 -2
  4. package/dist/html/controls.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts.map +1 -1
  6. package/dist/html/css/cascade.js +26 -4
  7. package/dist/html/css/cascade.js.map +1 -1
  8. package/dist/html/css/color.d.ts +18 -0
  9. package/dist/html/css/color.d.ts.map +1 -1
  10. package/dist/html/css/color.js +27 -0
  11. package/dist/html/css/color.js.map +1 -1
  12. package/dist/html/css/parse.d.ts +18 -4
  13. package/dist/html/css/parse.d.ts.map +1 -1
  14. package/dist/html/css/parse.js +66 -28
  15. package/dist/html/css/parse.js.map +1 -1
  16. package/dist/html/css/style.d.ts +128 -15
  17. package/dist/html/css/style.d.ts.map +1 -1
  18. package/dist/html/css/style.js +561 -144
  19. package/dist/html/css/style.js.map +1 -1
  20. package/dist/html/css/transform.d.ts +85 -0
  21. package/dist/html/css/transform.d.ts.map +1 -0
  22. package/dist/html/css/transform.js +425 -0
  23. package/dist/html/css/transform.js.map +1 -0
  24. package/dist/html/css/ua.d.ts.map +1 -1
  25. package/dist/html/css/ua.js +14 -2
  26. package/dist/html/css/ua.js.map +1 -1
  27. package/dist/html/css/values.d.ts +4 -0
  28. package/dist/html/css/values.d.ts.map +1 -1
  29. package/dist/html/css/values.js.map +1 -1
  30. package/dist/html/fonts.d.ts +55 -5
  31. package/dist/html/fonts.d.ts.map +1 -1
  32. package/dist/html/fonts.js +284 -23
  33. package/dist/html/fonts.js.map +1 -1
  34. package/dist/html/layout/axes.d.ts +14 -0
  35. package/dist/html/layout/axes.d.ts.map +1 -0
  36. package/dist/html/layout/axes.js +28 -0
  37. package/dist/html/layout/axes.js.map +1 -0
  38. package/dist/html/layout/block.d.ts +93 -12
  39. package/dist/html/layout/block.d.ts.map +1 -1
  40. package/dist/html/layout/block.js +389 -39
  41. package/dist/html/layout/block.js.map +1 -1
  42. package/dist/html/layout/boxes.d.ts +59 -1
  43. package/dist/html/layout/boxes.d.ts.map +1 -1
  44. package/dist/html/layout/boxes.js +22 -2
  45. package/dist/html/layout/boxes.js.map +1 -1
  46. package/dist/html/layout/cache.d.ts +1 -1
  47. package/dist/html/layout/cache.d.ts.map +1 -1
  48. package/dist/html/layout/cache.js +6 -0
  49. package/dist/html/layout/cache.js.map +1 -1
  50. package/dist/html/layout/css-grid.d.ts.map +1 -1
  51. package/dist/html/layout/css-grid.js +90 -18
  52. package/dist/html/layout/css-grid.js.map +1 -1
  53. package/dist/html/layout/flex.d.ts.map +1 -1
  54. package/dist/html/layout/flex.js +570 -83
  55. package/dist/html/layout/flex.js.map +1 -1
  56. package/dist/html/layout/inline.d.ts +3 -0
  57. package/dist/html/layout/inline.d.ts.map +1 -1
  58. package/dist/html/layout/inline.js +144 -54
  59. package/dist/html/layout/inline.js.map +1 -1
  60. package/dist/html/layout/multicol.d.ts +42 -0
  61. package/dist/html/layout/multicol.d.ts.map +1 -0
  62. package/dist/html/layout/multicol.js +897 -0
  63. package/dist/html/layout/multicol.js.map +1 -0
  64. package/dist/html/layout/shaping.d.ts +7 -0
  65. package/dist/html/layout/shaping.d.ts.map +1 -1
  66. package/dist/html/layout/shaping.js +25 -20
  67. package/dist/html/layout/shaping.js.map +1 -1
  68. package/dist/html/layout/table.d.ts.map +1 -1
  69. package/dist/html/layout/table.js +62 -18
  70. package/dist/html/layout/table.js.map +1 -1
  71. package/dist/html/node.d.ts +3 -0
  72. package/dist/html/node.d.ts.map +1 -1
  73. package/dist/html/node.js +263 -36
  74. package/dist/html/node.js.map +1 -1
  75. package/dist/html/paint.d.ts +79 -4
  76. package/dist/html/paint.d.ts.map +1 -1
  77. package/dist/html/paint.js +1213 -141
  78. package/dist/html/paint.js.map +1 -1
  79. package/dist/html/svg.d.ts +15 -2
  80. package/dist/html/svg.d.ts.map +1 -1
  81. package/dist/html/svg.js +249 -17
  82. package/dist/html/svg.js.map +1 -1
  83. package/dist/html/widgets.js +87 -7
  84. package/dist/html/widgets.js.map +1 -1
  85. package/dist/html/woff2.d.ts +14 -0
  86. package/dist/html/woff2.d.ts.map +1 -0
  87. package/dist/html/woff2.js +608 -0
  88. package/dist/html/woff2.js.map +1 -0
  89. package/dist/maps/controller.d.ts +17 -1
  90. package/dist/maps/controller.d.ts.map +1 -1
  91. package/dist/maps/controller.js +27 -7
  92. package/dist/maps/controller.js.map +1 -1
  93. package/dist/richtext/node.d.ts +8 -1
  94. package/dist/richtext/node.d.ts.map +1 -1
  95. package/dist/richtext/node.js +5 -1
  96. package/dist/richtext/node.js.map +1 -1
  97. package/dist/richtext/runs.d.ts +25 -6
  98. package/dist/richtext/runs.d.ts.map +1 -1
  99. package/dist/richtext/runs.js +240 -24
  100. package/dist/richtext/runs.js.map +1 -1
  101. package/package.json +1 -1
  102. package/src/html/controls.ts +85 -2
  103. package/src/html/css/cascade.ts +23 -0
  104. package/src/html/css/color.ts +29 -0
  105. package/src/html/css/parse.ts +71 -31
  106. package/src/html/css/style.ts +664 -147
  107. package/src/html/css/transform.ts +459 -0
  108. package/src/html/css/ua.ts +15 -2
  109. package/src/html/css/values.ts +4 -0
  110. package/src/html/fonts.ts +312 -29
  111. package/src/html/layout/axes.ts +62 -0
  112. package/src/html/layout/block.ts +444 -37
  113. package/src/html/layout/boxes.ts +65 -3
  114. package/src/html/layout/cache.ts +6 -0
  115. package/src/html/layout/css-grid.ts +95 -22
  116. package/src/html/layout/flex.ts +671 -77
  117. package/src/html/layout/inline.ts +149 -55
  118. package/src/html/layout/multicol.ts +977 -0
  119. package/src/html/layout/shaping.ts +27 -18
  120. package/src/html/layout/table.ts +62 -19
  121. package/src/html/node.ts +300 -37
  122. package/src/html/paint.ts +1406 -206
  123. package/src/html/svg.ts +277 -19
  124. package/src/html/widgets.ts +95 -9
  125. package/src/html/woff2.ts +612 -0
  126. package/src/maps/controller.ts +28 -7
  127. package/src/richtext/node.ts +13 -2
  128. 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
+ }
@@ -8,6 +8,12 @@
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
18
  import type { Declaration, Stylesheet } from './parse.js';
13
19
  import type { RootLook } from './style.js';
@@ -29,6 +35,7 @@ const CACHE = new Map<string, Stylesheet>();
29
35
 
30
36
  export function lookKey(look: RootLook): string {
31
37
  return [
38
+ look.colorScheme,
32
39
  look.color,
33
40
  look.fontFamily,
34
41
  look.fontSize,
@@ -72,6 +79,12 @@ export function uaStylesheet(look: RootLook): Stylesheet {
72
79
  */
73
80
  function uaText(look: RootLook): string {
74
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]})`;
75
88
  return `
76
89
  html, body, div, p, h1, h2, h3, h4, h5, h6, ol, ul, li, dl, dt, dd,
77
90
  blockquote, pre, hr, table, form, fieldset, figure, figcaption, address,
@@ -94,7 +107,7 @@ bdo { unicode-bidi: isolate-override; }
94
107
  /* the theme's colour and font are the root's (\`initialStyle\`), and the
95
108
  body inherits them, from an author's \`html\` rule too */
96
109
  body { margin: 8px; }
97
- html { color: ${look.color}; }
110
+ html { color: ${inScheme(look.color, 'text')}; }
98
111
 
99
112
  p { margin: 1em 0; }
100
113
  h1 { font-size: 2em; font-weight: bold; margin: 0.67em 0; }
@@ -159,7 +172,7 @@ q::after { content: close-quote; }
159
172
  pointer moves, and a rule that changes nothing (the link is already
160
173
  underlined) would make every plain document pay that. An 'a' with no
161
174
  'href' is an anchor, not a link, and is drawn as the text around it. */
162
- a[href] { color: ${look.linkColor}; text-decoration: underline; cursor: pointer; }
175
+ a[href] { color: ${inScheme(look.linkColor, 'link')}; text-decoration: underline; cursor: pointer; }
163
176
 
164
177
  hr {
165
178
  margin: 0.5em auto;
@@ -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;