@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.
Files changed (150) hide show
  1. package/dist/html/controls.d.ts +41 -5
  2. package/dist/html/controls.d.ts.map +1 -1
  3. package/dist/html/controls.js +85 -57
  4. package/dist/html/controls.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts +134 -20
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +545 -126
  8. package/dist/html/css/cascade.js.map +1 -1
  9. package/dist/html/css/color.d.ts +18 -0
  10. package/dist/html/css/color.d.ts.map +1 -1
  11. package/dist/html/css/color.js +27 -0
  12. package/dist/html/css/color.js.map +1 -1
  13. package/dist/html/css/parse.d.ts +47 -6
  14. package/dist/html/css/parse.d.ts.map +1 -1
  15. package/dist/html/css/parse.js +336 -30
  16. package/dist/html/css/parse.js.map +1 -1
  17. package/dist/html/css/style.d.ts +142 -15
  18. package/dist/html/css/style.d.ts.map +1 -1
  19. package/dist/html/css/style.js +563 -147
  20. package/dist/html/css/style.js.map +1 -1
  21. package/dist/html/css/transform.d.ts +85 -0
  22. package/dist/html/css/transform.d.ts.map +1 -0
  23. package/dist/html/css/transform.js +425 -0
  24. package/dist/html/css/transform.js.map +1 -0
  25. package/dist/html/css/ua.d.ts +11 -1
  26. package/dist/html/css/ua.d.ts.map +1 -1
  27. package/dist/html/css/ua.js +73 -9
  28. package/dist/html/css/ua.js.map +1 -1
  29. package/dist/html/css/values.d.ts +4 -0
  30. package/dist/html/css/values.d.ts.map +1 -1
  31. package/dist/html/css/values.js.map +1 -1
  32. package/dist/html/dom.d.ts.map +1 -1
  33. package/dist/html/dom.js +3 -1
  34. package/dist/html/dom.js.map +1 -1
  35. package/dist/html/fonts.d.ts +78 -4
  36. package/dist/html/fonts.d.ts.map +1 -1
  37. package/dist/html/fonts.js +373 -19
  38. package/dist/html/fonts.js.map +1 -1
  39. package/dist/html/form.d.ts +200 -0
  40. package/dist/html/form.d.ts.map +1 -0
  41. package/dist/html/form.js +801 -0
  42. package/dist/html/form.js.map +1 -0
  43. package/dist/html/index.d.ts +12 -0
  44. package/dist/html/index.d.ts.map +1 -1
  45. package/dist/html/index.js +30 -301
  46. package/dist/html/index.js.map +1 -1
  47. package/dist/html/layout/axes.d.ts +14 -0
  48. package/dist/html/layout/axes.d.ts.map +1 -0
  49. package/dist/html/layout/axes.js +28 -0
  50. package/dist/html/layout/axes.js.map +1 -0
  51. package/dist/html/layout/block.d.ts +93 -12
  52. package/dist/html/layout/block.d.ts.map +1 -1
  53. package/dist/html/layout/block.js +393 -39
  54. package/dist/html/layout/block.js.map +1 -1
  55. package/dist/html/layout/boxes.d.ts +90 -2
  56. package/dist/html/layout/boxes.d.ts.map +1 -1
  57. package/dist/html/layout/boxes.js +74 -10
  58. package/dist/html/layout/boxes.js.map +1 -1
  59. package/dist/html/layout/cache.d.ts +1 -1
  60. package/dist/html/layout/cache.d.ts.map +1 -1
  61. package/dist/html/layout/cache.js +6 -0
  62. package/dist/html/layout/cache.js.map +1 -1
  63. package/dist/html/layout/css-grid.d.ts.map +1 -1
  64. package/dist/html/layout/css-grid.js +90 -18
  65. package/dist/html/layout/css-grid.js.map +1 -1
  66. package/dist/html/layout/flex.d.ts.map +1 -1
  67. package/dist/html/layout/flex.js +574 -84
  68. package/dist/html/layout/flex.js.map +1 -1
  69. package/dist/html/layout/inline.d.ts +3 -0
  70. package/dist/html/layout/inline.d.ts.map +1 -1
  71. package/dist/html/layout/inline.js +154 -54
  72. package/dist/html/layout/inline.js.map +1 -1
  73. package/dist/html/layout/multicol.d.ts +42 -0
  74. package/dist/html/layout/multicol.d.ts.map +1 -0
  75. package/dist/html/layout/multicol.js +897 -0
  76. package/dist/html/layout/multicol.js.map +1 -0
  77. package/dist/html/layout/shaping.d.ts +7 -0
  78. package/dist/html/layout/shaping.d.ts.map +1 -1
  79. package/dist/html/layout/shaping.js +25 -20
  80. package/dist/html/layout/shaping.js.map +1 -1
  81. package/dist/html/layout/table.d.ts.map +1 -1
  82. package/dist/html/layout/table.js +62 -18
  83. package/dist/html/layout/table.js.map +1 -1
  84. package/dist/html/node.d.ts +90 -15
  85. package/dist/html/node.d.ts.map +1 -1
  86. package/dist/html/node.js +771 -186
  87. package/dist/html/node.js.map +1 -1
  88. package/dist/html/paint.d.ts +101 -5
  89. package/dist/html/paint.d.ts.map +1 -1
  90. package/dist/html/paint.js +1502 -160
  91. package/dist/html/paint.js.map +1 -1
  92. package/dist/html/svg.d.ts +15 -2
  93. package/dist/html/svg.d.ts.map +1 -1
  94. package/dist/html/svg.js +249 -17
  95. package/dist/html/svg.js.map +1 -1
  96. package/dist/html/widgets.d.ts +29 -0
  97. package/dist/html/widgets.d.ts.map +1 -0
  98. package/dist/html/widgets.js +700 -0
  99. package/dist/html/widgets.js.map +1 -0
  100. package/dist/html/woff2.d.ts +14 -0
  101. package/dist/html/woff2.d.ts.map +1 -0
  102. package/dist/html/woff2.js +608 -0
  103. package/dist/html/woff2.js.map +1 -0
  104. package/dist/index.d.ts +1 -1
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js.map +1 -1
  107. package/dist/maps/controller.d.ts +17 -1
  108. package/dist/maps/controller.d.ts.map +1 -1
  109. package/dist/maps/controller.js +27 -7
  110. package/dist/maps/controller.js.map +1 -1
  111. package/dist/richtext/node.d.ts +8 -1
  112. package/dist/richtext/node.d.ts.map +1 -1
  113. package/dist/richtext/node.js +5 -1
  114. package/dist/richtext/node.js.map +1 -1
  115. package/dist/richtext/runs.d.ts +25 -6
  116. package/dist/richtext/runs.d.ts.map +1 -1
  117. package/dist/richtext/runs.js +240 -24
  118. package/dist/richtext/runs.js.map +1 -1
  119. package/package.json +2 -2
  120. package/src/html/controls.ts +114 -51
  121. package/src/html/css/cascade.ts +614 -140
  122. package/src/html/css/color.ts +29 -0
  123. package/src/html/css/parse.ts +388 -33
  124. package/src/html/css/style.ts +688 -150
  125. package/src/html/css/transform.ts +459 -0
  126. package/src/html/css/ua.ts +77 -10
  127. package/src/html/css/values.ts +4 -0
  128. package/src/html/dom.ts +3 -1
  129. package/src/html/fonts.ts +417 -24
  130. package/src/html/form.ts +962 -0
  131. package/src/html/index.ts +49 -338
  132. package/src/html/layout/axes.ts +62 -0
  133. package/src/html/layout/block.ts +447 -37
  134. package/src/html/layout/boxes.ts +143 -14
  135. package/src/html/layout/cache.ts +6 -0
  136. package/src/html/layout/css-grid.ts +95 -22
  137. package/src/html/layout/flex.ts +675 -78
  138. package/src/html/layout/inline.ts +159 -55
  139. package/src/html/layout/multicol.ts +977 -0
  140. package/src/html/layout/shaping.ts +27 -18
  141. package/src/html/layout/table.ts +62 -19
  142. package/src/html/node.ts +798 -172
  143. package/src/html/paint.ts +1742 -241
  144. package/src/html/svg.ts +277 -19
  145. package/src/html/widgets.ts +821 -0
  146. package/src/html/woff2.ts +612 -0
  147. package/src/index.ts +1 -0
  148. package/src/maps/controller.ts +28 -7
  149. package/src/richtext/node.ts +13 -2
  150. package/src/richtext/runs.ts +313 -35
@@ -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
- /** 1px rule through the x-height, in this colour — `~~del~~`. */
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
  );
@@ -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 underline = rules !== 'over' ? span.underline : undefined;
200
- const strike = rules !== 'under' ? span.strike : undefined;
201
- if (underline) {
202
- ctx.fillStyle = underline;
203
- underlineRule(
204
- ctx,
205
- Math.round(dx + line.x + r.x),
206
- Math.round(dy + line.baseline + (span.underlineOffset ?? 2 * t)),
207
- Math.ceil(r.width),
208
- span.underlineStyle ?? 'single',
209
- span.underlineThickness === undefined
210
- ? t
211
- : Math.max(1, Math.round(span.underlineThickness)),
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 (strike) {
215
- const m = inkExtent(r, line);
216
- ctx.fillStyle = strike;
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 the pattern at
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 + 2 * t, width, t);
444
+ ctx.fillRect(x, y + t + gap, width, t);
255
445
  return;
256
446
  case 'dotted':
257
- for (let i = 0; i < width; i += 2 * t) {
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
- for (let i = 0; i < width; i += 6 * t)
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;