termdot 0.0.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.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # termdot
2
+
3
+ Draw in the terminal with Unicode braille characters (U+2800–U+28FF). Each character is a 2×4 pixel grid — giving you 8 dots per cell for high-resolution terminal graphics.
4
+
5
+ ## Install
6
+
7
+ ```sh
8
+ npm install termdot
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ import {createCanvas, set, frame, line} from 'termdot';
15
+
16
+ const c = createCanvas();
17
+
18
+ // Draw a sine wave
19
+ for (let x = 0; x < 100; x++) {
20
+ set(c, x, Math.sin(x / 10) * 10 + 10);
21
+ }
22
+
23
+ // Draw a line
24
+ for (const pt of line(0, 0, 50, 20)) {
25
+ set(c, pt.x, pt.y);
26
+ }
27
+
28
+ console.log(frame(c));
29
+ ```
30
+
31
+ ## API
32
+
33
+ ### Sparse Canvas
34
+
35
+ Auto-expanding canvas backed by a `Map`. Grows as you draw.
36
+
37
+ ```ts
38
+ createCanvas(options?) // Create a canvas
39
+ set(canvas, x, y) // Draw a pixel
40
+ unset(canvas, x, y) // Erase a pixel
41
+ toggle(canvas, x, y) // Toggle a pixel
42
+ get(canvas, x, y) // Check if pixel is set
43
+ clear(canvas) // Clear all pixels
44
+ setText(canvas, x, y, str) // Overlay text at position
45
+ frame(canvas, bounds?) // Render to string
46
+ rows(canvas, bounds?) // Render to string[]
47
+ ```
48
+
49
+ ### Fixed Canvas
50
+
51
+ Fixed-size canvas backed by a `Uint8Array`. Fast, predictable bounds.
52
+
53
+ ```ts
54
+ createFixedCanvas(width, height) // Create fixed canvas
55
+ fixedSet(canvas, x, y) // Draw a pixel
56
+ fixedUnset(canvas, x, y) // Erase a pixel
57
+ fixedToggle(canvas, x, y) // Toggle a pixel
58
+ fixedGet(canvas, x, y) // Check if pixel is set
59
+ fixedClear(canvas) // Clear all pixels
60
+ fixedFrame(canvas, delimiter?) // Render to string
61
+ ```
62
+
63
+ ### Drawing
64
+
65
+ ```ts
66
+ line(x1, y1, x2, y2) // Line generator (Bresenham)
67
+ polygon(centerX, centerY, sides, radius) // Regular polygon generator
68
+ ```
69
+
70
+ ### Turtle Graphics
71
+
72
+ ```ts
73
+ createTurtle(x?, y?) // Create turtle at position
74
+ turtleForward(t, step) // Move forward
75
+ turtleBack(t, step) // Move backward
76
+ turtleRight(t, angle) // Rotate clockwise
77
+ turtleLeft(t, angle) // Rotate counter-clockwise
78
+ turtleUp(t) // Lift brush (stop drawing)
79
+ turtleDown(t) // Lower brush (start drawing)
80
+ turtleMove(t, x, y) // Move to absolute position
81
+ ```
82
+
83
+ ### Utilities
84
+
85
+ ```ts
86
+ getTerminalSize() // Get terminal {width, height}
87
+ animate(canvas, generator, options?) // Run animation loop
88
+ normalize(coord) // Round to nearest int
89
+ getPos(x, y) // Pixel → cell coords
90
+ ```
91
+
92
+ ## Braille Dot Layout
93
+
94
+ Each character cell maps to a 2×4 pixel grid:
95
+
96
+ ```
97
+ ,___,
98
+ |1 4|
99
+ |2 5|
100
+ |3 6|
101
+ |7 8|
102
+ `````
103
+ ```
104
+
105
+ ## Examples
106
+
107
+ ```sh
108
+ npx tsx examples/basic.ts # Sine waves, fills, toggles
109
+ npx tsx examples/turtle.ts # Spirograph
110
+ npx tsx examples/clock.ts # Animated analog clock
111
+ npx tsx examples/sine-wave.ts # Animated sine wave
112
+ npx tsx examples/rotating-cube.ts # 3D wireframe cube
113
+ npx tsx examples/cube.ts # Perspective-projected cube
114
+ npx tsx examples/speed-test.ts # Benchmark
115
+ npx tsx examples/spinners.ts # Braille status indicators
116
+ npx tsx examples/kitchen-sink.ts # Every feature demo
117
+ ```
118
+
119
+ ## License
120
+
121
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,416 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BRAILLE_OFFSET: () => BRAILLE_OFFSET,
24
+ PIXEL_MAP: () => PIXEL_MAP,
25
+ animate: () => animate,
26
+ clear: () => clear,
27
+ createCanvas: () => createCanvas,
28
+ createFixedCanvas: () => createFixedCanvas,
29
+ createTurtle: () => createTurtle,
30
+ fixedClear: () => fixedClear,
31
+ fixedFrame: () => fixedFrame,
32
+ fixedGet: () => fixedGet,
33
+ fixedSet: () => fixedSet,
34
+ fixedToggle: () => fixedToggle,
35
+ fixedUnset: () => fixedUnset,
36
+ frame: () => frame,
37
+ get: () => get,
38
+ getPos: () => getPos,
39
+ getTerminalSize: () => getTerminalSize,
40
+ line: () => line,
41
+ normalize: () => normalize,
42
+ polygon: () => polygon,
43
+ rows: () => rows,
44
+ set: () => set,
45
+ setText: () => setText,
46
+ toggle: () => toggle,
47
+ turtleBack: () => turtleBack,
48
+ turtleDown: () => turtleDown,
49
+ turtleForward: () => turtleForward,
50
+ turtleLeft: () => turtleLeft,
51
+ turtleMove: () => turtleMove,
52
+ turtleRight: () => turtleRight,
53
+ turtleUp: () => turtleUp,
54
+ unset: () => unset
55
+ });
56
+ module.exports = __toCommonJS(index_exports);
57
+ var BRAILLE_OFFSET = 10240;
58
+ var PIXEL_MAP = [
59
+ [1, 8],
60
+ [2, 16],
61
+ [4, 32],
62
+ [64, 128]
63
+ ];
64
+ function normalize(coord) {
65
+ return Math.round(coord);
66
+ }
67
+ function getPos(x, y) {
68
+ return [Math.floor(normalize(x) / 2), Math.floor(normalize(y) / 4)];
69
+ }
70
+ function createCanvas(options) {
71
+ return {
72
+ chars: /* @__PURE__ */ new Map(),
73
+ lineEnding: options?.lineEnding ?? "\n"
74
+ };
75
+ }
76
+ function clear(canvas) {
77
+ canvas.chars.clear();
78
+ }
79
+ function set(canvas, x, y) {
80
+ const nx = normalize(x);
81
+ const ny = normalize(y);
82
+ const [col, row] = getPos(nx, ny);
83
+ let rowMap = canvas.chars.get(row);
84
+ if (!rowMap) {
85
+ rowMap = /* @__PURE__ */ new Map();
86
+ canvas.chars.set(row, rowMap);
87
+ }
88
+ const current = rowMap.get(col);
89
+ if (current !== void 0 && typeof current !== "number") {
90
+ return;
91
+ }
92
+ const mask = PIXEL_MAP[ny % 4]?.[nx % 2];
93
+ if (mask === void 0) return;
94
+ rowMap.set(col, (current ?? 0) | mask);
95
+ }
96
+ function unset(canvas, x, y) {
97
+ const nx = normalize(x);
98
+ const ny = normalize(y);
99
+ const [col, row] = getPos(nx, ny);
100
+ const rowMap = canvas.chars.get(row);
101
+ if (!rowMap) return;
102
+ const current = rowMap.get(col);
103
+ if (current === void 0 || typeof current !== "number") return;
104
+ const mask = PIXEL_MAP[ny % 4]?.[nx % 2];
105
+ if (mask === void 0) return;
106
+ const updated = current & ~mask;
107
+ if (updated === 0) {
108
+ rowMap.delete(col);
109
+ if (rowMap.size === 0) {
110
+ canvas.chars.delete(row);
111
+ }
112
+ return;
113
+ }
114
+ rowMap.set(col, updated);
115
+ }
116
+ function toggle(canvas, x, y) {
117
+ const nx = normalize(x);
118
+ const ny = normalize(y);
119
+ const [col, row] = getPos(nx, ny);
120
+ const rowMap = canvas.chars.get(row);
121
+ const current = rowMap?.get(col);
122
+ const mask = PIXEL_MAP[ny % 4]?.[nx % 2];
123
+ if (mask === void 0) return;
124
+ if (current !== void 0 && (typeof current !== "number" || (current & mask) !== 0)) {
125
+ unset(canvas, x, y);
126
+ } else {
127
+ set(canvas, x, y);
128
+ }
129
+ }
130
+ function get(canvas, x, y) {
131
+ const nx = normalize(x);
132
+ const ny = normalize(y);
133
+ const [col, row] = getPos(nx, ny);
134
+ const rowMap = canvas.chars.get(row);
135
+ if (!rowMap) return false;
136
+ const cell = rowMap.get(col);
137
+ if (cell === void 0) return false;
138
+ if (typeof cell !== "number") return true;
139
+ const mask = PIXEL_MAP[ny % 4]?.[nx % 2];
140
+ if (mask === void 0) return false;
141
+ return (cell & mask) !== 0;
142
+ }
143
+ function setText(canvas, x, y, text) {
144
+ const [col, row] = getPos(x, y);
145
+ let rowMap = canvas.chars.get(row);
146
+ if (!rowMap) {
147
+ rowMap = /* @__PURE__ */ new Map();
148
+ canvas.chars.set(row, rowMap);
149
+ }
150
+ for (let i = 0; i < text.length; i++) {
151
+ rowMap.set(col + i, text[i]);
152
+ }
153
+ }
154
+ function rows(canvas, bounds) {
155
+ if (canvas.chars.size === 0) return [];
156
+ const allRows = [...canvas.chars.keys()];
157
+ const minRow = bounds?.minY != null ? Math.floor(bounds.minY / 4) : Math.min(...allRows);
158
+ const maxRow = bounds?.maxY != null ? Math.floor((bounds.maxY - 1) / 4) : Math.max(...allRows);
159
+ let globalMinCol;
160
+ if (bounds?.minX != null) {
161
+ globalMinCol = Math.floor(bounds.minX / 2);
162
+ } else {
163
+ let min = Number.POSITIVE_INFINITY;
164
+ for (const rowMap of canvas.chars.values()) {
165
+ for (const c of rowMap.keys()) {
166
+ if (c < min) min = c;
167
+ }
168
+ }
169
+ globalMinCol = min === Number.POSITIVE_INFINITY ? 0 : min;
170
+ }
171
+ const result = [];
172
+ for (let rowNum = minRow; rowNum <= maxRow; rowNum++) {
173
+ const rowMap = canvas.chars.get(rowNum);
174
+ if (!rowMap) {
175
+ result.push("");
176
+ continue;
177
+ }
178
+ let maxCol;
179
+ if (bounds?.maxX != null) {
180
+ maxCol = Math.floor((bounds.maxX - 1) / 2);
181
+ } else {
182
+ maxCol = Math.max(...rowMap.keys());
183
+ }
184
+ const chars = [];
185
+ for (let c = globalMinCol; c <= maxCol; c++) {
186
+ const cell = rowMap.get(c);
187
+ if (cell === void 0 || cell === 0) {
188
+ chars.push(" ");
189
+ } else if (typeof cell !== "number") {
190
+ chars.push(cell);
191
+ } else {
192
+ chars.push(String.fromCharCode(BRAILLE_OFFSET + cell));
193
+ }
194
+ }
195
+ result.push(chars.join(""));
196
+ }
197
+ return result;
198
+ }
199
+ function frame(canvas, bounds) {
200
+ return rows(canvas, bounds).join(canvas.lineEnding);
201
+ }
202
+ function* line(x1, y1, x2, y2) {
203
+ const nx1 = normalize(x1);
204
+ const ny1 = normalize(y1);
205
+ const nx2 = normalize(x2);
206
+ const ny2 = normalize(y2);
207
+ const xDiff = Math.abs(nx2 - nx1);
208
+ const yDiff = Math.abs(ny2 - ny1);
209
+ const xDir = nx1 <= nx2 ? 1 : -1;
210
+ const yDir = ny1 <= ny2 ? 1 : -1;
211
+ const steps = Math.max(xDiff, yDiff);
212
+ for (let i = 0; i <= steps; i++) {
213
+ let px = nx1;
214
+ let py = ny1;
215
+ if (yDiff) {
216
+ py += Math.round(i * yDiff / steps) * yDir;
217
+ }
218
+ if (xDiff) {
219
+ px += Math.round(i * xDiff / steps) * xDir;
220
+ }
221
+ yield { x: px, y: py };
222
+ }
223
+ }
224
+ function* polygon(centerX = 0, centerY = 0, sides = 4, radius = 4) {
225
+ const degree = 360 / sides;
226
+ for (let n = 0; n < sides; n++) {
227
+ const a = n * degree;
228
+ const b = (n + 1) * degree;
229
+ const x1 = (centerX + Math.cos(a * Math.PI / 180)) * ((radius + 1) / 2);
230
+ const y1 = (centerY + Math.sin(a * Math.PI / 180)) * ((radius + 1) / 2);
231
+ const x2 = (centerX + Math.cos(b * Math.PI / 180)) * ((radius + 1) / 2);
232
+ const y2 = (centerY + Math.sin(b * Math.PI / 180)) * ((radius + 1) / 2);
233
+ for (const pt of line(x1, y1, x2, y2)) {
234
+ yield pt;
235
+ }
236
+ }
237
+ }
238
+ function createTurtle(posX = 0, posY = 0) {
239
+ return {
240
+ canvas: createCanvas(),
241
+ posX,
242
+ posY,
243
+ rotation: 0,
244
+ brushOn: true
245
+ };
246
+ }
247
+ function turtleUp(t) {
248
+ t.brushOn = false;
249
+ }
250
+ function turtleDown(t) {
251
+ t.brushOn = true;
252
+ }
253
+ function turtleMove(t, x, y) {
254
+ if (t.brushOn) {
255
+ for (const pt of line(t.posX, t.posY, x, y)) {
256
+ set(t.canvas, pt.x, pt.y);
257
+ }
258
+ }
259
+ t.posX = x;
260
+ t.posY = y;
261
+ }
262
+ function turtleForward(t, step) {
263
+ const x = t.posX + Math.cos(t.rotation * Math.PI / 180) * step;
264
+ const y = t.posY + Math.sin(t.rotation * Math.PI / 180) * step;
265
+ const prevBrush = t.brushOn;
266
+ t.brushOn = true;
267
+ turtleMove(t, x, y);
268
+ t.brushOn = prevBrush;
269
+ }
270
+ function turtleBack(t, step) {
271
+ turtleForward(t, -step);
272
+ }
273
+ function turtleRight(t, angle) {
274
+ t.rotation += angle;
275
+ }
276
+ function turtleLeft(t, angle) {
277
+ t.rotation -= angle;
278
+ }
279
+ function getTerminalSize() {
280
+ const width = process.stdout?.columns ?? 80;
281
+ const height = process.stdout?.rows ?? 25;
282
+ return { width, height };
283
+ }
284
+ function sleep(ms) {
285
+ return new Promise((resolve) => setTimeout(resolve, ms));
286
+ }
287
+ async function animate(canvas, fn, options) {
288
+ const delay = options?.delay ?? 1e3 / 24;
289
+ for (const points of fn()) {
290
+ if (options?.signal?.aborted) break;
291
+ for (const pt of points) {
292
+ set(canvas, pt.x, pt.y);
293
+ }
294
+ const f = frame(canvas);
295
+ process.stdout.write(`\x1B[H\x1B[J${f}
296
+ `);
297
+ if (delay > 0) {
298
+ await sleep(delay);
299
+ }
300
+ clear(canvas);
301
+ }
302
+ }
303
+ function createFixedCanvas(width, height) {
304
+ const w = Math.floor(width / 2) * 2;
305
+ const h = Math.floor(height / 4) * 4;
306
+ return {
307
+ width: w,
308
+ height: h,
309
+ content: new Uint8Array(w * h / 8)
310
+ };
311
+ }
312
+ function fixedClear(canvas) {
313
+ canvas.content.fill(0);
314
+ }
315
+ function fixedSet(canvas, x, y) {
316
+ if (!(x >= 0 && x < canvas.width && y >= 0 && y < canvas.height)) return;
317
+ const fx = Math.floor(x);
318
+ const fy = Math.floor(y);
319
+ const nx = Math.floor(fx / 2);
320
+ const ny = Math.floor(fy / 4);
321
+ const coord = nx + canvas.width / 2 * ny;
322
+ const mask = PIXEL_MAP[fy % 4]?.[fx % 2];
323
+ if (mask === void 0) return;
324
+ const val = canvas.content[coord];
325
+ if (val === void 0) return;
326
+ canvas.content[coord] = val | mask;
327
+ }
328
+ function fixedUnset(canvas, x, y) {
329
+ if (!(x >= 0 && x < canvas.width && y >= 0 && y < canvas.height)) return;
330
+ const fx = Math.floor(x);
331
+ const fy = Math.floor(y);
332
+ const nx = Math.floor(fx / 2);
333
+ const ny = Math.floor(fy / 4);
334
+ const coord = nx + canvas.width / 2 * ny;
335
+ const mask = PIXEL_MAP[fy % 4]?.[fx % 2];
336
+ if (mask === void 0) return;
337
+ const val = canvas.content[coord];
338
+ if (val === void 0) return;
339
+ canvas.content[coord] = val & ~mask;
340
+ }
341
+ function fixedToggle(canvas, x, y) {
342
+ if (!(x >= 0 && x < canvas.width && y >= 0 && y < canvas.height)) return;
343
+ const fx = Math.floor(x);
344
+ const fy = Math.floor(y);
345
+ const nx = Math.floor(fx / 2);
346
+ const ny = Math.floor(fy / 4);
347
+ const coord = nx + canvas.width / 2 * ny;
348
+ const mask = PIXEL_MAP[fy % 4]?.[fx % 2];
349
+ if (mask === void 0) return;
350
+ const val = canvas.content[coord];
351
+ if (val === void 0) return;
352
+ canvas.content[coord] = val ^ mask;
353
+ }
354
+ function fixedGet(canvas, x, y) {
355
+ if (!(x >= 0 && x < canvas.width && y >= 0 && y < canvas.height))
356
+ return false;
357
+ const fx = Math.floor(x);
358
+ const fy = Math.floor(y);
359
+ const nx = Math.floor(fx / 2);
360
+ const ny = Math.floor(fy / 4);
361
+ const coord = nx + canvas.width / 2 * ny;
362
+ const mask = PIXEL_MAP[fy % 4]?.[fx % 2];
363
+ if (mask === void 0) return false;
364
+ const val = canvas.content[coord];
365
+ if (val === void 0) return false;
366
+ return (val & mask) !== 0;
367
+ }
368
+ function fixedFrame(canvas, delimiter = "\n") {
369
+ const frameWidth = canvas.width / 2;
370
+ const parts = [];
371
+ for (let i = 0; i < canvas.content.length; i++) {
372
+ if (i % frameWidth === 0) {
373
+ parts.push(delimiter);
374
+ }
375
+ const val = canvas.content[i];
376
+ parts.push(val ? String.fromCharCode(BRAILLE_OFFSET + val) : " ");
377
+ }
378
+ parts.push(delimiter);
379
+ return parts.join("");
380
+ }
381
+ // Annotate the CommonJS export names for ESM import in node:
382
+ 0 && (module.exports = {
383
+ BRAILLE_OFFSET,
384
+ PIXEL_MAP,
385
+ animate,
386
+ clear,
387
+ createCanvas,
388
+ createFixedCanvas,
389
+ createTurtle,
390
+ fixedClear,
391
+ fixedFrame,
392
+ fixedGet,
393
+ fixedSet,
394
+ fixedToggle,
395
+ fixedUnset,
396
+ frame,
397
+ get,
398
+ getPos,
399
+ getTerminalSize,
400
+ line,
401
+ normalize,
402
+ polygon,
403
+ rows,
404
+ set,
405
+ setText,
406
+ toggle,
407
+ turtleBack,
408
+ turtleDown,
409
+ turtleForward,
410
+ turtleLeft,
411
+ turtleMove,
412
+ turtleRight,
413
+ turtleUp,
414
+ unset
415
+ });
416
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * btui — Braille TUI canvas for terminal drawing with Unicode braille characters.\n *\n * Braille dot layout per character cell:\n * ,___,\n * |1 4|\n * |2 5|\n * |3 6|\n * |7 8|\n * `````\n *\n * Each character maps to a 2x4 pixel grid.\n * Unicode braille characters: U+2800 to U+28FF.\n */\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst BRAILLE_OFFSET = 0x2800;\n\nconst PIXEL_MAP: readonly (readonly [number, number])[] = [\n\t[0x01, 0x08],\n\t[0x02, 0x10],\n\t[0x04, 0x20],\n\t[0x40, 0x80],\n] as const;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A single cell can hold a braille bitmask (number) or a text character (string). */\ntype CellValue = number | string;\n\n/** Sparse row → col → cell storage. */\ninterface CanvasChars {\n\treadonly [row: number]: {readonly [col: number]: CellValue} | undefined;\n}\n\n/** The braille canvas data structure. */\ninterface Canvas {\n\tchars: Map<number, Map<number, CellValue>>;\n\treadonly lineEnding: string;\n}\n\n/** Options for creating a canvas. */\ninterface CreateCanvasOptions {\n\treadonly lineEnding?: string;\n}\n\n/** Bounds for frame/rows rendering. */\ninterface FrameBounds {\n\treadonly minX?: number;\n\treadonly minY?: number;\n\treadonly maxX?: number;\n\treadonly maxY?: number;\n}\n\n/** A 2D point. */\ninterface Point {\n\treadonly x: number;\n\treadonly y: number;\n}\n\n/** Turtle graphics state. */\ninterface TurtleState {\n\treadonly canvas: Canvas;\n\tposX: number;\n\tposY: number;\n\trotation: number;\n\tbrushOn: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Coordinate helpers\n// ---------------------------------------------------------------------------\n\n/** Normalize a coordinate to an integer. */\nfunction normalize(coord: number): number {\n\treturn Math.round(coord);\n}\n\n/** Convert pixel (x, y) to cell (col, row). */\nfunction getPos(x: number, y: number): readonly [number, number] {\n\treturn [Math.floor(normalize(x) / 2), Math.floor(normalize(y) / 4)];\n}\n\n// ---------------------------------------------------------------------------\n// Canvas — creation & mutation\n// ---------------------------------------------------------------------------\n\n/** Create a new braille canvas. */\nfunction createCanvas(options?: CreateCanvasOptions): Canvas {\n\treturn {\n\t\tchars: new Map(),\n\t\tlineEnding: options?.lineEnding ?? '\\n',\n\t};\n}\n\n/** Remove all pixels from the canvas. */\nfunction clear(canvas: Canvas): void {\n\tcanvas.chars.clear();\n}\n\n/** Set (draw) a pixel at (x, y). */\nfunction set(canvas: Canvas, x: number, y: number): void {\n\tconst nx = normalize(x);\n\tconst ny = normalize(y);\n\tconst [col, row] = getPos(nx, ny);\n\n\tlet rowMap = canvas.chars.get(row);\n\tif (!rowMap) {\n\t\trowMap = new Map();\n\t\tcanvas.chars.set(row, rowMap);\n\t}\n\n\tconst current = rowMap.get(col);\n\tif (current !== undefined && typeof current !== 'number') {\n\t\treturn;\n\t}\n\n\tconst mask = PIXEL_MAP[ny % 4]?.[nx % 2];\n\tif (mask === undefined) return;\n\n\trowMap.set(col, ((current as number | undefined) ?? 0) | mask);\n}\n\n/** Unset (erase) a pixel at (x, y). */\nfunction unset(canvas: Canvas, x: number, y: number): void {\n\tconst nx = normalize(x);\n\tconst ny = normalize(y);\n\tconst [col, row] = getPos(nx, ny);\n\n\tconst rowMap = canvas.chars.get(row);\n\tif (!rowMap) return;\n\n\tconst current = rowMap.get(col);\n\tif (current === undefined || typeof current !== 'number') return;\n\n\tconst mask = PIXEL_MAP[ny % 4]?.[nx % 2];\n\tif (mask === undefined) return;\n\n\tconst updated = current & ~mask;\n\tif (updated === 0) {\n\t\trowMap.delete(col);\n\t\tif (rowMap.size === 0) {\n\t\t\tcanvas.chars.delete(row);\n\t\t}\n\t\treturn;\n\t}\n\n\trowMap.set(col, updated);\n}\n\n/** Toggle a pixel at (x, y). */\nfunction toggle(canvas: Canvas, x: number, y: number): void {\n\tconst nx = normalize(x);\n\tconst ny = normalize(y);\n\tconst [col, row] = getPos(nx, ny);\n\n\tconst rowMap = canvas.chars.get(row);\n\tconst current = rowMap?.get(col);\n\n\tconst mask = PIXEL_MAP[ny % 4]?.[nx % 2];\n\tif (mask === undefined) return;\n\n\tif (\n\t\tcurrent !== undefined &&\n\t\t(typeof current !== 'number' || (current & mask) !== 0)\n\t) {\n\t\tunset(canvas, x, y);\n\t} else {\n\t\tset(canvas, x, y);\n\t}\n}\n\n/** Get the state of a pixel at (x, y). Returns true if set. */\nfunction get(canvas: Canvas, x: number, y: number): boolean {\n\tconst nx = normalize(x);\n\tconst ny = normalize(y);\n\tconst [col, row] = getPos(nx, ny);\n\n\tconst rowMap = canvas.chars.get(row);\n\tif (!rowMap) return false;\n\n\tconst cell = rowMap.get(col);\n\tif (cell === undefined) return false;\n\tif (typeof cell !== 'number') return true;\n\n\tconst mask = PIXEL_MAP[ny % 4]?.[nx % 2];\n\tif (mask === undefined) return false;\n\n\treturn (cell & mask) !== 0;\n}\n\n/** Set text at the given pixel coords. Each character occupies one cell column. */\nfunction setText(canvas: Canvas, x: number, y: number, text: string): void {\n\tconst [col, row] = getPos(x, y);\n\n\tlet rowMap = canvas.chars.get(row);\n\tif (!rowMap) {\n\t\trowMap = new Map();\n\t\tcanvas.chars.set(row, rowMap);\n\t}\n\n\tfor (let i = 0; i < text.length; i++) {\n\t\trowMap.set(col + i, text[i] as string);\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Canvas — rendering\n// ---------------------------------------------------------------------------\n\n/** Return the canvas content as an array of strings (one per row). */\nfunction rows(canvas: Canvas, bounds?: FrameBounds): readonly string[] {\n\tif (canvas.chars.size === 0) return [];\n\n\tconst allRows = [...canvas.chars.keys()];\n\tconst minRow =\n\t\tbounds?.minY != null\n\t\t\t? Math.floor(bounds.minY / 4)\n\t\t\t: Math.min(...allRows);\n\tconst maxRow =\n\t\tbounds?.maxY != null\n\t\t\t? Math.floor((bounds.maxY - 1) / 4)\n\t\t\t: Math.max(...allRows);\n\n\t// Global min col for left alignment\n\tlet globalMinCol: number;\n\tif (bounds?.minX != null) {\n\t\tglobalMinCol = Math.floor(bounds.minX / 2);\n\t} else {\n\t\tlet min = Number.POSITIVE_INFINITY;\n\t\tfor (const rowMap of canvas.chars.values()) {\n\t\t\tfor (const c of rowMap.keys()) {\n\t\t\t\tif (c < min) min = c;\n\t\t\t}\n\t\t}\n\t\tglobalMinCol = min === Number.POSITIVE_INFINITY ? 0 : min;\n\t}\n\n\tconst result: string[] = [];\n\n\tfor (let rowNum = minRow; rowNum <= maxRow; rowNum++) {\n\t\tconst rowMap = canvas.chars.get(rowNum);\n\t\tif (!rowMap) {\n\t\t\tresult.push('');\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet maxCol: number;\n\t\tif (bounds?.maxX != null) {\n\t\t\tmaxCol = Math.floor((bounds.maxX - 1) / 2);\n\t\t} else {\n\t\t\tmaxCol = Math.max(...rowMap.keys());\n\t\t}\n\n\t\tconst chars: string[] = [];\n\t\tfor (let c = globalMinCol; c <= maxCol; c++) {\n\t\t\tconst cell = rowMap.get(c);\n\t\t\tif (cell === undefined || cell === 0) {\n\t\t\t\tchars.push(' ');\n\t\t\t} else if (typeof cell !== 'number') {\n\t\t\t\tchars.push(cell);\n\t\t\t} else {\n\t\t\t\tchars.push(String.fromCharCode(BRAILLE_OFFSET + cell));\n\t\t\t}\n\t\t}\n\n\t\tresult.push(chars.join(''));\n\t}\n\n\treturn result;\n}\n\n/** Render the canvas as a string. */\nfunction frame(canvas: Canvas, bounds?: FrameBounds): string {\n\treturn rows(canvas, bounds).join(canvas.lineEnding);\n}\n\n// ---------------------------------------------------------------------------\n// Drawing utilities — line\n// ---------------------------------------------------------------------------\n\n/** Generate coordinates along a line from (x1, y1) to (x2, y2) using Bresenham-style interpolation. */\nfunction* line(\n\tx1: number,\n\ty1: number,\n\tx2: number,\n\ty2: number,\n): Generator<Point, void, unknown> {\n\tconst nx1 = normalize(x1);\n\tconst ny1 = normalize(y1);\n\tconst nx2 = normalize(x2);\n\tconst ny2 = normalize(y2);\n\n\tconst xDiff = Math.abs(nx2 - nx1);\n\tconst yDiff = Math.abs(ny2 - ny1);\n\tconst xDir = nx1 <= nx2 ? 1 : -1;\n\tconst yDir = ny1 <= ny2 ? 1 : -1;\n\n\tconst steps = Math.max(xDiff, yDiff);\n\n\tfor (let i = 0; i <= steps; i++) {\n\t\tlet px = nx1;\n\t\tlet py = ny1;\n\n\t\tif (yDiff) {\n\t\t\tpy += Math.round((i * yDiff) / steps) * yDir;\n\t\t}\n\t\tif (xDiff) {\n\t\t\tpx += Math.round((i * xDiff) / steps) * xDir;\n\t\t}\n\n\t\tyield {x: px, y: py};\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Drawing utilities — polygon\n// ---------------------------------------------------------------------------\n\n/** Generate coordinates for a regular polygon. */\nfunction* polygon(\n\tcenterX = 0,\n\tcenterY = 0,\n\tsides = 4,\n\tradius = 4,\n): Generator<Point, void, unknown> {\n\tconst degree = 360 / sides;\n\n\tfor (let n = 0; n < sides; n++) {\n\t\tconst a = n * degree;\n\t\tconst b = (n + 1) * degree;\n\t\tconst x1 =\n\t\t\t(centerX + Math.cos((a * Math.PI) / 180)) * ((radius + 1) / 2);\n\t\tconst y1 =\n\t\t\t(centerY + Math.sin((a * Math.PI) / 180)) * ((radius + 1) / 2);\n\t\tconst x2 =\n\t\t\t(centerX + Math.cos((b * Math.PI) / 180)) * ((radius + 1) / 2);\n\t\tconst y2 =\n\t\t\t(centerY + Math.sin((b * Math.PI) / 180)) * ((radius + 1) / 2);\n\n\t\tfor (const pt of line(x1, y1, x2, y2)) {\n\t\t\tyield pt;\n\t\t}\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Turtle graphics\n// ---------------------------------------------------------------------------\n\n/** Create a new turtle state. */\nfunction createTurtle(posX = 0, posY = 0): TurtleState {\n\treturn {\n\t\tcanvas: createCanvas(),\n\t\tposX,\n\t\tposY,\n\t\trotation: 0,\n\t\tbrushOn: true,\n\t};\n}\n\n/** Pull the brush up (stop drawing on move). */\nfunction turtleUp(t: TurtleState): void {\n\tt.brushOn = false;\n}\n\n/** Push the brush down (draw on move). */\nfunction turtleDown(t: TurtleState): void {\n\tt.brushOn = true;\n}\n\n/** Move the turtle to an absolute position, drawing if the brush is down. */\nfunction turtleMove(t: TurtleState, x: number, y: number): void {\n\tif (t.brushOn) {\n\t\tfor (const pt of line(t.posX, t.posY, x, y)) {\n\t\t\tset(t.canvas, pt.x, pt.y);\n\t\t}\n\t}\n\tt.posX = x;\n\tt.posY = y;\n}\n\n/** Move the turtle forward by `step` pixels in its current direction. */\nfunction turtleForward(t: TurtleState, step: number): void {\n\tconst x = t.posX + Math.cos((t.rotation * Math.PI) / 180) * step;\n\tconst y = t.posY + Math.sin((t.rotation * Math.PI) / 180) * step;\n\tconst prevBrush = t.brushOn;\n\tt.brushOn = true;\n\tturtleMove(t, x, y);\n\tt.brushOn = prevBrush;\n}\n\n/** Move the turtle backward by `step` pixels. */\nfunction turtleBack(t: TurtleState, step: number): void {\n\tturtleForward(t, -step);\n}\n\n/** Rotate the turtle right (clockwise) by `angle` degrees. */\nfunction turtleRight(t: TurtleState, angle: number): void {\n\tt.rotation += angle;\n}\n\n/** Rotate the turtle left (counter-clockwise) by `angle` degrees. */\nfunction turtleLeft(t: TurtleState, angle: number): void {\n\tt.rotation -= angle;\n}\n\n// ---------------------------------------------------------------------------\n// Terminal size\n// ---------------------------------------------------------------------------\n\n/** Terminal dimensions in characters. */\ninterface TerminalSize {\n\treadonly width: number;\n\treadonly height: number;\n}\n\n/** Get terminal width and height. Falls back to 80x25. */\nfunction getTerminalSize(): TerminalSize {\n\tconst width = process.stdout?.columns ?? 80;\n\tconst height = process.stdout?.rows ?? 25;\n\treturn {width, height};\n}\n\n// ---------------------------------------------------------------------------\n// Animation\n// ---------------------------------------------------------------------------\n\n/** Options for the animate function. */\ninterface AnimateOptions {\n\treadonly delay?: number;\n\treadonly signal?: AbortSignal;\n}\n\n/** Frame generator yields arrays of points per frame. */\ntype FrameGenerator = Generator<readonly Point[], void, unknown>;\n\n/** Sleep for the given number of milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/** Run an animation loop. Clears the canvas each frame, draws points from the generator, and writes to stdout. */\nasync function animate(\n\tcanvas: Canvas,\n\tfn: () => FrameGenerator,\n\toptions?: AnimateOptions,\n): Promise<void> {\n\tconst delay = options?.delay ?? 1000 / 24;\n\n\tfor (const points of fn()) {\n\t\tif (options?.signal?.aborted) break;\n\n\t\tfor (const pt of points) {\n\t\t\tset(canvas, pt.x, pt.y);\n\t\t}\n\n\t\tconst f = frame(canvas);\n\t\tprocess.stdout.write(`\\x1b[H\\x1b[J${f}\\n`);\n\n\t\tif (delay > 0) {\n\t\t\tawait sleep(delay);\n\t\t}\n\n\t\tclear(canvas);\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Fixed-size canvas (node-drawille style)\n// ---------------------------------------------------------------------------\n\n/** A fixed-size canvas backed by a Uint8Array buffer. */\ninterface FixedCanvas {\n\treadonly width: number;\n\treadonly height: number;\n\treadonly content: Uint8Array;\n}\n\n/** Create a fixed-size canvas with the given dimensions. Width rounds to multiple of 2, height to multiple of 4. */\nfunction createFixedCanvas(width: number, height: number): FixedCanvas {\n\tconst w = Math.floor(width / 2) * 2;\n\tconst h = Math.floor(height / 4) * 4;\n\treturn {\n\t\twidth: w,\n\t\theight: h,\n\t\tcontent: new Uint8Array((w * h) / 8),\n\t};\n}\n\n/** Clear all pixels from a fixed canvas. */\nfunction fixedClear(canvas: FixedCanvas): void {\n\tcanvas.content.fill(0);\n}\n\n/** Set a pixel on a fixed canvas. */\nfunction fixedSet(canvas: FixedCanvas, x: number, y: number): void {\n\tif (!(x >= 0 && x < canvas.width && y >= 0 && y < canvas.height)) return;\n\tconst fx = Math.floor(x);\n\tconst fy = Math.floor(y);\n\tconst nx = Math.floor(fx / 2);\n\tconst ny = Math.floor(fy / 4);\n\tconst coord = nx + (canvas.width / 2) * ny;\n\tconst mask = PIXEL_MAP[fy % 4]?.[fx % 2];\n\tif (mask === undefined) return;\n\tconst val = canvas.content[coord];\n\tif (val === undefined) return;\n\tcanvas.content[coord] = val | mask;\n}\n\n/** Unset a pixel on a fixed canvas. */\nfunction fixedUnset(canvas: FixedCanvas, x: number, y: number): void {\n\tif (!(x >= 0 && x < canvas.width && y >= 0 && y < canvas.height)) return;\n\tconst fx = Math.floor(x);\n\tconst fy = Math.floor(y);\n\tconst nx = Math.floor(fx / 2);\n\tconst ny = Math.floor(fy / 4);\n\tconst coord = nx + (canvas.width / 2) * ny;\n\tconst mask = PIXEL_MAP[fy % 4]?.[fx % 2];\n\tif (mask === undefined) return;\n\tconst val = canvas.content[coord];\n\tif (val === undefined) return;\n\tcanvas.content[coord] = val & ~mask;\n}\n\n/** Toggle a pixel on a fixed canvas. */\nfunction fixedToggle(canvas: FixedCanvas, x: number, y: number): void {\n\tif (!(x >= 0 && x < canvas.width && y >= 0 && y < canvas.height)) return;\n\tconst fx = Math.floor(x);\n\tconst fy = Math.floor(y);\n\tconst nx = Math.floor(fx / 2);\n\tconst ny = Math.floor(fy / 4);\n\tconst coord = nx + (canvas.width / 2) * ny;\n\tconst mask = PIXEL_MAP[fy % 4]?.[fx % 2];\n\tif (mask === undefined) return;\n\tconst val = canvas.content[coord];\n\tif (val === undefined) return;\n\tcanvas.content[coord] = val ^ mask;\n}\n\n/** Get the state of a pixel on a fixed canvas. */\nfunction fixedGet(canvas: FixedCanvas, x: number, y: number): boolean {\n\tif (!(x >= 0 && x < canvas.width && y >= 0 && y < canvas.height))\n\t\treturn false;\n\tconst fx = Math.floor(x);\n\tconst fy = Math.floor(y);\n\tconst nx = Math.floor(fx / 2);\n\tconst ny = Math.floor(fy / 4);\n\tconst coord = nx + (canvas.width / 2) * ny;\n\tconst mask = PIXEL_MAP[fy % 4]?.[fx % 2];\n\tif (mask === undefined) return false;\n\tconst val = canvas.content[coord];\n\tif (val === undefined) return false;\n\treturn (val & mask) !== 0;\n}\n\n/** Render a fixed canvas as a string. */\nfunction fixedFrame(canvas: FixedCanvas, delimiter = '\\n'): string {\n\tconst frameWidth = canvas.width / 2;\n\tconst parts: string[] = [];\n\n\tfor (let i = 0; i < canvas.content.length; i++) {\n\t\tif (i % frameWidth === 0) {\n\t\t\tparts.push(delimiter);\n\t\t}\n\t\tconst val = canvas.content[i];\n\t\tparts.push(val ? String.fromCharCode(BRAILLE_OFFSET + val) : ' ');\n\t}\n\tparts.push(delimiter);\n\n\treturn parts.join('');\n}\n\n// ---------------------------------------------------------------------------\n// Exports\n// ---------------------------------------------------------------------------\n\nexport {\n\t// Constants\n\tBRAILLE_OFFSET,\n\tPIXEL_MAP,\n\t// Types\n\ttype AnimateOptions,\n\ttype Canvas,\n\ttype CanvasChars,\n\ttype CellValue,\n\ttype CreateCanvasOptions,\n\ttype FixedCanvas,\n\ttype FrameBounds,\n\ttype FrameGenerator,\n\ttype Point,\n\ttype TerminalSize,\n\ttype TurtleState,\n\t// Coordinate helpers\n\tnormalize,\n\tgetPos,\n\t// Terminal\n\tgetTerminalSize,\n\t// Sparse canvas\n\tcreateCanvas,\n\tclear,\n\tset,\n\tunset,\n\ttoggle,\n\tget,\n\tsetText,\n\trows,\n\tframe,\n\t// Animation\n\tanimate,\n\t// Drawing utilities\n\tline,\n\tpolygon,\n\t// Turtle\n\tcreateTurtle,\n\tturtleUp,\n\tturtleDown,\n\tturtleMove,\n\tturtleForward,\n\tturtleBack,\n\tturtleRight,\n\tturtleLeft,\n\t// Fixed canvas\n\tcreateFixedCanvas,\n\tfixedClear,\n\tfixedSet,\n\tfixedUnset,\n\tfixedToggle,\n\tfixedGet,\n\tfixedFrame,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmBA,IAAM,iBAAiB;AAEvB,IAAM,YAAoD;AAAA,EACzD,CAAC,GAAM,CAAI;AAAA,EACX,CAAC,GAAM,EAAI;AAAA,EACX,CAAC,GAAM,EAAI;AAAA,EACX,CAAC,IAAM,GAAI;AACZ;AAqDA,SAAS,UAAU,OAAuB;AACzC,SAAO,KAAK,MAAM,KAAK;AACxB;AAGA,SAAS,OAAO,GAAW,GAAsC;AAChE,SAAO,CAAC,KAAK,MAAM,UAAU,CAAC,IAAI,CAAC,GAAG,KAAK,MAAM,UAAU,CAAC,IAAI,CAAC,CAAC;AACnE;AAOA,SAAS,aAAa,SAAuC;AAC5D,SAAO;AAAA,IACN,OAAO,oBAAI,IAAI;AAAA,IACf,YAAY,SAAS,cAAc;AAAA,EACpC;AACD;AAGA,SAAS,MAAM,QAAsB;AACpC,SAAO,MAAM,MAAM;AACpB;AAGA,SAAS,IAAI,QAAgB,GAAW,GAAiB;AACxD,QAAM,KAAK,UAAU,CAAC;AACtB,QAAM,KAAK,UAAU,CAAC;AACtB,QAAM,CAAC,KAAK,GAAG,IAAI,OAAO,IAAI,EAAE;AAEhC,MAAI,SAAS,OAAO,MAAM,IAAI,GAAG;AACjC,MAAI,CAAC,QAAQ;AACZ,aAAS,oBAAI,IAAI;AACjB,WAAO,MAAM,IAAI,KAAK,MAAM;AAAA,EAC7B;AAEA,QAAM,UAAU,OAAO,IAAI,GAAG;AAC9B,MAAI,YAAY,UAAa,OAAO,YAAY,UAAU;AACzD;AAAA,EACD;AAEA,QAAM,OAAO,UAAU,KAAK,CAAC,IAAI,KAAK,CAAC;AACvC,MAAI,SAAS,OAAW;AAExB,SAAO,IAAI,MAAO,WAAkC,KAAK,IAAI;AAC9D;AAGA,SAAS,MAAM,QAAgB,GAAW,GAAiB;AAC1D,QAAM,KAAK,UAAU,CAAC;AACtB,QAAM,KAAK,UAAU,CAAC;AACtB,QAAM,CAAC,KAAK,GAAG,IAAI,OAAO,IAAI,EAAE;AAEhC,QAAM,SAAS,OAAO,MAAM,IAAI,GAAG;AACnC,MAAI,CAAC,OAAQ;AAEb,QAAM,UAAU,OAAO,IAAI,GAAG;AAC9B,MAAI,YAAY,UAAa,OAAO,YAAY,SAAU;AAE1D,QAAM,OAAO,UAAU,KAAK,CAAC,IAAI,KAAK,CAAC;AACvC,MAAI,SAAS,OAAW;AAExB,QAAM,UAAU,UAAU,CAAC;AAC3B,MAAI,YAAY,GAAG;AAClB,WAAO,OAAO,GAAG;AACjB,QAAI,OAAO,SAAS,GAAG;AACtB,aAAO,MAAM,OAAO,GAAG;AAAA,IACxB;AACA;AAAA,EACD;AAEA,SAAO,IAAI,KAAK,OAAO;AACxB;AAGA,SAAS,OAAO,QAAgB,GAAW,GAAiB;AAC3D,QAAM,KAAK,UAAU,CAAC;AACtB,QAAM,KAAK,UAAU,CAAC;AACtB,QAAM,CAAC,KAAK,GAAG,IAAI,OAAO,IAAI,EAAE;AAEhC,QAAM,SAAS,OAAO,MAAM,IAAI,GAAG;AACnC,QAAM,UAAU,QAAQ,IAAI,GAAG;AAE/B,QAAM,OAAO,UAAU,KAAK,CAAC,IAAI,KAAK,CAAC;AACvC,MAAI,SAAS,OAAW;AAExB,MACC,YAAY,WACX,OAAO,YAAY,aAAa,UAAU,UAAU,IACpD;AACD,UAAM,QAAQ,GAAG,CAAC;AAAA,EACnB,OAAO;AACN,QAAI,QAAQ,GAAG,CAAC;AAAA,EACjB;AACD;AAGA,SAAS,IAAI,QAAgB,GAAW,GAAoB;AAC3D,QAAM,KAAK,UAAU,CAAC;AACtB,QAAM,KAAK,UAAU,CAAC;AACtB,QAAM,CAAC,KAAK,GAAG,IAAI,OAAO,IAAI,EAAE;AAEhC,QAAM,SAAS,OAAO,MAAM,IAAI,GAAG;AACnC,MAAI,CAAC,OAAQ,QAAO;AAEpB,QAAM,OAAO,OAAO,IAAI,GAAG;AAC3B,MAAI,SAAS,OAAW,QAAO;AAC/B,MAAI,OAAO,SAAS,SAAU,QAAO;AAErC,QAAM,OAAO,UAAU,KAAK,CAAC,IAAI,KAAK,CAAC;AACvC,MAAI,SAAS,OAAW,QAAO;AAE/B,UAAQ,OAAO,UAAU;AAC1B;AAGA,SAAS,QAAQ,QAAgB,GAAW,GAAW,MAAoB;AAC1E,QAAM,CAAC,KAAK,GAAG,IAAI,OAAO,GAAG,CAAC;AAE9B,MAAI,SAAS,OAAO,MAAM,IAAI,GAAG;AACjC,MAAI,CAAC,QAAQ;AACZ,aAAS,oBAAI,IAAI;AACjB,WAAO,MAAM,IAAI,KAAK,MAAM;AAAA,EAC7B;AAEA,WAAS,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;AACrC,WAAO,IAAI,MAAM,GAAG,KAAK,CAAC,CAAW;AAAA,EACtC;AACD;AAOA,SAAS,KAAK,QAAgB,QAAyC;AACtE,MAAI,OAAO,MAAM,SAAS,EAAG,QAAO,CAAC;AAErC,QAAM,UAAU,CAAC,GAAG,OAAO,MAAM,KAAK,CAAC;AACvC,QAAM,SACL,QAAQ,QAAQ,OACb,KAAK,MAAM,OAAO,OAAO,CAAC,IAC1B,KAAK,IAAI,GAAG,OAAO;AACvB,QAAM,SACL,QAAQ,QAAQ,OACb,KAAK,OAAO,OAAO,OAAO,KAAK,CAAC,IAChC,KAAK,IAAI,GAAG,OAAO;AAGvB,MAAI;AACJ,MAAI,QAAQ,QAAQ,MAAM;AACzB,mBAAe,KAAK,MAAM,OAAO,OAAO,CAAC;AAAA,EAC1C,OAAO;AACN,QAAI,MAAM,OAAO;AACjB,eAAW,UAAU,OAAO,MAAM,OAAO,GAAG;AAC3C,iBAAW,KAAK,OAAO,KAAK,GAAG;AAC9B,YAAI,IAAI,IAAK,OAAM;AAAA,MACpB;AAAA,IACD;AACA,mBAAe,QAAQ,OAAO,oBAAoB,IAAI;AAAA,EACvD;AAEA,QAAM,SAAmB,CAAC;AAE1B,WAAS,SAAS,QAAQ,UAAU,QAAQ,UAAU;AACrD,UAAM,SAAS,OAAO,MAAM,IAAI,MAAM;AACtC,QAAI,CAAC,QAAQ;AACZ,aAAO,KAAK,EAAE;AACd;AAAA,IACD;AAEA,QAAI;AACJ,QAAI,QAAQ,QAAQ,MAAM;AACzB,eAAS,KAAK,OAAO,OAAO,OAAO,KAAK,CAAC;AAAA,IAC1C,OAAO;AACN,eAAS,KAAK,IAAI,GAAG,OAAO,KAAK,CAAC;AAAA,IACnC;AAEA,UAAM,QAAkB,CAAC;AACzB,aAAS,IAAI,cAAc,KAAK,QAAQ,KAAK;AAC5C,YAAM,OAAO,OAAO,IAAI,CAAC;AACzB,UAAI,SAAS,UAAa,SAAS,GAAG;AACrC,cAAM,KAAK,GAAG;AAAA,MACf,WAAW,OAAO,SAAS,UAAU;AACpC,cAAM,KAAK,IAAI;AAAA,MAChB,OAAO;AACN,cAAM,KAAK,OAAO,aAAa,iBAAiB,IAAI,CAAC;AAAA,MACtD;AAAA,IACD;AAEA,WAAO,KAAK,MAAM,KAAK,EAAE,CAAC;AAAA,EAC3B;AAEA,SAAO;AACR;AAGA,SAAS,MAAM,QAAgB,QAA8B;AAC5D,SAAO,KAAK,QAAQ,MAAM,EAAE,KAAK,OAAO,UAAU;AACnD;AAOA,UAAU,KACT,IACA,IACA,IACA,IACkC;AAClC,QAAM,MAAM,UAAU,EAAE;AACxB,QAAM,MAAM,UAAU,EAAE;AACxB,QAAM,MAAM,UAAU,EAAE;AACxB,QAAM,MAAM,UAAU,EAAE;AAExB,QAAM,QAAQ,KAAK,IAAI,MAAM,GAAG;AAChC,QAAM,QAAQ,KAAK,IAAI,MAAM,GAAG;AAChC,QAAM,OAAO,OAAO,MAAM,IAAI;AAC9B,QAAM,OAAO,OAAO,MAAM,IAAI;AAE9B,QAAM,QAAQ,KAAK,IAAI,OAAO,KAAK;AAEnC,WAAS,IAAI,GAAG,KAAK,OAAO,KAAK;AAChC,QAAI,KAAK;AACT,QAAI,KAAK;AAET,QAAI,OAAO;AACV,YAAM,KAAK,MAAO,IAAI,QAAS,KAAK,IAAI;AAAA,IACzC;AACA,QAAI,OAAO;AACV,YAAM,KAAK,MAAO,IAAI,QAAS,KAAK,IAAI;AAAA,IACzC;AAEA,UAAM,EAAC,GAAG,IAAI,GAAG,GAAE;AAAA,EACpB;AACD;AAOA,UAAU,QACT,UAAU,GACV,UAAU,GACV,QAAQ,GACR,SAAS,GACyB;AAClC,QAAM,SAAS,MAAM;AAErB,WAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC/B,UAAM,IAAI,IAAI;AACd,UAAM,KAAK,IAAI,KAAK;AACpB,UAAM,MACJ,UAAU,KAAK,IAAK,IAAI,KAAK,KAAM,GAAG,OAAO,SAAS,KAAK;AAC7D,UAAM,MACJ,UAAU,KAAK,IAAK,IAAI,KAAK,KAAM,GAAG,OAAO,SAAS,KAAK;AAC7D,UAAM,MACJ,UAAU,KAAK,IAAK,IAAI,KAAK,KAAM,GAAG,OAAO,SAAS,KAAK;AAC7D,UAAM,MACJ,UAAU,KAAK,IAAK,IAAI,KAAK,KAAM,GAAG,OAAO,SAAS,KAAK;AAE7D,eAAW,MAAM,KAAK,IAAI,IAAI,IAAI,EAAE,GAAG;AACtC,YAAM;AAAA,IACP;AAAA,EACD;AACD;AAOA,SAAS,aAAa,OAAO,GAAG,OAAO,GAAgB;AACtD,SAAO;AAAA,IACN,QAAQ,aAAa;AAAA,IACrB;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV,SAAS;AAAA,EACV;AACD;AAGA,SAAS,SAAS,GAAsB;AACvC,IAAE,UAAU;AACb;AAGA,SAAS,WAAW,GAAsB;AACzC,IAAE,UAAU;AACb;AAGA,SAAS,WAAW,GAAgB,GAAW,GAAiB;AAC/D,MAAI,EAAE,SAAS;AACd,eAAW,MAAM,KAAK,EAAE,MAAM,EAAE,MAAM,GAAG,CAAC,GAAG;AAC5C,UAAI,EAAE,QAAQ,GAAG,GAAG,GAAG,CAAC;AAAA,IACzB;AAAA,EACD;AACA,IAAE,OAAO;AACT,IAAE,OAAO;AACV;AAGA,SAAS,cAAc,GAAgB,MAAoB;AAC1D,QAAM,IAAI,EAAE,OAAO,KAAK,IAAK,EAAE,WAAW,KAAK,KAAM,GAAG,IAAI;AAC5D,QAAM,IAAI,EAAE,OAAO,KAAK,IAAK,EAAE,WAAW,KAAK,KAAM,GAAG,IAAI;AAC5D,QAAM,YAAY,EAAE;AACpB,IAAE,UAAU;AACZ,aAAW,GAAG,GAAG,CAAC;AAClB,IAAE,UAAU;AACb;AAGA,SAAS,WAAW,GAAgB,MAAoB;AACvD,gBAAc,GAAG,CAAC,IAAI;AACvB;AAGA,SAAS,YAAY,GAAgB,OAAqB;AACzD,IAAE,YAAY;AACf;AAGA,SAAS,WAAW,GAAgB,OAAqB;AACxD,IAAE,YAAY;AACf;AAaA,SAAS,kBAAgC;AACxC,QAAM,QAAQ,QAAQ,QAAQ,WAAW;AACzC,QAAM,SAAS,QAAQ,QAAQ,QAAQ;AACvC,SAAO,EAAC,OAAO,OAAM;AACtB;AAgBA,SAAS,MAAM,IAA2B;AACzC,SAAO,IAAI,QAAQ,aAAW,WAAW,SAAS,EAAE,CAAC;AACtD;AAGA,eAAe,QACd,QACA,IACA,SACgB;AAChB,QAAM,QAAQ,SAAS,SAAS,MAAO;AAEvC,aAAW,UAAU,GAAG,GAAG;AAC1B,QAAI,SAAS,QAAQ,QAAS;AAE9B,eAAW,MAAM,QAAQ;AACxB,UAAI,QAAQ,GAAG,GAAG,GAAG,CAAC;AAAA,IACvB;AAEA,UAAM,IAAI,MAAM,MAAM;AACtB,YAAQ,OAAO,MAAM,eAAe,CAAC;AAAA,CAAI;AAEzC,QAAI,QAAQ,GAAG;AACd,YAAM,MAAM,KAAK;AAAA,IAClB;AAEA,UAAM,MAAM;AAAA,EACb;AACD;AAcA,SAAS,kBAAkB,OAAe,QAA6B;AACtE,QAAM,IAAI,KAAK,MAAM,QAAQ,CAAC,IAAI;AAClC,QAAM,IAAI,KAAK,MAAM,SAAS,CAAC,IAAI;AACnC,SAAO;AAAA,IACN,OAAO;AAAA,IACP,QAAQ;AAAA,IACR,SAAS,IAAI,WAAY,IAAI,IAAK,CAAC;AAAA,EACpC;AACD;AAGA,SAAS,WAAW,QAA2B;AAC9C,SAAO,QAAQ,KAAK,CAAC;AACtB;AAGA,SAAS,SAAS,QAAqB,GAAW,GAAiB;AAClE,MAAI,EAAE,KAAK,KAAK,IAAI,OAAO,SAAS,KAAK,KAAK,IAAI,OAAO,QAAS;AAClE,QAAM,KAAK,KAAK,MAAM,CAAC;AACvB,QAAM,KAAK,KAAK,MAAM,CAAC;AACvB,QAAM,KAAK,KAAK,MAAM,KAAK,CAAC;AAC5B,QAAM,KAAK,KAAK,MAAM,KAAK,CAAC;AAC5B,QAAM,QAAQ,KAAM,OAAO,QAAQ,IAAK;AACxC,QAAM,OAAO,UAAU,KAAK,CAAC,IAAI,KAAK,CAAC;AACvC,MAAI,SAAS,OAAW;AACxB,QAAM,MAAM,OAAO,QAAQ,KAAK;AAChC,MAAI,QAAQ,OAAW;AACvB,SAAO,QAAQ,KAAK,IAAI,MAAM;AAC/B;AAGA,SAAS,WAAW,QAAqB,GAAW,GAAiB;AACpE,MAAI,EAAE,KAAK,KAAK,IAAI,OAAO,SAAS,KAAK,KAAK,IAAI,OAAO,QAAS;AAClE,QAAM,KAAK,KAAK,MAAM,CAAC;AACvB,QAAM,KAAK,KAAK,MAAM,CAAC;AACvB,QAAM,KAAK,KAAK,MAAM,KAAK,CAAC;AAC5B,QAAM,KAAK,KAAK,MAAM,KAAK,CAAC;AAC5B,QAAM,QAAQ,KAAM,OAAO,QAAQ,IAAK;AACxC,QAAM,OAAO,UAAU,KAAK,CAAC,IAAI,KAAK,CAAC;AACvC,MAAI,SAAS,OAAW;AACxB,QAAM,MAAM,OAAO,QAAQ,KAAK;AAChC,MAAI,QAAQ,OAAW;AACvB,SAAO,QAAQ,KAAK,IAAI,MAAM,CAAC;AAChC;AAGA,SAAS,YAAY,QAAqB,GAAW,GAAiB;AACrE,MAAI,EAAE,KAAK,KAAK,IAAI,OAAO,SAAS,KAAK,KAAK,IAAI,OAAO,QAAS;AAClE,QAAM,KAAK,KAAK,MAAM,CAAC;AACvB,QAAM,KAAK,KAAK,MAAM,CAAC;AACvB,QAAM,KAAK,KAAK,MAAM,KAAK,CAAC;AAC5B,QAAM,KAAK,KAAK,MAAM,KAAK,CAAC;AAC5B,QAAM,QAAQ,KAAM,OAAO,QAAQ,IAAK;AACxC,QAAM,OAAO,UAAU,KAAK,CAAC,IAAI,KAAK,CAAC;AACvC,MAAI,SAAS,OAAW;AACxB,QAAM,MAAM,OAAO,QAAQ,KAAK;AAChC,MAAI,QAAQ,OAAW;AACvB,SAAO,QAAQ,KAAK,IAAI,MAAM;AAC/B;AAGA,SAAS,SAAS,QAAqB,GAAW,GAAoB;AACrE,MAAI,EAAE,KAAK,KAAK,IAAI,OAAO,SAAS,KAAK,KAAK,IAAI,OAAO;AACxD,WAAO;AACR,QAAM,KAAK,KAAK,MAAM,CAAC;AACvB,QAAM,KAAK,KAAK,MAAM,CAAC;AACvB,QAAM,KAAK,KAAK,MAAM,KAAK,CAAC;AAC5B,QAAM,KAAK,KAAK,MAAM,KAAK,CAAC;AAC5B,QAAM,QAAQ,KAAM,OAAO,QAAQ,IAAK;AACxC,QAAM,OAAO,UAAU,KAAK,CAAC,IAAI,KAAK,CAAC;AACvC,MAAI,SAAS,OAAW,QAAO;AAC/B,QAAM,MAAM,OAAO,QAAQ,KAAK;AAChC,MAAI,QAAQ,OAAW,QAAO;AAC9B,UAAQ,MAAM,UAAU;AACzB;AAGA,SAAS,WAAW,QAAqB,YAAY,MAAc;AAClE,QAAM,aAAa,OAAO,QAAQ;AAClC,QAAM,QAAkB,CAAC;AAEzB,WAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,QAAQ,KAAK;AAC/C,QAAI,IAAI,eAAe,GAAG;AACzB,YAAM,KAAK,SAAS;AAAA,IACrB;AACA,UAAM,MAAM,OAAO,QAAQ,CAAC;AAC5B,UAAM,KAAK,MAAM,OAAO,aAAa,iBAAiB,GAAG,IAAI,GAAG;AAAA,EACjE;AACA,QAAM,KAAK,SAAS;AAEpB,SAAO,MAAM,KAAK,EAAE;AACrB;","names":[]}
@@ -0,0 +1,133 @@
1
+ /**
2
+ * btui — Braille TUI canvas for terminal drawing with Unicode braille characters.
3
+ *
4
+ * Braille dot layout per character cell:
5
+ * ,___,
6
+ * |1 4|
7
+ * |2 5|
8
+ * |3 6|
9
+ * |7 8|
10
+ * `````
11
+ *
12
+ * Each character maps to a 2x4 pixel grid.
13
+ * Unicode braille characters: U+2800 to U+28FF.
14
+ */
15
+ declare const BRAILLE_OFFSET = 10240;
16
+ declare const PIXEL_MAP: readonly (readonly [number, number])[];
17
+ /** A single cell can hold a braille bitmask (number) or a text character (string). */
18
+ type CellValue = number | string;
19
+ /** Sparse row → col → cell storage. */
20
+ interface CanvasChars {
21
+ readonly [row: number]: {
22
+ readonly [col: number]: CellValue;
23
+ } | undefined;
24
+ }
25
+ /** The braille canvas data structure. */
26
+ interface Canvas {
27
+ chars: Map<number, Map<number, CellValue>>;
28
+ readonly lineEnding: string;
29
+ }
30
+ /** Options for creating a canvas. */
31
+ interface CreateCanvasOptions {
32
+ readonly lineEnding?: string;
33
+ }
34
+ /** Bounds for frame/rows rendering. */
35
+ interface FrameBounds {
36
+ readonly minX?: number;
37
+ readonly minY?: number;
38
+ readonly maxX?: number;
39
+ readonly maxY?: number;
40
+ }
41
+ /** A 2D point. */
42
+ interface Point {
43
+ readonly x: number;
44
+ readonly y: number;
45
+ }
46
+ /** Turtle graphics state. */
47
+ interface TurtleState {
48
+ readonly canvas: Canvas;
49
+ posX: number;
50
+ posY: number;
51
+ rotation: number;
52
+ brushOn: boolean;
53
+ }
54
+ /** Normalize a coordinate to an integer. */
55
+ declare function normalize(coord: number): number;
56
+ /** Convert pixel (x, y) to cell (col, row). */
57
+ declare function getPos(x: number, y: number): readonly [number, number];
58
+ /** Create a new braille canvas. */
59
+ declare function createCanvas(options?: CreateCanvasOptions): Canvas;
60
+ /** Remove all pixels from the canvas. */
61
+ declare function clear(canvas: Canvas): void;
62
+ /** Set (draw) a pixel at (x, y). */
63
+ declare function set(canvas: Canvas, x: number, y: number): void;
64
+ /** Unset (erase) a pixel at (x, y). */
65
+ declare function unset(canvas: Canvas, x: number, y: number): void;
66
+ /** Toggle a pixel at (x, y). */
67
+ declare function toggle(canvas: Canvas, x: number, y: number): void;
68
+ /** Get the state of a pixel at (x, y). Returns true if set. */
69
+ declare function get(canvas: Canvas, x: number, y: number): boolean;
70
+ /** Set text at the given pixel coords. Each character occupies one cell column. */
71
+ declare function setText(canvas: Canvas, x: number, y: number, text: string): void;
72
+ /** Return the canvas content as an array of strings (one per row). */
73
+ declare function rows(canvas: Canvas, bounds?: FrameBounds): readonly string[];
74
+ /** Render the canvas as a string. */
75
+ declare function frame(canvas: Canvas, bounds?: FrameBounds): string;
76
+ /** Generate coordinates along a line from (x1, y1) to (x2, y2) using Bresenham-style interpolation. */
77
+ declare function line(x1: number, y1: number, x2: number, y2: number): Generator<Point, void, unknown>;
78
+ /** Generate coordinates for a regular polygon. */
79
+ declare function polygon(centerX?: number, centerY?: number, sides?: number, radius?: number): Generator<Point, void, unknown>;
80
+ /** Create a new turtle state. */
81
+ declare function createTurtle(posX?: number, posY?: number): TurtleState;
82
+ /** Pull the brush up (stop drawing on move). */
83
+ declare function turtleUp(t: TurtleState): void;
84
+ /** Push the brush down (draw on move). */
85
+ declare function turtleDown(t: TurtleState): void;
86
+ /** Move the turtle to an absolute position, drawing if the brush is down. */
87
+ declare function turtleMove(t: TurtleState, x: number, y: number): void;
88
+ /** Move the turtle forward by `step` pixels in its current direction. */
89
+ declare function turtleForward(t: TurtleState, step: number): void;
90
+ /** Move the turtle backward by `step` pixels. */
91
+ declare function turtleBack(t: TurtleState, step: number): void;
92
+ /** Rotate the turtle right (clockwise) by `angle` degrees. */
93
+ declare function turtleRight(t: TurtleState, angle: number): void;
94
+ /** Rotate the turtle left (counter-clockwise) by `angle` degrees. */
95
+ declare function turtleLeft(t: TurtleState, angle: number): void;
96
+ /** Terminal dimensions in characters. */
97
+ interface TerminalSize {
98
+ readonly width: number;
99
+ readonly height: number;
100
+ }
101
+ /** Get terminal width and height. Falls back to 80x25. */
102
+ declare function getTerminalSize(): TerminalSize;
103
+ /** Options for the animate function. */
104
+ interface AnimateOptions {
105
+ readonly delay?: number;
106
+ readonly signal?: AbortSignal;
107
+ }
108
+ /** Frame generator yields arrays of points per frame. */
109
+ type FrameGenerator = Generator<readonly Point[], void, unknown>;
110
+ /** Run an animation loop. Clears the canvas each frame, draws points from the generator, and writes to stdout. */
111
+ declare function animate(canvas: Canvas, fn: () => FrameGenerator, options?: AnimateOptions): Promise<void>;
112
+ /** A fixed-size canvas backed by a Uint8Array buffer. */
113
+ interface FixedCanvas {
114
+ readonly width: number;
115
+ readonly height: number;
116
+ readonly content: Uint8Array;
117
+ }
118
+ /** Create a fixed-size canvas with the given dimensions. Width rounds to multiple of 2, height to multiple of 4. */
119
+ declare function createFixedCanvas(width: number, height: number): FixedCanvas;
120
+ /** Clear all pixels from a fixed canvas. */
121
+ declare function fixedClear(canvas: FixedCanvas): void;
122
+ /** Set a pixel on a fixed canvas. */
123
+ declare function fixedSet(canvas: FixedCanvas, x: number, y: number): void;
124
+ /** Unset a pixel on a fixed canvas. */
125
+ declare function fixedUnset(canvas: FixedCanvas, x: number, y: number): void;
126
+ /** Toggle a pixel on a fixed canvas. */
127
+ declare function fixedToggle(canvas: FixedCanvas, x: number, y: number): void;
128
+ /** Get the state of a pixel on a fixed canvas. */
129
+ declare function fixedGet(canvas: FixedCanvas, x: number, y: number): boolean;
130
+ /** Render a fixed canvas as a string. */
131
+ declare function fixedFrame(canvas: FixedCanvas, delimiter?: string): string;
132
+
133
+ export { type AnimateOptions, BRAILLE_OFFSET, type Canvas, type CanvasChars, type CellValue, type CreateCanvasOptions, type FixedCanvas, type FrameBounds, type FrameGenerator, PIXEL_MAP, type Point, type TerminalSize, type TurtleState, animate, clear, createCanvas, createFixedCanvas, createTurtle, fixedClear, fixedFrame, fixedGet, fixedSet, fixedToggle, fixedUnset, frame, get, getPos, getTerminalSize, line, normalize, polygon, rows, set, setText, toggle, turtleBack, turtleDown, turtleForward, turtleLeft, turtleMove, turtleRight, turtleUp, unset };
@@ -0,0 +1,133 @@
1
+ /**
2
+ * btui — Braille TUI canvas for terminal drawing with Unicode braille characters.
3
+ *
4
+ * Braille dot layout per character cell:
5
+ * ,___,
6
+ * |1 4|
7
+ * |2 5|
8
+ * |3 6|
9
+ * |7 8|
10
+ * `````
11
+ *
12
+ * Each character maps to a 2x4 pixel grid.
13
+ * Unicode braille characters: U+2800 to U+28FF.
14
+ */
15
+ declare const BRAILLE_OFFSET = 10240;
16
+ declare const PIXEL_MAP: readonly (readonly [number, number])[];
17
+ /** A single cell can hold a braille bitmask (number) or a text character (string). */
18
+ type CellValue = number | string;
19
+ /** Sparse row → col → cell storage. */
20
+ interface CanvasChars {
21
+ readonly [row: number]: {
22
+ readonly [col: number]: CellValue;
23
+ } | undefined;
24
+ }
25
+ /** The braille canvas data structure. */
26
+ interface Canvas {
27
+ chars: Map<number, Map<number, CellValue>>;
28
+ readonly lineEnding: string;
29
+ }
30
+ /** Options for creating a canvas. */
31
+ interface CreateCanvasOptions {
32
+ readonly lineEnding?: string;
33
+ }
34
+ /** Bounds for frame/rows rendering. */
35
+ interface FrameBounds {
36
+ readonly minX?: number;
37
+ readonly minY?: number;
38
+ readonly maxX?: number;
39
+ readonly maxY?: number;
40
+ }
41
+ /** A 2D point. */
42
+ interface Point {
43
+ readonly x: number;
44
+ readonly y: number;
45
+ }
46
+ /** Turtle graphics state. */
47
+ interface TurtleState {
48
+ readonly canvas: Canvas;
49
+ posX: number;
50
+ posY: number;
51
+ rotation: number;
52
+ brushOn: boolean;
53
+ }
54
+ /** Normalize a coordinate to an integer. */
55
+ declare function normalize(coord: number): number;
56
+ /** Convert pixel (x, y) to cell (col, row). */
57
+ declare function getPos(x: number, y: number): readonly [number, number];
58
+ /** Create a new braille canvas. */
59
+ declare function createCanvas(options?: CreateCanvasOptions): Canvas;
60
+ /** Remove all pixels from the canvas. */
61
+ declare function clear(canvas: Canvas): void;
62
+ /** Set (draw) a pixel at (x, y). */
63
+ declare function set(canvas: Canvas, x: number, y: number): void;
64
+ /** Unset (erase) a pixel at (x, y). */
65
+ declare function unset(canvas: Canvas, x: number, y: number): void;
66
+ /** Toggle a pixel at (x, y). */
67
+ declare function toggle(canvas: Canvas, x: number, y: number): void;
68
+ /** Get the state of a pixel at (x, y). Returns true if set. */
69
+ declare function get(canvas: Canvas, x: number, y: number): boolean;
70
+ /** Set text at the given pixel coords. Each character occupies one cell column. */
71
+ declare function setText(canvas: Canvas, x: number, y: number, text: string): void;
72
+ /** Return the canvas content as an array of strings (one per row). */
73
+ declare function rows(canvas: Canvas, bounds?: FrameBounds): readonly string[];
74
+ /** Render the canvas as a string. */
75
+ declare function frame(canvas: Canvas, bounds?: FrameBounds): string;
76
+ /** Generate coordinates along a line from (x1, y1) to (x2, y2) using Bresenham-style interpolation. */
77
+ declare function line(x1: number, y1: number, x2: number, y2: number): Generator<Point, void, unknown>;
78
+ /** Generate coordinates for a regular polygon. */
79
+ declare function polygon(centerX?: number, centerY?: number, sides?: number, radius?: number): Generator<Point, void, unknown>;
80
+ /** Create a new turtle state. */
81
+ declare function createTurtle(posX?: number, posY?: number): TurtleState;
82
+ /** Pull the brush up (stop drawing on move). */
83
+ declare function turtleUp(t: TurtleState): void;
84
+ /** Push the brush down (draw on move). */
85
+ declare function turtleDown(t: TurtleState): void;
86
+ /** Move the turtle to an absolute position, drawing if the brush is down. */
87
+ declare function turtleMove(t: TurtleState, x: number, y: number): void;
88
+ /** Move the turtle forward by `step` pixels in its current direction. */
89
+ declare function turtleForward(t: TurtleState, step: number): void;
90
+ /** Move the turtle backward by `step` pixels. */
91
+ declare function turtleBack(t: TurtleState, step: number): void;
92
+ /** Rotate the turtle right (clockwise) by `angle` degrees. */
93
+ declare function turtleRight(t: TurtleState, angle: number): void;
94
+ /** Rotate the turtle left (counter-clockwise) by `angle` degrees. */
95
+ declare function turtleLeft(t: TurtleState, angle: number): void;
96
+ /** Terminal dimensions in characters. */
97
+ interface TerminalSize {
98
+ readonly width: number;
99
+ readonly height: number;
100
+ }
101
+ /** Get terminal width and height. Falls back to 80x25. */
102
+ declare function getTerminalSize(): TerminalSize;
103
+ /** Options for the animate function. */
104
+ interface AnimateOptions {
105
+ readonly delay?: number;
106
+ readonly signal?: AbortSignal;
107
+ }
108
+ /** Frame generator yields arrays of points per frame. */
109
+ type FrameGenerator = Generator<readonly Point[], void, unknown>;
110
+ /** Run an animation loop. Clears the canvas each frame, draws points from the generator, and writes to stdout. */
111
+ declare function animate(canvas: Canvas, fn: () => FrameGenerator, options?: AnimateOptions): Promise<void>;
112
+ /** A fixed-size canvas backed by a Uint8Array buffer. */
113
+ interface FixedCanvas {
114
+ readonly width: number;
115
+ readonly height: number;
116
+ readonly content: Uint8Array;
117
+ }
118
+ /** Create a fixed-size canvas with the given dimensions. Width rounds to multiple of 2, height to multiple of 4. */
119
+ declare function createFixedCanvas(width: number, height: number): FixedCanvas;
120
+ /** Clear all pixels from a fixed canvas. */
121
+ declare function fixedClear(canvas: FixedCanvas): void;
122
+ /** Set a pixel on a fixed canvas. */
123
+ declare function fixedSet(canvas: FixedCanvas, x: number, y: number): void;
124
+ /** Unset a pixel on a fixed canvas. */
125
+ declare function fixedUnset(canvas: FixedCanvas, x: number, y: number): void;
126
+ /** Toggle a pixel on a fixed canvas. */
127
+ declare function fixedToggle(canvas: FixedCanvas, x: number, y: number): void;
128
+ /** Get the state of a pixel on a fixed canvas. */
129
+ declare function fixedGet(canvas: FixedCanvas, x: number, y: number): boolean;
130
+ /** Render a fixed canvas as a string. */
131
+ declare function fixedFrame(canvas: FixedCanvas, delimiter?: string): string;
132
+
133
+ export { type AnimateOptions, BRAILLE_OFFSET, type Canvas, type CanvasChars, type CellValue, type CreateCanvasOptions, type FixedCanvas, type FrameBounds, type FrameGenerator, PIXEL_MAP, type Point, type TerminalSize, type TurtleState, animate, clear, createCanvas, createFixedCanvas, createTurtle, fixedClear, fixedFrame, fixedGet, fixedSet, fixedToggle, fixedUnset, frame, get, getPos, getTerminalSize, line, normalize, polygon, rows, set, setText, toggle, turtleBack, turtleDown, turtleForward, turtleLeft, turtleMove, turtleRight, turtleUp, unset };
package/dist/index.js ADDED
@@ -0,0 +1,360 @@
1
+ // src/index.ts
2
+ var BRAILLE_OFFSET = 10240;
3
+ var PIXEL_MAP = [
4
+ [1, 8],
5
+ [2, 16],
6
+ [4, 32],
7
+ [64, 128]
8
+ ];
9
+ function normalize(coord) {
10
+ return Math.round(coord);
11
+ }
12
+ function getPos(x, y) {
13
+ return [Math.floor(normalize(x) / 2), Math.floor(normalize(y) / 4)];
14
+ }
15
+ function createCanvas(options) {
16
+ return {
17
+ chars: /* @__PURE__ */ new Map(),
18
+ lineEnding: options?.lineEnding ?? "\n"
19
+ };
20
+ }
21
+ function clear(canvas) {
22
+ canvas.chars.clear();
23
+ }
24
+ function set(canvas, x, y) {
25
+ const nx = normalize(x);
26
+ const ny = normalize(y);
27
+ const [col, row] = getPos(nx, ny);
28
+ let rowMap = canvas.chars.get(row);
29
+ if (!rowMap) {
30
+ rowMap = /* @__PURE__ */ new Map();
31
+ canvas.chars.set(row, rowMap);
32
+ }
33
+ const current = rowMap.get(col);
34
+ if (current !== void 0 && typeof current !== "number") {
35
+ return;
36
+ }
37
+ const mask = PIXEL_MAP[ny % 4]?.[nx % 2];
38
+ if (mask === void 0) return;
39
+ rowMap.set(col, (current ?? 0) | mask);
40
+ }
41
+ function unset(canvas, x, y) {
42
+ const nx = normalize(x);
43
+ const ny = normalize(y);
44
+ const [col, row] = getPos(nx, ny);
45
+ const rowMap = canvas.chars.get(row);
46
+ if (!rowMap) return;
47
+ const current = rowMap.get(col);
48
+ if (current === void 0 || typeof current !== "number") return;
49
+ const mask = PIXEL_MAP[ny % 4]?.[nx % 2];
50
+ if (mask === void 0) return;
51
+ const updated = current & ~mask;
52
+ if (updated === 0) {
53
+ rowMap.delete(col);
54
+ if (rowMap.size === 0) {
55
+ canvas.chars.delete(row);
56
+ }
57
+ return;
58
+ }
59
+ rowMap.set(col, updated);
60
+ }
61
+ function toggle(canvas, x, y) {
62
+ const nx = normalize(x);
63
+ const ny = normalize(y);
64
+ const [col, row] = getPos(nx, ny);
65
+ const rowMap = canvas.chars.get(row);
66
+ const current = rowMap?.get(col);
67
+ const mask = PIXEL_MAP[ny % 4]?.[nx % 2];
68
+ if (mask === void 0) return;
69
+ if (current !== void 0 && (typeof current !== "number" || (current & mask) !== 0)) {
70
+ unset(canvas, x, y);
71
+ } else {
72
+ set(canvas, x, y);
73
+ }
74
+ }
75
+ function get(canvas, x, y) {
76
+ const nx = normalize(x);
77
+ const ny = normalize(y);
78
+ const [col, row] = getPos(nx, ny);
79
+ const rowMap = canvas.chars.get(row);
80
+ if (!rowMap) return false;
81
+ const cell = rowMap.get(col);
82
+ if (cell === void 0) return false;
83
+ if (typeof cell !== "number") return true;
84
+ const mask = PIXEL_MAP[ny % 4]?.[nx % 2];
85
+ if (mask === void 0) return false;
86
+ return (cell & mask) !== 0;
87
+ }
88
+ function setText(canvas, x, y, text) {
89
+ const [col, row] = getPos(x, y);
90
+ let rowMap = canvas.chars.get(row);
91
+ if (!rowMap) {
92
+ rowMap = /* @__PURE__ */ new Map();
93
+ canvas.chars.set(row, rowMap);
94
+ }
95
+ for (let i = 0; i < text.length; i++) {
96
+ rowMap.set(col + i, text[i]);
97
+ }
98
+ }
99
+ function rows(canvas, bounds) {
100
+ if (canvas.chars.size === 0) return [];
101
+ const allRows = [...canvas.chars.keys()];
102
+ const minRow = bounds?.minY != null ? Math.floor(bounds.minY / 4) : Math.min(...allRows);
103
+ const maxRow = bounds?.maxY != null ? Math.floor((bounds.maxY - 1) / 4) : Math.max(...allRows);
104
+ let globalMinCol;
105
+ if (bounds?.minX != null) {
106
+ globalMinCol = Math.floor(bounds.minX / 2);
107
+ } else {
108
+ let min = Number.POSITIVE_INFINITY;
109
+ for (const rowMap of canvas.chars.values()) {
110
+ for (const c of rowMap.keys()) {
111
+ if (c < min) min = c;
112
+ }
113
+ }
114
+ globalMinCol = min === Number.POSITIVE_INFINITY ? 0 : min;
115
+ }
116
+ const result = [];
117
+ for (let rowNum = minRow; rowNum <= maxRow; rowNum++) {
118
+ const rowMap = canvas.chars.get(rowNum);
119
+ if (!rowMap) {
120
+ result.push("");
121
+ continue;
122
+ }
123
+ let maxCol;
124
+ if (bounds?.maxX != null) {
125
+ maxCol = Math.floor((bounds.maxX - 1) / 2);
126
+ } else {
127
+ maxCol = Math.max(...rowMap.keys());
128
+ }
129
+ const chars = [];
130
+ for (let c = globalMinCol; c <= maxCol; c++) {
131
+ const cell = rowMap.get(c);
132
+ if (cell === void 0 || cell === 0) {
133
+ chars.push(" ");
134
+ } else if (typeof cell !== "number") {
135
+ chars.push(cell);
136
+ } else {
137
+ chars.push(String.fromCharCode(BRAILLE_OFFSET + cell));
138
+ }
139
+ }
140
+ result.push(chars.join(""));
141
+ }
142
+ return result;
143
+ }
144
+ function frame(canvas, bounds) {
145
+ return rows(canvas, bounds).join(canvas.lineEnding);
146
+ }
147
+ function* line(x1, y1, x2, y2) {
148
+ const nx1 = normalize(x1);
149
+ const ny1 = normalize(y1);
150
+ const nx2 = normalize(x2);
151
+ const ny2 = normalize(y2);
152
+ const xDiff = Math.abs(nx2 - nx1);
153
+ const yDiff = Math.abs(ny2 - ny1);
154
+ const xDir = nx1 <= nx2 ? 1 : -1;
155
+ const yDir = ny1 <= ny2 ? 1 : -1;
156
+ const steps = Math.max(xDiff, yDiff);
157
+ for (let i = 0; i <= steps; i++) {
158
+ let px = nx1;
159
+ let py = ny1;
160
+ if (yDiff) {
161
+ py += Math.round(i * yDiff / steps) * yDir;
162
+ }
163
+ if (xDiff) {
164
+ px += Math.round(i * xDiff / steps) * xDir;
165
+ }
166
+ yield { x: px, y: py };
167
+ }
168
+ }
169
+ function* polygon(centerX = 0, centerY = 0, sides = 4, radius = 4) {
170
+ const degree = 360 / sides;
171
+ for (let n = 0; n < sides; n++) {
172
+ const a = n * degree;
173
+ const b = (n + 1) * degree;
174
+ const x1 = (centerX + Math.cos(a * Math.PI / 180)) * ((radius + 1) / 2);
175
+ const y1 = (centerY + Math.sin(a * Math.PI / 180)) * ((radius + 1) / 2);
176
+ const x2 = (centerX + Math.cos(b * Math.PI / 180)) * ((radius + 1) / 2);
177
+ const y2 = (centerY + Math.sin(b * Math.PI / 180)) * ((radius + 1) / 2);
178
+ for (const pt of line(x1, y1, x2, y2)) {
179
+ yield pt;
180
+ }
181
+ }
182
+ }
183
+ function createTurtle(posX = 0, posY = 0) {
184
+ return {
185
+ canvas: createCanvas(),
186
+ posX,
187
+ posY,
188
+ rotation: 0,
189
+ brushOn: true
190
+ };
191
+ }
192
+ function turtleUp(t) {
193
+ t.brushOn = false;
194
+ }
195
+ function turtleDown(t) {
196
+ t.brushOn = true;
197
+ }
198
+ function turtleMove(t, x, y) {
199
+ if (t.brushOn) {
200
+ for (const pt of line(t.posX, t.posY, x, y)) {
201
+ set(t.canvas, pt.x, pt.y);
202
+ }
203
+ }
204
+ t.posX = x;
205
+ t.posY = y;
206
+ }
207
+ function turtleForward(t, step) {
208
+ const x = t.posX + Math.cos(t.rotation * Math.PI / 180) * step;
209
+ const y = t.posY + Math.sin(t.rotation * Math.PI / 180) * step;
210
+ const prevBrush = t.brushOn;
211
+ t.brushOn = true;
212
+ turtleMove(t, x, y);
213
+ t.brushOn = prevBrush;
214
+ }
215
+ function turtleBack(t, step) {
216
+ turtleForward(t, -step);
217
+ }
218
+ function turtleRight(t, angle) {
219
+ t.rotation += angle;
220
+ }
221
+ function turtleLeft(t, angle) {
222
+ t.rotation -= angle;
223
+ }
224
+ function getTerminalSize() {
225
+ const width = process.stdout?.columns ?? 80;
226
+ const height = process.stdout?.rows ?? 25;
227
+ return { width, height };
228
+ }
229
+ function sleep(ms) {
230
+ return new Promise((resolve) => setTimeout(resolve, ms));
231
+ }
232
+ async function animate(canvas, fn, options) {
233
+ const delay = options?.delay ?? 1e3 / 24;
234
+ for (const points of fn()) {
235
+ if (options?.signal?.aborted) break;
236
+ for (const pt of points) {
237
+ set(canvas, pt.x, pt.y);
238
+ }
239
+ const f = frame(canvas);
240
+ process.stdout.write(`\x1B[H\x1B[J${f}
241
+ `);
242
+ if (delay > 0) {
243
+ await sleep(delay);
244
+ }
245
+ clear(canvas);
246
+ }
247
+ }
248
+ function createFixedCanvas(width, height) {
249
+ const w = Math.floor(width / 2) * 2;
250
+ const h = Math.floor(height / 4) * 4;
251
+ return {
252
+ width: w,
253
+ height: h,
254
+ content: new Uint8Array(w * h / 8)
255
+ };
256
+ }
257
+ function fixedClear(canvas) {
258
+ canvas.content.fill(0);
259
+ }
260
+ function fixedSet(canvas, x, y) {
261
+ if (!(x >= 0 && x < canvas.width && y >= 0 && y < canvas.height)) return;
262
+ const fx = Math.floor(x);
263
+ const fy = Math.floor(y);
264
+ const nx = Math.floor(fx / 2);
265
+ const ny = Math.floor(fy / 4);
266
+ const coord = nx + canvas.width / 2 * ny;
267
+ const mask = PIXEL_MAP[fy % 4]?.[fx % 2];
268
+ if (mask === void 0) return;
269
+ const val = canvas.content[coord];
270
+ if (val === void 0) return;
271
+ canvas.content[coord] = val | mask;
272
+ }
273
+ function fixedUnset(canvas, x, y) {
274
+ if (!(x >= 0 && x < canvas.width && y >= 0 && y < canvas.height)) return;
275
+ const fx = Math.floor(x);
276
+ const fy = Math.floor(y);
277
+ const nx = Math.floor(fx / 2);
278
+ const ny = Math.floor(fy / 4);
279
+ const coord = nx + canvas.width / 2 * ny;
280
+ const mask = PIXEL_MAP[fy % 4]?.[fx % 2];
281
+ if (mask === void 0) return;
282
+ const val = canvas.content[coord];
283
+ if (val === void 0) return;
284
+ canvas.content[coord] = val & ~mask;
285
+ }
286
+ function fixedToggle(canvas, x, y) {
287
+ if (!(x >= 0 && x < canvas.width && y >= 0 && y < canvas.height)) return;
288
+ const fx = Math.floor(x);
289
+ const fy = Math.floor(y);
290
+ const nx = Math.floor(fx / 2);
291
+ const ny = Math.floor(fy / 4);
292
+ const coord = nx + canvas.width / 2 * ny;
293
+ const mask = PIXEL_MAP[fy % 4]?.[fx % 2];
294
+ if (mask === void 0) return;
295
+ const val = canvas.content[coord];
296
+ if (val === void 0) return;
297
+ canvas.content[coord] = val ^ mask;
298
+ }
299
+ function fixedGet(canvas, x, y) {
300
+ if (!(x >= 0 && x < canvas.width && y >= 0 && y < canvas.height))
301
+ return false;
302
+ const fx = Math.floor(x);
303
+ const fy = Math.floor(y);
304
+ const nx = Math.floor(fx / 2);
305
+ const ny = Math.floor(fy / 4);
306
+ const coord = nx + canvas.width / 2 * ny;
307
+ const mask = PIXEL_MAP[fy % 4]?.[fx % 2];
308
+ if (mask === void 0) return false;
309
+ const val = canvas.content[coord];
310
+ if (val === void 0) return false;
311
+ return (val & mask) !== 0;
312
+ }
313
+ function fixedFrame(canvas, delimiter = "\n") {
314
+ const frameWidth = canvas.width / 2;
315
+ const parts = [];
316
+ for (let i = 0; i < canvas.content.length; i++) {
317
+ if (i % frameWidth === 0) {
318
+ parts.push(delimiter);
319
+ }
320
+ const val = canvas.content[i];
321
+ parts.push(val ? String.fromCharCode(BRAILLE_OFFSET + val) : " ");
322
+ }
323
+ parts.push(delimiter);
324
+ return parts.join("");
325
+ }
326
+ export {
327
+ BRAILLE_OFFSET,
328
+ PIXEL_MAP,
329
+ animate,
330
+ clear,
331
+ createCanvas,
332
+ createFixedCanvas,
333
+ createTurtle,
334
+ fixedClear,
335
+ fixedFrame,
336
+ fixedGet,
337
+ fixedSet,
338
+ fixedToggle,
339
+ fixedUnset,
340
+ frame,
341
+ get,
342
+ getPos,
343
+ getTerminalSize,
344
+ line,
345
+ normalize,
346
+ polygon,
347
+ rows,
348
+ set,
349
+ setText,
350
+ toggle,
351
+ turtleBack,
352
+ turtleDown,
353
+ turtleForward,
354
+ turtleLeft,
355
+ turtleMove,
356
+ turtleRight,
357
+ turtleUp,
358
+ unset
359
+ };
360
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * btui — Braille TUI canvas for terminal drawing with Unicode braille characters.\n *\n * Braille dot layout per character cell:\n * ,___,\n * |1 4|\n * |2 5|\n * |3 6|\n * |7 8|\n * `````\n *\n * Each character maps to a 2x4 pixel grid.\n * Unicode braille characters: U+2800 to U+28FF.\n */\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst BRAILLE_OFFSET = 0x2800;\n\nconst PIXEL_MAP: readonly (readonly [number, number])[] = [\n\t[0x01, 0x08],\n\t[0x02, 0x10],\n\t[0x04, 0x20],\n\t[0x40, 0x80],\n] as const;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/** A single cell can hold a braille bitmask (number) or a text character (string). */\ntype CellValue = number | string;\n\n/** Sparse row → col → cell storage. */\ninterface CanvasChars {\n\treadonly [row: number]: {readonly [col: number]: CellValue} | undefined;\n}\n\n/** The braille canvas data structure. */\ninterface Canvas {\n\tchars: Map<number, Map<number, CellValue>>;\n\treadonly lineEnding: string;\n}\n\n/** Options for creating a canvas. */\ninterface CreateCanvasOptions {\n\treadonly lineEnding?: string;\n}\n\n/** Bounds for frame/rows rendering. */\ninterface FrameBounds {\n\treadonly minX?: number;\n\treadonly minY?: number;\n\treadonly maxX?: number;\n\treadonly maxY?: number;\n}\n\n/** A 2D point. */\ninterface Point {\n\treadonly x: number;\n\treadonly y: number;\n}\n\n/** Turtle graphics state. */\ninterface TurtleState {\n\treadonly canvas: Canvas;\n\tposX: number;\n\tposY: number;\n\trotation: number;\n\tbrushOn: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Coordinate helpers\n// ---------------------------------------------------------------------------\n\n/** Normalize a coordinate to an integer. */\nfunction normalize(coord: number): number {\n\treturn Math.round(coord);\n}\n\n/** Convert pixel (x, y) to cell (col, row). */\nfunction getPos(x: number, y: number): readonly [number, number] {\n\treturn [Math.floor(normalize(x) / 2), Math.floor(normalize(y) / 4)];\n}\n\n// ---------------------------------------------------------------------------\n// Canvas — creation & mutation\n// ---------------------------------------------------------------------------\n\n/** Create a new braille canvas. */\nfunction createCanvas(options?: CreateCanvasOptions): Canvas {\n\treturn {\n\t\tchars: new Map(),\n\t\tlineEnding: options?.lineEnding ?? '\\n',\n\t};\n}\n\n/** Remove all pixels from the canvas. */\nfunction clear(canvas: Canvas): void {\n\tcanvas.chars.clear();\n}\n\n/** Set (draw) a pixel at (x, y). */\nfunction set(canvas: Canvas, x: number, y: number): void {\n\tconst nx = normalize(x);\n\tconst ny = normalize(y);\n\tconst [col, row] = getPos(nx, ny);\n\n\tlet rowMap = canvas.chars.get(row);\n\tif (!rowMap) {\n\t\trowMap = new Map();\n\t\tcanvas.chars.set(row, rowMap);\n\t}\n\n\tconst current = rowMap.get(col);\n\tif (current !== undefined && typeof current !== 'number') {\n\t\treturn;\n\t}\n\n\tconst mask = PIXEL_MAP[ny % 4]?.[nx % 2];\n\tif (mask === undefined) return;\n\n\trowMap.set(col, ((current as number | undefined) ?? 0) | mask);\n}\n\n/** Unset (erase) a pixel at (x, y). */\nfunction unset(canvas: Canvas, x: number, y: number): void {\n\tconst nx = normalize(x);\n\tconst ny = normalize(y);\n\tconst [col, row] = getPos(nx, ny);\n\n\tconst rowMap = canvas.chars.get(row);\n\tif (!rowMap) return;\n\n\tconst current = rowMap.get(col);\n\tif (current === undefined || typeof current !== 'number') return;\n\n\tconst mask = PIXEL_MAP[ny % 4]?.[nx % 2];\n\tif (mask === undefined) return;\n\n\tconst updated = current & ~mask;\n\tif (updated === 0) {\n\t\trowMap.delete(col);\n\t\tif (rowMap.size === 0) {\n\t\t\tcanvas.chars.delete(row);\n\t\t}\n\t\treturn;\n\t}\n\n\trowMap.set(col, updated);\n}\n\n/** Toggle a pixel at (x, y). */\nfunction toggle(canvas: Canvas, x: number, y: number): void {\n\tconst nx = normalize(x);\n\tconst ny = normalize(y);\n\tconst [col, row] = getPos(nx, ny);\n\n\tconst rowMap = canvas.chars.get(row);\n\tconst current = rowMap?.get(col);\n\n\tconst mask = PIXEL_MAP[ny % 4]?.[nx % 2];\n\tif (mask === undefined) return;\n\n\tif (\n\t\tcurrent !== undefined &&\n\t\t(typeof current !== 'number' || (current & mask) !== 0)\n\t) {\n\t\tunset(canvas, x, y);\n\t} else {\n\t\tset(canvas, x, y);\n\t}\n}\n\n/** Get the state of a pixel at (x, y). Returns true if set. */\nfunction get(canvas: Canvas, x: number, y: number): boolean {\n\tconst nx = normalize(x);\n\tconst ny = normalize(y);\n\tconst [col, row] = getPos(nx, ny);\n\n\tconst rowMap = canvas.chars.get(row);\n\tif (!rowMap) return false;\n\n\tconst cell = rowMap.get(col);\n\tif (cell === undefined) return false;\n\tif (typeof cell !== 'number') return true;\n\n\tconst mask = PIXEL_MAP[ny % 4]?.[nx % 2];\n\tif (mask === undefined) return false;\n\n\treturn (cell & mask) !== 0;\n}\n\n/** Set text at the given pixel coords. Each character occupies one cell column. */\nfunction setText(canvas: Canvas, x: number, y: number, text: string): void {\n\tconst [col, row] = getPos(x, y);\n\n\tlet rowMap = canvas.chars.get(row);\n\tif (!rowMap) {\n\t\trowMap = new Map();\n\t\tcanvas.chars.set(row, rowMap);\n\t}\n\n\tfor (let i = 0; i < text.length; i++) {\n\t\trowMap.set(col + i, text[i] as string);\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Canvas — rendering\n// ---------------------------------------------------------------------------\n\n/** Return the canvas content as an array of strings (one per row). */\nfunction rows(canvas: Canvas, bounds?: FrameBounds): readonly string[] {\n\tif (canvas.chars.size === 0) return [];\n\n\tconst allRows = [...canvas.chars.keys()];\n\tconst minRow =\n\t\tbounds?.minY != null\n\t\t\t? Math.floor(bounds.minY / 4)\n\t\t\t: Math.min(...allRows);\n\tconst maxRow =\n\t\tbounds?.maxY != null\n\t\t\t? Math.floor((bounds.maxY - 1) / 4)\n\t\t\t: Math.max(...allRows);\n\n\t// Global min col for left alignment\n\tlet globalMinCol: number;\n\tif (bounds?.minX != null) {\n\t\tglobalMinCol = Math.floor(bounds.minX / 2);\n\t} else {\n\t\tlet min = Number.POSITIVE_INFINITY;\n\t\tfor (const rowMap of canvas.chars.values()) {\n\t\t\tfor (const c of rowMap.keys()) {\n\t\t\t\tif (c < min) min = c;\n\t\t\t}\n\t\t}\n\t\tglobalMinCol = min === Number.POSITIVE_INFINITY ? 0 : min;\n\t}\n\n\tconst result: string[] = [];\n\n\tfor (let rowNum = minRow; rowNum <= maxRow; rowNum++) {\n\t\tconst rowMap = canvas.chars.get(rowNum);\n\t\tif (!rowMap) {\n\t\t\tresult.push('');\n\t\t\tcontinue;\n\t\t}\n\n\t\tlet maxCol: number;\n\t\tif (bounds?.maxX != null) {\n\t\t\tmaxCol = Math.floor((bounds.maxX - 1) / 2);\n\t\t} else {\n\t\t\tmaxCol = Math.max(...rowMap.keys());\n\t\t}\n\n\t\tconst chars: string[] = [];\n\t\tfor (let c = globalMinCol; c <= maxCol; c++) {\n\t\t\tconst cell = rowMap.get(c);\n\t\t\tif (cell === undefined || cell === 0) {\n\t\t\t\tchars.push(' ');\n\t\t\t} else if (typeof cell !== 'number') {\n\t\t\t\tchars.push(cell);\n\t\t\t} else {\n\t\t\t\tchars.push(String.fromCharCode(BRAILLE_OFFSET + cell));\n\t\t\t}\n\t\t}\n\n\t\tresult.push(chars.join(''));\n\t}\n\n\treturn result;\n}\n\n/** Render the canvas as a string. */\nfunction frame(canvas: Canvas, bounds?: FrameBounds): string {\n\treturn rows(canvas, bounds).join(canvas.lineEnding);\n}\n\n// ---------------------------------------------------------------------------\n// Drawing utilities — line\n// ---------------------------------------------------------------------------\n\n/** Generate coordinates along a line from (x1, y1) to (x2, y2) using Bresenham-style interpolation. */\nfunction* line(\n\tx1: number,\n\ty1: number,\n\tx2: number,\n\ty2: number,\n): Generator<Point, void, unknown> {\n\tconst nx1 = normalize(x1);\n\tconst ny1 = normalize(y1);\n\tconst nx2 = normalize(x2);\n\tconst ny2 = normalize(y2);\n\n\tconst xDiff = Math.abs(nx2 - nx1);\n\tconst yDiff = Math.abs(ny2 - ny1);\n\tconst xDir = nx1 <= nx2 ? 1 : -1;\n\tconst yDir = ny1 <= ny2 ? 1 : -1;\n\n\tconst steps = Math.max(xDiff, yDiff);\n\n\tfor (let i = 0; i <= steps; i++) {\n\t\tlet px = nx1;\n\t\tlet py = ny1;\n\n\t\tif (yDiff) {\n\t\t\tpy += Math.round((i * yDiff) / steps) * yDir;\n\t\t}\n\t\tif (xDiff) {\n\t\t\tpx += Math.round((i * xDiff) / steps) * xDir;\n\t\t}\n\n\t\tyield {x: px, y: py};\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Drawing utilities — polygon\n// ---------------------------------------------------------------------------\n\n/** Generate coordinates for a regular polygon. */\nfunction* polygon(\n\tcenterX = 0,\n\tcenterY = 0,\n\tsides = 4,\n\tradius = 4,\n): Generator<Point, void, unknown> {\n\tconst degree = 360 / sides;\n\n\tfor (let n = 0; n < sides; n++) {\n\t\tconst a = n * degree;\n\t\tconst b = (n + 1) * degree;\n\t\tconst x1 =\n\t\t\t(centerX + Math.cos((a * Math.PI) / 180)) * ((radius + 1) / 2);\n\t\tconst y1 =\n\t\t\t(centerY + Math.sin((a * Math.PI) / 180)) * ((radius + 1) / 2);\n\t\tconst x2 =\n\t\t\t(centerX + Math.cos((b * Math.PI) / 180)) * ((radius + 1) / 2);\n\t\tconst y2 =\n\t\t\t(centerY + Math.sin((b * Math.PI) / 180)) * ((radius + 1) / 2);\n\n\t\tfor (const pt of line(x1, y1, x2, y2)) {\n\t\t\tyield pt;\n\t\t}\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Turtle graphics\n// ---------------------------------------------------------------------------\n\n/** Create a new turtle state. */\nfunction createTurtle(posX = 0, posY = 0): TurtleState {\n\treturn {\n\t\tcanvas: createCanvas(),\n\t\tposX,\n\t\tposY,\n\t\trotation: 0,\n\t\tbrushOn: true,\n\t};\n}\n\n/** Pull the brush up (stop drawing on move). */\nfunction turtleUp(t: TurtleState): void {\n\tt.brushOn = false;\n}\n\n/** Push the brush down (draw on move). */\nfunction turtleDown(t: TurtleState): void {\n\tt.brushOn = true;\n}\n\n/** Move the turtle to an absolute position, drawing if the brush is down. */\nfunction turtleMove(t: TurtleState, x: number, y: number): void {\n\tif (t.brushOn) {\n\t\tfor (const pt of line(t.posX, t.posY, x, y)) {\n\t\t\tset(t.canvas, pt.x, pt.y);\n\t\t}\n\t}\n\tt.posX = x;\n\tt.posY = y;\n}\n\n/** Move the turtle forward by `step` pixels in its current direction. */\nfunction turtleForward(t: TurtleState, step: number): void {\n\tconst x = t.posX + Math.cos((t.rotation * Math.PI) / 180) * step;\n\tconst y = t.posY + Math.sin((t.rotation * Math.PI) / 180) * step;\n\tconst prevBrush = t.brushOn;\n\tt.brushOn = true;\n\tturtleMove(t, x, y);\n\tt.brushOn = prevBrush;\n}\n\n/** Move the turtle backward by `step` pixels. */\nfunction turtleBack(t: TurtleState, step: number): void {\n\tturtleForward(t, -step);\n}\n\n/** Rotate the turtle right (clockwise) by `angle` degrees. */\nfunction turtleRight(t: TurtleState, angle: number): void {\n\tt.rotation += angle;\n}\n\n/** Rotate the turtle left (counter-clockwise) by `angle` degrees. */\nfunction turtleLeft(t: TurtleState, angle: number): void {\n\tt.rotation -= angle;\n}\n\n// ---------------------------------------------------------------------------\n// Terminal size\n// ---------------------------------------------------------------------------\n\n/** Terminal dimensions in characters. */\ninterface TerminalSize {\n\treadonly width: number;\n\treadonly height: number;\n}\n\n/** Get terminal width and height. Falls back to 80x25. */\nfunction getTerminalSize(): TerminalSize {\n\tconst width = process.stdout?.columns ?? 80;\n\tconst height = process.stdout?.rows ?? 25;\n\treturn {width, height};\n}\n\n// ---------------------------------------------------------------------------\n// Animation\n// ---------------------------------------------------------------------------\n\n/** Options for the animate function. */\ninterface AnimateOptions {\n\treadonly delay?: number;\n\treadonly signal?: AbortSignal;\n}\n\n/** Frame generator yields arrays of points per frame. */\ntype FrameGenerator = Generator<readonly Point[], void, unknown>;\n\n/** Sleep for the given number of milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n\treturn new Promise(resolve => setTimeout(resolve, ms));\n}\n\n/** Run an animation loop. Clears the canvas each frame, draws points from the generator, and writes to stdout. */\nasync function animate(\n\tcanvas: Canvas,\n\tfn: () => FrameGenerator,\n\toptions?: AnimateOptions,\n): Promise<void> {\n\tconst delay = options?.delay ?? 1000 / 24;\n\n\tfor (const points of fn()) {\n\t\tif (options?.signal?.aborted) break;\n\n\t\tfor (const pt of points) {\n\t\t\tset(canvas, pt.x, pt.y);\n\t\t}\n\n\t\tconst f = frame(canvas);\n\t\tprocess.stdout.write(`\\x1b[H\\x1b[J${f}\\n`);\n\n\t\tif (delay > 0) {\n\t\t\tawait sleep(delay);\n\t\t}\n\n\t\tclear(canvas);\n\t}\n}\n\n// ---------------------------------------------------------------------------\n// Fixed-size canvas (node-drawille style)\n// ---------------------------------------------------------------------------\n\n/** A fixed-size canvas backed by a Uint8Array buffer. */\ninterface FixedCanvas {\n\treadonly width: number;\n\treadonly height: number;\n\treadonly content: Uint8Array;\n}\n\n/** Create a fixed-size canvas with the given dimensions. Width rounds to multiple of 2, height to multiple of 4. */\nfunction createFixedCanvas(width: number, height: number): FixedCanvas {\n\tconst w = Math.floor(width / 2) * 2;\n\tconst h = Math.floor(height / 4) * 4;\n\treturn {\n\t\twidth: w,\n\t\theight: h,\n\t\tcontent: new Uint8Array((w * h) / 8),\n\t};\n}\n\n/** Clear all pixels from a fixed canvas. */\nfunction fixedClear(canvas: FixedCanvas): void {\n\tcanvas.content.fill(0);\n}\n\n/** Set a pixel on a fixed canvas. */\nfunction fixedSet(canvas: FixedCanvas, x: number, y: number): void {\n\tif (!(x >= 0 && x < canvas.width && y >= 0 && y < canvas.height)) return;\n\tconst fx = Math.floor(x);\n\tconst fy = Math.floor(y);\n\tconst nx = Math.floor(fx / 2);\n\tconst ny = Math.floor(fy / 4);\n\tconst coord = nx + (canvas.width / 2) * ny;\n\tconst mask = PIXEL_MAP[fy % 4]?.[fx % 2];\n\tif (mask === undefined) return;\n\tconst val = canvas.content[coord];\n\tif (val === undefined) return;\n\tcanvas.content[coord] = val | mask;\n}\n\n/** Unset a pixel on a fixed canvas. */\nfunction fixedUnset(canvas: FixedCanvas, x: number, y: number): void {\n\tif (!(x >= 0 && x < canvas.width && y >= 0 && y < canvas.height)) return;\n\tconst fx = Math.floor(x);\n\tconst fy = Math.floor(y);\n\tconst nx = Math.floor(fx / 2);\n\tconst ny = Math.floor(fy / 4);\n\tconst coord = nx + (canvas.width / 2) * ny;\n\tconst mask = PIXEL_MAP[fy % 4]?.[fx % 2];\n\tif (mask === undefined) return;\n\tconst val = canvas.content[coord];\n\tif (val === undefined) return;\n\tcanvas.content[coord] = val & ~mask;\n}\n\n/** Toggle a pixel on a fixed canvas. */\nfunction fixedToggle(canvas: FixedCanvas, x: number, y: number): void {\n\tif (!(x >= 0 && x < canvas.width && y >= 0 && y < canvas.height)) return;\n\tconst fx = Math.floor(x);\n\tconst fy = Math.floor(y);\n\tconst nx = Math.floor(fx / 2);\n\tconst ny = Math.floor(fy / 4);\n\tconst coord = nx + (canvas.width / 2) * ny;\n\tconst mask = PIXEL_MAP[fy % 4]?.[fx % 2];\n\tif (mask === undefined) return;\n\tconst val = canvas.content[coord];\n\tif (val === undefined) return;\n\tcanvas.content[coord] = val ^ mask;\n}\n\n/** Get the state of a pixel on a fixed canvas. */\nfunction fixedGet(canvas: FixedCanvas, x: number, y: number): boolean {\n\tif (!(x >= 0 && x < canvas.width && y >= 0 && y < canvas.height))\n\t\treturn false;\n\tconst fx = Math.floor(x);\n\tconst fy = Math.floor(y);\n\tconst nx = Math.floor(fx / 2);\n\tconst ny = Math.floor(fy / 4);\n\tconst coord = nx + (canvas.width / 2) * ny;\n\tconst mask = PIXEL_MAP[fy % 4]?.[fx % 2];\n\tif (mask === undefined) return false;\n\tconst val = canvas.content[coord];\n\tif (val === undefined) return false;\n\treturn (val & mask) !== 0;\n}\n\n/** Render a fixed canvas as a string. */\nfunction fixedFrame(canvas: FixedCanvas, delimiter = '\\n'): string {\n\tconst frameWidth = canvas.width / 2;\n\tconst parts: string[] = [];\n\n\tfor (let i = 0; i < canvas.content.length; i++) {\n\t\tif (i % frameWidth === 0) {\n\t\t\tparts.push(delimiter);\n\t\t}\n\t\tconst val = canvas.content[i];\n\t\tparts.push(val ? String.fromCharCode(BRAILLE_OFFSET + val) : ' ');\n\t}\n\tparts.push(delimiter);\n\n\treturn parts.join('');\n}\n\n// ---------------------------------------------------------------------------\n// Exports\n// ---------------------------------------------------------------------------\n\nexport {\n\t// Constants\n\tBRAILLE_OFFSET,\n\tPIXEL_MAP,\n\t// Types\n\ttype AnimateOptions,\n\ttype Canvas,\n\ttype CanvasChars,\n\ttype CellValue,\n\ttype CreateCanvasOptions,\n\ttype FixedCanvas,\n\ttype FrameBounds,\n\ttype FrameGenerator,\n\ttype Point,\n\ttype TerminalSize,\n\ttype TurtleState,\n\t// Coordinate helpers\n\tnormalize,\n\tgetPos,\n\t// Terminal\n\tgetTerminalSize,\n\t// Sparse canvas\n\tcreateCanvas,\n\tclear,\n\tset,\n\tunset,\n\ttoggle,\n\tget,\n\tsetText,\n\trows,\n\tframe,\n\t// Animation\n\tanimate,\n\t// Drawing utilities\n\tline,\n\tpolygon,\n\t// Turtle\n\tcreateTurtle,\n\tturtleUp,\n\tturtleDown,\n\tturtleMove,\n\tturtleForward,\n\tturtleBack,\n\tturtleRight,\n\tturtleLeft,\n\t// Fixed canvas\n\tcreateFixedCanvas,\n\tfixedClear,\n\tfixedSet,\n\tfixedUnset,\n\tfixedToggle,\n\tfixedGet,\n\tfixedFrame,\n};\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "termdot",
3
+ "version": "0.0.1",
4
+ "description": "Braille TUI — draw in the terminal with Unicode braille characters",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": ["dist"],
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "test": "vitest run",
20
+ "test:watch": "vitest",
21
+ "lint": "biome check .",
22
+ "lint:fix": "biome check --fix .",
23
+ "typecheck": "tsc --noEmit"
24
+ },
25
+ "keywords": [
26
+ "braille",
27
+ "tui",
28
+ "terminal",
29
+ "canvas",
30
+ "draw",
31
+ "unicode",
32
+ "cli"
33
+ ],
34
+ "license": "MIT",
35
+ "devDependencies": {
36
+ "@biomejs/biome": "^1.9.4",
37
+ "@types/node": "^25.2.3",
38
+ "tsup": "^8.4.0",
39
+ "typescript": "^5.7.3",
40
+ "vitest": "^3.0.5"
41
+ }
42
+ }