novac 2.1.1 → 2.2.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 (138) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +0 -0
  3. package/demo.nv +0 -0
  4. package/demo_builtins.nv +0 -0
  5. package/demo_http.nv +0 -0
  6. package/examples/bf.nv +69 -0
  7. package/examples/math.nv +21 -0
  8. package/kits/birdAPI/kitdef.js +954 -0
  9. package/kits/kitRNG/kitdef.js +740 -0
  10. package/kits/kitSSH/kitdef.js +1272 -0
  11. package/kits/kitadb/kitdef.js +606 -0
  12. package/kits/kitai/kitdef.js +2185 -0
  13. package/kits/kitcanvas/kitdef.js +914 -0
  14. package/kits/kitclippy/kitdef.js +925 -0
  15. package/kits/kitgps/kitdef.js +1862 -0
  16. package/kits/kitlibproc/kitdef.js +3 -2
  17. package/kits/kitmorse/kitdef.js +229 -0
  18. package/kits/kitmpatch/kitdef.js +906 -0
  19. package/kits/kitnet/kitdef.js +1401 -0
  20. package/kits/kitproto/kitdef.js +613 -0
  21. package/kits/kitqr/kitdef.js +637 -0
  22. package/kits/kitrequire/kitdef.js +1599 -0
  23. package/kits/libtea/kitdef.js +2691 -0
  24. package/kits/libterm/kitdef.js +2 -0
  25. package/novac/LICENSE +21 -0
  26. package/novac/README.md +1823 -0
  27. package/novac/bin/novac +950 -0
  28. package/novac/bin/nvc +522 -0
  29. package/novac/bin/nvml +542 -0
  30. package/novac/demo.nv +245 -0
  31. package/novac/demo_builtins.nv +209 -0
  32. package/novac/demo_http.nv +62 -0
  33. package/novac/examples/bf.nv +69 -0
  34. package/novac/examples/math.nv +21 -0
  35. package/novac/kits/kitai/kitdef.js +2185 -0
  36. package/novac/kits/kitansi/kitdef.js +1402 -0
  37. package/novac/kits/kitformat/kitdef.js +1485 -0
  38. package/novac/kits/kitgps/kitdef.js +1862 -0
  39. package/novac/kits/kitlibfs/kitdef.js +231 -0
  40. package/{examples/example-project/nova_modules → novac/kits}/kitlibproc/kitdef.js +3 -2
  41. package/novac/kits/kitmatrix/ex.js +19 -0
  42. package/novac/kits/kitmatrix/kitdef.js +960 -0
  43. package/novac/kits/kitmpatch/kitdef.js +906 -0
  44. package/novac/kits/kitnovacweb/README.md +1572 -0
  45. package/novac/kits/kitnovacweb/demo.nv +12 -0
  46. package/novac/kits/kitnovacweb/demo.nvml +71 -0
  47. package/novac/kits/kitnovacweb/index.nova +12 -0
  48. package/novac/kits/kitnovacweb/kitdef.js +692 -0
  49. package/novac/kits/kitnovacweb/nova.kit.json +8 -0
  50. package/novac/kits/kitnovacweb/nvml/executor.js +739 -0
  51. package/novac/kits/kitnovacweb/nvml/index.js +67 -0
  52. package/novac/kits/kitnovacweb/nvml/lexer.js +263 -0
  53. package/novac/kits/kitnovacweb/nvml/parser.js +508 -0
  54. package/novac/kits/kitnovacweb/nvml/renderer.js +924 -0
  55. package/novac/kits/kitparse/kitdef.js +1688 -0
  56. package/novac/kits/kitregex++/kitdef.js +1353 -0
  57. package/novac/kits/kitrequire/kitdef.js +1599 -0
  58. package/novac/kits/kitx11/kitdef.js +1 -0
  59. package/novac/kits/kitx11/kitx11.js +2472 -0
  60. package/novac/kits/kitx11/kitx11_conn.js +948 -0
  61. package/novac/kits/kitx11/kitx11_worker.js +121 -0
  62. package/novac/kits/libterm/ex.js +285 -0
  63. package/novac/kits/libterm/kitdef.js +1927 -0
  64. package/novac/node_modules/chalk/license +9 -0
  65. package/novac/node_modules/chalk/package.json +83 -0
  66. package/novac/node_modules/chalk/readme.md +297 -0
  67. package/novac/node_modules/chalk/source/index.d.ts +325 -0
  68. package/novac/node_modules/chalk/source/index.js +225 -0
  69. package/novac/node_modules/chalk/source/utilities.js +33 -0
  70. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.d.ts +236 -0
  71. package/novac/node_modules/chalk/source/vendor/ansi-styles/index.js +223 -0
  72. package/novac/node_modules/chalk/source/vendor/supports-color/browser.d.ts +1 -0
  73. package/novac/node_modules/chalk/source/vendor/supports-color/browser.js +34 -0
  74. package/novac/node_modules/chalk/source/vendor/supports-color/index.d.ts +55 -0
  75. package/novac/node_modules/chalk/source/vendor/supports-color/index.js +190 -0
  76. package/novac/node_modules/commander/LICENSE +22 -0
  77. package/novac/node_modules/commander/Readme.md +1176 -0
  78. package/novac/node_modules/commander/esm.mjs +16 -0
  79. package/novac/node_modules/commander/index.js +24 -0
  80. package/novac/node_modules/commander/lib/argument.js +150 -0
  81. package/novac/node_modules/commander/lib/command.js +2777 -0
  82. package/novac/node_modules/commander/lib/error.js +39 -0
  83. package/novac/node_modules/commander/lib/help.js +747 -0
  84. package/novac/node_modules/commander/lib/option.js +380 -0
  85. package/novac/node_modules/commander/lib/suggestSimilar.js +101 -0
  86. package/novac/node_modules/commander/package-support.json +19 -0
  87. package/novac/node_modules/commander/package.json +82 -0
  88. package/novac/node_modules/commander/typings/esm.d.mts +3 -0
  89. package/novac/node_modules/commander/typings/index.d.ts +1113 -0
  90. package/novac/node_modules/node-addon-api/LICENSE.md +9 -0
  91. package/novac/node_modules/node-addon-api/README.md +95 -0
  92. package/novac/node_modules/node-addon-api/common.gypi +21 -0
  93. package/novac/node_modules/node-addon-api/except.gypi +25 -0
  94. package/novac/node_modules/node-addon-api/index.js +14 -0
  95. package/novac/node_modules/node-addon-api/napi-inl.deprecated.h +186 -0
  96. package/novac/node_modules/node-addon-api/napi-inl.h +7165 -0
  97. package/novac/node_modules/node-addon-api/napi.h +3364 -0
  98. package/novac/node_modules/node-addon-api/node_addon_api.gyp +42 -0
  99. package/novac/node_modules/node-addon-api/node_api.gyp +9 -0
  100. package/novac/node_modules/node-addon-api/noexcept.gypi +26 -0
  101. package/novac/node_modules/node-addon-api/package-support.json +21 -0
  102. package/novac/node_modules/node-addon-api/package.json +480 -0
  103. package/novac/node_modules/node-addon-api/tools/README.md +73 -0
  104. package/novac/node_modules/node-addon-api/tools/check-napi.js +99 -0
  105. package/novac/node_modules/node-addon-api/tools/clang-format.js +71 -0
  106. package/novac/node_modules/node-addon-api/tools/conversion.js +301 -0
  107. package/novac/node_modules/serialize-javascript/LICENSE +27 -0
  108. package/novac/node_modules/serialize-javascript/README.md +149 -0
  109. package/novac/node_modules/serialize-javascript/index.js +297 -0
  110. package/novac/node_modules/serialize-javascript/package.json +33 -0
  111. package/novac/package.json +27 -0
  112. package/novac/scripts/update-bin.js +24 -0
  113. package/novac/src/core/bstd.js +1035 -0
  114. package/novac/src/core/config.js +155 -0
  115. package/novac/src/core/describe.js +187 -0
  116. package/novac/src/core/emitter.js +499 -0
  117. package/novac/src/core/error.js +86 -0
  118. package/novac/src/core/executor.js +5606 -0
  119. package/novac/src/core/formatter.js +686 -0
  120. package/novac/src/core/lexer.js +1026 -0
  121. package/novac/src/core/nova_builtins.js +717 -0
  122. package/novac/src/core/nova_thread_worker.js +166 -0
  123. package/novac/src/core/parser.js +2181 -0
  124. package/novac/src/core/types.js +112 -0
  125. package/novac/src/index.js +28 -0
  126. package/novac/src/runtime/stdlib.js +244 -0
  127. package/package.json +3 -2
  128. package/scripts/update-bin.js +0 -0
  129. package/src/core/bstd.js +835 -361
  130. package/src/core/executor.js +427 -246
  131. package/src/core/lexer.js +19 -2
  132. package/src/core/parser.js +13 -0
  133. package/src/index.js +0 -0
  134. package/examples/example-project/README.md +0 -3
  135. package/examples/example-project/src/main.nova +0 -3
  136. package/src/core/environment.js +0 -0
  137. /package/{kits → novac/kits}/libtea/tf.js +0 -0
  138. /package/{examples/example-project/bin/example-project.nv → novac/node_modules/node-addon-api/nothing.c} +0 -0
