@react-x11/components 0.13.0 → 0.14.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +42 -1
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +336 -16
- 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/shapes.d.ts +44 -0
- package/dist/html/css/shapes.d.ts.map +1 -0
- package/dist/html/css/shapes.js +194 -0
- package/dist/html/css/shapes.js.map +1 -0
- 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 +547 -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 +104 -12
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +410 -39
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +72 -1
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +40 -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 +144 -24
- 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 +585 -85
- 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 +97 -31
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +4 -0
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +319 -47
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +83 -5
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +1474 -217
- package/dist/html/paint.js.map +1 -1
- package/dist/html/svg.d.ts +39 -9
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +482 -37
- 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 +366 -8
- package/src/html/css/color.ts +29 -0
- package/src/html/css/parse.ts +71 -31
- package/src/html/css/shapes.ts +239 -0
- package/src/html/css/style.ts +652 -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 +466 -37
- package/src/html/layout/boxes.ts +95 -3
- package/src/html/layout/cache.ts +6 -0
- package/src/html/layout/css-grid.ts +169 -32
- package/src/html/layout/flex.ts +686 -79
- 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 +105 -35
- package/src/html/node.ts +354 -48
- package/src/html/paint.ts +1804 -315
- package/src/html/svg.ts +573 -36
- 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/richtext/node.ts
CHANGED
|
@@ -110,8 +110,15 @@ export interface TextRun {
|
|
|
110
110
|
* `text-decoration-thickness`. Unset, two pixels below and one thick. */
|
|
111
111
|
underlineOffset?: number;
|
|
112
112
|
underlineThickness?: number;
|
|
113
|
-
/**
|
|
113
|
+
/** Rule through the x-height, in this colour — `~~del~~`. Its middle is
|
|
114
|
+
* a third of the run's own ascent above the baseline. */
|
|
114
115
|
strike?: string;
|
|
116
|
+
/** The rule `strike` draws, as `underlineStyle` names them. Default
|
|
117
|
+
* `'single'`; ignored without `strike`. */
|
|
118
|
+
strikeStyle?: 'single' | 'double' | 'curly' | 'dotted' | 'dashed';
|
|
119
|
+
/** How thick the rule through is, in the unit `size` is in — CSS's
|
|
120
|
+
* `text-decoration-thickness` on a line through. Unset, one pixel. */
|
|
121
|
+
strikeThickness?: number;
|
|
115
122
|
/** Link target. `null` is a link still streaming in (not clickable). */
|
|
116
123
|
href?: string | null;
|
|
117
124
|
/** OpenType features the run is shaped with, by tag — `{ tnum: 1 }`,
|
|
@@ -490,7 +497,8 @@ export class RichTextNode extends Node {
|
|
|
490
497
|
return runs.map((r) =>
|
|
491
498
|
typeof r.size === 'number' ||
|
|
492
499
|
r.underlineOffset !== undefined ||
|
|
493
|
-
r.underlineThickness !== undefined
|
|
500
|
+
r.underlineThickness !== undefined ||
|
|
501
|
+
r.strikeThickness !== undefined
|
|
494
502
|
? {
|
|
495
503
|
...r,
|
|
496
504
|
...(typeof r.size === 'number' ? { size: r.size * scale } : null),
|
|
@@ -500,6 +508,9 @@ export class RichTextNode extends Node {
|
|
|
500
508
|
...(r.underlineThickness !== undefined
|
|
501
509
|
? { underlineThickness: r.underlineThickness * scale }
|
|
502
510
|
: null),
|
|
511
|
+
...(r.strikeThickness !== undefined
|
|
512
|
+
? { strikeThickness: r.strikeThickness * scale }
|
|
513
|
+
: null),
|
|
503
514
|
}
|
|
504
515
|
: r,
|
|
505
516
|
);
|
package/src/richtext/runs.ts
CHANGED
|
@@ -23,6 +23,11 @@ export interface FillContext {
|
|
|
23
23
|
save(): void;
|
|
24
24
|
restore(): void;
|
|
25
25
|
fillRect(x: number, y: number, w: number, h: number): void;
|
|
26
|
+
/** A path, where the backend has one: a thick dotted rule's dots are
|
|
27
|
+
* round through these, and square without them. */
|
|
28
|
+
beginPath?(): void;
|
|
29
|
+
roundRect?(x: number, y: number, w: number, h: number, radii: number[]): void;
|
|
30
|
+
fill?(): void;
|
|
26
31
|
}
|
|
27
32
|
|
|
28
33
|
export function canFill(ctx: unknown): ctx is FillContext {
|
|
@@ -180,10 +185,18 @@ export function paintRunBackgrounds(
|
|
|
180
185
|
* is one logical pixel thick — `scale` device pixels, so a link on a 2x
|
|
181
186
|
* panel is underlined as heavily as on a 1x one, not with a hairline —
|
|
182
187
|
* and an underline two below the baseline, where the run sets neither
|
|
183
|
-
* (`underlineThickness`, `underlineOffset`, in device pixels by now).
|
|
188
|
+
* (`underlineThickness`, `underlineOffset`, in device pixels by now). A
|
|
189
|
+
* line through is a logical pixel as well where the run names no
|
|
190
|
+
* `strikeThickness`, and is drawn where its face puts it (`strikeTop`).
|
|
184
191
|
* `rules` picks a pass: CSS draws an underline under the glyphs and a
|
|
185
192
|
* line through over them (CSS 2.1 Appendix E), which is two passes around
|
|
186
|
-
* the glyphs'; drawn in one, both go over.
|
|
193
|
+
* the glyphs'; drawn in one, both go over.
|
|
194
|
+
*
|
|
195
|
+
* A rule is drawn a stretch at a time and not a run: a layout hands
|
|
196
|
+
* back a run a word, and a pattern begun again under each — dots spread
|
|
197
|
+
* from one end of their rule to the other, above all — is a pattern with
|
|
198
|
+
* a seam at every space. Runs that touch and draw the same rule share
|
|
199
|
+
* one. */
|
|
187
200
|
export function paintRunRules(
|
|
188
201
|
ctx: FillContext,
|
|
189
202
|
line: LaidLine,
|
|
@@ -193,52 +206,228 @@ export function paintRunRules(
|
|
|
193
206
|
rules: 'under' | 'over' | 'all' = 'all',
|
|
194
207
|
): void {
|
|
195
208
|
const t = ruleThickness(scale);
|
|
209
|
+
if (rules !== 'over') {
|
|
210
|
+
// the stretch being gathered: the span whose rule it is, and its ends
|
|
211
|
+
let ruled: TextRun | null = null;
|
|
212
|
+
let from = 0;
|
|
213
|
+
let to = 0;
|
|
214
|
+
for (const r of line.runs) {
|
|
215
|
+
const span = r.span;
|
|
216
|
+
if (!span?.underline) continue;
|
|
217
|
+
const end = r.x + r.width;
|
|
218
|
+
if (
|
|
219
|
+
ruled &&
|
|
220
|
+
// on either side: a right-to-left stretch's runs come last first
|
|
221
|
+
(Math.abs(r.x - to) <= 0.5 || Math.abs(end - from) <= 0.5) &&
|
|
222
|
+
sameRule(ruled, span)
|
|
223
|
+
) {
|
|
224
|
+
from = Math.min(from, r.x);
|
|
225
|
+
to = Math.max(to, end);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
if (ruled) stretchRule(ctx, ruled, line, dx + from, dx + to, dy, t);
|
|
229
|
+
ruled = span;
|
|
230
|
+
from = r.x;
|
|
231
|
+
to = end;
|
|
232
|
+
}
|
|
233
|
+
if (ruled) stretchRule(ctx, ruled, line, dx + from, dx + to, dy, t);
|
|
234
|
+
}
|
|
235
|
+
if (rules === 'under') return;
|
|
236
|
+
// A line through, gathered the same way, and ended as well where the
|
|
237
|
+
// font size changes: it is drawn across each size where that size has
|
|
238
|
+
// it (`strikeTop`). The faces of one size are one stretch, at the height
|
|
239
|
+
// they have it between them — a word the face has no glyphs for is set
|
|
240
|
+
// in another, and the rule does not step for it.
|
|
241
|
+
let struck: TextRun | null = null;
|
|
242
|
+
let size: number | undefined;
|
|
243
|
+
let from = 0;
|
|
244
|
+
let to = 0;
|
|
245
|
+
// the stretch's ascent: its first run's, and its runs' by how much of
|
|
246
|
+
// the stretch each sets, which is read only where they are not all the
|
|
247
|
+
// first's
|
|
248
|
+
let ascent = 0;
|
|
249
|
+
let summed = 0;
|
|
250
|
+
let widths = 0;
|
|
251
|
+
let mixed = false;
|
|
196
252
|
for (const r of line.runs) {
|
|
197
253
|
const span = r.span;
|
|
198
|
-
if (!span) continue;
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
if (
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
254
|
+
if (!span?.strike) continue;
|
|
255
|
+
const own = inkExtent(r, line).ascent;
|
|
256
|
+
const end = r.x + r.width;
|
|
257
|
+
if (
|
|
258
|
+
struck &&
|
|
259
|
+
r.run?.size === size &&
|
|
260
|
+
(Math.abs(r.x - to) <= 0.5 || Math.abs(end - from) <= 0.5) &&
|
|
261
|
+
sameStrike(struck, span)
|
|
262
|
+
) {
|
|
263
|
+
from = Math.min(from, r.x);
|
|
264
|
+
to = Math.max(to, end);
|
|
265
|
+
summed += own * r.width;
|
|
266
|
+
widths += r.width;
|
|
267
|
+
if (own !== ascent) mixed = true;
|
|
268
|
+
continue;
|
|
213
269
|
}
|
|
214
|
-
if (
|
|
215
|
-
const
|
|
216
|
-
ctx
|
|
217
|
-
ctx.fillRect(
|
|
218
|
-
Math.round(dx + line.x + r.x),
|
|
219
|
-
Math.round(dy + line.baseline - m.ascent * 0.38),
|
|
220
|
-
Math.ceil(r.width),
|
|
221
|
-
t,
|
|
222
|
-
);
|
|
270
|
+
if (struck) {
|
|
271
|
+
const mean = mixed && widths > 0 ? summed / widths : ascent;
|
|
272
|
+
strikeRule(ctx, struck, line, dx + from, dx + to, dy, mean, t);
|
|
223
273
|
}
|
|
274
|
+
struck = span;
|
|
275
|
+
size = r.run?.size;
|
|
276
|
+
from = r.x;
|
|
277
|
+
to = end;
|
|
278
|
+
ascent = own;
|
|
279
|
+
summed = own * r.width;
|
|
280
|
+
widths = r.width;
|
|
281
|
+
mixed = false;
|
|
282
|
+
}
|
|
283
|
+
if (struck) {
|
|
284
|
+
const mean = mixed && widths > 0 ? summed / widths : ascent;
|
|
285
|
+
strikeRule(ctx, struck, line, dx + from, dx + to, dy, mean, t);
|
|
224
286
|
}
|
|
225
287
|
}
|
|
226
288
|
|
|
289
|
+
/** The underline of a stretch of a line, from `from` to `to` of it, as
|
|
290
|
+
* `span` has it; `t` is the thickness of a rule that names none. */
|
|
291
|
+
function stretchRule(
|
|
292
|
+
ctx: FillContext,
|
|
293
|
+
span: TextRun,
|
|
294
|
+
line: LaidLine,
|
|
295
|
+
from: number,
|
|
296
|
+
to: number,
|
|
297
|
+
dy: number,
|
|
298
|
+
t: number,
|
|
299
|
+
): void {
|
|
300
|
+
const left = Math.round(line.x + from);
|
|
301
|
+
ctx.fillStyle = span.underline;
|
|
302
|
+
underlineRule(
|
|
303
|
+
ctx,
|
|
304
|
+
left,
|
|
305
|
+
Math.round(dy + line.baseline + (span.underlineOffset ?? 2 * t)),
|
|
306
|
+
Math.ceil(line.x + to - left),
|
|
307
|
+
span.underlineStyle ?? 'single',
|
|
308
|
+
span.underlineThickness === undefined
|
|
309
|
+
? t
|
|
310
|
+
: Math.max(1, Math.round(span.underlineThickness)),
|
|
311
|
+
t,
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/** How thick a run's line through is: what it says, on whole pixels, or
|
|
316
|
+
* `t`, a rule's own thickness. */
|
|
317
|
+
function strikeThickness(span: TextRun, t: number): number {
|
|
318
|
+
return span.strikeThickness === undefined
|
|
319
|
+
? t
|
|
320
|
+
: Math.max(1, Math.round(span.strikeThickness));
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Where the top of a line through is, across text whose faces' ascent is
|
|
325
|
+
* `ascent`. Through the middle of the letters with no ascender, which a
|
|
326
|
+
* face does not say the height of in a way both text engines report: a
|
|
327
|
+
* third of its ascent above the baseline, where Blink has it, and the
|
|
328
|
+
* rule's middle there, so that a thick one grows both ways from where a
|
|
329
|
+
* thin one is. The ascent is the text's own, and not that of the font the
|
|
330
|
+
* rule was set in: a line through is worked out again across text of
|
|
331
|
+
* another font size, from the metrics of the fonts that size is set in,
|
|
332
|
+
* so that the text is still crossed out (CSS Text Decoration 4, 2.5), and
|
|
333
|
+
* is not held to the one position an underline is (2.9). Blink measures
|
|
334
|
+
* the ascent of the font the rule was set in from the top of the text it
|
|
335
|
+
* crosses, which puts it near the top of larger text and under smaller.
|
|
336
|
+
* The curl is two levels and straddles the middle; a double rule's first
|
|
337
|
+
* line is the single one's, and its second under it, as Blink draws it.
|
|
338
|
+
*
|
|
339
|
+
* A run that names no thickness keeps the pixel it always had: the rule's
|
|
340
|
+
* top 38% of the ascent up, which at a text size is the same place, and a
|
|
341
|
+
* pixel off it at some.
|
|
342
|
+
*/
|
|
343
|
+
function strikeTop(
|
|
344
|
+
line: LaidLine,
|
|
345
|
+
dy: number,
|
|
346
|
+
span: TextRun,
|
|
347
|
+
ascent: number,
|
|
348
|
+
t: number,
|
|
349
|
+
): number {
|
|
350
|
+
if (span.strikeThickness === undefined) {
|
|
351
|
+
return Math.round(dy + line.baseline - ascent * 0.38);
|
|
352
|
+
}
|
|
353
|
+
const thick = strikeThickness(span, t);
|
|
354
|
+
return Math.round(
|
|
355
|
+
dy +
|
|
356
|
+
line.baseline -
|
|
357
|
+
ascent / 3 -
|
|
358
|
+
(span.strikeStyle === 'curly' ? thick : thick / 2),
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** The line through a stretch of a line, from `from` to `to` of it, as
|
|
363
|
+
* `span` has it, over faces whose ascent is `ascent`. */
|
|
364
|
+
function strikeRule(
|
|
365
|
+
ctx: FillContext,
|
|
366
|
+
span: TextRun,
|
|
367
|
+
line: LaidLine,
|
|
368
|
+
from: number,
|
|
369
|
+
to: number,
|
|
370
|
+
dy: number,
|
|
371
|
+
ascent: number,
|
|
372
|
+
t: number,
|
|
373
|
+
): void {
|
|
374
|
+
const left = Math.round(line.x + from);
|
|
375
|
+
ctx.fillStyle = span.strike;
|
|
376
|
+
underlineRule(
|
|
377
|
+
ctx,
|
|
378
|
+
left,
|
|
379
|
+
strikeTop(line, dy, span, ascent, t),
|
|
380
|
+
Math.ceil(line.x + to - left),
|
|
381
|
+
span.strikeStyle ?? 'single',
|
|
382
|
+
strikeThickness(span, t),
|
|
383
|
+
t,
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/** Whether two spans are struck through with one rule: the same ink, style
|
|
388
|
+
* and thickness. Where it is drawn is their faces' to say (`strikeTop`). */
|
|
389
|
+
function sameStrike(a: TextRun, b: TextRun): boolean {
|
|
390
|
+
return (
|
|
391
|
+
a === b ||
|
|
392
|
+
(a.strike === b.strike &&
|
|
393
|
+
a.strikeStyle === b.strikeStyle &&
|
|
394
|
+
a.strikeThickness === b.strikeThickness)
|
|
395
|
+
);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/** Whether two spans are underlined with one rule: the same ink, style,
|
|
399
|
+
* place and thickness. */
|
|
400
|
+
function sameRule(a: TextRun, b: TextRun): boolean {
|
|
401
|
+
return (
|
|
402
|
+
a === b ||
|
|
403
|
+
(a.underline === b.underline &&
|
|
404
|
+
a.underlineStyle === b.underlineStyle &&
|
|
405
|
+
a.underlineOffset === b.underlineOffset &&
|
|
406
|
+
a.underlineThickness === b.underlineThickness)
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
|
|
227
410
|
/** One logical pixel on the device grid, never less than one device pixel. */
|
|
228
411
|
function ruleThickness(scale: number): number {
|
|
229
412
|
return Math.max(1, Math.round(scale));
|
|
230
413
|
}
|
|
231
414
|
|
|
232
415
|
/**
|
|
233
|
-
* The rule under a run, in one of SGR 4's five styles.
|
|
416
|
+
* The rule under a run, or through it, in one of SGR 4's five styles.
|
|
234
417
|
*
|
|
235
418
|
* All five are built from rectangles `t` pixels thick — one logical pixel —
|
|
236
419
|
* rather than a stroked path: the mock backend has no path API, and a
|
|
237
420
|
* hairline stroke on a text baseline is not worth an antialiased path even
|
|
238
421
|
* where there is one. The curl is a two-level square wave — at a text size
|
|
239
422
|
* it reads as a squiggle, which is the entire job. The dot pitch and the
|
|
240
|
-
* dash length scale with the thickness, so the pattern is
|
|
241
|
-
* any display scale.
|
|
423
|
+
* dash length scale with the thickness (`dashedRule`), so the pattern is
|
|
424
|
+
* the pattern at any display scale. The one path is a thick dotted rule's
|
|
425
|
+
* (`dottedRule`): a square is a dot only while it is too small to be seen
|
|
426
|
+
* as a square.
|
|
427
|
+
*
|
|
428
|
+
* `gap` is what parts the two lines of a double rule: a logical pixel
|
|
429
|
+
* however thick they are, as Blink parts them, which for a rule of that
|
|
430
|
+
* thickness is the thickness between them it always was.
|
|
242
431
|
*/
|
|
243
432
|
export function underlineRule(
|
|
244
433
|
ctx: FillContext,
|
|
@@ -247,20 +436,18 @@ export function underlineRule(
|
|
|
247
436
|
width: number,
|
|
248
437
|
style: NonNullable<TextRun['underlineStyle']>,
|
|
249
438
|
t = 1,
|
|
439
|
+
gap = t,
|
|
250
440
|
): void {
|
|
251
441
|
switch (style) {
|
|
252
442
|
case 'double':
|
|
253
443
|
ctx.fillRect(x, y, width, t);
|
|
254
|
-
ctx.fillRect(x, y +
|
|
444
|
+
ctx.fillRect(x, y + t + gap, width, t);
|
|
255
445
|
return;
|
|
256
446
|
case 'dotted':
|
|
257
|
-
|
|
258
|
-
ctx.fillRect(x + i, y, Math.min(t, width - i), t);
|
|
259
|
-
}
|
|
447
|
+
dottedRule(ctx, x, y, width, t);
|
|
260
448
|
return;
|
|
261
449
|
case 'dashed':
|
|
262
|
-
|
|
263
|
-
ctx.fillRect(x + i, y, Math.min(3 * t, width - i), t);
|
|
450
|
+
dashedRule(ctx, x, y, width, t);
|
|
264
451
|
return;
|
|
265
452
|
case 'curly':
|
|
266
453
|
for (let i = 0; i < width; i += 2 * t) {
|
|
@@ -278,6 +465,97 @@ export function underlineRule(
|
|
|
278
465
|
}
|
|
279
466
|
}
|
|
280
467
|
|
|
468
|
+
/** The thickness a dashed rule's dashes stop being the long ones of a thin
|
|
469
|
+
* rule at. */
|
|
470
|
+
const THIN_DASHES = 3;
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* A dashed rule, its dashes the lengths Blink's are: three times the rule's
|
|
474
|
+
* thickness and two apart while it is thin, where a shorter dash reads as
|
|
475
|
+
* a dot and a nearer one as no gap, and twice and one from three pixels up.
|
|
476
|
+
* The first dash starts the rule and the last ends it, with as many between
|
|
477
|
+
* as leave the gaps nearest that: a rule that stopped where its length ran
|
|
478
|
+
* out ended on a sliver of a dash, or on nothing. One with room for two
|
|
479
|
+
* dashes and less than their gap is two, scaled to fit, and one too short
|
|
480
|
+
* for two is a line.
|
|
481
|
+
*/
|
|
482
|
+
function dashedRule(
|
|
483
|
+
ctx: FillContext,
|
|
484
|
+
x: number,
|
|
485
|
+
y: number,
|
|
486
|
+
width: number,
|
|
487
|
+
t: number,
|
|
488
|
+
): void {
|
|
489
|
+
const thin = t < THIN_DASHES;
|
|
490
|
+
const dash = t * (thin ? 3 : 2);
|
|
491
|
+
const gap = t * (thin ? 2 : 1);
|
|
492
|
+
if (width <= 2 * dash) {
|
|
493
|
+
ctx.fillRect(x, y, width, t);
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
if (width <= 2 * dash + gap) {
|
|
497
|
+
const each = Math.round((dash * width) / (2 * dash + gap));
|
|
498
|
+
ctx.fillRect(x, y, each, t);
|
|
499
|
+
ctx.fillRect(x + width - each, y, each, t);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
// the fewest dashes that gap fits between, and one more: whichever
|
|
503
|
+
// count's gap is nearer it
|
|
504
|
+
const few = Math.floor((width + gap) / (dash + gap));
|
|
505
|
+
const wide = (width - few * dash) / (few - 1);
|
|
506
|
+
const narrow = (width - (few + 1) * dash) / few;
|
|
507
|
+
const count =
|
|
508
|
+
narrow <= 0 || Math.abs(wide - gap) < Math.abs(narrow - gap)
|
|
509
|
+
? few
|
|
510
|
+
: few + 1;
|
|
511
|
+
const pitch = (width - dash) / (count - 1);
|
|
512
|
+
for (let i = 0; i < count; i += 1) {
|
|
513
|
+
ctx.fillRect(x + Math.round(i * pitch), y, dash, t);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** The thickest a dotted rule's dots are squares at: past it a square
|
|
518
|
+
* reads as one. Where Blink's dotted stroke turns its caps round. */
|
|
519
|
+
const SQUARE_DOTS = 3;
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* A dotted rule: squares a thickness apart while it is thin, and round dots
|
|
523
|
+
* once it is thick enough for the shape to show — under a title, where the
|
|
524
|
+
* thickness follows the font size. The round ones are spread over the
|
|
525
|
+
* width, the first at its start and the last at its end, as many as leave
|
|
526
|
+
* the gap between two nearest to a dot's own width; a rule with room for
|
|
527
|
+
* fewer than two is one dot. A context with no path draws the squares.
|
|
528
|
+
*/
|
|
529
|
+
function dottedRule(
|
|
530
|
+
ctx: FillContext,
|
|
531
|
+
x: number,
|
|
532
|
+
y: number,
|
|
533
|
+
width: number,
|
|
534
|
+
t: number,
|
|
535
|
+
): void {
|
|
536
|
+
if (t <= SQUARE_DOTS || !ctx.beginPath || !ctx.roundRect || !ctx.fill) {
|
|
537
|
+
for (let i = 0; i < width; i += 2 * t) {
|
|
538
|
+
ctx.fillRect(x + i, y, Math.min(t, width - i), t);
|
|
539
|
+
}
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
// the fewest dots a gap of their own width fits, and one more: whichever
|
|
543
|
+
// count's gap is nearer that width
|
|
544
|
+
const few = Math.max(1, Math.floor((width + t) / (2 * t)));
|
|
545
|
+
const wide = few > 1 ? (width - few * t) / (few - 1) : Infinity;
|
|
546
|
+
const narrow = (width - (few + 1) * t) / few;
|
|
547
|
+
const count =
|
|
548
|
+
narrow <= 0 || Math.abs(wide - t) < Math.abs(narrow - t) ? few : few + 1;
|
|
549
|
+
const pitch = count > 1 ? (width - t) / (count - 1) : 0;
|
|
550
|
+
for (let i = 0; i < count; i += 1) {
|
|
551
|
+
// a path a dot: ntk knows a path that is one `roundRect` and fills it
|
|
552
|
+
// from its corners, where a path of them all is rasterized whole
|
|
553
|
+
ctx.beginPath();
|
|
554
|
+
ctx.roundRect(x + i * pitch, y, t, t, [t / 2]);
|
|
555
|
+
ctx.fill();
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
281
559
|
/** A selected empty line still shows as a sliver, so a selection spanning a
|
|
282
560
|
* blank line does not appear to skip it. Core's own width for the same. */
|
|
283
561
|
export const EMPTY_LINE_BAND = 4;
|