buninu-linux 0.3.0 → 0.4.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 (75) hide show
  1. package/LICENSES/Apache-2.0.txt +202 -0
  2. package/LICENSES/BSD-3-Clause-Skia.txt +29 -0
  3. package/LICENSES/Bitstream-Vera.txt +44 -0
  4. package/LICENSES/MIT-xterm.js.txt +21 -0
  5. package/LICENSES/OFL-1.1.txt +91 -0
  6. package/NOTICE.md +18 -1
  7. package/README.md +226 -22
  8. package/fetch-alpine.sh +9 -4
  9. package/graphics.md +213 -0
  10. package/initramfs/bin/bunterm +66 -0
  11. package/initramfs/bin/ip +2 -1
  12. package/initramfs/bin/mount +2 -1
  13. package/initramfs/bin/poweroff +6 -27
  14. package/initramfs/bin/ps +256 -0
  15. package/initramfs/bin/reboot +10 -0
  16. package/initramfs/bin/tar +169 -0
  17. package/initramfs/bin/top +129 -0
  18. package/initramfs/bin/umount +2 -1
  19. package/initramfs/buninu/CHANGELOG.md +40 -0
  20. package/initramfs/buninu/apps/bunmsh/CHANGELOG.md +36 -0
  21. package/initramfs/buninu/apps/bunmsh/README.md +7 -0
  22. package/initramfs/buninu/apps/bunmsh/package.json +1 -1
  23. package/initramfs/buninu/apps/bunmsh/src/fancy-ls.js +29 -14
  24. package/initramfs/buninu/apps/bunmsh/src/line-edit.js +43 -0
  25. package/initramfs/buninu/apps/bunmsh/src/main.js +15 -1
  26. package/initramfs/buninu/apps/bunmsh/src/mouse.js +29 -4
  27. package/initramfs/buninu/apps/bunmsh/src/shell.js +9 -1
  28. package/initramfs/buninu/apps/bunmsh/test/shell1.test.js +70 -0
  29. package/initramfs/buninu/apps/jsgotty/gotty.js +4 -3
  30. package/initramfs/buninu/apps/jsgotty/package.json +1 -1
  31. package/initramfs/buninu/apps/jsmdcui/CHANGELOG.md +10 -0
  32. package/initramfs/buninu/apps/jsmdcui/README.md +2 -0
  33. package/initramfs/buninu/apps/jsmdcui/package.json +1 -1
  34. package/initramfs/buninu/apps/jsmdcui/runtime/help/commands.md +4 -1
  35. package/initramfs/buninu/apps/jsmdcui/runtime/help/help.md +2 -0
  36. package/initramfs/buninu/apps/jsmdcui/src/index.js +40 -13
  37. package/initramfs/buninu/package.json +1 -1
  38. package/initramfs/init.js +6 -0
  39. package/initramfs/lib/bunterm/glyphs.js +446 -0
  40. package/initramfs/lib/bunterm/images.js +225 -0
  41. package/initramfs/lib/bunterm/input.js +140 -0
  42. package/initramfs/lib/bunterm/renderer.js +424 -0
  43. package/initramfs/lib/bunterm/terminal.js +203 -0
  44. package/initramfs/lib/canvas.js +181 -0
  45. package/initramfs/lib/canvaskit/LICENSE +29 -0
  46. package/initramfs/lib/canvaskit/canvaskit.js +264 -0
  47. package/initramfs/lib/canvaskit/canvaskit.wasm +0 -0
  48. package/initramfs/lib/dlopen.js +14 -11
  49. package/initramfs/lib/document.js +6 -0
  50. package/initramfs/lib/fbdev.js +581 -0
  51. package/initramfs/lib/power.js +35 -0
  52. package/initramfs/lib/xterm/LICENSE +21 -0
  53. package/initramfs/lib/xterm/addon-unicode-graphemes.mjs +44 -0
  54. package/initramfs/lib/xterm/xterm-headless.mjs +46 -0
  55. package/initramfs/usr/share/doc/buninu-linux/bunterm.md +83 -0
  56. package/initramfs/usr/share/doc/buninu-linux/canvas.md +91 -0
  57. package/initramfs/usr/share/doc/buninu-linux/fbdev.md +113 -0
  58. package/initramfs/usr/share/doc/buninu-linux/ps.md +94 -0
  59. package/initramfs/usr/share/doc/buninu-linux/reboot.md +15 -0
  60. package/initramfs/usr/share/doc/buninu-linux/tar.md +73 -0
  61. package/initramfs/usr/share/doc/buninu-linux/top.md +23 -0
  62. package/initramfs/usr/share/fonts/DejaVuSansMono-Bold.ttf +0 -0
  63. package/initramfs/usr/share/fonts/DejaVuSansMono.ttf +0 -0
  64. package/initramfs/usr/share/fonts/LICENSE-Apache-2.0.txt +202 -0
  65. package/initramfs/usr/share/fonts/LICENSE-DejaVu.txt +44 -0
  66. package/initramfs/usr/share/fonts/LICENSE-OFL-1.1.txt +91 -0
  67. package/initramfs/usr/share/fonts/NotoColorEmoji.ttf +0 -0
  68. package/initramfs/usr/share/fonts/NotoColorEmojiFlags.ttf +0 -0
  69. package/initramfs/usr/share/fonts/NotoSansCJK-Regular.ttc +0 -0
  70. package/initramfs/usr/share/fonts/NotoSansSymbols-Regular-Subsetted.ttf +0 -0
  71. package/initramfs/usr/share/fonts/NotoSansSymbols-Regular-Subsetted2.ttf +0 -0
  72. package/initramfs/usr/share/fonts/Roboto-Regular.ttf +0 -0
  73. package/initramfs/usr/share/fonts/fonts.json +28 -0
  74. package/package.json +2 -2
  75. package/run-qemu.sh +1 -1
@@ -793,6 +793,8 @@ selection, search, and copy remain available.
793
793
  | `Ctrl-F` | Search; use `Ctrl-N` and `Ctrl-P` for the next and previous match. |
794
794
  | `Ctrl-G` | Open the editor help. |
795
795
  | `Alt-G` | Show or hide the shortcut bar. |
796
+ | `Ctrl-W` | Switch to the next pane in the current tab. |
797
+ | `Ctrl-W` twice quickly | Switch to the next tab like `Alt-T`. At the rightmost tab, if every tab contains only terminal panes, create a new editor tab instead of cycling back. |
796
798
  | `Ctrl-Q` or `Alt-Q` | Close the current UI. |
797
799
 
798
800
  To preview another color theme, press `Ctrl-E` or click on `€`, type `theme `, then use `Tab`
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jsmdcui",
3
- "version": "0.19.1",
3
+ "version": "0.19.2",
4
4
  "description": "Markdown as a Common UI for Terminals and Web Browsers",
