@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
@@ -42,8 +42,10 @@ import {
42
42
  scrolls,
43
43
  } from '../css/style.js';
44
44
  import { inkColor, isTransparent, resolve } from '../css/values.js';
45
+ import { tagOf } from '../dom.js';
45
46
  import {
46
47
  BOX_RAISES,
48
+ GRID_TRACKS,
47
49
  LINE_BOX_RAISES,
48
50
  PADDED_FACES,
49
51
  isOffset,
@@ -415,13 +417,21 @@ interface Cut {
415
417
  function cutOf(
416
418
  style: ComputedStyle,
417
419
  clamp: InlineOptions['clamp'],
420
+ width: number,
418
421
  ): Cut | null {
419
422
  // the ellipsis a clamp ends in is placed apart (`ellipsized`)
420
423
  if (clamp) return { maxLines: clamp.lines, overflow: 'clip' };
424
+ // `text-overflow` is how the line is drawn where it runs out of its
425
+ // box, and no part of how wide it is (CSS Overflow 3, 5.1): in a box of
426
+ // no width, which shows none of it and is what the box's content is
427
+ // measured in at its least (`MIN_CONTENT_PROBE`), the line is left
428
+ // whole. Cut there, ntk left the ellipsis alone, and a button whose
429
+ // label is a `truncate` span was measured as wide as one
421
430
  if (
422
431
  style.textOverflow === 'ellipsis' &&
423
432
  !wraps(style) &&
424
- style.overflowX !== 'visible'
433
+ style.overflowX !== 'visible' &&
434
+ width > 0
425
435
  ) {
426
436
  return { maxLines: 1, overflow: 'ellipsis', wrap: false };
427
437
  }
@@ -644,7 +654,7 @@ function linesOf(
644
654
  // line at a time, or the lines after the first were lost. A clamp is
645
655
  // the paragraph's, and stays one layout.
646
656
  const clamp = options.clamp;
647
- let cut = cutOf(style, clamp);
657
+ let cut = cutOf(style, clamp, options.width);
648
658
  const perLine = cut !== null && !clamp && hasNewline;
649
659
  // an embedding cannot be cut into chunks
650
660
  if (perLine && !hasControls(items)) {
@@ -1420,6 +1430,7 @@ function linesOf(
1420
1430
  if (!text) continue;
1421
1431
  const m = hung.exec(text);
1422
1432
  if (!m) break;
1433
+ if (!trailing) placed.trailRun = segment.runs[i];
1423
1434
  trailing = m[0] + trailing;
1424
1435
  if (m[0].length < text.length) break;
1425
1436
  }
@@ -1436,6 +1447,7 @@ function linesOf(
1436
1447
  : spaceAdvance(fonts, segment.runs[segment.runs.length - 1]) *
1437
1448
  trailing.length;
1438
1449
  open.x += open.hang;
1450
+ placed.trail = open.hang;
1439
1451
  }
1440
1452
  } else if (hung.total) holdHung();
1441
1453
  index = segment.nextIndex;
@@ -2390,6 +2402,24 @@ function finishLine(
2390
2402
  if (!open.texts.length && !open.atomics.length && !open.edges.length) {
2391
2403
  return null;
2392
2404
  }
2405
+ if (open.hang > 0) {
2406
+ // The line ends on the spaces its last text ends on, and they are
2407
+ // removed (CSS Text 3, 4.1.2): the text takes no room for them, and
2408
+ // what was set after them, the end edges of the boxes they are in,
2409
+ // stands where the text's content ends. Left a space past it, a list
2410
+ // item that ended its line was a space wider than its link.
2411
+ for (let i = open.order.length - 1; i >= 0; i -= 1) {
2412
+ const placed = open.order[i];
2413
+ if (placed.kind === 'text') {
2414
+ placed.item.trail = 0;
2415
+ placed.item.trailRun = undefined;
2416
+ break;
2417
+ }
2418
+ if (placed.kind !== 'edge') break;
2419
+ placed.at -= open.hang;
2420
+ placed.item.x -= open.hang;
2421
+ }
2422
+ }
2393
2423
  const reordered = levels
2394
2424
  ? levelPieces(open, levels) ||
2395
2425
  open.order.some((p) => p.kind === 'edge' && needsOrdering(p, rtl))
@@ -3298,7 +3328,19 @@ function atomicRaise(
3298
3328
  * be after.
3299
3329
  */
3300
3330
  function atomicBaseline(box: Box): number {
3301
- const bottom = box.height + box.marginTop + box.marginBottom;
3331
+ const margin = box.height + box.marginTop + box.marginBottom;
3332
+ const button = isButton(box);
3333
+ // A `<button>` with no line in it sits on the bottom of its content box,
3334
+ // not of its margin box: where its label would have sat. No specification
3335
+ // says so and every browser does — Blink gives the element the content
3336
+ // box as the edge its baseline is synthesized from
3337
+ // (`HTMLButtonElement::AdjustStyle`). An icon button drawn of blocks, the
3338
+ // three bars of a menu toggle, stood its padding under the baseline, and
3339
+ // the line it was on was as much taller: GitHub's header, by four pixels.
3340
+ // And so one whose layout containment keeps its label's baseline in.
3341
+ const bottom = button
3342
+ ? margin - box.marginBottom - box.borderBottom - box.padBottom
3343
+ : margin;
3302
3344
  if (
3303
3345
  box.kind === 'replaced' ||
3304
3346
  // a box that clips sits on its bottom margin edge, for legacy reasons
@@ -3307,8 +3349,8 @@ function atomicBaseline(box: Box): number {
3307
3349
  // sits on its first item's, as it does unclipped. A MediaWiki button
3308
3350
  // is an `overflow: hidden` inline flex box of one icon, and stood its
3309
3351
  // whole height on the baseline, the line under it the strut's descent
3310
- // taller
3311
- (scrolls(box.style) && box.kind !== 'flex') ||
3352
+ // taller. Nor a `<button>`, which sits on its label clipped or not
3353
+ (scrolls(box.style) && box.kind !== 'flex' && !button) ||
3312
3354
  // layout containment keeps its baseline in (CSS Containment 2, 3.3)
3313
3355
  box.style.contain & CONTAIN_LAYOUT
3314
3356
  ) {
@@ -3325,17 +3367,31 @@ function atomicBaseline(box: Box): number {
3325
3367
  return baseline === null ? bottom : box.marginTop + (baseline - box.y);
3326
3368
  }
3327
3369
 
3370
+ /** Whether a box is a `<button>` element's: one drawn as the document's,
3371
+ * which a replaced control is not. */
3372
+ export function isButton(box: Box): boolean {
3373
+ return (
3374
+ box.el !== null && box.kind !== 'replaced' && tagOf(box.el) === 'button'
3375
+ );
3376
+ }
3377
+
3328
3378
  /**
3329
- * A flex container's first baseline, or its last (CSS Flexbox 8.5): that
3330
- * of the first item on its first line that is aligned by its baseline — the
3331
- * last on its last line — or where none is, of its first item, or last,
3332
- * in `order`. An item gives its own, or where it has none, its border
3333
- * box's bottom edge. Not its last child's: an `inline-flex` of a small
3334
- * item and a big one sat on the big one's baseline. Null for a box with no
3335
- * item.
3379
+ * A flex container's baseline, its first (CSS Flexbox 8.5): that of the
3380
+ * first item on its first line that is aligned by its baseline, or where
3381
+ * none is, of its first item, in `order`. An item gives its own, or where
3382
+ * it has none, its border box's bottom edge. Not its last child's: an
3383
+ * `inline-flex` of a small item and a big one sat on the big one's
3384
+ * baseline. Null for a box with no item.
3385
+ *
3386
+ * A grid's is that of its first item in row-major order, by where it was
3387
+ * placed and not where it was written (CSS Grid 1, 10.6).
3336
3388
  */
3337
- function flexBaseline(box: Box, first: boolean): number | null {
3338
- let items: Box[] = [];
3389
+ function flexBaseline(box: Box): number | null {
3390
+ if (box.style.grid) {
3391
+ const item = GRID_TRACKS.get(box)?.first;
3392
+ return item ? itemBaselineOf(item) : null;
3393
+ }
3394
+ const items: Box[] = [];
3339
3395
  let reordered = false;
3340
3396
  for (const child of box.children) {
3341
3397
  if (child.outOfFlow || child.isFloat) continue;
@@ -3345,15 +3401,11 @@ function flexBaseline(box: Box, first: boolean): number | null {
3345
3401
  }
3346
3402
  if (!items.length) return null;
3347
3403
  if (reordered) items.sort((a, b) => a.style.order - b.style.order);
3348
- if (!first) items = items.reverse();
3349
3404
  const style = box.style;
3350
- if (style.flexDirection.startsWith('row') && !style.grid) {
3351
- // a line ends where the next item does not go on along the main axis,
3352
- // which from the last item is where it goes on
3405
+ if (style.flexDirection.startsWith('row')) {
3406
+ // a line ends where the next item does not go on along the main axis
3353
3407
  const back =
3354
- ((style.flexDirection === 'row-reverse') !==
3355
- (style.direction === 'rtl')) !==
3356
- !first;
3408
+ (style.flexDirection === 'row-reverse') !== (style.direction === 'rtl');
3357
3409
  let last = NaN;
3358
3410
  for (const item of items) {
3359
3411
  if (!Number.isNaN(last) && (back ? item.x >= last : item.x <= last)) {
@@ -3364,24 +3416,20 @@ function flexBaseline(box: Box, first: boolean): number | null {
3364
3416
  item.style.alignSelf === 'auto'
3365
3417
  ? style.alignItems
3366
3418
  : item.style.alignSelf;
3367
- if (align === 'baseline') return itemBaselineOf(item, first, style);
3419
+ if (align === 'baseline') return itemBaselineOf(item);
3368
3420
  }
3369
3421
  }
3370
- return itemBaselineOf(items[0], first, style);
3422
+ return itemBaselineOf(items[0]);
3371
3423
  }
3372
3424
 
3373
- /** A flex item's baseline, in document coordinates: its first line's or
3374
- * its last's — its bottom margin edge where it clips what it holds — or
3375
- * where it has none, its border box's bottom edge, which a grid does not
3376
- * take from its item, and has none of its own then (CSS Grid 1, 9). */
3377
- function itemBaselineOf(
3378
- item: Box,
3379
- first: boolean,
3380
- container: ComputedStyle,
3381
- ): number | null {
3382
- const found = childBaseline(item, first ? firstBaselineIn : lastBaselineIn);
3383
- if (found !== null) return found;
3384
- return container.grid ? null : item.y + item.height;
3425
+ /** A flex item's baseline or a grid item's, in document coordinates: its
3426
+ * first line's, or where it has none, one synthesized from its border
3427
+ * edges, the bottom one (CSS Flexbox 8.5, CSS Grid 1, 10.6). A grid took
3428
+ * none from such an item and had none of its own: a grid of icons stood
3429
+ * its whole height on the baseline, where a browser stands it on the
3430
+ * first icon's bottom edge. */
3431
+ function itemBaselineOf(item: Box): number {
3432
+ return childBaseline(item, firstBaselineIn) ?? item.y + item.height;
3385
3433
  }
3386
3434
 
3387
3435
  /**
@@ -3409,10 +3457,18 @@ function tableBaseline(table: Box): number | null {
3409
3457
  * what overflows it gives its bottom margin edge and no deeper, whatever
3410
3458
  * text it holds (CSS Box Alignment's synthesized baseline). Null where
3411
3459
  * nothing in it has one.
3460
+ *
3461
+ * A flex box or a grid among them gives its first baseline, the one it has
3462
+ * on a line of its own, and a table gives none: what an inline-block takes
3463
+ * from a block-level child in Blink, where only a block container is asked
3464
+ * for its last (`UseLastBaselineForInlineBaseline`), and in Gecko, where a
3465
+ * child is asked for its default one and a table is passed over
3466
+ * (`nsBlockFrame::GetNaturalBaselineBOffset`). Asked for its last, a grid
3467
+ * of an icon, a label and a chevron in a button sat the button on the
3468
+ * chevron, and the line it was on came out shorter than a browser's.
3412
3469
  */
3413
3470
  function lastBaselineIn(box: Box): number | null {
3414
- // a flex box's is its items', in the order it lays them out
3415
- if (box.kind === 'flex') return flexBaseline(box, false);
3471
+ if (box.kind === 'flex') return flexBaseline(box);
3416
3472
  if (box.lines?.length) {
3417
3473
  const line = box.lines[box.lines.length - 1];
3418
3474
  return line.y + line.baseline;
@@ -3427,7 +3483,7 @@ function lastBaselineIn(box: Box): number | null {
3427
3483
  /** The same, from the first line box or child: a table's baseline is its
3428
3484
  * first row's, and so is a table cell's. */
3429
3485
  export function firstBaselineIn(box: Box): number | null {
3430
- if (box.kind === 'flex') return flexBaseline(box, true);
3486
+ if (box.kind === 'flex') return flexBaseline(box);
3431
3487
  if (box.lines?.length) return box.lines[0].y + box.lines[0].baseline;
3432
3488
  for (const child of box.children) {
3433
3489
  const found = childBaseline(child, firstBaselineIn);
@@ -3443,12 +3499,17 @@ function childBaseline(
3443
3499
  if (child.outOfFlow || child.isFloat || child.kind === 'replaced') {
3444
3500
  return null;
3445
3501
  }
3502
+ // a table is passed over for a block's last baseline (`lastBaselineIn`)
3503
+ if (inside === lastBaselineIn && child.kind === 'table') return null;
3446
3504
  // the legacy rule is a block container's last baseline alone (CSS Box
3447
- // Alignment 3, 9.2): its first is its first line's, clipped or not
3505
+ // Alignment 3, 9.2): its first is its first line's, clipped or not —
3506
+ // and a button that clips keeps its label's, here as on a line of its
3507
+ // own (`atomicBaseline`)
3448
3508
  if (
3449
3509
  inside === lastBaselineIn &&
3450
3510
  child.kind !== 'flex' &&
3451
- scrolls(child.style)
3511
+ scrolls(child.style) &&
3512
+ !isButton(child)
3452
3513
  ) {
3453
3514
  return child.y + child.height + child.marginBottom;
3454
3515
  }
@@ -4143,6 +4204,8 @@ const RUN_STYLE = [
4143
4204
  'underlineOffset',
4144
4205
  'underlineThickness',
4145
4206
  'strike',
4207
+ 'strikeStyle',
4208
+ 'strikeThickness',
4146
4209
  ] as const;
4147
4210
 
4148
4211
  /**
@@ -4710,26 +4773,57 @@ export function runFor(text: string, style: ComputedStyle): TextRun {
4710
4773
  // colours of the elements that set them (`decorate`)
4711
4774
  if (style.underline) {
4712
4775
  run.underline = style.underline;
4713
- // CSS's five rule styles and SGR 4's five are the same set under two
4714
- // names; richtext speaks SGR's, so `solid` is `single` and `wavy` is
4715
- // `curly`. The other three are spelled identically.
4716
- run.underlineStyle =
4717
- style.underlineStyle === 'wavy'
4718
- ? 'curly'
4719
- : style.underlineStyle === 'solid'
4720
- ? 'single'
4721
- : style.underlineStyle;
4722
- if (style.underlineOffset !== null) {
4723
- run.underlineOffset = style.underlineOffset;
4724
- }
4725
- if (style.underlineThickness !== null) {
4726
- run.underlineThickness = style.underlineThickness;
4776
+ run.underlineStyle = ruleStyle(style.underlineStyle);
4777
+ // The band the rule is drawn in: as thick as the box that set it made
4778
+ // it (`usedThickness`), and as far under the baseline as that box said.
4779
+ // `auto` leaves how far to the user agent, at or under the baseline
4780
+ // (CSS Text Decoration 4, 2.7 and 2.8), and it is half the rule's
4781
+ // thickness, a pixel at the least — Blink's gap, which keeps a thick
4782
+ // rule as clear of its letters as a thin one. A length is from the
4783
+ // baseline itself.
4784
+ const band = style.underlineThickness ?? 1;
4785
+ const top =
4786
+ style.underlineOffset === null
4787
+ ? Math.max(1, Math.ceil(band / 2))
4788
+ : Math.round(style.underlineOffset);
4789
+ if (run.underlineStyle === 'dotted' || run.underlineStyle === 'dashed') {
4790
+ // dots and dashes are a stroke along the band's middle, its width the
4791
+ // nearest whole pixel to the band's: 1.6 is two, where a solid rule's
4792
+ // is the one pixel it fills
4793
+ const width = Math.round(band);
4794
+ run.underlineThickness = width;
4795
+ run.underlineOffset =
4796
+ top + Math.floor(Math.max(band / 2, 0.5)) - Math.floor(width / 2);
4797
+ } else {
4798
+ run.underlineThickness = Math.floor(band);
4799
+ run.underlineOffset = top;
4727
4800
  }
4728
4801
  }
4729
- if (style.lineThrough) run.strike = style.lineThrough;
4802
+ if (style.lineThrough) {
4803
+ run.strike = style.lineThrough;
4804
+ run.strikeStyle = ruleStyle(style.lineThroughStyle);
4805
+ // as thick as the box that set it made it, on whole pixels as the
4806
+ // underline's is: a solid rule fills the ones it covers, and dots and
4807
+ // dashes are the nearest whole pixel wide. Where it is drawn is each
4808
+ // run's own face's to say (`paintRunRules`).
4809
+ const band = style.lineThroughThickness ?? 1;
4810
+ run.strikeThickness =
4811
+ run.strikeStyle === 'dotted' || run.strikeStyle === 'dashed'
4812
+ ? Math.round(band)
4813
+ : Math.floor(band);
4814
+ }
4730
4815
  return run;
4731
4816
  }
4732
4817
 
4818
+ /** A CSS rule style as richtext names it. CSS's five and SGR 4's five are
4819
+ * the same set under two names; richtext speaks SGR's, so `solid` is
4820
+ * `single` and `wavy` is `curly`. The other three are spelled identically. */
4821
+ function ruleStyle(
4822
+ style: ComputedStyle['underlineStyle'],
4823
+ ): NonNullable<TextRun['underlineStyle']> {
4824
+ return style === 'wavy' ? 'curly' : style === 'solid' ? 'single' : style;
4825
+ }
4826
+
4733
4827
  /**
4734
4828
  * Maps a code-unit offset in text handed to `TextLayout` back to its offset
4735
4829
  * in the document index. The two differ because a layout covers one segment