@dontfeedmachines/fieldkit 0.0.0-stage → 0.4.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/LICENSE +203 -0
  3. package/NOTICE +131 -0
  4. package/README.md +12 -2
  5. package/css/base.css +150 -0
  6. package/package.json +43 -3
  7. package/pouches/sutures/astro/AnsiLogo.astro +51 -0
  8. package/pouches/sutures/astro/Barcode.astro +19 -0
  9. package/pouches/sutures/astro/BomTable.astro +44 -0
  10. package/pouches/sutures/astro/Breadcrumb.astro +56 -0
  11. package/pouches/sutures/astro/BuildStep.astro +42 -0
  12. package/pouches/sutures/astro/BuildSteps.astro +63 -0
  13. package/pouches/sutures/astro/Button.astro +5 -0
  14. package/pouches/sutures/astro/CTA.astro +58 -0
  15. package/pouches/sutures/astro/Difficulty.astro +27 -0
  16. package/pouches/sutures/astro/Footer.astro +80 -0
  17. package/pouches/sutures/astro/Glossary.astro +64 -0
  18. package/pouches/sutures/astro/Grid.astro +54 -0
  19. package/pouches/sutures/astro/Header.astro +270 -0
  20. package/pouches/sutures/astro/Hero.astro +38 -0
  21. package/pouches/sutures/astro/ImageMod.astro +66 -0
  22. package/pouches/sutures/astro/InteractiveCircuit.astro +192 -0
  23. package/pouches/sutures/astro/LegalWarning.astro +18 -0
  24. package/pouches/sutures/astro/MobileMenuToggle.astro +105 -0
  25. package/pouches/sutures/astro/PageFrame.astro +166 -0
  26. package/pouches/sutures/astro/Search.astro +541 -0
  27. package/pouches/sutures/astro/Section.astro +14 -0
  28. package/pouches/sutures/astro/SidebarNav.astro +106 -0
  29. package/pouches/sutures/astro/SidebarRestorePoint.astro +10 -0
  30. package/pouches/sutures/astro/SidebarSublist.astro +257 -0
  31. package/pouches/sutures/astro/SiteTitle.astro +65 -0
  32. package/pouches/sutures/astro/SpecTable.astro +64 -0
  33. package/pouches/sutures/astro/TeletextChar.astro +51 -0
  34. package/pouches/sutures/astro/ThemeSwitch.astro +93 -0
  35. package/pouches/sutures/astro/ThreatLevel.astro +77 -0
  36. package/pouches/sutures/astro/ThreatModel.astro +32 -0
  37. package/pouches/sutures/astro/TwoColumnContent.astro +84 -0
  38. package/pouches/sutures/astro/Win.astro +13 -0
  39. package/pouches/sutures/astro/index.ts +21 -0
  40. package/src/elements.ts +404 -0
  41. package/src/lib/scrapeview.ts +61 -0
  42. package/src/lib/seal.ts +157 -0
  43. package/src/lib/teletext-chars.ts +723 -0
  44. package/src/node/astro.ts +121 -0
  45. package/src/node/registry.ts +221 -0
  46. package/src/node/seal.ts +7 -0
  47. package/src/wiki/ansi.ts +15 -0
  48. package/src/wiki/config.ts +5 -0
  49. package/src/wiki/teletext.ts +21 -0
  50. package/src/wiki/textConverter.ts +16 -0