5
5
  "type": "module",
6
6
  "main": "./src/index.js",
@@ -151,7 +151,10 @@ quotes here but these are not necessary when entering the command in micro.
151
151
 
152
152
  * `term ['exec']`: Open a terminal emulator running the given executable. If no
153
153
  executable is given, this will open the default shell in the terminal
154
- emulator.
154
+ emulator. `Ctrl-W` switches panes in both editor and terminal panes. Press
155
+ it twice quickly to cycle to the next tab like `Alt-T`; at the rightmost
156
+ tab, if every tab is a terminal, this creates a new editor tab instead of
157
+ cycling back.
155
158
 
156
159
  ---
157
160
 
@@ -793,6 +793,8 @@ selection, search, and copy remain available.
793
793
  | `Ctrl-F` | Search; use `Ctrl-N` and `Ctrl-P` for the next and previous match. |
794
794
  | `Ctrl-G` | Open the editor help. |
795
795
  | `Alt-G` | Show or hide the shortcut bar. |
796
+ | `Ctrl-W` | Switch to the next pane in the current tab. |
797
+ | `Ctrl-W` twice quickly | Switch to the next tab like `Alt-T`. At the rightmost tab, if every tab contains only terminal panes, create a new editor tab instead of cycling back. |
796
798
  | `Ctrl-Q` or `Alt-Q` | Close the current UI. |
797
799
 
798
800
  To preview another color theme, press `Ctrl-E` or click on `€`, type `theme `, then use `Tab`