@@ -0,0 +1,914 @@
1
+ // kitcanvas — novac drawing kit
2
+ // Renderer-agnostic: YOU supply the renderer, kitcanvas does the drawing.
3
+ //
4
+ // Minimum renderer contract:
5
+ // renderer.width — number
6
+ // renderer.height — number
7
+ // renderer.setPixel(x, y, { color, alpha?, ... })
8
+ //
9
+ // Optional renderer extras (kitcanvas uses them if present):
10
+ // renderer.writeText(x, y, text, opts)
11
+ // renderer.clear(color?)
12
+ // renderer.flush()
13
+ // renderer.getPixel(x, y) → { color, ... }
14
+ // renderer.resize(w, h)
15
+ // renderer.save(path) — for file-backed renderers
16
+ // renderer.toBuffer() — returns raw pixel buffer
17
+ // renderer.toDataURL() — returns base64 data URL
18
+
19
+ 'use strict';
20
+
21
+ // ─── COLOR UTILS ─────────────────────────────────────────────────────────────
22
+
23
+ const NAMED_COLORS = {
24
+ red: [255, 0, 0 ], green: [0, 128, 0 ], blue: [0, 0, 255],
25
+ white: [255, 255, 255], black: [0, 0, 0 ], yellow: [255, 255, 0 ],
26
+ cyan: [0, 255, 255], magenta: [255, 0, 255], orange: [255, 165, 0 ],
27
+ purple: [128, 0, 128], pink: [255, 192, 203], brown: [165, 42, 42 ],
28
+ gray: [128, 128, 128], grey: [128, 128, 128], silver: [192, 192, 192],
29
+ gold: [255, 215, 0 ], lime: [0, 255, 0 ], navy: [0, 0, 128],
30
+ teal: [0, 128, 128], maroon: [128, 0, 0 ], olive: [128, 128, 0 ],
31
+ coral: [255, 127, 80 ], salmon: [250, 128, 114], khaki: [240, 230, 140],
32
+ indigo: [75, 0, 130], violet: [238, 130, 238], beige: [245, 245, 220],
33
+ ivory: [255, 255, 240], lavender:[230, 230, 250], mint: [152, 255, 152],
34
+ transparent: [0, 0, 0],
35
+ };
36
+
37
+ /**
38
+ * Parse a color value to { r, g, b, a }.
39
+ * Accepts:
40
+ * - named color string: 'red', 'blue', ...
41
+ * - hex string: '#rgb', '#rrggbb', '#rrggbbaa'
42
+ * - rgb string: 'rgb(255,0,0)'
43
+ * - rgba string: 'rgba(255,0,0,0.5)'
44
+ * - [r, g, b] / [r, g, b, a] array
45
+ * - { r, g, b, a? } object
46
+ * - number 0xRRGGBB
47
+ */
48
+ function parseColor(color, alpha = 255) {
49
+ if (!color) return { r: 0, g: 0, b: 0, a: alpha };
50
+
51
+ if (typeof color === 'number') {
52
+ return { r: (color >> 16) & 0xff, g: (color >> 8) & 0xff, b: color & 0xff, a: alpha };
53
+ }
54
+
55
+ if (Array.isArray(color)) {
56
+ return { r: color[0] ?? 0, g: color[1] ?? 0, b: color[2] ?? 0, a: color[3] ?? alpha };
57
+ }
58
+
59
+ if (typeof color === 'object') {
60
+ return { r: color.r ?? 0, g: color.g ?? 0, b: color.b ?? 0, a: color.a ?? alpha };
61
+ }
62
+
63
+ if (typeof color === 'string') {
64
+ const lower = color.toLowerCase().trim();
65
+
66
+ // named
67
+ if (NAMED_COLORS[lower]) {
68
+ const [r, g, b] = NAMED_COLORS[lower];
69
+ return { r, g, b, a: lower === 'transparent' ? 0 : alpha };
70
+ }
71
+
72
+ // hex
73
+ if (lower.startsWith('#')) {
74
+ const hex = lower.slice(1);
75
+ if (hex.length === 3) {
76
+ return { r: parseInt(hex[0]+hex[0],16), g: parseInt(hex[1]+hex[1],16), b: parseInt(hex[2]+hex[2],16), a: alpha };
77
+ }
78
+ if (hex.length === 6) {
79
+ return { r: parseInt(hex.slice(0,2),16), g: parseInt(hex.slice(2,4),16), b: parseInt(hex.slice(4,6),16), a: alpha };
80
+ }
81
+ if (hex.length === 8) {
82
+ return { r: parseInt(hex.slice(0,2),16), g: parseInt(hex.slice(2,4),16), b: parseInt(hex.slice(4,6),16), a: parseInt(hex.slice(6,8),16) };
83
+ }
84
+ }
85
+
86
+ // rgb(...)
87
+ const rgbMatch = lower.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)(?:\s*,\s*([\d.]+))?\s*\)$/);
88
+ if (rgbMatch) {
89
+ const a = rgbMatch[4] !== undefined ? Math.round(parseFloat(rgbMatch[4]) * 255) : alpha;
90
+ return { r: +rgbMatch[1], g: +rgbMatch[2], b: +rgbMatch[3], a };
91
+ }
92
+ }
93
+
94
+ throw new Error(`kitcanvas: Cannot parse color: ${JSON.stringify(color)}`);
95
+ }
96
+
97
+ function colorToHex({ r, g, b, a }) {
98
+ const h = n => n.toString(16).padStart(2, '0');
99
+ return `#${h(r)}${h(g)}${h(b)}${a !== 255 ? h(a) : ''}`;
100
+ }
101
+
102
+ function lerpColor(c1, c2, t) {
103
+ const a = parseColor(c1), b = parseColor(c2);
104
+ return {
105
+ r: Math.round(a.r + (b.r - a.r) * t),
106
+ g: Math.round(a.g + (b.g - a.g) * t),
107
+ b: Math.round(a.b + (b.b - a.b) * t),
108
+ a: Math.round(a.a + (b.a - a.a) * t),
109
+ };
110
+ }
111
+
112
+ // ─── CANVAS CLASS ─────────────────────────────────────────────────────────────
113
+
114
+ class Canvas {
115
+ /**
116
+ * Create a Canvas backed by a renderer.
117
+ * @param {object} renderer - Must implement width, height, setPixel(x,y,opts).
118
+ */
119
+ constructor(renderer) {
120
+ if (!renderer) throw new Error('kitcanvas: renderer is required');
121
+ if (!renderer.setPixel) throw new Error('kitcanvas: renderer must implement setPixel(x, y, opts)');
122
+ if (renderer.width == null) throw new Error('kitcanvas: renderer must have .width');
123
+ if (renderer.height == null) throw new Error('kitcanvas: renderer must have .height');
124
+
125
+ this._r = renderer;
126
+ this._color = '#000000';
127
+ this._alpha = 255;
128
+ this._fill = '#000000';
129
+ this._lineWidth = 1;
130
+ this._transform = { tx: 0, ty: 0, sx: 1, sy: 1 };
131
+ this._clipRect = null;
132
+ this._stack = [];
133
+ }
134
+
135
+ get width() { return this._r.width; }
136
+ get height() { return this._r.height; }
137
+
138
+ // ── State ──────────────────────────────────────────────────────────────────
139
+
140
+ /** Set stroke color. Accepts any color format. */
141
+ color(c) { this._color = c; return this; }
142
+
143
+ /** Set fill color. */
144
+ fill(c) { this._fill = c; return this; }
145
+
146
+ /** Set stroke alpha (0-255 or 0.0-1.0). */
147
+ alpha(a) { this._alpha = a <= 1 ? Math.round(a * 255) : Math.round(a); return this; }
148
+
149
+ /** Set line width. */
150
+ lineWidth(w) { this._lineWidth = Math.max(1, Math.round(w)); return this; }
151
+
152
+ /** Save current state (color, fill, alpha, lineWidth, transform, clip). */
153
+ save() {
154
+ this._stack.push({
155
+ color: this._color, fill: this._fill, alpha: this._alpha,
156
+ lineWidth: this._lineWidth, transform: { ...this._transform },
157
+ clipRect: this._clipRect ? { ...this._clipRect } : null,
158
+ });
159
+ return this;
160
+ }
161
+
162
+ /** Restore last saved state. */
163
+ restore() {
164
+ const s = this._stack.pop();
165
+ if (s) Object.assign(this, { _color: s.color, _fill: s.fill, _alpha: s.alpha, _lineWidth: s.lineWidth, _transform: s.transform, _clipRect: s.clipRect });
166
+ return this;
167
+ }
168
+
169
+ /** Translate origin. */
170
+ translate(tx, ty) { this._transform.tx += tx; this._transform.ty += ty; return this; }
171
+
172
+ /** Scale drawing. */
173
+ scale(sx, sy = sx) { this._transform.sx *= sx; this._transform.sy *= sy; return this; }
174
+
175
+ /** Reset transform. */
176
+ resetTransform() { this._transform = { tx: 0, ty: 0, sx: 1, sy: 1 }; return this; }
177
+
178
+ /** Set clip rectangle — nothing outside is drawn. */
179
+ clip(x, y, w, h) { this._clipRect = { x, y, w, h }; return this; }
180
+
181
+ /** Remove clip. */
182
+ unclip() { this._clipRect = null; return this; }
183
+
184
+ // ── Low-level ──────────────────────────────────────────────────────────────
185
+
186
+ _tx(x) { return Math.round(x * this._transform.sx + this._transform.tx); }
187
+ _ty(y) { return Math.round(y * this._transform.sy + this._transform.ty); }
188
+
189
+ _inBounds(x, y) {
190
+ if (x < 0 || y < 0 || x >= this._r.width || y >= this._r.height) return false;
191
+ if (this._clipRect) {
192
+ const c = this._clipRect;
193
+ if (x < c.x || y < c.y || x >= c.x + c.w || y >= c.y + c.h) return false;
194
+ }
195
+ return true;
196
+ }
197
+
198
+ /**
199
+ * Set a single pixel.
200
+ * @param {number} x
201
+ * @param {number} y
202
+ * @param {object} [opts] - { color, alpha }
203
+ */
204
+ setPixel(x, y, opts = {}) {
205
+ const px = this._tx(x), py = this._ty(y);
206
+ if (!this._inBounds(px, py)) return this;
207
+ const color = parseColor(opts.color ?? this._color, opts.alpha ?? this._alpha);
208
+ this._r.setPixel(px, py, { ...opts, color });
209
+ return this;
210
+ }
211
+
212
+ // ── Clear ──────────────────────────────────────────────────────────────────
213
+
214
+ /**
215
+ * Clear the canvas.
216
+ * @param {string|Array} [color='white']
217
+ */
218
+ clear(color = 'white') {
219
+ if (this._r.clear) { this._r.clear(parseColor(color)); return this; }
220
+ const c = parseColor(color);
221
+ for (let y = 0; y < this._r.height; y++)
222
+ for (let x = 0; x < this._r.width; x++)
223
+ this._r.setPixel(x, y, { color: c });
224
+ return this;
225
+ }
226
+
227
+ // ── Shapes ─────────────────────────────────────────────────────────────────
228
+
229
+ /**
230
+ * Draw a single pixel point.
231
+ */
232
+ point(x, y, opts = {}) { return this.setPixel(x, y, opts); }
233
+
234
+ /**
235
+ * Draw a line using Bresenham's algorithm.
236
+ * @param {number} x1
237
+ * @param {number} y1
238
+ * @param {number} x2
239
+ * @param {number} y2
240
+ * @param {object} [opts]
241
+ */
242
+ line(x1, y1, x2, y2, opts = {}) {
243
+ const color = parseColor(opts.color ?? this._color, opts.alpha ?? this._alpha);
244
+ const w = Math.max(1, opts.lineWidth ?? this._lineWidth);
245
+ const draw = (x, y) => {
246
+ for (let dy = -Math.floor(w/2); dy <= Math.floor(w/2); dy++)
247
+ for (let dx = -Math.floor(w/2); dx <= Math.floor(w/2); dx++) {
248
+ const px = this._tx(x+dx), py = this._ty(y+dy);
249
+ if (this._inBounds(px, py)) this._r.setPixel(px, py, { ...opts, color });
250
+ }
251
+ };
252
+
253
+ let [ax, ay, bx, by] = [Math.round(x1), Math.round(y1), Math.round(x2), Math.round(y2)];
254
+ const dx = Math.abs(bx - ax), dy = Math.abs(by - ay);
255
+ const sx = ax < bx ? 1 : -1, sy = ay < by ? 1 : -1;
256
+ let err = dx - dy;
257
+ while (true) {
258
+ draw(ax, ay);
259
+ if (ax === bx && ay === by) break;
260
+ const e2 = 2 * err;
261
+ if (e2 > -dy) { err -= dy; ax += sx; }
262
+ if (e2 < dx) { err += dx; ay += sy; }
263
+ }
264
+ return this;
265
+ }
266
+
267
+ /**
268
+ * Draw a rectangle outline.
269
+ */
270
+ box(x, y, w, h, opts = {}) {
271
+ this.line(x, y, x+w-1, y, opts);
272
+ this.line(x+w-1, y, x+w-1, y+h-1, opts);
273
+ this.line(x+w-1, y+h-1, x, y+h-1, opts);
274
+ this.line(x, y+h-1, x, y, opts);
275
+ return this;
276
+ }
277
+
278
+ /**
279
+ * Draw a filled rectangle.
280
+ */
281
+ fillBox(x, y, w, h, opts = {}) {
282
+ const color = parseColor(opts.color ?? this._fill, opts.alpha ?? this._alpha);
283
+ for (let row = y; row < y + h; row++) {
284
+ for (let col = x; col < x + w; col++) {
285
+ const px = this._tx(col), py = this._ty(row);
286
+ if (this._inBounds(px, py)) this._r.setPixel(px, py, { ...opts, color });
287
+ }
288
+ }
289
+ return this;
290
+ }
291
+
292
+ /**
293
+ * Draw a circle outline using Bresenham's circle algorithm.
294
+ */
295
+ circle(cx, cy, radius, opts = {}) {
296
+ const color = parseColor(opts.color ?? this._color, opts.alpha ?? this._alpha);
297
+ const plot = (x, y) => { const px = this._tx(x), py = this._ty(y); if (this._inBounds(px, py)) this._r.setPixel(px, py, { ...opts, color }); };
298
+ let x = 0, y = radius, d = 3 - 2 * radius;
299
+ while (y >= x) {
300
+ plot(cx+x, cy+y); plot(cx-x, cy+y); plot(cx+x, cy-y); plot(cx-x, cy-y);
301
+ plot(cx+y, cy+x); plot(cx-y, cy+x); plot(cx+y, cy-x); plot(cx-y, cy-x);
302
+ if (d < 0) d += 4 * x + 6;
303
+ else { d += 4 * (x - y) + 10; y--; }
304
+ x++;
305
+ }
306
+ return this;
307
+ }
308
+
309
+ /**
310
+ * Draw a filled circle.
311
+ */
312
+ fillCircle(cx, cy, radius, opts = {}) {
313
+ const color = parseColor(opts.color ?? this._fill, opts.alpha ?? this._alpha);
314
+ for (let y = -radius; y <= radius; y++) {
315
+ const halfW = Math.round(Math.sqrt(radius * radius - y * y));
316
+ for (let x = -halfW; x <= halfW; x++) {
317
+ const px = this._tx(cx+x), py = this._ty(cy+y);
318
+ if (this._inBounds(px, py)) this._r.setPixel(px, py, { ...opts, color });
319
+ }
320
+ }
321
+ return this;
322
+ }
323
+
324
+ /**
325
+ * Draw an ellipse outline.
326
+ */
327
+ ellipse(cx, cy, rx, ry, opts = {}) {
328
+ const color = parseColor(opts.color ?? this._color, opts.alpha ?? this._alpha);
329
+ const plot = (x, y) => { const px = this._tx(x), py = this._ty(y); if (this._inBounds(px, py)) this._r.setPixel(px, py, { ...opts, color }); };
330
+ const steps = Math.max(36, Math.round(2 * Math.PI * Math.max(rx, ry)));
331
+ for (let i = 0; i < steps; i++) {
332
+ const t = (2 * Math.PI * i) / steps;
333
+ plot(Math.round(cx + rx * Math.cos(t)), Math.round(cy + ry * Math.sin(t)));
334
+ }
335
+ return this;
336
+ }
337
+
338
+ /**
339
+ * Draw a filled ellipse.
340
+ */
341
+ fillEllipse(cx, cy, rx, ry, opts = {}) {
342
+ const color = parseColor(opts.color ?? this._fill, opts.alpha ?? this._alpha);
343
+ for (let y = -ry; y <= ry; y++) {
344
+ const halfW = Math.round(rx * Math.sqrt(1 - (y/ry)**2));
345
+ for (let x = -halfW; x <= halfW; x++) {
346
+ const px = this._tx(cx+x), py = this._ty(cy+y);
347
+ if (this._inBounds(px, py)) this._r.setPixel(px, py, { ...opts, color });
348
+ }
349
+ }
350
+ return this;
351
+ }
352
+
353
+ /**
354
+ * Draw a triangle outline.
355
+ */
356
+ triangle(x1, y1, x2, y2, x3, y3, opts = {}) {
357
+ this.line(x1, y1, x2, y2, opts);
358
+ this.line(x2, y2, x3, y3, opts);
359
+ this.line(x3, y3, x1, y1, opts);
360
+ return this;
361
+ }
362
+
363
+ /**
364
+ * Draw a filled triangle using scanline fill.
365
+ */
366
+ fillTriangle(x1, y1, x2, y2, x3, y3, opts = {}) {
367
+ const color = parseColor(opts.color ?? this._fill, opts.alpha ?? this._alpha);
368
+ // Sort vertices by y
369
+ let pts = [[x1,y1],[x2,y2],[x3,y3]].sort((a,b) => a[1]-b[1]);
370
+ const [[ax,ay],[bx,by],[cx,cy]] = pts;
371
+
372
+ const interpX = (y, p1, p2) => p1[1] === p2[1] ? p1[0] : p1[0] + (y - p1[1]) * (p2[0] - p1[0]) / (p2[1] - p1[1]);
373
+
374
+ for (let y = ay; y <= cy; y++) {
375
+ const xLeft = y <= by ? interpX(y, [ax,ay], [bx,by]) : interpX(y, [bx,by], [cx,cy]);
376
+ const xRight = interpX(y, [ax,ay], [cx,cy]);
377
+ const xMin = Math.round(Math.min(xLeft, xRight));
378
+ const xMax = Math.round(Math.max(xLeft, xRight));
379
+ for (let x = xMin; x <= xMax; x++) {
380
+ const px = this._tx(x), py = this._ty(y);
381
+ if (this._inBounds(px, py)) this._r.setPixel(px, py, { ...opts, color });
382
+ }
383
+ }
384
+ return this;
385
+ }
386
+
387
+ /**
388
+ * Draw a polygon outline from an array of [x,y] points.
389
+ */
390
+ polygon(points, opts = {}) {
391
+ for (let i = 0; i < points.length; i++) {
392
+ const [x1,y1] = points[i];
393
+ const [x2,y2] = points[(i+1) % points.length];
394
+ this.line(x1, y1, x2, y2, opts);
395
+ }
396
+ return this;
397
+ }
398
+
399
+ /**
400
+ * Draw a filled polygon using scanline fill.
401
+ */
402
+ fillPolygon(points, opts = {}) {
403
+ const color = parseColor(opts.color ?? this._fill, opts.alpha ?? this._alpha);
404
+ const ys = points.map(p => p[1]);
405
+ const minY = Math.min(...ys), maxY = Math.max(...ys);
406
+
407
+ for (let y = minY; y <= maxY; y++) {
408
+ const intersects = [];
409
+ for (let i = 0; i < points.length; i++) {
410
+ const [x1,y1] = points[i];
411
+ const [x2,y2] = points[(i+1) % points.length];
412
+ if ((y1 <= y && y2 > y) || (y2 <= y && y1 > y)) {
413
+ intersects.push(x1 + (y - y1) * (x2 - x1) / (y2 - y1));
414
+ }
415
+ }
416
+ intersects.sort((a,b) => a-b);
417
+ for (let i = 0; i < intersects.length - 1; i += 2) {
418
+ for (let x = Math.round(intersects[i]); x <= Math.round(intersects[i+1]); x++) {
419
+ const px = this._tx(x), py = this._ty(y);
420
+ if (this._inBounds(px, py)) this._r.setPixel(px, py, { ...opts, color });
421
+ }
422
+ }
423
+ }
424
+ return this;
425
+ }
426
+
427
+ /**
428
+ * Draw a rounded rectangle.
429
+ */
430
+ roundedBox(x, y, w, h, r, opts = {}) {
431
+ r = Math.min(r, Math.floor(w/2), Math.floor(h/2));
432
+ this.line(x+r, y, x+w-r, y, opts);
433
+ this.line(x+r, y+h, x+w-r, y+h, opts);
434
+ this.line(x, y+r, x, y+h-r, opts);
435
+ this.line(x+w, y+r, x+w, y+h-r, opts);
436
+ this._arc(x+r, y+r, r, Math.PI, 1.5*Math.PI, opts);
437
+ this._arc(x+w-r, y+r, r, 1.5*Math.PI, 2*Math.PI, opts);
438
+ this._arc(x+w-r, y+h-r, r, 0, 0.5*Math.PI, opts);
439
+ this._arc(x+r, y+h-r, r, 0.5*Math.PI, Math.PI, opts);
440
+ return this;
441
+ }
442
+
443
+ /**
444
+ * Draw an arc.
445
+ */
446
+ arc(cx, cy, radius, startAngle, endAngle, opts = {}) {
447
+ return this._arc(cx, cy, radius, startAngle, endAngle, opts);
448
+ }
449
+
450
+ _arc(cx, cy, radius, startAngle, endAngle, opts = {}) {
451
+ const color = parseColor(opts.color ?? this._color, opts.alpha ?? this._alpha);
452
+ const steps = Math.max(16, Math.round(radius * Math.abs(endAngle - startAngle)));
453
+ for (let i = 0; i <= steps; i++) {
454
+ const t = startAngle + (endAngle - startAngle) * i / steps;
455
+ const px = this._tx(Math.round(cx + radius * Math.cos(t)));
456
+ const py = this._ty(Math.round(cy + radius * Math.sin(t)));
457
+ if (this._inBounds(px, py)) this._r.setPixel(px, py, { ...opts, color });
458
+ }
459
+ return this;
460
+ }
461
+
462
+ /**
463
+ * Bezier curve — cubic.
464
+ */
465
+ bezier(x1, y1, cp1x, cp1y, cp2x, cp2y, x2, y2, opts = {}) {
466
+ const color = parseColor(opts.color ?? this._color, opts.alpha ?? this._alpha);
467
+ const steps = 100;
468
+ for (let i = 0; i <= steps; i++) {
469
+ const t = i / steps;
470
+ const mt = 1 - t;
471
+ const x = Math.round(mt**3*x1 + 3*mt**2*t*cp1x + 3*mt*t**2*cp2x + t**3*x2);
472
+ const y = Math.round(mt**3*y1 + 3*mt**2*t*cp1y + 3*mt*t**2*cp2y + t**3*y2);
473
+ const px = this._tx(x), py = this._ty(y);
474
+ if (this._inBounds(px, py)) this._r.setPixel(px, py, { ...opts, color });
475
+ }
476
+ return this;
477
+ }
478
+
479
+ /**
480
+ * Quadratic bezier curve.
481
+ */
482
+ quadBezier(x1, y1, cpx, cpy, x2, y2, opts = {}) {
483
+ const color = parseColor(opts.color ?? this._color, opts.alpha ?? this._alpha);
484
+ const steps = 100;
485
+ for (let i = 0; i <= steps; i++) {
486
+ const t = i / steps;
487
+ const mt = 1 - t;
488
+ const x = Math.round(mt**2*x1 + 2*mt*t*cpx + t**2*x2);
489
+ const y = Math.round(mt**2*y1 + 2*mt*t*cpy + t**2*y2);
490
+ const px = this._tx(x), py = this._ty(y);
491
+ if (this._inBounds(px, py)) this._r.setPixel(px, py, { ...opts, color });
492
+ }
493
+ return this;
494
+ }
495
+
496
+ // ── Text ───────────────────────────────────────────────────────────────────
497
+
498
+ /**
499
+ * Write text. Uses renderer.writeText() if available,
500
+ * otherwise falls back to a built-in 5×7 bitmap font.
501
+ */
502
+ text(x, y, str, opts = {}) {
503
+ if (this._r.writeText) {
504
+ const color = parseColor(opts.color ?? this._color, opts.alpha ?? this._alpha);
505
+ this._r.writeText(this._tx(x), this._ty(y), str, { ...opts, color });
506
+ return this;
507
+ }
508
+ return this._bitmapText(x, y, str, opts);
509
+ }
510
+
511
+ _bitmapText(x, y, str, opts = {}) {
512
+ const color = parseColor(opts.color ?? this._color, opts.alpha ?? this._alpha);
513
+ const scale = opts.scale ?? 1;
514
+ let curX = x;
515
+ for (const ch of str) {
516
+ const glyph = FONT5X7[ch] ?? FONT5X7['?'];
517
+ for (let row = 0; row < 7; row++) {
518
+ const bits = glyph[row] ?? 0;
519
+ for (let col = 0; col < 5; col++) {
520
+ if (bits & (1 << (4 - col))) {
521
+ for (let sy = 0; sy < scale; sy++)
522
+ for (let sx = 0; sx < scale; sx++) {
523
+ const px = this._tx(curX + col * scale + sx);
524
+ const py = this._ty(y + row * scale + sy);
525
+ if (this._inBounds(px, py)) this._r.setPixel(px, py, { ...opts, color });
526
+ }
527
+ }
528
+ }
529
+ }
530
+ curX += (5 + 1) * scale;
531
+ }
532
+ return this;
533
+ }
534
+
535
+ // ── Effects ────────────────────────────────────────────────────────────────
536
+
537
+ /**
538
+ * Flood-fill from (x,y) with a color. Requires renderer.getPixel().
539
+ */
540
+ fill_(x, y, fillColor, opts = {}) {
541
+ if (!this._r.getPixel) throw new Error('kitcanvas: fill() requires renderer.getPixel(x, y)');
542
+ const color = parseColor(fillColor ?? this._fill);
543
+ const target = this._r.getPixel(this._tx(x), this._ty(y));
544
+ const tHex = colorToHex(parseColor(target.color ?? target));
545
+ const fHex = colorToHex(color);
546
+ if (tHex === fHex) return this;
547
+
548
+ const stack = [[Math.round(x), Math.round(y)]];
549
+ const seen = new Set();
550
+
551
+ while (stack.length) {
552
+ const [cx, cy] = stack.pop();
553
+ const px = this._tx(cx), py = this._ty(cy);
554
+ if (!this._inBounds(px, py)) continue;
555
+ const key = `${px},${py}`;
556
+ if (seen.has(key)) continue;
557
+ seen.add(key);
558
+ const cur = this._r.getPixel(px, py);
559
+ if (colorToHex(parseColor(cur.color ?? cur)) !== tHex) continue;
560
+ this._r.setPixel(px, py, { ...opts, color });
561
+ stack.push([cx+1,cy],[cx-1,cy],[cx,cy+1],[cx,cy-1]);
562
+ }
563
+ return this;
564
+ }
565
+
566
+ /**
567
+ * Draw a gradient-filled rectangle (linear, left→right or top→bottom).
568
+ */
569
+ gradient(x, y, w, h, colorFrom, colorTo, opts = {}) {
570
+ const dir = opts.direction ?? 'horizontal';
571
+ const c1 = parseColor(colorFrom), c2 = parseColor(colorTo);
572
+ for (let row = 0; row < h; row++) {
573
+ for (let col = 0; col < w; col++) {
574
+ const t = dir === 'horizontal' ? col / (w - 1) : row / (h - 1);
575
+ const color = lerpColor(c1, c2, t);
576
+ const px = this._tx(x+col), py = this._ty(y+row);
577
+ if (this._inBounds(px, py)) this._r.setPixel(px, py, { ...opts, color });
578
+ }
579
+ }
580
+ return this;
581
+ }
582
+
583
+ /**
584
+ * Draw a radial gradient in a circle.
585
+ */
586
+ radialGradient(cx, cy, radius, colorInner, colorOuter, opts = {}) {
587
+ const c1 = parseColor(colorInner), c2 = parseColor(colorOuter);
588
+ for (let y = -radius; y <= radius; y++) {
589
+ for (let x = -radius; x <= radius; x++) {
590
+ const dist = Math.sqrt(x*x + y*y);
591
+ if (dist > radius) continue;
592
+ const t = dist / radius;
593
+ const color = lerpColor(c1, c2, t);
594
+ const px = this._tx(cx+x), py = this._ty(cy+y);
595
+ if (this._inBounds(px, py)) this._r.setPixel(px, py, { ...opts, color });
596
+ }
597
+ }
598
+ return this;
599
+ }
600
+
601
+ // ── Image ops ──────────────────────────────────────────────────────────────
602
+
603
+ /**
604
+ * Copy a region from another Canvas onto this one.
605
+ * Requires source renderer.getPixel().
606
+ */
607
+ drawCanvas(srcCanvas, sx, sy, sw, sh, dx, dy, opts = {}) {
608
+ if (!srcCanvas._r.getPixel) throw new Error('kitcanvas: drawCanvas requires source renderer.getPixel()');
609
+ for (let y = 0; y < sh; y++) {
610
+ for (let x = 0; x < sw; x++) {
611
+ const pixel = srcCanvas._r.getPixel(sx+x, sy+y);
612
+ if (!pixel) continue;
613
+ const color = parseColor(pixel.color ?? pixel);
614
+ const px = this._tx(dx+x), py = this._ty(dy+y);
615
+ if (this._inBounds(px, py)) this._r.setPixel(px, py, { ...opts, color });
616
+ }
617
+ }
618
+ return this;
619
+ }
620
+
621
+ // ── Renderer passthrough ───────────────────────────────────────────────────
622
+
623
+ /** Flush renderer (if supported). */
624
+ flush() { if (this._r.flush) this._r.flush(); return this; }
625
+ /** Save to file (if supported). */
626
+ save(path) { if (this._r.save) this._r.save(path); return this; }
627
+ /** Get raw buffer (if supported). */
628
+ toBuffer() { return this._r.toBuffer ? this._r.toBuffer() : null; }
629
+ /** Get data URL (if supported). */
630
+ toDataURL() { return this._r.toDataURL ? this._r.toDataURL() : null; }
631
+ /** Resize (if supported). */
632
+ resize(w, h) { if (this._r.resize) this._r.resize(w, h); return this; }
633
+ /** Access raw renderer. */
634
+ get renderer(){ return this._r; }
635
+ }
636
+
637
+ // ─── BUILT-IN RENDERERS ───────────────────────────────────────────────────────
638
+
639
+ /**
640
+ * In-memory pixel buffer renderer.
641
+ * Stores pixels as { r, g, b, a } in a flat array.
642
+ * Useful for testing, server-side rendering, or custom export.
643
+ */
644
+ class BufferRenderer {
645
+ constructor(width, height, background = 'white') {
646
+ this.width = width;
647
+ this.height = height;
648
+ const bg = parseColor(background);
649
+ this._buf = new Array(width * height).fill(null).map(() => ({ ...bg }));
650
+ }
651
+
652
+ setPixel(x, y, opts) {
653
+ if (x < 0 || y < 0 || x >= this.width || y >= this.height) return;
654
+ const color = parseColor(opts.color ?? opts);
655
+ this._buf[y * this.width + x] = { r: color.r, g: color.g, b: color.b, a: color.a };
656
+ }
657
+
658
+ getPixel(x, y) {
659
+ if (x < 0 || y < 0 || x >= this.width || y >= this.height) return null;
660
+ return { color: { ...this._buf[y * this.width + x] } };
661
+ }
662
+
663
+ clear(color = { r:255, g:255, b:255, a:255 }) {
664
+ const c = parseColor(color);
665
+ this._buf.fill(null).forEach((_, i) => this._buf[i] = { ...c });
666
+ }
667
+
668
+ toBuffer() {
669
+ // Returns raw RGBA Buffer
670
+ const buf = Buffer.allocUnsafe(this.width * this.height * 4);
671
+ for (let i = 0; i < this._buf.length; i++) {
672
+ const { r, g, b, a } = this._buf[i];
673
+ buf[i*4+0] = r; buf[i*4+1] = g; buf[i*4+2] = b; buf[i*4+3] = a;
674
+ }
675
+ return buf;
676
+ }
677
+
678
+ toDataURL() {
679
+ // Returns a simple PPM-encoded data URL (works anywhere without deps)
680
+ const header = `P6\n${this.width} ${this.height}\n255\n`;
681
+ const pixels = Buffer.allocUnsafe(this.width * this.height * 3);
682
+ for (let i = 0; i < this._buf.length; i++) {
683
+ pixels[i*3+0] = this._buf[i].r;
684
+ pixels[i*3+1] = this._buf[i].g;
685
+ pixels[i*3+2] = this._buf[i].b;
686
+ }
687
+ return 'data:image/x-portable-pixmap;base64,' + Buffer.concat([Buffer.from(header), pixels]).toString('base64');
688
+ }
689
+
690
+ save(path) {
691
+ const fs = require('fs');
692
+ const buf = this.toBuffer();
693
+ // Save as raw RGBA binary
694
+ fs.writeFileSync(path, buf);
695
+ }
696
+
697
+ toPPM() {
698
+ const lines = [`P3`, `${this.width} ${this.height}`, `255`];
699
+ for (let y = 0; y < this.height; y++) {
700
+ const row = [];
701
+ for (let x = 0; x < this.width; x++) {
702
+ const { r, g, b } = this._buf[y * this.width + x];
703
+ row.push(r, g, b);
704
+ }
705
+ lines.push(row.join(' '));
706
+ }
707
+ return lines.join('\n');
708
+ }
709
+
710
+ savePPM(path) {
711
+ require('fs').writeFileSync(path, this.toPPM());
712
+ }
713
+ }
714
+
715
+ /**
716
+ * ASCII/terminal renderer.
717
+ * Converts pixels to colored terminal output using ANSI 256-color or truecolor.
718
+ */
719
+ class TerminalRenderer {
720
+ constructor(width, height) {
721
+ this.width = width;
722
+ this.height = height;
723
+ this._buf = new Array(width * height).fill({ r:0,g:0,b:0,a:0 });
724
+ }
725
+
726
+ setPixel(x, y, opts) {
727
+ if (x < 0 || y < 0 || x >= this.width || y >= this.height) return;
728
+ this._buf[y * this.width + x] = parseColor(opts.color ?? opts);
729
+ }
730
+
731
+ getPixel(x, y) {
732
+ if (x < 0 || y < 0 || x >= this.width || y >= this.height) return null;
733
+ return { color: { ...this._buf[y * this.width + x] } };
734
+ }
735
+
736
+ clear(color = { r:0,g:0,b:0,a:255 }) {
737
+ const c = parseColor(color);
738
+ this._buf = this._buf.map(() => ({ ...c }));
739
+ }
740
+
741
+ flush() { process.stdout.write(this.toString()); }
742
+
743
+ toString() {
744
+ const lines = [];
745
+ // Use half-block trick: each terminal row = 2 pixel rows
746
+ for (let y = 0; y < this.height; y += 2) {
747
+ let line = '';
748
+ for (let x = 0; x < this.width; x++) {
749
+ const top = this._buf[y * this.width + x] ?? { r:0,g:0,b:0 };
750
+ const bot = (y+1 < this.height) ? (this._buf[(y+1) * this.width + x] ?? { r:0,g:0,b:0 }) : { r:0,g:0,b:0 };
751
+ line += `\x1b[38;2;${top.r};${top.g};${top.b}m\x1b[48;2;${bot.r};${bot.g};${bot.b}m▀`;
752
+ }
753
+ lines.push(line + '\x1b[0m');
754
+ }
755
+ return lines.join('\n') + '\n';
756
+ }
757
+ }
758
+
759
+ /**
760
+ * SVG renderer — builds an SVG string from draw calls.
761
+ */
762
+ class SVGRenderer {
763
+ constructor(width, height, background = 'white') {
764
+ this.width = width;
765
+ this.height = height;
766
+ this._bg = background;
767
+ this._elements = [];
768
+ }
769
+
770
+ setPixel(x, y, opts) {
771
+ const { r, g, b, a } = parseColor(opts.color ?? opts);
772
+ const fill = `rgba(${r},${g},${b},${(a/255).toFixed(3)})`;
773
+ this._elements.push(`<rect x="${x}" y="${y}" width="1" height="1" fill="${fill}"/>`);
774
+ }
775
+
776
+ writeText(x, y, text, opts = {}) {
777
+ const { r, g, b } = parseColor(opts.color ?? 'black');
778
+ const size = opts.fontSize ?? 12;
779
+ const font = opts.fontFamily ?? 'monospace';
780
+ const fill = `rgb(${r},${g},${b})`;
781
+ this._elements.push(`<text x="${x}" y="${y}" font-size="${size}" font-family="${font}" fill="${fill}">${text.replace(/&/g,'&amp;').replace(/</g,'&lt;')}</text>`);
782
+ }
783
+
784
+ clear(color) {
785
+ const c = parseColor(color ?? this._bg);
786
+ this._bg = `rgb(${c.r},${c.g},${c.b})`;
787
+ this._elements = [];
788
+ }
789
+
790
+ flush() { /* no-op for SVG */ }
791
+
792
+ toString() {
793
+ return `<?xml version="1.0" encoding="UTF-8"?>\n` +
794
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${this.width}" height="${this.height}">\n` +
795
+ ` <rect width="${this.width}" height="${this.height}" fill="${this._bg}"/>\n` +
796
+ ` ${this._elements.join('\n ')}\n` +
797
+ `</svg>`;
798
+ }
799
+
800
+ save(path) { require('fs').writeFileSync(path, this.toString()); }
801
+ toDataURL() { return 'data:image/svg+xml;base64,' + Buffer.from(this.toString()).toString('base64'); }
802
+ }
803
+
804
+ // ─── 5×7 BITMAP FONT ─────────────────────────────────────────────────────────
805
+ // Each glyph is 7 rows of 5 bits (MSB = leftmost pixel)
806
+
807
+ const FONT5X7 = {
808
+ ' ': [0b00000,0b00000,0b00000,0b00000,0b00000,0b00000,0b00000],
809
+ '!': [0b00100,0b00100,0b00100,0b00100,0b00000,0b00000,0b00100],
810
+ '"': [0b01010,0b01010,0b00000,0b00000,0b00000,0b00000,0b00000],
811
+ '#': [0b01010,0b11111,0b01010,0b01010,0b11111,0b01010,0b00000],
812
+ '$': [0b00100,0b01111,0b10100,0b01110,0b00101,0b11110,0b00100],
813
+ '%': [0b11000,0b11001,0b00010,0b00100,0b01001,0b00011,0b00000],
814
+ '&': [0b01100,0b10010,0b10100,0b01000,0b10101,0b10010,0b01101],
815
+ "'": [0b00100,0b00100,0b00000,0b00000,0b00000,0b00000,0b00000],
816
+ '(': [0b00010,0b00100,0b01000,0b01000,0b01000,0b00100,0b00010],
817
+ ')': [0b01000,0b00100,0b00010,0b00010,0b00010,0b00100,0b01000],
818
+ '*': [0b00000,0b00100,0b10101,0b01110,0b10101,0b00100,0b00000],
819
+ '+': [0b00000,0b00100,0b00100,0b11111,0b00100,0b00100,0b00000],
820
+ ',': [0b00000,0b00000,0b00000,0b00000,0b00110,0b00100,0b01000],
821
+ '-': [0b00000,0b00000,0b00000,0b11111,0b00000,0b00000,0b00000],
822
+ '.': [0b00000,0b00000,0b00000,0b00000,0b00000,0b00110,0b00110],
823
+ '/': [0b00001,0b00010,0b00100,0b01000,0b10000,0b00000,0b00000],
824
+ '0': [0b01110,0b10001,0b10011,0b10101,0b11001,0b10001,0b01110],
825
+ '1': [0b00100,0b01100,0b00100,0b00100,0b00100,0b00100,0b01110],
826
+ '2': [0b01110,0b10001,0b00001,0b00110,0b01000,0b10000,0b11111],
827
+ '3': [0b11111,0b00010,0b00100,0b00010,0b00001,0b10001,0b01110],
828
+ '4': [0b00010,0b00110,0b01010,0b10010,0b11111,0b00010,0b00010],
829
+ '5': [0b11111,0b10000,0b11110,0b00001,0b00001,0b10001,0b01110],
830
+ '6': [0b00110,0b01000,0b10000,0b11110,0b10001,0b10001,0b01110],
831
+ '7': [0b11111,0b00001,0b00010,0b00100,0b01000,0b01000,0b01000],
832
+ '8': [0b01110,0b10001,0b10001,0b01110,0b10001,0b10001,0b01110],
833
+ '9': [0b01110,0b10001,0b10001,0b01111,0b00001,0b00010,0b01100],
834
+ ':': [0b00000,0b00110,0b00110,0b00000,0b00110,0b00110,0b00000],
835
+ ';': [0b00000,0b00110,0b00110,0b00000,0b00110,0b00100,0b01000],
836
+ '<': [0b00010,0b00100,0b01000,0b10000,0b01000,0b00100,0b00010],
837
+ '=': [0b00000,0b00000,0b11111,0b00000,0b11111,0b00000,0b00000],
838
+ '>': [0b01000,0b00100,0b00010,0b00001,0b00010,0b00100,0b01000],
839
+ '?': [0b01110,0b10001,0b00001,0b00110,0b00100,0b00000,0b00100],
840
+ '@': [0b01110,0b10001,0b10101,0b10111,0b10110,0b10000,0b01110],
841
+ 'A': [0b01110,0b10001,0b10001,0b11111,0b10001,0b10001,0b10001],
842
+ 'B': [0b11110,0b10001,0b10001,0b11110,0b10001,0b10001,0b11110],
843
+ 'C': [0b01110,0b10001,0b10000,0b10000,0b10000,0b10001,0b01110],
844
+ 'D': [0b11100,0b10010,0b10001,0b10001,0b10001,0b10010,0b11100],
845
+ 'E': [0b11111,0b10000,0b10000,0b11110,0b10000,0b10000,0b11111],
846
+ 'F': [0b11111,0b10000,0b10000,0b11110,0b10000,0b10000,0b10000],
847
+ 'G': [0b01110,0b10001,0b10000,0b10111,0b10001,0b10001,0b01111],
848
+ 'H': [0b10001,0b10001,0b10001,0b11111,0b10001,0b10001,0b10001],
849
+ 'I': [0b01110,0b00100,0b00100,0b00100,0b00100,0b00100,0b01110],
850
+ 'J': [0b00111,0b00010,0b00010,0b00010,0b10010,0b10010,0b01100],
851
+ 'K': [0b10001,0b10010,0b10100,0b11000,0b10100,0b10010,0b10001],
852
+ 'L': [0b10000,0b10000,0b10000,0b10000,0b10000,0b10000,0b11111],
853
+ 'M': [0b10001,0b11011,0b10101,0b10001,0b10001,0b10001,0b10001],
854
+ 'N': [0b10001,0b11001,0b10101,0b10011,0b10001,0b10001,0b10001],
855
+ 'O': [0b01110,0b10001,0b10001,0b10001,0b10001,0b10001,0b01110],
856
+ 'P': [0b11110,0b10001,0b10001,0b11110,0b10000,0b10000,0b10000],
857
+ 'Q': [0b01110,0b10001,0b10001,0b10001,0b10101,0b10010,0b01101],
858
+ 'R': [0b11110,0b10001,0b10001,0b11110,0b10100,0b10010,0b10001],
859
+ 'S': [0b01111,0b10000,0b10000,0b01110,0b00001,0b00001,0b11110],
860
+ 'T': [0b11111,0b00100,0b00100,0b00100,0b00100,0b00100,0b00100],
861
+ 'U': [0b10001,0b10001,0b10001,0b10001,0b10001,0b10001,0b01110],
862
+ 'V': [0b10001,0b10001,0b10001,0b10001,0b10001,0b01010,0b00100],
863
+ 'W': [0b10001,0b10001,0b10001,0b10101,0b10101,0b11011,0b10001],
864
+ 'X': [0b10001,0b10001,0b01010,0b00100,0b01010,0b10001,0b10001],
865
+ 'Y': [0b10001,0b10001,0b01010,0b00100,0b00100,0b00100,0b00100],
866
+ 'Z': [0b11111,0b00001,0b00010,0b00100,0b01000,0b10000,0b11111],
867
+ '[': [0b01110,0b01000,0b01000,0b01000,0b01000,0b01000,0b01110],
868
+ ']': [0b01110,0b00010,0b00010,0b00010,0b00010,0b00010,0b01110],
869
+ '^': [0b00100,0b01010,0b10001,0b00000,0b00000,0b00000,0b00000],
870
+ '_': [0b00000,0b00000,0b00000,0b00000,0b00000,0b00000,0b11111],
871
+ 'a': [0b00000,0b00000,0b01110,0b00001,0b01111,0b10001,0b01111],
872
+ 'b': [0b10000,0b10000,0b11110,0b10001,0b10001,0b10001,0b11110],
873
+ 'c': [0b00000,0b00000,0b01110,0b10000,0b10000,0b10001,0b01110],
874
+ 'd': [0b00001,0b00001,0b01111,0b10001,0b10001,0b10001,0b01111],
875
+ 'e': [0b00000,0b00000,0b01110,0b10001,0b11111,0b10000,0b01110],
876
+ 'f': [0b00110,0b01001,0b01000,0b11100,0b01000,0b01000,0b01000],
877
+ 'g': [0b00000,0b01111,0b10001,0b10001,0b01111,0b00001,0b01110],
878
+ 'h': [0b10000,0b10000,0b11110,0b10001,0b10001,0b10001,0b10001],
879
+ 'i': [0b00100,0b00000,0b01100,0b00100,0b00100,0b00100,0b01110],
880
+ 'j': [0b00010,0b00000,0b00110,0b00010,0b00010,0b10010,0b01100],
881
+ 'k': [0b10000,0b10010,0b10100,0b11000,0b10100,0b10010,0b10001],
882
+ 'l': [0b01100,0b00100,0b00100,0b00100,0b00100,0b00100,0b01110],
883
+ 'm': [0b00000,0b00000,0b11010,0b10101,0b10101,0b10001,0b10001],
884
+ 'n': [0b00000,0b00000,0b11110,0b10001,0b10001,0b10001,0b10001],
885
+ 'o': [0b00000,0b00000,0b01110,0b10001,0b10001,0b10001,0b01110],
886
+ 'p': [0b00000,0b11110,0b10001,0b10001,0b11110,0b10000,0b10000],
887
+ 'q': [0b00000,0b01111,0b10001,0b10001,0b01111,0b00001,0b00001],
888
+ 'r': [0b00000,0b00000,0b10110,0b11001,0b10000,0b10000,0b10000],
889
+ 's': [0b00000,0b00000,0b01110,0b10000,0b01110,0b00001,0b11110],
890
+ 't': [0b01000,0b01000,0b11110,0b01000,0b01000,0b01001,0b00110],
891
+ 'u': [0b00000,0b00000,0b10001,0b10001,0b10001,0b10011,0b01101],
892
+ 'v': [0b00000,0b00000,0b10001,0b10001,0b10001,0b01010,0b00100],
893
+ 'w': [0b00000,0b00000,0b10001,0b10001,0b10101,0b10101,0b01010],
894
+ 'x': [0b00000,0b00000,0b10001,0b01010,0b00100,0b01010,0b10001],
895
+ 'y': [0b00000,0b00000,0b10001,0b10001,0b01111,0b00001,0b01110],
896
+ 'z': [0b00000,0b00000,0b11111,0b00010,0b00100,0b01000,0b11111],
897
+ };
898
+
899
+ // ─── EXPORTS ─────────────────────────────────────────────────────────────────
900
+
901
+ module.exports = {
902
+ kitdef: {
903
+ Canvas,
904
+ // Built-in renderers
905
+ BufferRenderer,
906
+ TerminalRenderer,
907
+ SVGRenderer,
908
+ // Color utilities
909
+ parseColor,
910
+ colorToHex,
911
+ lerpColor,
912
+ NAMED_COLORS,
913
+ }
914
+ };