@@ -0,0 +1,723 @@
1
+ // crisp 9x9 pixel glyphs + 2x3 teletext G1 mosaics for fonts missing unicode symbols
2
+
3
+ export type TeletextCharOpts = {
4
+ size?: string;
5
+ label?: string;
6
+ className?: string;
7
+ title?: string;
8
+ };
9
+
10
+ export type TeletextMosaicOpts = TeletextCharOpts & {
11
+ separated?: boolean;
12
+ };
13
+
14
+ export type Rect = { x: number; y: number; w: number; h: number };
15
+
16
+ export type GlyphSpec = {
17
+ w: number;
18
+ h: number;
19
+ rects: Rect[];
20
+ d: string;
21
+ };
22
+
23
+ const BITMAPS = {
24
+ external: [
25
+ ".....####",
26
+ ".......##",
27
+ "####..#.#",
28
+ "#....#..#",
29
+ "#...#....",
30
+ "#.......#",
31
+ "#.......#",
32
+ "#.......#",
33
+ "#########",
34
+ ],
35
+ "arrow-ne": [
36
+ ".........",
37
+ "...#####.",
38
+ "......##.",
39
+ ".....#.#.",
40
+ "....#..#.",
41
+ "...#...#.",
42
+ "..#......",
43
+ ".#.......",
44
+ ".........",
45
+ ],
46
+ "arrow-nw": [
47
+ ".........",
48
+ ".#####...",
49
+ ".##......",
50
+ ".#.#.....",
51
+ ".#..#....",
52
+ ".#...#...",
53
+ "......#..",
54
+ ".......#.",
55
+ ".........",
56
+ ],
57
+ "arrow-se": [
58
+ ".........",
59
+ ".#.......",
60
+ "..#......",
61
+ "...#...#.",
62
+ "....#..#.",
63
+ ".....#.#.",
64
+ "......##.",
65
+ "...#####.",
66
+ ".........",
67
+ ],
68
+ "arrow-sw": [
69
+ ".........",
70
+ ".......#.",
71
+ "......#..",
72
+ ".#...#...",
73
+ ".#..#....",
74
+ ".#.#.....",
75
+ ".##......",
76
+ ".#####...",
77
+ ".........",
78
+ ],
79
+ "arrow-up": [
80
+ "....#....",
81
+ "...###...",
82
+ "..#.#.#..",
83
+ ".#..#..#.",
84
+ "....#....",
85
+ "....#....",
86
+ "....#....",
87
+ "....#....",
88
+ "....#....",
89
+ ],
90
+ "arrow-down": [
91
+ "....#....",
92
+ "....#....",
93
+ "....#....",
94
+ "....#....",
95
+ "....#....",
96
+ ".#..#..#.",
97
+ "..#.#.#..",
98
+ "...###...",
99
+ "....#....",
100
+ ],
101
+ "arrow-left": [
102
+ ".........",
103
+ "...#.....",
104
+ "..#......",
105
+ ".#.......",
106
+ "#########",
107
+ ".#.......",
108
+ "..#......",
109
+ "...#.....",
110
+ ".........",
111
+ ],
112
+ "arrow-right": [
113
+ ".........",
114
+ ".....#...",
115
+ "......#..",
116
+ ".......#.",
117
+ "#########",
118
+ ".......#.",
119
+ "......#..",
120
+ ".....#...",
121
+ ".........",
122
+ ],
123
+ "corner-ur": [
124
+ ".....#...",
125
+ "....###..",
126
+ "...#.#.#.",
127
+ ".....#...",
128
+ ".....#...",
129
+ ".....#...",
130
+ "....#....",
131
+ ".###.....",
132
+ ".........",
133
+ ],
134
+ return: [
135
+ ".......#.",
136
+ ".......#.",
137
+ ".......#.",
138
+ "..#....#.",
139
+ ".#.....#.",
140
+ "########.",
141
+ ".#.......",
142
+ "..#......",
143
+ ".........",
144
+ ],
145
+ "chevron-right": [
146
+ ".........",
147
+ "..##.....",
148
+ "...##....",
149
+ "....##...",
150
+ ".....##..",
151
+ "....##...",
152
+ "...##....",
153
+ "..##.....",
154
+ ".........",
155
+ ],
156
+ "chevron-left": [
157
+ ".........",
158
+ ".....##..",
159
+ "....##...",
160
+ "...##....",
161
+ "..##.....",
162
+ "...##....",
163
+ "....##...",
164
+ ".....##..",
165
+ ".........",
166
+ ],
167
+ "chevron-up": [
168
+ ".........",
169
+ ".........",
170
+ "....#....",
171
+ "...###...",
172
+ "..##.##..",
173
+ ".##...##.",
174
+ ".........",
175
+ ".........",
176
+ ".........",
177
+ ],
178
+ "chevron-down": [
179
+ ".........",
180
+ ".........",
181
+ ".##...##.",
182
+ "..##.##..",
183
+ "...###...",
184
+ "....#....",
185
+ ".........",
186
+ ".........",
187
+ ".........",
188
+ ],
189
+ check: [
190
+ ".........",
191
+ "........#",
192
+ ".......##",
193
+ "......##.",
194
+ ".#...##..",
195
+ ".##.##...",
196
+ "..###....",
197
+ "...#.....",
198
+ ".........",
199
+ ],
200
+ cross: [
201
+ ".........",
202
+ ".#.....#.",
203
+ "..#...#..",
204
+ "...#.#...",
205
+ "....#....",
206
+ "...#.#...",
207
+ "..#...#..",
208
+ ".#.....#.",
209
+ ".........",
210
+ ],
211
+ plus: [
212
+ ".........",
213
+ "....#....",
214
+ "....#....",
215
+ "....#....",
216
+ ".#######.",
217
+ "....#....",
218
+ "....#....",
219
+ "....#....",
220
+ ".........",
221
+ ],
222
+ minus: [
223
+ ".........",
224
+ ".........",
225
+ ".........",
226
+ ".........",
227
+ ".#######.",
228
+ ".........",
229
+ ".........",
230
+ ".........",
231
+ ".........",
232
+ ],
233
+ bullet: [
234
+ ".........",
235
+ ".........",
236
+ "...###...",
237
+ "..#####..",
238
+ "..#####..",
239
+ "..#####..",
240
+ "...###...",
241
+ ".........",
242
+ ".........",
243
+ ],
244
+ dot: [
245
+ ".........",
246
+ ".........",
247
+ ".........",
248
+ "...###...",
249
+ "...###...",
250
+ "...###...",
251
+ ".........",
252
+ ".........",
253
+ ".........",
254
+ ],
255
+ ring: [
256
+ ".........",
257
+ "...###...",
258
+ "..#...#..",
259
+ ".#.....#.",
260
+ ".#.....#.",
261
+ ".#.....#.",
262
+ "..#...#..",
263
+ "...###...",
264
+ ".........",
265
+ ],
266
+ square: [
267
+ ".........",
268
+ ".#######.",
269
+ ".#######.",
270
+ ".#######.",
271
+ ".#######.",
272
+ ".#######.",
273
+ ".#######.",
274
+ ".#######.",
275
+ ".........",
276
+ ],
277
+ box: [
278
+ ".........",
279
+ ".#######.",
280
+ ".#.....#.",
281
+ ".#.....#.",
282
+ ".#.....#.",
283
+ ".#.....#.",
284
+ ".#.....#.",
285
+ ".#######.",
286
+ ".........",
287
+ ],
288
+ diamond: [
289
+ "....#....",
290
+ "...###...",
291
+ "..#####..",
292
+ ".#######.",
293
+ "#########",
294
+ ".#######.",
295
+ "..#####..",
296
+ "...###...",
297
+ "....#....",
298
+ ],
299
+ "diamond-open": [
300
+ "....#....",
301
+ "...#.#...",
302
+ "..#...#..",
303
+ ".#.....#.",
304
+ "#.......#",
305
+ ".#.....#.",
306
+ "..#...#..",
307
+ "...#.#...",
308
+ "....#....",
309
+ ],
310
+ warn: [
311
+ "....#....",
312
+ "...#.#...",
313
+ "...#.#...",
314
+ "..#.#.#..",
315
+ "..#.#.#..",
316
+ ".#..#..#.",
317
+ ".#.....#.",
318
+ "#...#...#",
319
+ "#########",
320
+ ],
321
+ info: [
322
+ ".#######.",
323
+ "#.......#",
324
+ "#...#...#",
325
+ "#.......#",
326
+ "#...#...#",
327
+ "#...#...#",
328
+ "#...#...#",
329
+ "#.......#",
330
+ ".#######.",
331
+ ],
332
+ lock: [
333
+ "...###...",
334
+ "..#...#..",
335
+ "..#...#..",
336
+ ".#######.",
337
+ ".#######.",
338
+ ".###.###.",
339
+ ".###.###.",
340
+ ".#######.",
341
+ ".........",
342
+ ],
343
+ unlock: [
344
+ "...###...",
345
+ "..#...#..",
346
+ "......#..",
347
+ ".#######.",
348
+ ".#######.",
349
+ ".###.###.",
350
+ ".###.###.",
351
+ ".#######.",
352
+ ".........",
353
+ ],
354
+ key: [
355
+ "..###....",
356
+ ".#...#...",
357
+ ".#...#...",
358
+ "..###....",
359
+ "...#.....",
360
+ "...#.....",
361
+ "...###...",
362
+ "...#.....",
363
+ "...###...",
364
+ ],
365
+ rss: [
366
+ "####.....",
367
+ "....##...",
368
+ "......#..",
369
+ "###....#.",
370
+ "...#....#",
371
+ "....#...#",
372
+ "##...#..#",
373
+ "##...#..#",
374
+ ".........",
375
+ ],
376
+ search: [
377
+ "..####...",
378
+ ".#....#..",
379
+ "#......#.",
380
+ "#......#.",
381
+ "#......#.",
382
+ ".#....#..",
383
+ "..#####..",
384
+ ".......#.",
385
+ "........#",
386
+ ],
387
+ clock: [
388
+ "..#####..",
389
+ ".#.....#.",
390
+ "#...#...#",
391
+ "#...#...#",
392
+ "#...###.#",
393
+ "#.......#",
394
+ "#.......#",
395
+ ".#.....#.",
396
+ "..#####..",
397
+ ],
398
+ eye: [
399
+ ".........",
400
+ "..#####..",
401
+ ".#.....#.",
402
+ "#..###..#",
403
+ "#..#.#..#",
404
+ "#..###..#",
405
+ ".#.....#.",
406
+ "..#####..",
407
+ ".........",
408
+ ],
409
+ bell: [
410
+ "....#....",
411
+ "...###...",
412
+ "..#...#..",
413
+ "..#...#..",
414
+ "..#...#..",
415
+ ".#.....#.",
416
+ "#########",
417
+ "....#....",
418
+ ".........",
419
+ ],
420
+ copy: [
421
+ "...######",
422
+ "...#....#",
423
+ "...#....#",
424
+ "######..#",
425
+ "#....#..#",
426
+ "#....####",
427
+ "#....#...",
428
+ "#....#...",
429
+ "######...",
430
+ ],
431
+ download: [
432
+ "....#....",
433
+ "....#....",
434
+ "..#.#.#..",
435
+ "...###...",
436
+ "....#....",
437
+ ".........",
438
+ "#.......#",
439
+ "#.......#",
440
+ "#########",
441
+ ],
442
+ star: [
443
+ "....#....",
444
+ "....#....",
445
+ "..#####..",
446
+ "#########",
447
+ "..#####..",
448
+ "..##.##..",
449
+ ".#.....#.",
450
+ "#.......#",
451
+ ".........",
452
+ ],
453
+ bolt: [
454
+ ".....#...",
455
+ "....#....",
456
+ "...#.....",
457
+ "..######.",
458
+ "....#....",
459
+ "...#.....",
460
+ "..#......",
461
+ ".#.......",
462
+ ".........",
463
+ ],
464
+ home: [
465
+ "....#....",
466
+ "...###...",
467
+ "..#####..",
468
+ ".#.....#.",
469
+ "#########",
470
+ ".#.....#.",
471
+ ".#.###.#.",
472
+ ".#.###.#.",
473
+ ".#######.",
474
+ ],
475
+ link: [
476
+ "....####.",
477
+ "...#....#",
478
+ "...#..#.#",
479
+ "..####..#",
480
+ "....#....",
481
+ "#..####..",
482
+ "#.#..#...",
483
+ "#....#...",
484
+ ".####....",
485
+ ],
486
+ play: [
487
+ "..#......",
488
+ "..##.....",
489
+ "..###....",
490
+ "..####...",
491
+ "..#####..",
492
+ "..####...",
493
+ "..###....",
494
+ "..##.....",
495
+ "..#......",
496
+ ],
497
+ pause: [
498
+ ".........",
499
+ "..##.##..",
500
+ "..##.##..",
501
+ "..##.##..",
502
+ "..##.##..",
503
+ "..##.##..",
504
+ "..##.##..",
505
+ "..##.##..",
506
+ ".........",
507
+ ],
508
+ stop: [
509
+ ".........",
510
+ ".#######.",
511
+ ".#######.",
512
+ ".#######.",
513
+ ".#######.",
514
+ ".#######.",
515
+ ".#######.",
516
+ ".#######.",
517
+ ".........",
518
+ ],
519
+ ellipsis: [
520
+ ".........",
521
+ ".........",
522
+ ".........",
523
+ ".........",
524
+ ".........",
525
+ ".........",
526
+ ".#..#..#.",
527
+ ".#..#..#.",
528
+ ".........",
529
+ ],
530
+ } as const;
531
+
532
+ export type TeletextCharName = keyof typeof BITMAPS;
533
+
534
+ export const TT_CHAR_NAMES = Object.keys(BITMAPS) as TeletextCharName[];
535
+
536
+ const ALIASES: Record<string, TeletextCharName> = {
537
+ ext: "external",
538
+ outbound: "external",
539
+ "↗": "arrow-ne",
540
+ "↖": "arrow-nw",
541
+ "↘": "arrow-se",
542
+ "↙": "arrow-sw",
543
+ "↑": "arrow-up",
544
+ "↓": "arrow-down",
545
+ "←": "arrow-left",
546
+ "→": "arrow-right",
547
+ "⤴": "corner-ur",
548
+ "↵": "return",
549
+ "›": "chevron-right",
550
+ "‹": "chevron-left",
551
+ "⌃": "chevron-up",
552
+ "⌄": "chevron-down",
553
+ "✓": "check",
554
+ "✔": "check",
555
+ "✕": "cross",
556
+ "✖": "cross",
557
+ "✗": "cross",
558
+ "×": "cross",
559
+ "+": "plus",
560
+ "−": "minus",
561
+ "•": "bullet",
562
+ "·": "dot",
563
+ "○": "ring",
564
+ "■": "square",
565
+ "□": "box",
566
+ "◆": "diamond",
567
+ "◇": "diamond-open",
568
+ "⚠": "warn",
569
+ "ℹ": "info",
570
+ "🔒": "lock",
571
+ "🔓": "unlock",
572
+ "🔑": "key",
573
+ "🔍": "search",
574
+ "🕒": "clock",
575
+ "👁": "eye",
576
+ "🔔": "bell",
577
+ "★": "star",
578
+ "⚡": "bolt",
579
+ "▶": "play",
580
+ "⏸": "pause",
581
+ "⏹": "stop",
582
+ "…": "ellipsis",
583
+ };
584
+
585
+ function mergeRows(rows: readonly string[]): Rect[] {
586
+ const rects: Rect[] = [];
587
+ for (let y = 0; y < rows.length; y++) {
588
+ const row = rows[y];
589
+ let x = 0;
590
+ while (x < row.length) {
591
+ if (row[x] !== "#") {
592
+ x++;
593
+ continue;
594
+ }
595
+ const start = x;
596
+ while (x < row.length && row[x] === "#") x++;
597
+ rects.push({ x: start, y, w: x - start, h: 1 });
598
+ }
599
+ }
600
+ return rects;
601
+ }
602
+
603
+ function rectsToPath(rects: readonly Rect[]): string {
604
+ return rects.map((r) => `M${r.x} ${r.y}h${r.w}v${r.h}h-${r.w}z`).join("");
605
+ }
606
+
607
+ const CACHE = new Map<TeletextCharName, GlyphSpec>();
608
+
609
+ export function resolveTeletextChar(nameOrChar: string): TeletextCharName | null {
610
+ if (nameOrChar in BITMAPS) return nameOrChar as TeletextCharName;
611
+ const trimmed = nameOrChar.trim();
612
+ if (trimmed in BITMAPS) return trimmed as TeletextCharName;
613
+ return ALIASES[trimmed] ?? ALIASES[trimmed.toLowerCase()] ?? null;
614
+ }
615
+
616
+ export function ttCharSpec(nameOrChar: string): GlyphSpec | null {
617
+ const name = resolveTeletextChar(nameOrChar);
618
+ if (!name) return null;
619
+ let spec = CACHE.get(name);
620
+ if (!spec) {
621
+ const rows = BITMAPS[name];
622
+ const rects = mergeRows(rows);
623
+ spec = { w: rows[0].length, h: rows.length, rects, d: rectsToPath(rects) };
624
+ CACHE.set(name, spec);
625
+ }
626
+ return spec;
627
+ }
628
+
629
+ function escAttr(s: string): string {
630
+ return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
631
+ }
632
+
633
+ function escHtml(s: string): string {
634
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
635
+ }
636
+
637
+ function buildSvg(spec: GlyphSpec, wCss: string, hCss: string, opts: TeletextCharOpts = {}): string {
638
+ const cls = opts.className ? `tt-char ${opts.className}` : "tt-char";
639
+ const a11y = opts.label
640
+ ? `role="img" aria-label="${escAttr(opts.label)}"`
641
+ : `aria-hidden="true"`;
642
+ const title = opts.title ? `<title>${escHtml(opts.title)}</title>` : "";
643
+ const style = `display:inline-block;width:${wCss};height:${hCss};vertical-align:calc(0.5cap - (${hCss}) / 2 + var(--tt-char-voffset,-0.02em));flex:none`;
644
+ return `<svg xmlns="http://www.w3.org/2000/svg" class="${escAttr(cls)}" viewBox="0 0 ${spec.w} ${spec.h}" fill="currentColor" shape-rendering="crispEdges" style="${escAttr(style)}" ${a11y}>${title}<path d="${spec.d}"/></svg>`;
645
+ }
646
+
647
+ export function ttCharSvg(nameOrChar: string, opts: TeletextCharOpts = {}): string {
648
+ const spec = ttCharSpec(nameOrChar);
649
+ if (!spec) return escHtml(nameOrChar);
650
+ const size = opts.size ?? "var(--tt-char-size,0.85em)";
651
+ return buildSvg(spec, size, size, opts);
652
+ }
653
+
654
+ export function ttCharDataUri(nameOrChar: string): string | null {
655
+ const spec = ttCharSpec(nameOrChar);
656
+ if (!spec) return null;
657
+ const svg = `<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 ${spec.w} ${spec.h}' fill='black' shape-rendering='crispEdges'><path d='${spec.d}'/></svg>`;
658
+ return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
659
+ }
660
+
661
+ // unicode sextants skip 0 [space], 21 [left half], 42 [right half], 63 [full block]
662
+ export function sextantToMask(input: number | string): number | null {
663
+ if (typeof input === "number") return Number.isInteger(input) && input >= 0 && input <= 63 ? input : null;
664
+ if (!input) return null;
665
+ if (input === " ") return 0;
666
+ const trimmed = input.trim();
667
+ if (/^(?:0|[1-9]\d*|0x[0-9a-f]+)$/i.test(trimmed)) {
668
+ const n = trimmed.startsWith("0x") || trimmed.startsWith("0X") ? parseInt(trimmed, 16) : Number(trimmed);
669
+ return Number.isInteger(n) && n >= 0 && n <= 63 ? n : null;
670
+ }
671
+ if (input === "▌") return 0b010101;
672
+ if (input === "▐") return 0b101010;
673
+ if (input === "█") return 0b111111;
674
+ if (input === "▀") return 0b000011;
675
+ if (input === "▄") return 0b110000;
676
+ const cp = input.codePointAt(0);
677
+ if (cp === undefined || cp < 0x1fb00 || cp > 0x1fb3b) return null;
678
+ const idx = cp - 0x1fb00;
679
+ let mask = idx + 1;
680
+ if (mask >= 21) mask++;
681
+ if (mask >= 42) mask++;
682
+ return mask;
683
+ }
684
+
685
+ export function ttMosaicSpec(maskOrChar: number | string, separated = false): GlyphSpec | null {
686
+ const mask = sextantToMask(maskOrChar);
687
+ if (mask === null) return null;
688
+ // 12x18 gives a 2:3 teletext cell; separated shrinks each 6x6 sixel to 4x4 inset by 1
689
+ const cw = 6;
690
+ const ch = 6;
691
+ const inset = separated ? 1 : 0;
692
+ const rects: Rect[] = [];
693
+ for (let bit = 0; bit < 6; bit++) {
694
+ if ((mask & (1 << bit)) === 0) continue;
695
+ const col = bit & 1;
696
+ const row = bit >> 1;
697
+ rects.push({
698
+ x: col * cw + inset,
699
+ y: row * ch + inset,
700
+ w: cw - inset * 2,
701
+ h: ch - inset * 2,
702
+ });
703
+ }
704
+ return { w: 12, h: 18, rects, d: rectsToPath(rects) };
705
+ }
706
+
707
+ export function ttMosaicSvg(maskOrChar: number | string, opts: TeletextMosaicOpts = {}): string {
708
+ const spec = ttMosaicSpec(maskOrChar, Boolean(opts.separated));
709
+ if (!spec) return typeof maskOrChar === "string" ? escHtml(maskOrChar) : "";
710
+ const h = opts.size ?? "var(--tt-mosaic-height,1em)";
711
+ const w = `calc((${h}) * 2 / 3)`;
712
+ return buildSvg(spec, w, h, { ...opts, className: opts.className ? `tt-mosaic ${opts.className}` : "tt-mosaic" });
713
+ }
714
+
715
+ export function ttReplaceText(text: string, opts: TeletextCharOpts = {}): string {
716
+ let out = "";
717
+ for (const ch of text) {
718
+ if (ttCharSpec(ch)) out += ttCharSvg(ch, opts);
719
+ else if (sextantToMask(ch) !== null && ch !== " ") out += ttMosaicSvg(ch, opts);
720
+ else out += escHtml(ch);
721
+ }
722
+ return out;
723
+ }