@@ -2796,6 +2796,8 @@ class App {
2796
2796
  this.tabRects = [];
2797
2797
  this._escBuf = null; // pending lone ESC bytes waiting for alt-key combo
2798
2798
  this._escTimer = null;
2799
+ this._ctrlWTime = 0;
2800
+ this._ctrlWTimer = null;
2799
2801
  this._suggestionsRow = null;
2800
2802
  this._suggestionRects = [];
2801
2803
  this._acHScroll = 0;
@@ -3790,7 +3792,7 @@ class App {
3790
3792
  const titleStyle = { ...defaultStyle, reverse: true };
3791
3793
  const scrollMsg = vt && vt.scrollOffset > 0
3792
3794
  ? ` -- SCROLLBACK (${vt.scrollOffset}/${vt.scrollback.length}) wheel↑↓ to browse, any key to return`
3793
- : (isActive ? " [Esc: close Ctrl-W: switch pane]" : "");
3795
+ : (isActive ? " [Esc=close,Ctrl-W/*2=Next Pane/Tab]" : "");
3794
3796
  putText(this.screen, pane.x, pane.y, ` Terminal${scrollMsg}`.padEnd(pane.w), titleStyle, pane.w);
3795
3797
  if (!vt) return;
3796
3798
  const renderRows = pane.h - 1;
@@ -4228,14 +4230,12 @@ class App {
4228
4230
  this.render();
4229
4231
  return;
4230
4232
  }
4231
- // Ctrl-W: switch pane focus without closing
4233
+ // Ctrl-W: switch pane focus without closing. A quick second Ctrl-W
4234
+ // cycles tabs, with an editor-tab escape hatch when every tab is a
4235
+ // terminal and the current one is already the rightmost tab.
4232
4236
  if (text === "\x17") {
4233
- const panes = this.tab.panes();
4234
- if (panes.length > 1) {
4235
- const idx = panes.indexOf(this.tab.activePane);
4236
- this.tab.activePane = panes[(idx + 1) % panes.length];
4237
- this.render();
4238
- }
4237
+ await this.handleCtrlW();
4238
+ this.render();
4239
4239
  return;
4240
4240
  }
4241
4241
 
@@ -4525,11 +4525,7 @@ class App {
4525
4525
  await runAction("AddTab", this);
4526
4526
  break;
4527
4527
  case "ctrl-w": { //switchPane
4528
- const panes = this.tab.panes();
4529
- if (panes.length > 1) {
4530
- const idx = panes.indexOf(this.tab.activePane);
4531
- this.tab.activePane = panes[(idx + 1) % panes.length];
4532
- }
4528
+ await this.handleCtrlW();
4533
4529
  break;
4534
4530
  }
4535
4531
  case "ctrl-y": //redo
@@ -5403,6 +5399,37 @@ class App {
5403
5399
  return this.setActiveTab((this.activeTabIdx + 1) % this.tabs.length);
5404
5400
  }
5405
5401
 
5402
+ _clearCtrlW() {
5403
+ if (this._ctrlWTimer) clearTimeout(this._ctrlWTimer);
5404
+ this._ctrlWTimer = null;
5405
+ this._ctrlWTime = 0;
5406
+ }
5407
+
5408
+ async handleCtrlW() {
5409
+ const now = Date.now();
5410
+ const isDouble = this._ctrlWTime > 0 && now - this._ctrlWTime < 400;
5411
+ this._clearCtrlW();
5412
+
5413
+ if (isDouble) {
5414
+ const atRightmostTab = this.activeTabIdx === this.tabs.length - 1;
5415
+ const allTabsAreTerminal = this.tabs.every((tab) =>
5416
+ tab.panes().length > 0 && tab.panes().every((pane) => pane.type === "term")
5417
+ );
5418
+ if (atRightmostTab && allTabsAreTerminal) await this.addTab();
5419
+ else this.nextTab();
5420
+ return;
5421
+ }
5422
+
5423
+ this._ctrlWTime = now;
5424
+ this._ctrlWTimer = setTimeout(() => this._clearCtrlW(), 400);
5425
+
5426
+ const panes = this.tab.panes();
5427
+ if (panes.length > 1) {
5428
+ const idx = panes.indexOf(this.tab.activePane);
5429
+ this.tab.activePane = panes[(idx + 1) % panes.length];
5430
+ }
5431
+ }
5432
+
5406
5433
  async handlePrompt(text) {
5407
5434
  const key = parseKey(text);
5408
5435
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "buninu",
3
- "version": "0.4.12",
3
+ "version": "0.4.15",
4
4
  "description": "Buninu is a portable, self-bootstrapping Unix-like userspace built on Bun that runs across operating systems",
5
5
  "type": "module",
6
6
  "main": "./bin/init.js",
package/initramfs/init.js CHANGED
@@ -341,6 +341,12 @@ const launchBunmsh = () => {
341
341
 
342
342
  const launchBuninu = () => {
343
343
 
344
+ console.log(`
345
+ Type bunterm in bunmsh: Graphical Terminal
346
+ Shows Emojis, CJK chars
347
+ Supports Kitty Graphics Protocol
348
+ `)
349
+
344
350
  const entry = `/buninu/bin/init.js`;
345
351
  console.log(`buninu: Starting ${entry} --local`);
346
352
  const shell = Bun.spawnSync([
@@ -0,0 +1,446 @@
1
+ // /lib/bunterm/glyphs.js — box drawing, block, shade, Powerline and legacy
2
+ // computing characters drawn as vectors instead of font glyphs, so they meet
3
+ // their neighbours exactly at every cell size.
4
+ //
5
+ // The shape tables are ported from xterm.js's addon-webgl CustomGlyphs.ts
6
+ // (Copyright (c) 2021 The xterm.js authors, MIT License; see /lib/xterm/
7
+ // LICENSE). The SVG-style path strings are kept as they are there; only the
8
+ // drawing side is rewritten for Skia.
9
+
10
+ // Block elements: rectangles on an 8x8 grid of the cell.
11
+ const blockElementDefinitions = {
12
+ "▀": [{ x: 0, y: 0, w: 8, h: 4 }],
13
+ "▁": [{ x: 0, y: 7, w: 8, h: 1 }],
14
+ "▂": [{ x: 0, y: 6, w: 8, h: 2 }],
15
+ "▃": [{ x: 0, y: 5, w: 8, h: 3 }],
16
+ "▄": [{ x: 0, y: 4, w: 8, h: 4 }],
17
+ "▅": [{ x: 0, y: 3, w: 8, h: 5 }],
18
+ "▆": [{ x: 0, y: 2, w: 8, h: 6 }],
19
+ "▇": [{ x: 0, y: 1, w: 8, h: 7 }],
20
+ "█": [{ x: 0, y: 0, w: 8, h: 8 }],
21
+ "▉": [{ x: 0, y: 0, w: 7, h: 8 }],
22
+ "▊": [{ x: 0, y: 0, w: 6, h: 8 }],
23
+ "▋": [{ x: 0, y: 0, w: 5, h: 8 }],
24
+ "▌": [{ x: 0, y: 0, w: 4, h: 8 }],
25
+ "▍": [{ x: 0, y: 0, w: 3, h: 8 }],
26
+ "▎": [{ x: 0, y: 0, w: 2, h: 8 }],
27
+ "▏": [{ x: 0, y: 0, w: 1, h: 8 }],
28
+ "▐": [{ x: 4, y: 0, w: 4, h: 8 }],
29
+ "▔": [{ x: 0, y: 0, w: 8, h: 1 }],
30
+ "▕": [{ x: 7, y: 0, w: 1, h: 8 }],
31
+ "▖": [{ x: 0, y: 4, w: 4, h: 4 }],
32
+ "▗": [{ x: 4, y: 4, w: 4, h: 4 }],
33
+ "▘": [{ x: 0, y: 0, w: 4, h: 4 }],
34
+ "▙": [{ x: 0, y: 0, w: 4, h: 8 }, { x: 0, y: 4, w: 8, h: 4 }],
35
+ "▚": [{ x: 0, y: 0, w: 4, h: 4 }, { x: 4, y: 4, w: 4, h: 4 }],
36
+ "▛": [{ x: 0, y: 0, w: 4, h: 8 }, { x: 4, y: 0, w: 4, h: 4 }],
37
+ "▜": [{ x: 0, y: 0, w: 8, h: 4 }, { x: 4, y: 0, w: 4, h: 8 }],
38
+ "▝": [{ x: 4, y: 0, w: 4, h: 4 }],
39
+ "▞": [{ x: 4, y: 0, w: 4, h: 4 }, { x: 0, y: 4, w: 4, h: 4 }],
40
+ "▟": [{ x: 4, y: 0, w: 4, h: 8 }, { x: 0, y: 4, w: 8, h: 4 }],
41
+ // Legacy computing: vertical and horizontal one eighth blocks
42
+ "\u{1FB70}": [{ x: 1, y: 0, w: 1, h: 8 }],
43
+ "\u{1FB71}": [{ x: 2, y: 0, w: 1, h: 8 }],
44
+ "\u{1FB72}": [{ x: 3, y: 0, w: 1, h: 8 }],
45
+ "\u{1FB73}": [{ x: 4, y: 0, w: 1, h: 8 }],
46
+ "\u{1FB74}": [{ x: 5, y: 0, w: 1, h: 8 }],
47
+ "\u{1FB75}": [{ x: 6, y: 0, w: 1, h: 8 }],
48
+ "\u{1FB76}": [{ x: 0, y: 1, w: 8, h: 1 }],
49
+ "\u{1FB77}": [{ x: 0, y: 2, w: 8, h: 1 }],
50
+ "\u{1FB78}": [{ x: 0, y: 3, w: 8, h: 1 }],
51
+ "\u{1FB79}": [{ x: 0, y: 4, w: 8, h: 1 }],
52
+ "\u{1FB7A}": [{ x: 0, y: 5, w: 8, h: 1 }],
53
+ "\u{1FB7B}": [{ x: 0, y: 6, w: 8, h: 1 }],
54
+ "\u{1FB7C}": [{ x: 0, y: 0, w: 1, h: 8 }, { x: 0, y: 7, w: 8, h: 1 }],
55
+ "\u{1FB7D}": [{ x: 0, y: 0, w: 1, h: 8 }, { x: 0, y: 0, w: 8, h: 1 }],
56
+ "\u{1FB7E}": [{ x: 7, y: 0, w: 1, h: 8 }, { x: 0, y: 0, w: 8, h: 1 }],
57
+ "\u{1FB7F}": [{ x: 7, y: 0, w: 1, h: 8 }, { x: 0, y: 7, w: 8, h: 1 }],
58
+ "\u{1FB80}": [{ x: 0, y: 0, w: 8, h: 1 }, { x: 0, y: 7, w: 8, h: 1 }],
59
+ "\u{1FB81}": [{ x: 0, y: 0, w: 8, h: 1 }, { x: 0, y: 2, w: 8, h: 1 }, { x: 0, y: 4, w: 8, h: 1 }, { x: 0, y: 7, w: 8, h: 1 }],
60
+ "\u{1FB82}": [{ x: 0, y: 0, w: 8, h: 2 }],
61
+ "\u{1FB83}": [{ x: 0, y: 0, w: 8, h: 3 }],
62
+ "\u{1FB84}": [{ x: 0, y: 0, w: 8, h: 5 }],
63
+ "\u{1FB85}": [{ x: 0, y: 0, w: 8, h: 6 }],
64
+ "\u{1FB86}": [{ x: 0, y: 0, w: 8, h: 7 }],
65
+ "\u{1FB87}": [{ x: 6, y: 0, w: 2, h: 8 }],
66
+ "\u{1FB88}": [{ x: 5, y: 0, w: 3, h: 8 }],
67
+ "\u{1FB89}": [{ x: 3, y: 0, w: 5, h: 8 }],
68
+ "\u{1FB8A}": [{ x: 2, y: 0, w: 6, h: 8 }],
69
+ "\u{1FB8B}": [{ x: 1, y: 0, w: 7, h: 8 }],
70
+ "\u{1FB95}": [
71
+ { x: 0, y: 0, w: 2, h: 2 }, { x: 4, y: 0, w: 2, h: 2 },
72
+ { x: 2, y: 2, w: 2, h: 2 }, { x: 6, y: 2, w: 2, h: 2 },
73
+ { x: 0, y: 4, w: 2, h: 2 }, { x: 4, y: 4, w: 2, h: 2 },
74
+ { x: 2, y: 6, w: 2, h: 2 }, { x: 6, y: 6, w: 2, h: 2 },
75
+ ],
76
+ "\u{1FB96}": [
77
+ { x: 2, y: 0, w: 2, h: 2 }, { x: 6, y: 0, w: 2, h: 2 },
78
+ { x: 0, y: 2, w: 2, h: 2 }, { x: 4, y: 2, w: 2, h: 2 },
79
+ { x: 2, y: 4, w: 2, h: 2 }, { x: 6, y: 4, w: 2, h: 2 },
80
+ { x: 0, y: 6, w: 2, h: 2 }, { x: 4, y: 6, w: 2, h: 2 },
81
+ ],
82
+ "\u{1FB97}": [{ x: 0, y: 2, w: 8, h: 2 }, { x: 0, y: 6, w: 8, h: 2 }],
83
+ };
84
+
85
+ // Shade characters: a repeating pixel pattern (1 = filled).
86
+ const patternDefinitions = {
87
+ "░": [[1, 0, 0, 0], [0, 0, 0, 0], [0, 0, 1, 0], [0, 0, 0, 0]],
88
+ "▒": [[1, 0], [0, 0], [0, 1], [0, 0]],
89
+ "▓": [[0, 1], [1, 1], [1, 0], [1, 1]],
90
+ };
91
+
92
+ const Shapes = {
93
+ TOP_TO_BOTTOM: "M.5,0 L.5,1",
94
+ LEFT_TO_RIGHT: "M0,.5 L1,.5",
95
+ TOP_TO_RIGHT: "M.5,0 L.5,.5 L1,.5",
96
+ TOP_TO_LEFT: "M.5,0 L.5,.5 L0,.5",
97
+ LEFT_TO_BOTTOM: "M0,.5 L.5,.5 L.5,1",
98
+ RIGHT_TO_BOTTOM: "M0.5,1 L.5,.5 L1,.5",
99
+ MIDDLE_TO_TOP: "M.5,.5 L.5,0",
100
+ MIDDLE_TO_LEFT: "M.5,.5 L0,.5",
101
+ MIDDLE_TO_RIGHT: "M.5,.5 L1,.5",
102
+ MIDDLE_TO_BOTTOM: "M.5,.5 L.5,1",
103
+ T_TOP: "M0,.5 L1,.5 M.5,.5 L.5,0",
104
+ T_LEFT: "M.5,0 L.5,1 M.5,.5 L0,.5",
105
+ T_RIGHT: "M.5,0 L.5,1 M.5,.5 L1,.5",
106
+ T_BOTTOM: "M0,.5 L1,.5 M.5,.5 L.5,1",
107
+ CROSS: "M0,.5 L1,.5 M.5,0 L.5,1",
108
+ TWO_DASHES_HORIZONTAL: "M.1,.5 L.4,.5 M.6,.5 L.9,.5",
109
+ THREE_DASHES_HORIZONTAL: "M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5",
110
+ FOUR_DASHES_HORIZONTAL: "M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5",
111
+ TWO_DASHES_VERTICAL: "M.5,.1 L.5,.4 M.5,.6 L.5,.9",
112
+ THREE_DASHES_VERTICAL: "M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333",
113
+ FOUR_DASHES_VERTICAL: "M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95",
114
+ };
115
+
116
+ const NORMAL = 1;
117
+ const BOLD = 3;
118
+
119
+ // Box drawing: per line weight, a path string or a function of the double
120
+ // line spacing (xp, yp) that returns one.
121
+ const S = Shapes;
122
+ const boxDrawingDefinitions = {
123
+ "─": { [NORMAL]: S.LEFT_TO_RIGHT },
124
+ "━": { [BOLD]: S.LEFT_TO_RIGHT },
125
+ "│": { [NORMAL]: S.TOP_TO_BOTTOM },
126
+ "┃": { [BOLD]: S.TOP_TO_BOTTOM },
127
+ "┌": { [NORMAL]: S.RIGHT_TO_BOTTOM },
128
+ "┏": { [BOLD]: S.RIGHT_TO_BOTTOM },
129
+ "┐": { [NORMAL]: S.LEFT_TO_BOTTOM },
130
+ "┓": { [BOLD]: S.LEFT_TO_BOTTOM },
131
+ "└": { [NORMAL]: S.TOP_TO_RIGHT },
132
+ "┗": { [BOLD]: S.TOP_TO_RIGHT },
133
+ "┘": { [NORMAL]: S.TOP_TO_LEFT },
134
+ "┛": { [BOLD]: S.TOP_TO_LEFT },
135
+ "├": { [NORMAL]: S.T_RIGHT },
136
+ "┣": { [BOLD]: S.T_RIGHT },
137
+ "┤": { [NORMAL]: S.T_LEFT },
138
+ "┫": { [BOLD]: S.T_LEFT },
139
+ "┬": { [NORMAL]: S.T_BOTTOM },
140
+ "┳": { [BOLD]: S.T_BOTTOM },
141
+ "┴": { [NORMAL]: S.T_TOP },
142
+ "┻": { [BOLD]: S.T_TOP },
143
+ "┼": { [NORMAL]: S.CROSS },
144
+ "╋": { [BOLD]: S.CROSS },
145
+ "╴": { [NORMAL]: S.MIDDLE_TO_LEFT },
146
+ "╸": { [BOLD]: S.MIDDLE_TO_LEFT },
147
+ "╵": { [NORMAL]: S.MIDDLE_TO_TOP },
148
+ "╹": { [BOLD]: S.MIDDLE_TO_TOP },
149
+ "╶": { [NORMAL]: S.MIDDLE_TO_RIGHT },
150
+ "╺": { [BOLD]: S.MIDDLE_TO_RIGHT },
151
+ "╷": { [NORMAL]: S.MIDDLE_TO_BOTTOM },
152
+ "╻": { [BOLD]: S.MIDDLE_TO_BOTTOM },
153
+ // Double
154
+ "═": { [NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` },
155
+ "║": { [NORMAL]: (xp) => `M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` },
156
+ "╒": { [NORMAL]: (xp, yp) => `M.5,1 L.5,${.5 - yp} L1,${.5 - yp} M.5,${.5 + yp} L1,${.5 + yp}` },
157
+ "╓": { [NORMAL]: (xp) => `M${.5 - xp},1 L${.5 - xp},.5 L1,.5 M${.5 + xp},.5 L${.5 + xp},1` },
158
+ "╔": { [NORMAL]: (xp, yp) => `M1,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1` },
159
+ "╕": { [NORMAL]: (xp, yp) => `M0,${.5 - yp} L.5,${.5 - yp} L.5,1 M0,${.5 + yp} L.5,${.5 + yp}` },
160
+ "╖": { [NORMAL]: (xp) => `M${.5 + xp},1 L${.5 + xp},.5 L0,.5 M${.5 - xp},.5 L${.5 - xp},1` },
161
+ "╗": { [NORMAL]: (xp, yp) => `M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M0,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},1` },
162
+ "╘": { [NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 + yp} L1,${.5 + yp} M.5,${.5 - yp} L1,${.5 - yp}` },
163
+ "╙": { [NORMAL]: (xp) => `M1,.5 L${.5 - xp},.5 L${.5 - xp},0 M${.5 + xp},.5 L${.5 + xp},0` },
164
+ "╚": { [NORMAL]: (xp, yp) => `M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0 M1,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},0` },
165
+ "╛": { [NORMAL]: (xp, yp) => `M0,${.5 + yp} L.5,${.5 + yp} L.5,0 M0,${.5 - yp} L.5,${.5 - yp}` },
166
+ "╜": { [NORMAL]: (xp) => `M0,.5 L${.5 + xp},.5 L${.5 + xp},0 M${.5 - xp},.5 L${.5 - xp},0` },
167
+ "╝": { [NORMAL]: (xp, yp) => `M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M0,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},0` },
168
+ "╞": { [NORMAL]: (xp, yp) => `${S.TOP_TO_BOTTOM} M.5,${.5 - yp} L1,${.5 - yp} M.5,${.5 + yp} L1,${.5 + yp}` },
169
+ "╟": { [NORMAL]: (xp) => `M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1 M${.5 + xp},.5 L1,.5` },
170
+ "╠": { [NORMAL]: (xp, yp) => `M${.5 - xp},0 L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` },
171
+ "╡": { [NORMAL]: (xp, yp) => `${S.TOP_TO_BOTTOM} M0,${.5 - yp} L.5,${.5 - yp} M0,${.5 + yp} L.5,${.5 + yp}` },
172
+ "╢": { [NORMAL]: (xp) => `M0,.5 L${.5 - xp},.5 M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` },
173
+ "╣": { [NORMAL]: (xp, yp) => `M${.5 + xp},0 L${.5 + xp},1 M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0` },
174
+ "╤": { [NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp} M.5,${.5 + yp} L.5,1` },
175
+ "╥": { [NORMAL]: (xp) => `${S.LEFT_TO_RIGHT} M${.5 - xp},.5 L${.5 - xp},1 M${.5 + xp},.5 L${.5 + xp},1` },
176
+ "╦": { [NORMAL]: (xp, yp) => `M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1` },
177
+ "╧": { [NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 - yp} M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` },
178
+ "╨": { [NORMAL]: (xp) => `${S.LEFT_TO_RIGHT} M${.5 - xp},.5 L${.5 - xp},0 M${.5 + xp},.5 L${.5 + xp},0` },
179
+ "╩": { [NORMAL]: (xp, yp) => `M0,${.5 + yp} L1,${.5 + yp} M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` },
180
+ "╪": { [NORMAL]: (xp, yp) => `${S.TOP_TO_BOTTOM} M0,${.5 - yp} L1,${.5 - yp} M0,${.5 + yp} L1,${.5 + yp}` },
181
+ "╫": { [NORMAL]: (xp) => `${S.LEFT_TO_RIGHT} M${.5 - xp},0 L${.5 - xp},1 M${.5 + xp},0 L${.5 + xp},1` },
182
+ "╬": { [NORMAL]: (xp, yp) => `M0,${.5 + yp} L${.5 - xp},${.5 + yp} L${.5 - xp},1 M1,${.5 + yp} L${.5 + xp},${.5 + yp} L${.5 + xp},1 M0,${.5 - yp} L${.5 - xp},${.5 - yp} L${.5 - xp},0 M1,${.5 - yp} L${.5 + xp},${.5 - yp} L${.5 + xp},0` },
183
+ // Diagonal
184
+ "╱": { [NORMAL]: "M1,0 L0,1" },
185
+ "╲": { [NORMAL]: "M0,0 L1,1" },
186
+ "╳": { [NORMAL]: "M1,0 L0,1 M0,0 L1,1" },
187
+ // Mixed weight
188
+ "╼": { [NORMAL]: S.MIDDLE_TO_LEFT, [BOLD]: S.MIDDLE_TO_RIGHT },
189
+ "╽": { [NORMAL]: S.MIDDLE_TO_TOP, [BOLD]: S.MIDDLE_TO_BOTTOM },
190
+ "╾": { [NORMAL]: S.MIDDLE_TO_RIGHT, [BOLD]: S.MIDDLE_TO_LEFT },
191
+ "╿": { [NORMAL]: S.MIDDLE_TO_BOTTOM, [BOLD]: S.MIDDLE_TO_TOP },
192
+ "┍": { [NORMAL]: S.MIDDLE_TO_BOTTOM, [BOLD]: S.MIDDLE_TO_RIGHT },
193
+ "┎": { [NORMAL]: S.MIDDLE_TO_RIGHT, [BOLD]: S.MIDDLE_TO_BOTTOM },
194
+ "┑": { [NORMAL]: S.MIDDLE_TO_BOTTOM, [BOLD]: S.MIDDLE_TO_LEFT },
195
+ "┒": { [NORMAL]: S.MIDDLE_TO_LEFT, [BOLD]: S.MIDDLE_TO_BOTTOM },
196
+ "┕": { [NORMAL]: S.MIDDLE_TO_TOP, [BOLD]: S.MIDDLE_TO_RIGHT },
197
+ "┖": { [NORMAL]: S.MIDDLE_TO_RIGHT, [BOLD]: S.MIDDLE_TO_TOP },
198
+ "┙": { [NORMAL]: S.MIDDLE_TO_TOP, [BOLD]: S.MIDDLE_TO_LEFT },
199
+ "┚": { [NORMAL]: S.MIDDLE_TO_LEFT, [BOLD]: S.MIDDLE_TO_TOP },
200
+ "┝": { [NORMAL]: S.TOP_TO_BOTTOM, [BOLD]: S.MIDDLE_TO_RIGHT },
201
+ "┞": { [NORMAL]: S.RIGHT_TO_BOTTOM, [BOLD]: S.MIDDLE_TO_TOP },
202
+ "┟": { [NORMAL]: S.TOP_TO_RIGHT, [BOLD]: S.MIDDLE_TO_BOTTOM },
203
+ "┠": { [NORMAL]: S.MIDDLE_TO_RIGHT, [BOLD]: S.TOP_TO_BOTTOM },
204
+ "┡": { [NORMAL]: S.MIDDLE_TO_BOTTOM, [BOLD]: S.TOP_TO_RIGHT },
205
+ "┢": { [NORMAL]: S.MIDDLE_TO_TOP, [BOLD]: S.RIGHT_TO_BOTTOM },
206
+ "┥": { [NORMAL]: S.TOP_TO_BOTTOM, [BOLD]: S.MIDDLE_TO_LEFT },
207
+ "┦": { [NORMAL]: S.LEFT_TO_BOTTOM, [BOLD]: S.MIDDLE_TO_TOP },
208
+ "┧": { [NORMAL]: S.TOP_TO_LEFT, [BOLD]: S.MIDDLE_TO_BOTTOM },
209
+ "┨": { [NORMAL]: S.MIDDLE_TO_LEFT, [BOLD]: S.TOP_TO_BOTTOM },
210
+ "┩": { [NORMAL]: S.MIDDLE_TO_BOTTOM, [BOLD]: S.TOP_TO_LEFT },
211
+ "┪": { [NORMAL]: S.MIDDLE_TO_TOP, [BOLD]: S.LEFT_TO_BOTTOM },
212
+ "┭": { [NORMAL]: S.RIGHT_TO_BOTTOM, [BOLD]: S.MIDDLE_TO_LEFT },
213
+ "┮": { [NORMAL]: S.LEFT_TO_BOTTOM, [BOLD]: S.MIDDLE_TO_RIGHT },
214
+ "┯": { [NORMAL]: S.MIDDLE_TO_BOTTOM, [BOLD]: S.LEFT_TO_RIGHT },
215
+ "┰": { [NORMAL]: S.LEFT_TO_RIGHT, [BOLD]: S.MIDDLE_TO_BOTTOM },
216
+ "┱": { [NORMAL]: S.MIDDLE_TO_RIGHT, [BOLD]: S.LEFT_TO_BOTTOM },
217
+ "┲": { [NORMAL]: S.MIDDLE_TO_LEFT, [BOLD]: S.RIGHT_TO_BOTTOM },
218
+ "┵": { [NORMAL]: S.TOP_TO_RIGHT, [BOLD]: S.MIDDLE_TO_LEFT },
219
+ "┶": { [NORMAL]: S.TOP_TO_LEFT, [BOLD]: S.MIDDLE_TO_RIGHT },
220
+ "┷": { [NORMAL]: S.MIDDLE_TO_TOP, [BOLD]: S.LEFT_TO_RIGHT },
221
+ "┸": { [NORMAL]: S.LEFT_TO_RIGHT, [BOLD]: S.MIDDLE_TO_TOP },
222
+ "┹": { [NORMAL]: S.MIDDLE_TO_RIGHT, [BOLD]: S.TOP_TO_LEFT },
223
+ "┺": { [NORMAL]: S.MIDDLE_TO_LEFT, [BOLD]: S.TOP_TO_RIGHT },
224
+ "┽": { [NORMAL]: `${S.TOP_TO_BOTTOM} ${S.MIDDLE_TO_RIGHT}`, [BOLD]: S.MIDDLE_TO_LEFT },
225
+ "┾": { [NORMAL]: `${S.TOP_TO_BOTTOM} ${S.MIDDLE_TO_LEFT}`, [BOLD]: S.MIDDLE_TO_RIGHT },
226
+ "┿": { [NORMAL]: S.TOP_TO_BOTTOM, [BOLD]: S.LEFT_TO_RIGHT },
227
+ "╀": { [NORMAL]: `${S.LEFT_TO_RIGHT} ${S.MIDDLE_TO_BOTTOM}`, [BOLD]: S.MIDDLE_TO_TOP },
228
+ "╁": { [NORMAL]: `${S.MIDDLE_TO_TOP} ${S.LEFT_TO_RIGHT}`, [BOLD]: S.MIDDLE_TO_BOTTOM },
229
+ "╂": { [NORMAL]: S.LEFT_TO_RIGHT, [BOLD]: S.TOP_TO_BOTTOM },
230
+ "╃": { [NORMAL]: S.RIGHT_TO_BOTTOM, [BOLD]: S.TOP_TO_LEFT },
231
+ "╄": { [NORMAL]: S.LEFT_TO_BOTTOM, [BOLD]: S.TOP_TO_RIGHT },
232
+ "╅": { [NORMAL]: S.TOP_TO_RIGHT, [BOLD]: S.LEFT_TO_BOTTOM },
233
+ "╆": { [NORMAL]: S.TOP_TO_LEFT, [BOLD]: S.RIGHT_TO_BOTTOM },
234
+ "╇": { [NORMAL]: S.MIDDLE_TO_BOTTOM, [BOLD]: `${S.MIDDLE_TO_TOP} ${S.LEFT_TO_RIGHT}` },
235
+ "╈": { [NORMAL]: S.MIDDLE_TO_TOP, [BOLD]: `${S.LEFT_TO_RIGHT} ${S.MIDDLE_TO_BOTTOM}` },
236
+ "╉": { [NORMAL]: S.MIDDLE_TO_RIGHT, [BOLD]: `${S.TOP_TO_BOTTOM} ${S.MIDDLE_TO_LEFT}` },
237
+ "╊": { [NORMAL]: S.MIDDLE_TO_LEFT, [BOLD]: `${S.TOP_TO_BOTTOM} ${S.MIDDLE_TO_RIGHT}` },
238
+ // Dashed
239
+ "╌": { [NORMAL]: S.TWO_DASHES_HORIZONTAL },
240
+ "╍": { [BOLD]: S.TWO_DASHES_HORIZONTAL },
241
+ "┄": { [NORMAL]: S.THREE_DASHES_HORIZONTAL },
242
+ "┅": { [BOLD]: S.THREE_DASHES_HORIZONTAL },
243
+ "┈": { [NORMAL]: S.FOUR_DASHES_HORIZONTAL },
244
+ "┉": { [BOLD]: S.FOUR_DASHES_HORIZONTAL },
245
+ "╎": { [NORMAL]: S.TWO_DASHES_VERTICAL },
246
+ "╏": { [BOLD]: S.TWO_DASHES_VERTICAL },
247
+ "┆": { [NORMAL]: S.THREE_DASHES_VERTICAL },
248
+ "┇": { [BOLD]: S.THREE_DASHES_VERTICAL },
249
+ "┊": { [NORMAL]: S.FOUR_DASHES_VERTICAL },
250
+ "┋": { [BOLD]: S.FOUR_DASHES_VERTICAL },
251
+ // Curved
252
+ "╭": { [NORMAL]: (xp, yp) => `M.5,1 L.5,${.5 + (yp / .15 * .5)} C.5,${.5 + (yp / .15 * .5)},.5,.5,1,.5` },
253
+ "╮": { [NORMAL]: (xp, yp) => `M.5,1 L.5,${.5 + (yp / .15 * .5)} C.5,${.5 + (yp / .15 * .5)},.5,.5,0,.5` },
254
+ "╯": { [NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 - (yp / .15 * .5)} C.5,${.5 - (yp / .15 * .5)},.5,.5,0,.5` },
255
+ "╰": { [NORMAL]: (xp, yp) => `M.5,0 L.5,${.5 - (yp / .15 * .5)} C.5,${.5 - (yp / .15 * .5)},.5,.5,1,.5` },
256
+ };
257
+
258
+ const FILL = 0;
259
+ const STROKE = 1;
260
+
261
+ // Powerline and private-use separators (https://github.com/powerline/fontpatcher).
262
+ // Line variants extend past the cell and are clipped so the line ends stay hidden.
263
+ const powerlineDefinitions = {
264
+ "\u{E0A0}": { d: "M.3,1 L.03,1 L.03,.88 C.03,.82,.06,.78,.11,.73 C.15,.7,.2,.68,.28,.65 L.43,.6 C.49,.58,.53,.56,.56,.53 C.59,.5,.6,.47,.6,.43 L.6,.27 L.4,.27 L.69,.1 L.98,.27 L.78,.27 L.78,.46 C.78,.52,.76,.56,.72,.61 C.68,.66,.63,.67,.56,.7 L.48,.72 C.42,.74,.38,.76,.35,.78 C.32,.8,.31,.84,.31,.88 L.31,1 M.3,.5 L.03,.59 L.03,.09 L.3,.09 L.3,.655", type: FILL },
265
+ "\u{E0A1}": { d: "M.7,.4 L.7,.47 L.2,.47 L.2,.03 L.355,.03 L.355,.4 L.705,.4 M.7,.5 L.86,.5 L.86,.95 L.69,.95 L.44,.66 L.46,.86 L.46,.95 L.3,.95 L.3,.49 L.46,.49 L.71,.78 L.69,.565 L.69,.5", type: FILL },
266
+ "\u{E0A2}": { d: "M.25,.94 C.16,.94,.11,.92,.11,.87 L.11,.53 C.11,.48,.15,.455,.23,.45 L.23,.3 C.23,.25,.26,.22,.31,.19 C.36,.16,.43,.15,.51,.15 C.59,.15,.66,.16,.71,.19 C.77,.22,.79,.26,.79,.3 L.79,.45 C.87,.45,.91,.48,.91,.53 L.91,.87 C.91,.92,.86,.94,.77,.94 L.24,.94 M.53,.2 C.49,.2,.45,.21,.42,.23 C.39,.25,.38,.27,.38,.3 L.38,.45 L.68,.45 L.68,.3 C.68,.27,.67,.25,.64,.23 C.61,.21,.58,.2,.53,.2 M.58,.82 L.58,.66 C.63,.65,.65,.63,.65,.6 C.65,.58,.64,.57,.61,.56 C.58,.55,.56,.54,.52,.54 C.48,.54,.46,.55,.43,.56 C.4,.57,.39,.59,.39,.6 C.39,.63,.41,.64,.46,.66 L.46,.82 L.57,.82", type: FILL },
267
+ "\u{E0B0}": { d: "M0,0 L1,.5 L0,1", type: FILL, rightPadding: 2 },
268
+ "\u{E0B1}": { d: "M-1,-.5 L1,.5 L-1,1.5", type: STROKE, leftPadding: 1, rightPadding: 1 },
269
+ "\u{E0B2}": { d: "M1,0 L0,.5 L1,1", type: FILL, leftPadding: 2 },
270
+ "\u{E0B3}": { d: "M2,-.5 L0,.5 L2,1.5", type: STROKE, leftPadding: 1, rightPadding: 1 },
271
+ "\u{E0B4}": { d: "M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0", type: FILL, rightPadding: 1 },
272
+ "\u{E0B5}": { d: "M.2,1 C.422,1,.8,.826,.78,.5 C.8,.174,0.422,0,.2,0", type: STROKE, rightPadding: 1 },
273
+ "\u{E0B6}": { d: "M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0", type: FILL, leftPadding: 1 },
274
+ "\u{E0B7}": { d: "M.8,1 C0.578,1,0.2,.826,.22,.5 C0.2,0.174,0.578,0,0.8,0", type: STROKE, leftPadding: 1 },
275
+ "\u{E0B8}": { d: "M-.5,-.5 L1.5,1.5 L-.5,1.5", type: FILL },
276
+ "\u{E0B9}": { d: "M-.5,-.5 L1.5,1.5", type: STROKE, leftPadding: 1, rightPadding: 1 },
277
+ "\u{E0BA}": { d: "M1.5,-.5 L-.5,1.5 L1.5,1.5", type: FILL },
278
+ "\u{E0BC}": { d: "M1.5,-.5 L-.5,1.5 L-.5,-.5", type: FILL },
279
+ "\u{E0BD}": { d: "M1.5,-.5 L-.5,1.5", type: STROKE, leftPadding: 1, rightPadding: 1 },
280
+ "\u{E0BE}": { d: "M-.5,-.5 L1.5,1.5 L1.5,-.5", type: FILL },
281
+ };
282
+ powerlineDefinitions["\u{E0BB}"] = powerlineDefinitions["\u{E0BD}"];
283
+ powerlineDefinitions["\u{E0BF}"] = powerlineDefinitions["\u{E0B9}"];
284
+
285
+ export const hasCustomGlyph = (character) =>
286
+ character in blockElementDefinitions
287
+ || character in patternDefinitions
288
+ || character in boxDrawingDefinitions
289
+ || character in powerlineDefinitions;
290
+
291
+ // "M.5,0 L.5,1 C..." → [["M", .5, 0], ["L", .5, 1], ["C", ...]]
292
+ const parsePath = (instructions) => instructions.split(" ").map((instruction) => {
293
+ const numbers = instruction.slice(1).split(",").map(Number);
294
+ return [instruction[0], ...numbers];
295
+ }).filter((op) => op.length >= 3 && "MLC".includes(op[0]));
296
+
297
+ const parsedPaths = new Map();
298
+
299
+ const pathFor = (instructions) => {
300
+ let ops = parsedPaths.get(instructions);
301
+ if (!ops) {
302
+ ops = parsePath(instructions);
303
+ parsedPaths.set(instructions, ops);
304
+ }
305
+ return ops;
306
+ };
307
+
308
+ const clamp = (value, maximum) => Math.max(Math.min(value, maximum), 0);
309
+
310
+ // Scales 0–1 path coordinates to the cell. Box drawing coordinates snap to
311
+ // pixel centres (odd line widths) or pixel edges (even) so lines are crisp
312
+ // and meet the neighbouring cell without a seam.
313
+ const buildPath = (CanvasKit, ops, x, y, width, height, {
314
+ snap = false, lineWidth = 1, leftPadding = 0, rightPadding = 0,
315
+ } = {}) => {
316
+ const builder = new CanvasKit.PathBuilder();
317
+ const drawWidth = width - leftPadding - rightPadding;
318
+ const half = lineWidth % 2 ? 0.5 : 0;
319
+ const translateX = (value) => {
320
+ let result = value * drawWidth;
321
+ if (snap && result !== 0) result = clamp(Math.round(result + half) - half, width);
322
+ return result + x + leftPadding;
323
+ };
324
+ const translateY = (value) => {
325
+ let result = value * height;
326
+ if (snap && result !== 0) result = clamp(Math.round(result + half) - half, height);
327
+ return result + y;
328
+ };
329
+ for (const op of ops) {
330
+ if (op[0] === "M") builder.moveTo(translateX(op[1]), translateY(op[2]));
331
+ else if (op[0] === "L") builder.lineTo(translateX(op[1]), translateY(op[2]));
332
+ else if (op[0] === "C") {
333
+ builder.cubicTo(translateX(op[1]), translateY(op[2]), translateX(op[3]), translateY(op[4]),
334
+ translateX(op[5]), translateY(op[6]));
335
+ }
336
+ }
337
+ const path = builder.detach();
338
+ builder.delete();
339
+ return path;
340
+ };
341
+
342
+ // Shade characters are a repeating image shader, one per (pattern, color).
343
+ const shaderCache = new Map();
344
+
345
+ const patternShader = (CanvasKit, pattern, color) => {
346
+ const key = pattern.length + ":" + color.join(",");
347
+ let entry = shaderCache.get(key);
348
+ if (!entry) {
349
+ const height = pattern.length;
350
+ const width = pattern[0].length;
351
+ const pixels = new Uint8Array(width * height * 4);
352
+ for (let row = 0; row < height; row++) {
353
+ for (let column = 0; column < width; column++) {
354
+ const offset = (row * width + column) * 4;
355
+ pixels[offset] = color[0];
356
+ pixels[offset + 1] = color[1];
357
+ pixels[offset + 2] = color[2];
358
+ pixels[offset + 3] = pattern[row][column] ? 255 : 0;
359
+ }
360
+ }
361
+ const image = CanvasKit.MakeImage({
362
+ width, height,
363
+ colorType: CanvasKit.ColorType.RGBA_8888,
364
+ alphaType: CanvasKit.AlphaType.Unpremul,
365
+ colorSpace: CanvasKit.ColorSpace.SRGB,
366
+ }, pixels, width * 4);
367
+ const shader = image.makeShaderOptions(CanvasKit.TileMode.Repeat, CanvasKit.TileMode.Repeat,
368
+ CanvasKit.FilterMode.Nearest, CanvasKit.MipmapMode.None);
369
+ entry = { image, shader };
370
+ shaderCache.set(key, entry);
371
+ }
372
+ return entry.shader;
373
+ };
374
+
375
+ // Draws `character` into the cell at (x, y) if it has a vector definition.
376
+ // `paint` is a fill paint carrying the foreground colour; `color` is the
377
+ // same colour as [r, g, b] for the shade shader cache. `fontSize` scales the
378
+ // line widths: 1 px up to 23 px, then one more pixel per 12 px, the way
379
+ // xterm.js's Powerline glyphs scale. Returns false when the character is not
380
+ // a custom glyph and should come from a font.
381
+ export const drawCustomGlyph = (CanvasKit, canvas, character, x, y, width, height, fontSize, paint, color) => {
382
+ const block = blockElementDefinitions[character];
383
+ if (block) {
384
+ const eighthX = width / 8;
385
+ const eighthY = height / 8;
386
+ for (const box of block) {
387
+ canvas.drawRect(CanvasKit.XYWHRect(x + box.x * eighthX, y + box.y * eighthY,
388
+ box.w * eighthX, box.h * eighthY), paint);
389
+ }
390
+ return true;
391
+ }
392
+
393
+ const pattern = patternDefinitions[character];
394
+ if (pattern) {
395
+ const shaded = new CanvasKit.Paint();
396
+ shaded.setShader(patternShader(CanvasKit, pattern, color));
397
+ canvas.drawRect(CanvasKit.XYWHRect(x, y, width, height), shaded);
398
+ shaded.delete();
399
+ return true;
400
+ }
401
+
402
+ const unit = Math.max(1, Math.floor(fontSize / 12));
403
+ const box = boxDrawingDefinitions[character];
404
+ if (box) {
405
+ const stroke = paint.copy();
406
+ stroke.setStyle(CanvasKit.PaintStyle.Stroke);
407
+ stroke.setAntiAlias(false);
408
+ for (const [weight, instructions] of Object.entries(box)) {
409
+ const lineWidth = unit * Number(weight);
410
+ stroke.setStrokeWidth(lineWidth);
411
+ const resolved = typeof instructions === "function"
412
+ ? instructions(0.15, 0.15 / height * width)
413
+ : instructions;
414
+ const path = buildPath(CanvasKit, pathFor(resolved), x, y, width, height, { snap: true, lineWidth });
415
+ canvas.drawPath(path, stroke);
416
+ path.delete();
417
+ }
418
+ stroke.delete();
419
+ return true;
420
+ }
421
+
422
+ const powerline = powerlineDefinitions[character];
423
+ if (powerline) {
424
+ const lineWidth = fontSize / 12;
425
+ const path = buildPath(CanvasKit, pathFor(powerline.d), x, y, width, height, {
426
+ leftPadding: (powerline.leftPadding ?? 0) * lineWidth / 2,
427
+ rightPadding: (powerline.rightPadding ?? 0) * lineWidth / 2,
428
+ });
429
+ canvas.save();
430
+ canvas.clipRect(CanvasKit.XYWHRect(x, y, width, height), CanvasKit.ClipOp.Intersect, false);
431
+ if (powerline.type === STROKE) {
432
+ const stroke = paint.copy();
433
+ stroke.setStyle(CanvasKit.PaintStyle.Stroke);
434
+ stroke.setStrokeWidth(lineWidth);
435
+ canvas.drawPath(path, stroke);
436
+ stroke.delete();
437
+ } else {
438
+ canvas.drawPath(path, paint);
439
+ }
440
+ canvas.restore();
441
+ path.delete();
442
+ return true;
443
+ }
444
+
445
+ return false;
446
+ };