textmode.js 0.1.6-beta.4 → 0.1.6-beta.5

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.
@@ -1,4372 +0,0 @@
1
- var ee = Object.defineProperty;
2
- var te = (h, e, t) => e in h ? ee(h, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : h[e] = t;
3
- var o = (h, e, t) => te(h, typeof e != "symbol" ? e + "" : e, t);
4
- class x extends Error {
5
- constructor(t, r, i = {}) {
6
- const s = x.createFormattedMessage(t, i);
7
- super(s);
8
- o(this, "originalError");
9
- o(this, "context");
10
- this.name = "TextmodeError", this.originalError = r, this.context = i;
11
- }
12
- /**
13
- * Create a formatted error message that includes context
14
- */
15
- static createFormattedMessage(t, r) {
16
- let i = t;
17
- if (r && Object.keys(r).length > 0) {
18
- i += `
19
-
20
- 📋 Context:`;
21
- for (const [s, a] of Object.entries(r)) {
22
- const n = x.formatValue(a);
23
- i += `
24
- - ${s}: ${n}`;
25
- }
26
- }
27
- return i += `
28
-
29
- `, i += "↓".repeat(24) + `
30
- `, i;
31
- }
32
- /**
33
- * Format values for better display in error messages
34
- */
35
- static formatValue(t) {
36
- if (t === null) return "null";
37
- if (t === void 0) return "undefined";
38
- if (typeof t == "string") return `"${t}"`;
39
- if (typeof t == "number" || typeof t == "boolean") return String(t);
40
- if (Array.isArray(t))
41
- return t.length === 0 ? "[]" : t.length <= 5 ? `[${t.map((r) => x.formatValue(r)).join(", ")}]` : `[${t.slice(0, 3).map((r) => x.formatValue(r)).join(", ")}, ... +${t.length - 3} more]`;
42
- if (typeof t == "object") {
43
- const r = Object.keys(t);
44
- return r.length === 0 ? "{}" : r.length <= 3 ? `{ ${r.map((a) => `${a}: ${x.formatValue(t[a])}`).join(", ")} }` : `{ ${r.slice(0, 2).map((s) => `${s}: ${x.formatValue(t[s])}`).join(", ")}, ... +${r.length - 2} more }`;
45
- }
46
- return String(t);
47
- }
48
- }
49
- var re = /* @__PURE__ */ ((h) => (h[h.SILENT = 0] = "SILENT", h[h.WARNING = 1] = "WARNING", h[h.ERROR = 2] = "ERROR", h[h.THROW = 3] = "THROW", h))(re || {});
50
- const A = class A {
51
- constructor() {
52
- o(this, "_options", {
53
- globalLevel: 3
54
- /* THROW */
55
- });
56
- }
57
- static getInstance() {
58
- return A._instance || (A._instance = new A()), A._instance;
59
- }
60
- /**
61
- * Handle an error based on the configured settings
62
- * @returns true if execution should continue, false if error was handled
63
- */
64
- _handle(e, t, r) {
65
- const i = "[textmode.js]";
66
- switch (this._options.globalLevel) {
67
- case 0:
68
- return !1;
69
- // Validation failed, handled silently
70
- case 1:
71
- return console.group(
72
- `%c${i} Oops! (╯°□°)╯︵ Something went wrong in your code.`,
73
- "color: #f44336; font-weight: bold; background: #ffebee; padding: 2px 6px; border-radius: 3px;"
74
- ), console.warn(x.createFormattedMessage(e, t)), console.groupEnd(), !1;
75
- case 2:
76
- return console.group(
77
- `%c${i} Oops! (╯°□°)╯︵ Something went wrong in your code.`,
78
- "color: #f44336; font-weight: bold; background: #ffebee; padding: 2px 6px; border-radius: 3px;"
79
- ), console.error(x.createFormattedMessage(e, t)), console.groupEnd(), !1;
80
- case 3:
81
- default:
82
- const s = new x(e, r, t);
83
- throw console.group(
84
- `%c${i} Oops! (╯°□°)╯︵ Something went wrong in your code.`,
85
- "color: #d32f2f; font-weight: bold; background: #ffcdd2; padding: 2px 6px; border-radius: 3px;"
86
- ), s;
87
- }
88
- }
89
- /**
90
- * Validate a condition and handle errors if validation fails
91
- * @param condition The condition to validate
92
- * @param message Error message if validation fails
93
- * @param context Additional context for debugging
94
- * @returns true if validation passed, false if validation failed and was handled
95
- */
96
- validate(e, t, r) {
97
- return e ? !0 : (this._handle(t, r), !1);
98
- }
99
- /**
100
- * Set global error level
101
- */
102
- setGlobalLevel(e) {
103
- this._options.globalLevel = e;
104
- }
105
- };
106
- o(A, "_instance", null);
107
- let z = A;
108
- const _ = z.getInstance(), Z = /* @__PURE__ */ new WeakMap(), ie = /* @__PURE__ */ new WeakMap();
109
- function P(h, e) {
110
- Z.set(h, e);
111
- }
112
- function j(h) {
113
- return Z.get(h);
114
- }
115
- function B(h, e) {
116
- ie.set(h, e);
117
- }
118
- const I = class I {
119
- constructor(e, t, r = t, i = {}) {
120
- o(this, "gl");
121
- o(this, "_framebuffer");
122
- o(this, "_texture");
123
- o(this, "_width");
124
- o(this, "_height");
125
- o(this, "options");
126
- o(this, "previousState", null);
127
- o(this, "_pixels", null);
128
- // Tracks the last upload origin; no longer required for rendering since we flip at upload time
129
- o(this, "_isFromCanvasSource", !1);
130
- this.gl = e, this._width = t, this._height = r, this.options = {
131
- filter: "nearest",
132
- wrap: "clamp",
133
- format: "rgba",
134
- type: "unsigned_byte",
135
- ...i
136
- }, this._texture = this.createTexture(), this._framebuffer = e.createFramebuffer(), this.attachTexture();
137
- }
138
- getMaxTextureUnits() {
139
- let e = I.maxTextureUnitsMap.get(this.gl);
140
- return e === void 0 && (e = this.gl.getParameter(this.gl.MAX_TEXTURE_IMAGE_UNITS), I.maxTextureUnitsMap.set(this.gl, e)), e;
141
- }
142
- /**
143
- * Unbind the provided texture from any texture unit where it is currently bound.
144
- * This prevents framebuffer-texture feedback loops when rendering to this framebuffer.
145
- */
146
- unbindTextureAcrossUnits(e) {
147
- const t = this.gl, r = t.getParameter(t.ACTIVE_TEXTURE), i = this.getMaxTextureUnits();
148
- for (let s = 0; s < i; s++)
149
- t.activeTexture(t.TEXTURE0 + s), t.getParameter(t.TEXTURE_BINDING_2D) === e && t.bindTexture(t.TEXTURE_2D, null);
150
- t.activeTexture(r);
151
- }
152
- createTexture() {
153
- const { gl: e } = this, t = e.createTexture();
154
- e.bindTexture(e.TEXTURE_2D, t);
155
- const r = this.options.filter === "linear" ? e.LINEAR : e.NEAREST, i = this.options.wrap === "repeat" ? e.REPEAT : e.CLAMP_TO_EDGE;
156
- return e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MIN_FILTER, r), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MAG_FILTER, r), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_S, i), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_T, i), this.updateTextureSize(), t;
157
- }
158
- updateTextureSize() {
159
- const { gl: e } = this, t = e.RGBA, r = e.RGBA, i = this.options.type === "float" ? e.FLOAT : e.UNSIGNED_BYTE;
160
- e.texImage2D(e.TEXTURE_2D, 0, t, this._width, this._height, 0, r, i, null);
161
- }
162
- attachTexture() {
163
- const { gl: e } = this;
164
- e.bindFramebuffer(e.FRAMEBUFFER, this._framebuffer), e.framebufferTexture2D(e.FRAMEBUFFER, e.COLOR_ATTACHMENT0, e.TEXTURE_2D, this._texture, 0), e.bindFramebuffer(e.FRAMEBUFFER, null);
165
- }
166
- /**
167
- * Update the framebuffer texture with canvas or video content
168
- */
169
- update(e) {
170
- const { gl: t } = this;
171
- if (e instanceof HTMLVideoElement && e.readyState < 2)
172
- return;
173
- t.bindTexture(t.TEXTURE_2D, this._texture);
174
- const r = t.getParameter(t.UNPACK_FLIP_Y_WEBGL);
175
- t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL, 1), t.texImage2D(t.TEXTURE_2D, 0, t.RGBA, t.RGBA, t.UNSIGNED_BYTE, e), t.pixelStorei(t.UNPACK_FLIP_Y_WEBGL, r ? 1 : 0), t.bindTexture(t.TEXTURE_2D, null), this._isFromCanvasSource = !1;
176
- }
177
- /**
178
- * Update the framebuffer texture with pixel data
179
- */
180
- updatePixels(e, t, r) {
181
- const { gl: i } = this;
182
- i.bindTexture(i.TEXTURE_2D, this._texture), i.texImage2D(i.TEXTURE_2D, 0, i.RGBA, t, r, 0, i.RGBA, i.UNSIGNED_BYTE, e), i.bindTexture(i.TEXTURE_2D, null), this._isFromCanvasSource = !1;
183
- }
184
- /**
185
- * Resize the framebuffer
186
- */
187
- resize(e, t) {
188
- const { gl: r } = this;
189
- this._width = e, this._height = t, this._pixels = null, r.bindTexture(r.TEXTURE_2D, this._texture), this.updateTextureSize(), r.bindTexture(r.TEXTURE_2D, null);
190
- }
191
- /**
192
- * Begin rendering to this framebuffer
193
- */
194
- begin() {
195
- const { gl: e } = this;
196
- this.previousState = {
197
- framebuffer: e.getParameter(e.FRAMEBUFFER_BINDING),
198
- viewport: e.getParameter(e.VIEWPORT)
199
- }, e.bindFramebuffer(e.FRAMEBUFFER, this._framebuffer), e.viewport(0, 0, this._width, this._height), this.unbindTextureAcrossUnits(this._texture), B(e, !0), P(e, [0, 0, this._width, this._height]), this._isFromCanvasSource = !1;
200
- }
201
- /**
202
- * End rendering to this framebuffer and restore previous state
203
- */
204
- end() {
205
- if (!this.previousState) return;
206
- const { gl: e } = this;
207
- e.bindFramebuffer(e.FRAMEBUFFER, this.previousState.framebuffer), e.viewport(...this.previousState.viewport), B(e, !!this.previousState.framebuffer), P(e, this.previousState.viewport), this.previousState = null;
208
- }
209
- /**
210
- * Load pixel data from the framebuffer into the pixels array
211
- */
212
- loadPixels() {
213
- const { gl: e } = this;
214
- this._pixels || (this._pixels = new Uint8Array(this._width * this._height * 4));
215
- const t = e.getParameter(e.FRAMEBUFFER_BINDING);
216
- e.bindFramebuffer(e.FRAMEBUFFER, this._framebuffer), e.readPixels(0, 0, this._width, this._height, e.RGBA, e.UNSIGNED_BYTE, this._pixels), e.bindFramebuffer(e.FRAMEBUFFER, t);
217
- }
218
- get(e, t, r, i) {
219
- const { gl: s } = this;
220
- if (e === void 0 && t === void 0) {
221
- const a = new Uint8Array(this._width * this._height * 4), n = s.getParameter(s.FRAMEBUFFER_BINDING);
222
- return s.bindFramebuffer(s.FRAMEBUFFER, this._framebuffer), s.readPixels(0, 0, this._width, this._height, s.RGBA, s.UNSIGNED_BYTE, a), s.bindFramebuffer(s.FRAMEBUFFER, n), a;
223
- } else if (r === void 0 && i === void 0) {
224
- (e < 0 || t < 0 || e >= this._width || t >= this._height) && (console.warn("The x and y values passed to Framebuffer.get are outside of its range and will be clamped."), e = Math.max(0, Math.min(e, this._width - 1)), t = Math.max(0, Math.min(t, this._height - 1)));
225
- const a = new Uint8Array(4), n = s.getParameter(s.FRAMEBUFFER_BINDING);
226
- return s.bindFramebuffer(s.FRAMEBUFFER, this._framebuffer), s.readPixels(e, t, 1, 1, s.RGBA, s.UNSIGNED_BYTE, a), s.bindFramebuffer(s.FRAMEBUFFER, n), [a[0], a[1], a[2], a[3]];
227
- } else {
228
- e = Math.max(0, Math.min(e, this._width - 1)), t = Math.max(0, Math.min(t, this._height - 1)), r = Math.max(1, Math.min(r, this._width - e)), i = Math.max(1, Math.min(i, this._height - t));
229
- const a = new Uint8Array(r * i * 4), n = s.getParameter(s.FRAMEBUFFER_BINDING);
230
- return s.bindFramebuffer(s.FRAMEBUFFER, this._framebuffer), s.readPixels(e, t, r, i, s.RGBA, s.UNSIGNED_BYTE, a), s.bindFramebuffer(s.FRAMEBUFFER, n), a;
231
- }
232
- }
233
- /**
234
- * Dispose of WebGL resources used by this framebuffer.
235
- * This method is idempotent and safe to call multiple times.
236
- */
237
- dispose() {
238
- const { gl: e } = this;
239
- this._framebuffer && (e.deleteFramebuffer(this._framebuffer), this._framebuffer = null), this._texture && (e.deleteTexture(this._texture), this._texture = null), this._pixels = null;
240
- }
241
- get framebuffer() {
242
- return this._framebuffer;
243
- }
244
- get texture() {
245
- return this._texture;
246
- }
247
- get width() {
248
- return this._width;
249
- }
250
- get height() {
251
- return this._height;
252
- }
253
- get pixels() {
254
- return this._pixels;
255
- }
256
- /** True if the last content update came from a canvas/video or raw pixels (top-left origin). False if rendered via FBO (bottom-left). */
257
- get isFromCanvasSource() {
258
- return this._isFromCanvasSource;
259
- }
260
- };
261
- /** Cache for MAX_TEXTURE_IMAGE_UNITS per-context */
262
- o(I, "maxTextureUnitsMap", /* @__PURE__ */ new WeakMap());
263
- let $ = I;
264
- class y {
265
- constructor(e, t, r) {
266
- o(this, "gl");
267
- o(this, "program");
268
- o(this, "uniformLocations", /* @__PURE__ */ new Map());
269
- o(this, "attributeLocations", /* @__PURE__ */ new Map());
270
- o(this, "textureUnitCounter", 0);
271
- this.gl = e, this.program = this.createProgram(t, r), this.cacheLocations();
272
- }
273
- createProgram(e, t) {
274
- const r = this.createShader(this.gl.VERTEX_SHADER, e), i = this.createShader(this.gl.FRAGMENT_SHADER, t), s = this.gl.createProgram();
275
- if (this.gl.attachShader(s, r), this.gl.attachShader(s, i), this.gl.linkProgram(s), !this.gl.getProgramParameter(s, this.gl.LINK_STATUS)) {
276
- const a = this.gl.getProgramInfoLog(s);
277
- throw new Error(`Shader program link error: ${a}`);
278
- }
279
- return this.gl.deleteShader(r), this.gl.deleteShader(i), s;
280
- }
281
- createShader(e, t) {
282
- const r = this.gl.createShader(e);
283
- if (this.gl.shaderSource(r, t), this.gl.compileShader(r), !this.gl.getShaderParameter(r, this.gl.COMPILE_STATUS)) {
284
- const i = this.gl.getShaderInfoLog(r);
285
- throw this.gl.deleteShader(r), new Error(`Shader compilation error: ${i}`);
286
- }
287
- return r;
288
- }
289
- cacheLocations() {
290
- const e = this.gl.getProgramParameter(this.program, this.gl.ACTIVE_UNIFORMS);
291
- for (let r = 0; r < e; r++) {
292
- const i = this.gl.getActiveUniform(this.program, r);
293
- if (i) {
294
- const s = this.gl.getUniformLocation(this.program, i.name);
295
- s && this.uniformLocations.set(i.name, s);
296
- }
297
- }
298
- const t = this.gl.getProgramParameter(this.program, this.gl.ACTIVE_ATTRIBUTES);
299
- for (let r = 0; r < t; r++) {
300
- const i = this.gl.getActiveAttrib(this.program, r);
301
- if (i) {
302
- const s = this.gl.getAttribLocation(this.program, i.name);
303
- this.attributeLocations.set(i.name, s);
304
- }
305
- }
306
- }
307
- /**
308
- * Use this shader program
309
- */
310
- use() {
311
- this.gl.useProgram(this.program), this.resetTextureUnits();
312
- }
313
- /**
314
- * Set a single uniform value with automatic texture unit management
315
- */
316
- setUniform(e, t) {
317
- const r = this.uniformLocations.get(e);
318
- if (!r)
319
- return;
320
- const i = this.getUniformInfo(e);
321
- if (typeof t == "number")
322
- i && i.type === this.gl.INT ? this.gl.uniform1i(r, Math.floor(t)) : this.gl.uniform1f(r, t);
323
- else if (typeof t == "boolean")
324
- this.gl.uniform1i(r, t ? 1 : 0);
325
- else if (Array.isArray(t))
326
- if (i && (i.type === this.gl.INT_VEC2 || i.type === this.gl.INT_VEC3 || i.type === this.gl.INT_VEC4)) {
327
- const s = t.map((a) => Math.floor(a));
328
- switch (s.length) {
329
- case 2:
330
- this.gl.uniform2iv(r, s);
331
- break;
332
- case 3:
333
- this.gl.uniform3iv(r, s);
334
- break;
335
- case 4:
336
- this.gl.uniform4iv(r, s);
337
- break;
338
- default:
339
- console.warn(`Unsupported array length ${s.length} for uniform '${e}'`);
340
- }
341
- } else
342
- switch (t.length) {
343
- case 2:
344
- this.gl.uniform2f(r, t[0], t[1]);
345
- break;
346
- case 3:
347
- this.gl.uniform3f(r, t[0], t[1], t[2]);
348
- break;
349
- case 4:
350
- this.gl.uniform4f(r, t[0], t[1], t[2], t[3]);
351
- break;
352
- default:
353
- console.warn(`Unsupported array length ${t.length} for uniform '${e}'`);
354
- }
355
- else if (t instanceof WebGLTexture) {
356
- const s = this.getNextTextureUnit();
357
- this.gl.uniform1i(r, s), this.gl.activeTexture(this.gl.TEXTURE0 + s), this.gl.bindTexture(this.gl.TEXTURE_2D, t);
358
- } else if (t && typeof t == "object" && "texture" in t) {
359
- const s = this.getNextTextureUnit();
360
- this.gl.uniform1i(r, s), this.gl.activeTexture(this.gl.TEXTURE0 + s), this.gl.bindTexture(this.gl.TEXTURE_2D, t.texture);
361
- } else
362
- console.warn(`Unsupported uniform type for '${e}':`, typeof t);
363
- }
364
- /**
365
- * Get uniform info to determine the correct WebGL type
366
- */
367
- getUniformInfo(e) {
368
- const t = this.gl.getProgramParameter(this.program, this.gl.ACTIVE_UNIFORMS);
369
- for (let r = 0; r < t; r++) {
370
- const i = this.gl.getActiveUniform(this.program, r);
371
- if (i && i.name === e)
372
- return i;
373
- }
374
- return null;
375
- }
376
- getNextTextureUnit() {
377
- return this.textureUnitCounter++;
378
- }
379
- /**
380
- * Check if this shader has a specific uniform
381
- */
382
- hasUniform(e) {
383
- return this.uniformLocations.has(e);
384
- }
385
- /**
386
- * Check if this shader has a specific attribute
387
- */
388
- hasAttribute(e) {
389
- return this.attributeLocations.has(e);
390
- }
391
- /**
392
- * Get the WebGL program
393
- */
394
- get glProgram() {
395
- return this.program;
396
- }
397
- /**
398
- * Dispose of WebGL resources used by this shader.
399
- * This method is idempotent and safe to call multiple times.
400
- */
401
- dispose() {
402
- this.program && (this.gl.deleteProgram(this.program), this.program = null), this.uniformLocations.clear(), this.attributeLocations.clear(), this.textureUnitCounter = 0;
403
- }
404
- /**
405
- * Reset texture unit counter (useful when starting a new frame)
406
- */
407
- resetTextureUnits() {
408
- this.textureUnitCounter = 0;
409
- }
410
- }
411
- var T = "attribute vec2 a_position;attribute vec2 a_texCoord;varying vec2 v_uv;uniform float u_rotation;uniform vec2 u_center;uniform float u_aspectRatio;mat2 rotate2D(float angle){float s=sin(angle);float c=cos(angle);return mat2(c,-s,s,c);}void main(){v_uv=a_texCoord;vec2 pos=a_position;pos-=u_center;pos.x*=u_aspectRatio;pos=rotate2D(-u_rotation)*pos;pos.x/=u_aspectRatio;pos+=u_center;gl_Position=vec4(pos,0.0,1.0);}", se = "precision lowp float;uniform sampler2D u_texture;varying vec2 v_uv;void main(){gl_FragColor=texture2D(u_texture,v_uv);}", ae = "precision lowp float;uniform vec4 u_color;void main(){gl_FragColor=u_color;}";
412
- class ne {
413
- constructor(e) {
414
- o(this, "gl");
415
- o(this, "imageShader");
416
- o(this, "solidColorShader");
417
- o(this, "currentShader", null);
418
- // Cached attrib locations for current program
419
- o(this, "attribCache");
420
- // Fill state management - default: white fill enabled
421
- o(this, "currentFillColor", [1, 1, 1, 1]);
422
- o(this, "fillMode", !0);
423
- // Stroke state management - default: black stroke enabled, weight 1
424
- o(this, "currentStrokeColor", [0, 0, 0, 1]);
425
- o(this, "currentStrokeWeight", 1);
426
- o(this, "strokeMode", !0);
427
- // Transformation state management
428
- o(this, "currentRotation", 0);
429
- // in degrees
430
- // State stack for push/pop functionality
431
- o(this, "stateStack", []);
432
- // ----- Immediate-mode helpers that use a single static VBO (no per-call allocation) -----
433
- o(this, "unitBuffer", null);
434
- o(this, "bytesPerVertex", 16);
435
- // vec2 + vec2
436
- o(this, "scratchFramebuffer", null);
437
- this.gl = e, this.imageShader = new y(this.gl, T, se), this.solidColorShader = new y(this.gl, T, ae), this.attribCache = /* @__PURE__ */ new Map(), this.gl.enable(this.gl.BLEND), this.gl.blendEquation(this.gl.FUNC_ADD), this.gl.blendFunc(this.gl.ONE, this.gl.ONE_MINUS_SRC_ALPHA), P(this.gl, [0, 0, this.gl.canvas.width, this.gl.canvas.height]), B(this.gl, !1);
438
- }
439
- /**
440
- * Set the current shader
441
- */
442
- shader(e) {
443
- this.currentShader !== e && (this.currentShader = e, e.use());
444
- }
445
- /**
446
- * Sets the fill color for subsequent rendering operations
447
- * @param r Red component *(0-255)*
448
- * @param g Green component *(0-255, optional)*
449
- * @param b Blue component *(0-255, optional)*
450
- * @param a Alpha component *(0-255, optional)*
451
- */
452
- fill(e, t, r, i) {
453
- if (this.fillMode = !0, t === void 0 && r === void 0 && i === void 0) {
454
- const s = e / 255;
455
- this.currentFillColor = [s, s, s, 1];
456
- } else if (r !== void 0 && i === void 0)
457
- this.currentFillColor = [e / 255, t / 255, r / 255, 1];
458
- else if (r !== void 0 && i !== void 0)
459
- this.currentFillColor = [e / 255, t / 255, r / 255, i / 255];
460
- else
461
- throw new Error("Invalid fill parameters. Use fill(gray), fill(r,g,b), or fill(r,g,b,a)");
462
- }
463
- /**
464
- * Sets the stroke color for subsequent rendering operations
465
- * @param r Red component *(0-255)*
466
- * @param g Green component *(0-255, optional)*
467
- * @param b Blue component *(0-255, optional)*
468
- * @param a Alpha component *(0-255, optional)*
469
- */
470
- stroke(e, t, r, i) {
471
- if (this.strokeMode = !0, t === void 0 && r === void 0 && i === void 0) {
472
- const s = e / 255;
473
- this.currentStrokeColor = [s, s, s, 1];
474
- } else if (r !== void 0 && i === void 0)
475
- this.currentStrokeColor = [e / 255, t / 255, r / 255, 1];
476
- else if (r !== void 0 && i !== void 0)
477
- this.currentStrokeColor = [e / 255, t / 255, r / 255, i / 255];
478
- else
479
- throw new Error("Invalid stroke parameters. Use stroke(gray), stroke(r,g,b), or stroke(r,g,b,a)");
480
- }
481
- /**
482
- * Sets the stroke weight (thickness) for subsequent stroke operations
483
- * @param weight The stroke thickness in pixels
484
- */
485
- strokeWeight(e) {
486
- if (e < 0)
487
- throw new Error("Stroke weight must be non-negative");
488
- this.currentStrokeWeight = e;
489
- }
490
- /**
491
- * Disables stroke rendering for subsequent operations
492
- */
493
- noStroke() {
494
- this.strokeMode = !1;
495
- }
496
- /**
497
- * Disables fill rendering for subsequent operations
498
- */
499
- noFill() {
500
- this.fillMode = !1;
501
- }
502
- /**
503
- * Sets the rotation angle for subsequent rendering operations
504
- * @param degrees The rotation angle in degrees
505
- */
506
- rotate(e) {
507
- this.currentRotation = e;
508
- }
509
- /**
510
- * Save the current rendering state (fill, stroke, etc.) to the state stack
511
- */
512
- push() {
513
- this.stateStack.push({
514
- fillColor: [...this.currentFillColor],
515
- fillMode: this.fillMode,
516
- strokeColor: [...this.currentStrokeColor],
517
- strokeWeight: this.currentStrokeWeight,
518
- strokeMode: this.strokeMode,
519
- rotation: this.currentRotation
520
- });
521
- }
522
- /**
523
- * Restore the most recently saved rendering state from the state stack
524
- */
525
- pop() {
526
- const e = this.stateStack.pop();
527
- e ? (this.currentFillColor = e.fillColor, this.fillMode = e.fillMode, this.currentStrokeColor = e.strokeColor, this.currentStrokeWeight = e.strokeWeight, this.strokeMode = e.strokeMode, this.currentRotation = e.rotation) : console.warn("pop() called without matching push()");
528
- }
529
- /**
530
- * Reset frame-specific state - called automatically after each frame.
531
- * Note: This does not reset fill/stroke state as that should persist
532
- * across frames and be managed by push/pop or explicit calls.
533
- */
534
- reset() {
535
- this.currentShader = null, this.stateStack = [], this.currentRotation = 0;
536
- }
537
- createShader(e, t) {
538
- return new y(this.gl, e, t);
539
- }
540
- createFilterShader(e) {
541
- return new y(this.gl, T, e);
542
- }
543
- /**
544
- * Set a uniform value for the current shader
545
- */
546
- setUniform(e, t) {
547
- this.currentShader.setUniform(e, t);
548
- }
549
- /**
550
- * Draw a rectangle with the current fill and/or stroke settings
551
- */
552
- rect(e, t, r, i) {
553
- if (this.currentShader !== null) {
554
- if (this.currentRotation !== 0) {
555
- const { centerX: f, centerY: g, radians: v, aspectRatio: p } = this.calculateRotationParams(e, t, r, i);
556
- this.setUniform("u_rotation", v), this.setUniform("u_center", [f, g]), this.setUniform("u_aspectRatio", p);
557
- } else
558
- this.setUniform("u_rotation", 0), this.setUniform("u_center", [0, 0]), this.setUniform("u_aspectRatio", 1);
559
- this.drawRectImmediate(e, t, r, i), this.currentShader = null;
560
- return;
561
- }
562
- const a = this.solidColorShader, n = this.currentRotation !== 0;
563
- let l = 0, c = 0, u = 0, d = 1;
564
- if (n) {
565
- const f = this.calculateRotationParams(e, t, r, i);
566
- l = f.centerX, c = f.centerY, u = f.radians, d = f.aspectRatio;
567
- }
568
- this.fillMode && (this.shader(a), this.setUniform("u_color", this.currentFillColor), n && (this.setUniform("u_rotation", u), this.setUniform("u_center", [l, c]), this.setUniform("u_aspectRatio", d)), this.drawRectImmediate(e, t, r, i)), this.strokeMode && this.currentStrokeWeight > 0 && (this.shader(a), this.setUniform("u_color", this.currentStrokeColor), n && (this.setUniform("u_rotation", u), this.setUniform("u_center", [l, c]), this.setUniform("u_aspectRatio", d)), this.drawRectStrokeImmediate(e, t, r, i, this.currentStrokeWeight));
569
- }
570
- /**
571
- * Draw a line from (x1, y1) to (x2, y2) with the current stroke settings.
572
- * Lines only support stroke rendering - fill properties are ignored.
573
- * @param x1 X-coordinate of the line start point
574
- * @param y1 Y-coordinate of the line start point
575
- * @param x2 X-coordinate of the line end point
576
- * @param y2 Y-coordinate of the line end point
577
- */
578
- line(e, t, r, i) {
579
- if (this.currentShader !== null) {
580
- const b = (e + r) / 2, w = (t + i) / 2, F = Math.abs(r - e) || 1, C = Math.abs(i - t) || 1;
581
- if (this.currentRotation !== 0) {
582
- const { centerX: U, centerY: S, radians: R, aspectRatio: E } = this.calculateRotationParams(b - F / 2, w - C / 2, F, C);
583
- this.setUniform("u_rotation", R), this.setUniform("u_center", [U, S]), this.setUniform("u_aspectRatio", E);
584
- } else
585
- this.setUniform("u_rotation", 0), this.setUniform("u_center", [0, 0]), this.setUniform("u_aspectRatio", 1);
586
- const G = this.currentStrokeWeight > 0 ? this.currentStrokeWeight : 1;
587
- this.drawLineImmediate(e, t, r, i, G), this.currentShader = null;
588
- return;
589
- }
590
- if (!this.strokeMode || this.currentStrokeWeight <= 0) return;
591
- const a = this.solidColorShader, n = (e + r) / 2, l = (t + i) / 2, c = Math.abs(r - e) || 1, u = Math.abs(i - t) || 1, d = this.currentRotation !== 0;
592
- let f = 0, g = 0, v = 0, p = 1;
593
- if (d) {
594
- const b = this.calculateRotationParams(n - c / 2, l - u / 2, c, u);
595
- f = b.centerX, g = b.centerY, v = b.radians, p = b.aspectRatio;
596
- }
597
- this.shader(a), this.setUniform("u_color", this.currentStrokeColor), d && (this.setUniform("u_rotation", v), this.setUniform("u_center", [f, g]), this.setUniform("u_aspectRatio", p)), this.drawLineImmediate(e, t, r, i, this.currentStrokeWeight);
598
- }
599
- /**
600
- * Calculate rotation parameters for built-in shaders (NDC coordinates)
601
- */
602
- calculateRotationParams(e, t, r, i) {
603
- const s = j(this.gl) || [0, 0, this.gl.canvas.width, this.gl.canvas.height], a = s[2], n = s[3], l = a / n, c = e + r / 2, u = t + i / 2, d = c / a * 2 - 1, f = 1 - u / n * 2, g = this.currentRotation * Math.PI / 180;
604
- return { centerX: d, centerY: f, radians: g, aspectRatio: l };
605
- }
606
- /**
607
- * Create a new framebuffer
608
- */
609
- createFramebuffer(e, t, r = {}) {
610
- return new $(this.gl, e, t, r);
611
- }
612
- /**
613
- * Fill the current framebuffer with a solid color
614
- */
615
- background(e, t = e, r = e, i = 255) {
616
- this.clear(e / 255, t / 255, r / 255, i / 255);
617
- }
618
- /**
619
- * Clear the current framebuffer
620
- */
621
- clear(e = 0, t = 0, r = 0, i = 0) {
622
- this.gl.clearColor(e, t, r, i), this.gl.clear(this.gl.COLOR_BUFFER_BIT);
623
- }
624
- /**
625
- * Ensure viewport matches canvas dimensions
626
- */
627
- resetViewport() {
628
- this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height), P(this.gl, [0, 0, this.gl.canvas.width, this.gl.canvas.height]), B(this.gl, !1);
629
- }
630
- /**
631
- * Get the WebGL context
632
- */
633
- get context() {
634
- return this.gl;
635
- }
636
- /**
637
- * Dispose of all WebGL resources managed by this renderer.
638
- * This method is idempotent and safe to call multiple times.
639
- */
640
- dispose() {
641
- this.scratchFramebuffer && (this.scratchFramebuffer.dispose(), this.scratchFramebuffer = null), this.imageShader.dispose(), this.solidColorShader.dispose(), this.imageShader = null, this.solidColorShader = null, this.currentShader = null, this.stateStack = [];
642
- }
643
- /**
644
- * Render a framebuffer at a specific position with optional scaling
645
- */
646
- image(e, t, r, i, s) {
647
- const a = this.gl, n = i ?? e.width, l = s ?? e.height, c = a.getParameter(a.FRAMEBUFFER_BINDING);
648
- if (c && e.framebuffer === c) {
649
- (!this.scratchFramebuffer || this.scratchFramebuffer.width !== n || this.scratchFramebuffer.height !== l) && (this.scratchFramebuffer && this.scratchFramebuffer.dispose(), this.scratchFramebuffer = this.createFramebuffer(n, l)), this.scratchFramebuffer.begin(), this.clear(0, 0, 0, 0), this.shader(this.imageShader), this.setUniform("u_texture", e.texture), this.setUniform("u_rotation", 0), this.setUniform("u_center", [0, 0]), this.setUniform("u_aspectRatio", 1), this.drawRectImmediate(0, 0, n, l), this.scratchFramebuffer.end(), this.shader(this.imageShader);
650
- const d = this.currentRotation !== 0 ? this.calculateRotationParams(t, r, n, l) : { centerX: 0, centerY: 0, radians: 0, aspectRatio: 1 };
651
- this.setUniform("u_texture", this.scratchFramebuffer.texture), this.setUniform("u_rotation", d.radians), this.setUniform("u_center", [d.centerX, d.centerY]), this.setUniform("u_aspectRatio", d.aspectRatio), this.drawRectImmediate(t, r, n, l), a.bindTexture(a.TEXTURE_2D, null), this.currentShader = null;
652
- return;
653
- }
654
- this.shader(this.imageShader), this.setUniform("u_texture", e.texture);
655
- const u = this.currentRotation !== 0 ? this.calculateRotationParams(t, r, n, l) : { centerX: 0, centerY: 0, radians: 0, aspectRatio: 1 };
656
- this.setUniform("u_rotation", u.radians), this.setUniform("u_center", [u.centerX, u.centerY]), this.setUniform("u_aspectRatio", u.aspectRatio), this.drawRectImmediate(t, r, n, l), a.bindTexture(a.TEXTURE_2D, null), this.currentShader = null;
657
- }
658
- ensureUnitBuffer() {
659
- if (this.unitBuffer) return;
660
- const e = this.gl;
661
- this.unitBuffer = e.createBuffer(), e.bindBuffer(e.ARRAY_BUFFER, this.unitBuffer);
662
- }
663
- enableAttribs() {
664
- const e = this.gl, t = e.getParameter(e.CURRENT_PROGRAM);
665
- let r = this.attribCache.get(t);
666
- return r || (r = {
667
- a_position: e.getAttribLocation(t, "a_position"),
668
- a_texCoord: e.getAttribLocation(t, "a_texCoord")
669
- }, this.attribCache.set(t, r)), e.enableVertexAttribArray(r.a_position), e.vertexAttribPointer(r.a_position, 2, e.FLOAT, !1, this.bytesPerVertex, 0), e.enableVertexAttribArray(r.a_texCoord), e.vertexAttribPointer(r.a_texCoord, 2, e.FLOAT, !1, this.bytesPerVertex, 8), { positionLoc: r.a_position, texLoc: r.a_texCoord };
670
- }
671
- disableAttribs(e, t) {
672
- const r = this.gl;
673
- r.disableVertexAttribArray(e), r.disableVertexAttribArray(t);
674
- }
675
- uploadQuadNDC(e, t, r, i) {
676
- const s = this.gl;
677
- this.ensureUnitBuffer(), s.bindBuffer(s.ARRAY_BUFFER, this.unitBuffer);
678
- const a = 0, n = 0, l = 1, c = 1, u = new Float32Array([
679
- e,
680
- i,
681
- 0,
682
- a,
683
- // bottom-left
684
- r,
685
- i,
686
- 1,
687
- n,
688
- // bottom-right
689
- e,
690
- t,
691
- 0,
692
- l,
693
- // top-left
694
- e,
695
- t,
696
- 0,
697
- l,
698
- // top-left
699
- r,
700
- i,
701
- 1,
702
- n,
703
- // bottom-right
704
- r,
705
- t,
706
- 1,
707
- c
708
- // top-right
709
- ]);
710
- s.bufferData(s.ARRAY_BUFFER, u, s.DYNAMIC_DRAW);
711
- }
712
- toNDC(e, t) {
713
- const r = this.gl, i = j(r) || [0, 0, r.canvas.width, r.canvas.height], s = i[2], a = i[3], n = e / s * 2 - 1, l = 1 - t / a * 2;
714
- return { nx: n, ny: l };
715
- }
716
- drawRectImmediate(e, t, r, i) {
717
- const s = this.toNDC(e, t), a = this.toNDC(e + r, t + i);
718
- this.uploadQuadNDC(s.nx, s.ny, a.nx, a.ny);
719
- const n = this.enableAttribs();
720
- this.gl.drawArrays(this.gl.TRIANGLES, 0, 6), this.disableAttribs(n.positionLoc, n.texLoc);
721
- }
722
- drawRectStrokeImmediate(e, t, r, i, s) {
723
- this.drawRectImmediate(e, t, r, s), this.drawRectImmediate(e + r - s, t, s, i), this.drawRectImmediate(e, t + i - s, r, s), this.drawRectImmediate(e, t, s, i);
724
- }
725
- drawLineImmediate(e, t, r, i, s) {
726
- const a = r - e, n = i - t, l = Math.hypot(a, n);
727
- if (l === 0) {
728
- this.drawRectImmediate(e - s / 2, t - s / 2, s, s);
729
- return;
730
- }
731
- const c = a / l, d = -(n / l), f = c, g = s / 2, v = e + d * g, p = t + f * g, b = e - d * g, w = t - f * g, F = r + d * g, C = i + f * g, G = r - d * g, U = i - f * g, S = this.toNDC(v, p), R = this.toNDC(b, w), E = this.toNDC(F, C), W = this.toNDC(G, U), M = this.gl;
732
- this.ensureUnitBuffer(), M.bindBuffer(M.ARRAY_BUFFER, this.unitBuffer);
733
- const J = new Float32Array([
734
- S.nx,
735
- S.ny,
736
- 0,
737
- 0,
738
- R.nx,
739
- R.ny,
740
- 0,
741
- 1,
742
- E.nx,
743
- E.ny,
744
- 1,
745
- 0,
746
- R.nx,
747
- R.ny,
748
- 0,
749
- 1,
750
- W.nx,
751
- W.ny,
752
- 1,
753
- 1,
754
- E.nx,
755
- E.ny,
756
- 1,
757
- 0
758
- ]);
759
- M.bufferData(M.ARRAY_BUFFER, J, M.DYNAMIC_DRAW);
760
- const Y = this.enableAttribs();
761
- M.drawArrays(M.TRIANGLES, 0, 6), this.disableAttribs(Y.positionLoc, Y.texLoc);
762
- }
763
- }
764
- var m = {};
765
- m.parse = function(h) {
766
- var e = function(s, a, n, l) {
767
- var c = m.T, u = {
768
- cmap: c.cmap,
769
- head: c.head,
770
- hhea: c.hhea,
771
- maxp: c.maxp,
772
- hmtx: c.hmtx,
773
- loca: c.loca,
774
- glyf: c.glyf
775
- }, d = { _data: s, _index: a, _offset: n };
776
- for (var f in u) {
777
- var g = m.findTable(s, f, n);
778
- if (g) {
779
- var v = g[0], p = l[v];
780
- p == null && (p = u[f].parseTab(s, v, g[1], d)), d[f] = l[v] = p;
781
- }
782
- }
783
- return d;
784
- }, t = new Uint8Array(h), r = {}, i = e(t, 0, 0, r);
785
- return [i];
786
- };
787
- m.findTable = function(h, e, t) {
788
- for (var r = m.B, i = r.readUshort(h, t + 4), s = t + 12, a = 0; a < i; a++) {
789
- var n = r.readASCII(h, s, 4);
790
- r.readUint(h, s + 4);
791
- var l = r.readUint(h, s + 8), c = r.readUint(h, s + 12);
792
- if (n == e) return [l, c];
793
- s += 16;
794
- }
795
- return null;
796
- };
797
- m.T = {};
798
- m.B = {
799
- readShort: function(h, e) {
800
- var t = m.B.t.uint16;
801
- return t[0] = h[e] << 8 | h[e + 1], m.B.t.int16[0];
802
- },
803
- readUshort: function(h, e) {
804
- return h[e] << 8 | h[e + 1];
805
- },
806
- readUshorts: function(h, e, t) {
807
- for (var r = [], i = 0; i < t; i++)
808
- r.push(m.B.readUshort(h, e + i * 2));
809
- return r;
810
- },
811
- readUint: function(h, e) {
812
- var t = m.B.t.uint8;
813
- return t[3] = h[e], t[2] = h[e + 1], t[1] = h[e + 2], t[0] = h[e + 3], m.B.t.uint32[0];
814
- },
815
- readASCII: function(h, e, t) {
816
- for (var r = "", i = 0; i < t; i++) r += String.fromCharCode(h[e + i]);
817
- return r;
818
- },
819
- // Simplified typed array buffer - only what's needed
820
- t: function() {
821
- var h = new ArrayBuffer(8);
822
- return {
823
- uint8: new Uint8Array(h),
824
- int16: new Int16Array(h),
825
- uint16: new Uint16Array(h),
826
- uint32: new Uint32Array(h)
827
- };
828
- }()
829
- };
830
- m.T.cmap = {
831
- parseTab: function(h, e, t) {
832
- var r = { tables: [], ids: {}, off: e };
833
- h = new Uint8Array(h.buffer, e, t), e = 0;
834
- var i = m.B, s = i.readUshort, a = m.T.cmap;
835
- s(h, e), e += 2;
836
- var n = s(h, e);
837
- e += 2;
838
- for (var l = [], c = 0; c < n; c++) {
839
- var u = s(h, e);
840
- e += 2;
841
- var d = s(h, e);
842
- e += 2;
843
- var f = i.readUint(h, e);
844
- e += 4;
845
- var g = "p" + u + "e" + d, v = l.indexOf(f);
846
- if (v == -1) {
847
- v = r.tables.length;
848
- var p = {};
849
- l.push(f);
850
- var b = p.format = s(h, f);
851
- b == 4 ? p = a.parse4(h, f, p) : b == 12 && (p = a.parse12(h, f, p)), r.tables.push(p);
852
- }
853
- r.ids[g] != null && console.log("multiple tables for one platform+encoding: " + g), r.ids[g] = v;
854
- }
855
- return r;
856
- },
857
- parse4: function(h, e, t) {
858
- var r = m.B, i = r.readUshort, s = r.readUshorts, a = e;
859
- e += 2;
860
- var n = i(h, e);
861
- e += 2, i(h, e), e += 2;
862
- var l = i(h, e);
863
- e += 2;
864
- var c = l >>> 1;
865
- t.searchRange = i(h, e), e += 2, t.entrySelector = i(h, e), e += 2, t.rangeShift = i(h, e), e += 2, t.endCount = s(h, e, c), e += c * 2, e += 2, t.startCount = s(h, e, c), e += c * 2, t.idDelta = [];
866
- for (var u = 0; u < c; u++)
867
- t.idDelta.push(r.readShort(h, e)), e += 2;
868
- return t.idRangeOffset = s(h, e, c), e += c * 2, t.glyphIdArray = s(h, e, a + n - e >> 1), t;
869
- },
870
- parse12: function(h, e, t) {
871
- var r = m.B, i = r.readUint;
872
- e += 4, i(h, e), e += 4, i(h, e), e += 4;
873
- var s = i(h, e) * 3;
874
- e += 4;
875
- for (var a = t.groups = new Uint32Array(s), n = 0; n < s; n += 3)
876
- a[n] = i(h, e + (n << 2)), a[n + 1] = i(h, e + (n << 2) + 4), a[n + 2] = i(h, e + (n << 2) + 8);
877
- return t;
878
- }
879
- };
880
- m.T.head = {
881
- parseTab: function(h, e, t) {
882
- var r = m.B, i = {};
883
- return e += 18, i.unitsPerEm = r.readUshort(h, e), e += 2, e += 16, i.xMin = r.readShort(h, e), e += 2, i.yMin = r.readShort(h, e), e += 2, i.xMax = r.readShort(h, e), e += 2, i.yMax = r.readShort(h, e), e += 2, e += 6, i.indexToLocFormat = r.readShort(h, e), i;
884
- }
885
- };
886
- m.T.hhea = {
887
- parseTab: function(h, e, t) {
888
- var r = m.B, i = {};
889
- e += 4;
890
- for (var s = [
891
- "ascender",
892
- "descender",
893
- "lineGap",
894
- "advanceWidthMax",
895
- "minLeftSideBearing",
896
- "minRightSideBearing",
897
- "xMaxExtent",
898
- "caretSlopeRise",
899
- "caretSlopeRun",
900
- "caretOffset",
901
- "res0",
902
- "res1",
903
- "res2",
904
- "res3",
905
- "metricDataFormat",
906
- "numberOfHMetrics"
907
- ], a = 0; a < s.length; a++) {
908
- var n = s[a], l = n == "advanceWidthMax" || n == "numberOfHMetrics" ? r.readUshort : r.readShort;
909
- i[n] = l(h, e + a * 2);
910
- }
911
- return i;
912
- }
913
- };
914
- m.T.hmtx = {
915
- parseTab: function(h, e, t, r) {
916
- for (var i = m.B, s = [], a = [], n = r.maxp.numGlyphs, l = r.hhea.numberOfHMetrics, c = 0, u = 0, d = 0; d < l; )
917
- c = i.readUshort(h, e + (d << 2)), u = i.readShort(h, e + (d << 2) + 2), s.push(c), a.push(u), d++;
918
- for (; d < n; )
919
- s.push(c), a.push(u), d++;
920
- return { aWidth: s, lsBearing: a };
921
- }
922
- };
923
- m.T.maxp = {
924
- parseTab: function(h, e, t) {
925
- var r = m.B, i = r.readUshort, s = {};
926
- return r.readUint(h, e), e += 4, s.numGlyphs = i(h, e), e += 2, s;
927
- }
928
- };
929
- m.T.loca = {
930
- parseTab: function(h, e, t, r) {
931
- var i = m.B, s = [], a = r.head.indexToLocFormat, n = r.maxp.numGlyphs + 1;
932
- if (a == 0) for (var l = 0; l < n; l++) s.push(i.readUshort(h, e + (l << 1)) << 1);
933
- if (a == 1) for (var l = 0; l < n; l++) s.push(i.readUint(h, e + (l << 2)));
934
- return s;
935
- }
936
- };
937
- m.T.glyf = {
938
- parseTab: function(h, e, t, r) {
939
- for (var i = [], s = r.maxp.numGlyphs, a = 0; a < s; a++) i.push(null);
940
- return i;
941
- },
942
- _parseGlyf: function(h, e) {
943
- var t = m.B, r = h._data, i = h.loca;
944
- if (i[e] == i[e + 1]) return null;
945
- var s = m.findTable(r, "glyf", h._offset)[0] + i[e], a = {};
946
- if (a.noc = t.readShort(r, s), s += 2, a.xMin = t.readShort(r, s), s += 2, a.yMin = t.readShort(r, s), s += 2, a.xMax = t.readShort(r, s), s += 2, a.yMax = t.readShort(r, s), s += 2, a.xMin >= a.xMax || a.yMin >= a.yMax) return null;
947
- if (a.noc > 0) {
948
- a.endPts = [];
949
- for (var n = 0; n < a.noc; n++)
950
- a.endPts.push(t.readUshort(r, s)), s += 2;
951
- var l = t.readUshort(r, s);
952
- if (s += 2, r.length - s < l) return null;
953
- s += l;
954
- var c = a.endPts[a.noc - 1] + 1;
955
- a.flags = [];
956
- for (var n = 0; n < c; n++) {
957
- var u = r[s];
958
- if (s++, a.flags.push(u), u & 8) {
959
- var d = r[s];
960
- s++;
961
- for (var f = 0; f < d; f++)
962
- a.flags.push(u), n++;
963
- }
964
- }
965
- a.xs = [];
966
- for (var n = 0; n < c; n++) {
967
- var g = (a.flags[n] & 2) != 0, v = (a.flags[n] & 16) != 0;
968
- g ? (a.xs.push(v ? r[s] : -r[s]), s++) : v ? a.xs.push(0) : (a.xs.push(t.readShort(r, s)), s += 2);
969
- }
970
- a.ys = [];
971
- for (var n = 0; n < c; n++) {
972
- var g = (a.flags[n] & 4) != 0, v = (a.flags[n] & 32) != 0;
973
- g ? (a.ys.push(v ? r[s] : -r[s]), s++) : v ? a.ys.push(0) : (a.ys.push(t.readShort(r, s)), s += 2);
974
- }
975
- for (var p = 0, b = 0, n = 0; n < c; n++)
976
- p += a.xs[n], b += a.ys[n], a.xs[n] = p, a.ys[n] = b;
977
- } else
978
- a.parts = [];
979
- return a;
980
- }
981
- };
982
- typeof module < "u" && module.exports ? module.exports = m : typeof window < "u" && (window.Typr = m);
983
- class oe {
984
- /**
985
- * Extracts all available characters from a font's cmap tables.
986
- * @param font The parsed font object from Typr
987
- * @returns Array of unique character strings
988
- */
989
- extractCharacters(e) {
990
- var r;
991
- const t = [];
992
- return (r = e == null ? void 0 : e.cmap) != null && r.tables ? (e.cmap.tables.forEach((i) => {
993
- if (i.format === 4) {
994
- const s = this._extractCharactersFromFormat4Table(i);
995
- t.push(...s);
996
- } else if (i.format === 12) {
997
- const s = this._extractCharactersFromFormat12Table(i);
998
- t.push(...s);
999
- }
1000
- }), [...new Set(t)]) : [];
1001
- }
1002
- /**
1003
- * Extracts characters from a Format 4 cmap table (Basic Multilingual Plane).
1004
- * @param table The Format 4 cmap table
1005
- * @returns Array of character strings
1006
- */
1007
- _extractCharactersFromFormat4Table(e) {
1008
- const t = [];
1009
- if (!e.startCount || !e.endCount || !e.idRangeOffset || !e.idDelta)
1010
- return t;
1011
- for (let r = 0; r < e.startCount.length; r++) {
1012
- const i = e.startCount[r], s = e.endCount[r];
1013
- if (!(i === 65535 && s === 65535)) {
1014
- for (let a = i; a <= s; a++)
1015
- if (this._calculateGlyphIndexFormat4(e, a, r) > 0) {
1016
- const l = String.fromCodePoint(a);
1017
- t.push(l);
1018
- }
1019
- }
1020
- }
1021
- return t;
1022
- }
1023
- /**
1024
- * Extracts characters from a Format 12 cmap table (Extended Unicode ranges).
1025
- * @param table The Format 12 cmap table
1026
- * @returns Array of character strings
1027
- */
1028
- _extractCharactersFromFormat12Table(e) {
1029
- const t = [];
1030
- if (!e.groups)
1031
- return t;
1032
- for (let r = 0; r < e.groups.length; r += 3) {
1033
- const i = e.groups[r], s = e.groups[r + 1], a = e.groups[r + 2];
1034
- for (let n = i; n <= s; n++)
1035
- if (a + (n - i) > 0) {
1036
- const c = String.fromCodePoint(n);
1037
- t.push(c);
1038
- }
1039
- }
1040
- return t;
1041
- }
1042
- /**
1043
- * Calculates the glyph index for a character in a Format 4 cmap table.
1044
- * @param table The Format 4 cmap table
1045
- * @param codePoint The Unicode code point
1046
- * @param rangeIndex The index of the character range
1047
- * @returns The glyph index, or 0 if not found
1048
- */
1049
- _calculateGlyphIndexFormat4(e, t, r) {
1050
- if (e.idRangeOffset[r] === 0)
1051
- return t + e.idDelta[r] & 65535;
1052
- {
1053
- const i = e.idRangeOffset[r] / 2 + (t - e.startCount[r]) - (e.startCount.length - r);
1054
- if (i >= 0 && e.glyphIdArray && i < e.glyphIdArray.length) {
1055
- const s = e.glyphIdArray[i];
1056
- if (s !== 0)
1057
- return s + e.idDelta[r] & 65535;
1058
- }
1059
- }
1060
- return 0;
1061
- }
1062
- /**
1063
- * Filters out problematic characters that might cause rendering issues.
1064
- * @param characters Array of character strings to filter
1065
- * @returns Filtered array of character strings
1066
- */
1067
- filterProblematicCharacters(e) {
1068
- return e.filter((t) => this._isValidCharacter(t));
1069
- }
1070
- /**
1071
- * Checks if a character is valid for rendering.
1072
- * @param char The character to check
1073
- * @returns True if the character is valid, false otherwise
1074
- */
1075
- _isValidCharacter(e) {
1076
- const t = e.codePointAt(0) || 0;
1077
- return !(t >= 0 && t <= 31 && t !== 9 && t !== 10 && t !== 13 || t >= 127 && t <= 159);
1078
- }
1079
- }
1080
- class he {
1081
- /**
1082
- * Creates a new TextureAtlasCreation instance.
1083
- * @param renderer The WebGL renderer instance
1084
- */
1085
- constructor(e) {
1086
- o(this, "_textureCanvas");
1087
- o(this, "_textureContext");
1088
- o(this, "_renderer");
1089
- this._renderer = e, this._textureCanvas = document.createElement("canvas"), this._textureContext = this._textureCanvas.getContext("2d", { willReadFrequently: !0, alpha: !1 });
1090
- }
1091
- /**
1092
- * Creates a texture atlas from the given characters.
1093
- * @param characters Array of TextmodeCharacter objects
1094
- * @param maxGlyphDimensions Maximum dimensions of glyphs
1095
- * @param fontSize Font size for rendering
1096
- * @param fontFamilyName Font family name to use
1097
- * @returns Object containing framebuffer, columns, and rows
1098
- */
1099
- createTextureAtlas(e, t, r, i) {
1100
- const s = e.length, a = Math.ceil(Math.sqrt(s)), n = Math.ceil(s / a), l = t.width * a, c = t.height * n;
1101
- this._setupCanvas(l, c, r, i), this._renderCharactersToCanvas(e, t, a, r), this._applyBlackWhiteThreshold();
1102
- const u = this._renderer.createFramebuffer(l, c, { filter: "nearest" });
1103
- return u.update(this._textureCanvas), {
1104
- framebuffer: u,
1105
- columns: a,
1106
- rows: n
1107
- };
1108
- }
1109
- /**
1110
- * Sets up the canvas for rendering.
1111
- * @param width Canvas buffer width
1112
- * @param height Canvas buffer height
1113
- * @param fontSize Font size
1114
- * @param fontFamilyName Font family name
1115
- * @param logicalWidth Logical width for scaling context
1116
- * @param logicalHeight Logical height for scaling context
1117
- */
1118
- _setupCanvas(e, t, r, i) {
1119
- this._textureCanvas.width = e, this._textureCanvas.height = t, this._textureCanvas.style.width = e + "px", this._textureCanvas.style.height = e + "px", this._textureContext.imageSmoothingEnabled = !1, this._textureCanvas.style.imageRendering = "pixelated", this._textureContext.fillStyle = "black", this._textureContext.fillRect(0, 0, e, t), this._textureContext.font = `${r}px ${i}`, this._textureContext.textBaseline = "top", this._textureContext.textAlign = "left", this._textureContext.fillStyle = "white";
1120
- }
1121
- /**
1122
- * Renders all characters to the canvas in a grid layout.
1123
- * @param characters Array of characters to render
1124
- * @param maxGlyphDimensions Maximum glyph dimensions
1125
- * @param textureColumns Number of columns in the texture
1126
- * @param fontSize Font size
1127
- */
1128
- _renderCharactersToCanvas(e, t, r, i) {
1129
- for (let s = 0; s < e.length; s++) {
1130
- const a = s % r, n = Math.floor(s / r), l = a * t.width + t.width * 0.5, c = n * t.height + t.height * 0.5, u = Math.round(l - t.width * 0.5), d = Math.round(c - i * 0.5);
1131
- this._textureContext.fillText(e[s].character, u, d);
1132
- }
1133
- }
1134
- /**
1135
- * Applies a black and white threshold filter to the canvas.
1136
- * This converts antialiased grayscale pixels to pure black or white,
1137
- * ensuring crisp text rendering suitable for NEAREST texture filtering.
1138
- * @param threshold Threshold value (0-255) for black/white conversion
1139
- */
1140
- _applyBlackWhiteThreshold(e = 128) {
1141
- const t = this._textureContext.getImageData(0, 0, this._textureCanvas.width, this._textureCanvas.height), r = t.data;
1142
- for (let i = 0; i < r.length; i += 4) {
1143
- const s = 0.299 * r[i] + 0.587 * r[i + 1] + 0.114 * r[i + 2], a = e + 32, n = s > a ? 255 : 0;
1144
- r[i] = n, r[i + 1] = n, r[i + 2] = n;
1145
- }
1146
- this._textureContext.putImageData(t, 0, 0);
1147
- }
1148
- }
1149
- class le {
1150
- /**
1151
- * Creates a new MetricsCalculation instance.
1152
- */
1153
- constructor() {
1154
- o(this, "_tempCanvas");
1155
- o(this, "_tempContext");
1156
- this._tempCanvas = document.createElement("canvas"), this._tempContext = this._tempCanvas.getContext("2d");
1157
- }
1158
- /**
1159
- * Calculates the maximum glyph dimensions for a given set of characters.
1160
- * @param characters Array of character strings
1161
- * @param fontSize Font size to use for measurement
1162
- * @param fontFamilyName Font family name
1163
- * @param fontFace FontFace object (optional, for validation)
1164
- * @returns Object containing width and height dimensions
1165
- */
1166
- calculateMaxGlyphDimensions(e, t, r) {
1167
- this._tempContext.font = `${t}px ${r}`;
1168
- let i = 0, s = 0;
1169
- for (const a of e) {
1170
- const n = this._tempContext.measureText(a), l = n.width, c = n.actualBoundingBoxAscent + n.actualBoundingBoxDescent;
1171
- l > 0 && (i = Math.max(i, l), s = Math.max(s, c));
1172
- }
1173
- return {
1174
- width: Math.ceil(i),
1175
- height: Math.ceil(s)
1176
- };
1177
- }
1178
- }
1179
- class ce {
1180
- /**
1181
- * Creates TextmodeCharacter objects with unique color assignments.
1182
- * @param characters Array of character strings
1183
- * @param font The parsed font object from Typr
1184
- * @returns Array of TextmodeCharacter objects with colors
1185
- */
1186
- createCharacterObjects(e, t) {
1187
- return e.map((r, i) => {
1188
- const s = r.codePointAt(0) || 0, a = this._generateCharacterColor(i);
1189
- let n = 0;
1190
- if (t.hmtx && t.hmtx.aWidth) {
1191
- const l = this._getGlyphIndex(t, s);
1192
- l > 0 && t.hmtx.aWidth[l] !== void 0 && (n = t.hmtx.aWidth[l]);
1193
- }
1194
- return {
1195
- character: r,
1196
- unicode: s,
1197
- color: a,
1198
- advanceWidth: n
1199
- };
1200
- });
1201
- }
1202
- /**
1203
- * Gets the glyph index for a given Unicode code point in a Typr.js font
1204
- * This is a simplified version for advance width lookup only
1205
- * @param fontData The Typr.js font data
1206
- * @param codePoint The Unicode code point to look up
1207
- * @returns The glyph index, or 0 if not found
1208
- */
1209
- _getGlyphIndex(e, t) {
1210
- const r = e.cmap;
1211
- if (!r || !r.tables) return 0;
1212
- for (const i of r.tables)
1213
- if (i.format === 4) {
1214
- for (let s = 0; s < i.startCount.length; s++)
1215
- if (t >= i.startCount[s] && t <= i.endCount[s]) {
1216
- if (i.idRangeOffset[s] === 0)
1217
- return t + i.idDelta[s] & 65535;
1218
- {
1219
- const a = i.idRangeOffset[s] / 2 + (t - i.startCount[s]) - (i.startCount.length - s);
1220
- if (a >= 0 && a < i.glyphIdArray.length) {
1221
- const n = i.glyphIdArray[a];
1222
- if (n !== 0)
1223
- return n + i.idDelta[s] & 65535;
1224
- }
1225
- }
1226
- }
1227
- }
1228
- return 0;
1229
- }
1230
- /**
1231
- * Generates a unique RGB color for a character based on its index.
1232
- * @param index The index of the character
1233
- * @returns RGB color as a tuple [r, g, b]
1234
- */
1235
- _generateCharacterColor(e) {
1236
- const t = e % 256, r = Math.floor(e / 256) % 256, i = Math.floor(e / 65536) % 256;
1237
- return [t, r, i];
1238
- }
1239
- /**
1240
- * Gets the color for a specific character.
1241
- * @param character The character to get the color for
1242
- * @param characters Array of TextmodeCharacter objects
1243
- * @returns RGB color as a tuple [r, g, b], or [0, 0, 0] if not found
1244
- */
1245
- getCharacterColor(e, t) {
1246
- if (!_.validate(
1247
- typeof e == "string" && e.length === 1,
1248
- "Character must be a single character string.",
1249
- { method: "getCharacterColor", providedValue: e }
1250
- ))
1251
- return [0, 0, 0];
1252
- const r = t.find((i) => i.character === e);
1253
- return r ? r.color : [0, 0, 0];
1254
- }
1255
- /**
1256
- * Gets colors for multiple characters.
1257
- * @param characterString String of characters to get colors for
1258
- * @param characters Array of TextmodeCharacter objects
1259
- * @returns Array of RGB colors for each character
1260
- */
1261
- getCharacterColors(e, t) {
1262
- return _.validate(
1263
- typeof e == "string" && e.length > 0,
1264
- "Characters must be a string with at least one character.",
1265
- { method: "getCharacterColors", providedValue: e }
1266
- ) ? e.split("").map((r) => this.getCharacterColor(r, t) || [0, 0, 0]) : [[0, 0, 0]];
1267
- }
1268
- }
1269
- class ue {
1270
- /**
1271
- * Creates a new TextmodeFont instance.
1272
- * @param renderer Renderer instance for texture creation
1273
- * @param fontSize Font size to use for the texture atlas
1274
- * @ignore
1275
- */
1276
- constructor(e, t = 16) {
1277
- o(this, "_font");
1278
- o(this, "_characters", []);
1279
- o(this, "_fontFramebuffer");
1280
- o(this, "_fontSize", 16);
1281
- o(this, "_textureColumns", 0);
1282
- o(this, "_textureRows", 0);
1283
- o(this, "_maxGlyphDimensions", { width: 0, height: 0 });
1284
- o(this, "_fontFace");
1285
- o(this, "_fontFamilyName", "UrsaFont");
1286
- // Component classes
1287
- o(this, "_characterExtractor");
1288
- o(this, "_textureAtlas");
1289
- o(this, "_metricsCalculator");
1290
- o(this, "_characterColorMapper");
1291
- this._fontSize = t, this._characterExtractor = new oe(), this._textureAtlas = new he(e), this._metricsCalculator = new le(), this._characterColorMapper = new ce();
1292
- }
1293
- /**
1294
- * Initializes the font manager by loading the font and creating the texture atlas.
1295
- * @param fontSource Optional URL to load a custom font. If not provided, uses embedded font (full builds only).
1296
- * @returns Promise that resolves when initialization is complete
1297
- * @ignore
1298
- */
1299
- async initialize(e) {
1300
- let t;
1301
- if (e) {
1302
- const r = await fetch(e);
1303
- if (!r.ok)
1304
- throw new x(`Failed to load font file: ${r.status} ${r.statusText}`);
1305
- t = await r.arrayBuffer();
1306
- } else
1307
- throw new x("Embedded font not available. This appears to be a minified build - please provide `fontSource`.");
1308
- await this._loadFontFace(t), this._font = m.parse(t)[0], await this._initializeFont();
1309
- }
1310
- /**
1311
- * Sets the font size for rendering.
1312
- * @param size The font size to set. If undefined, returns the current font size.
1313
- * @ignore
1314
- */
1315
- setFontSize(e) {
1316
- if (e === void 0) return this._fontSize;
1317
- this._fontSize = e, this._maxGlyphDimensions = this._metricsCalculator.calculateMaxGlyphDimensions(
1318
- this._characters.map((r) => r.character),
1319
- this._fontSize,
1320
- this._fontFamilyName
1321
- );
1322
- const t = this._textureAtlas.createTextureAtlas(
1323
- this._characters,
1324
- this._maxGlyphDimensions,
1325
- this._fontSize,
1326
- this._fontFamilyName
1327
- );
1328
- this._fontFramebuffer = t.framebuffer, this._textureColumns = t.columns, this._textureRows = t.rows;
1329
- }
1330
- /**
1331
- * Loads a new font from a file path.
1332
- * @param fontPath Path to the .otf or .ttf font file
1333
- * @returns Promise that resolves when font loading is complete
1334
- * @ignore
1335
- */
1336
- async loadFont(e) {
1337
- try {
1338
- const t = await fetch(e);
1339
- if (!t.ok)
1340
- throw new x(`Failed to load font file: ${t.status} ${t.statusText}`);
1341
- const r = await t.arrayBuffer();
1342
- await this._loadFontFace(r);
1343
- const i = m.parse(r);
1344
- if (!i || i.length === 0)
1345
- throw new Error("Failed to parse font file");
1346
- this._font = i[0], await this._initializeFont();
1347
- } catch (t) {
1348
- throw new x(`Failed to load font: ${t instanceof Error ? t.message : "Unknown error"}`, t);
1349
- }
1350
- }
1351
- /**
1352
- * Loads a FontFace from a font buffer.
1353
- * @param fontBuffer ArrayBuffer containing font data
1354
- */
1355
- async _loadFontFace(e) {
1356
- const t = Date.now();
1357
- this._fontFamilyName = this._fontFamilyName === "UrsaFont" ? "UrsaFont" : `CustomFont_${t}`, this._fontFace = new FontFace(this._fontFamilyName, e), await this._fontFace.load(), document.fonts.add(this._fontFace);
1358
- }
1359
- /**
1360
- * Initializes all font-dependent properties using the component classes.
1361
- */
1362
- async _initializeFont() {
1363
- const e = this._characterExtractor.extractCharacters(this._font), t = this._characterExtractor.filterProblematicCharacters(e);
1364
- this._characters = this._characterColorMapper.createCharacterObjects(t, this._font), this._maxGlyphDimensions = this._metricsCalculator.calculateMaxGlyphDimensions(
1365
- t,
1366
- this._fontSize,
1367
- this._fontFamilyName
1368
- );
1369
- const r = this._textureAtlas.createTextureAtlas(
1370
- this._characters,
1371
- this._maxGlyphDimensions,
1372
- this._fontSize,
1373
- this._fontFamilyName
1374
- );
1375
- this._fontFramebuffer = r.framebuffer, this._textureColumns = r.columns, this._textureRows = r.rows;
1376
- }
1377
- /**
1378
- * Get the color associated with a character.
1379
- * @param character The character to get the color for.
1380
- * @returns The RGB color as an array `[r, g, b]`.
1381
- */
1382
- getCharacterColor(e) {
1383
- return this._characterColorMapper.getCharacterColor(e, this._characters);
1384
- }
1385
- /**
1386
- * Get the colors associated with a string of characters.
1387
- * @param characters The string of characters to get colors for.
1388
- * @returns An array of RGB colors for each character in the string.
1389
- * Each color is represented as an array `[r, g, b]`.
1390
- */
1391
- getCharacterColors(e) {
1392
- return this._characterColorMapper.getCharacterColors(e, this._characters);
1393
- }
1394
- /**
1395
- * Checks if all characters in the given string exist in the font.
1396
- * @param str The string to check.
1397
- * @returns `true` if all characters exist in the font, `false` otherwise.
1398
- */
1399
- hasAllCharacters(e) {
1400
- if (typeof e != "string" || e.length === 0) return !1;
1401
- const t = new Set(this._characters.map((r) => r.character));
1402
- for (const r of e)
1403
- if (!t.has(r)) return !1;
1404
- return !0;
1405
- }
1406
- /**
1407
- * Returns the WebGL framebuffer containing the font texture atlas.
1408
- * @ignore
1409
- */
1410
- get fontFramebuffer() {
1411
- return this._fontFramebuffer;
1412
- }
1413
- /** Returns the array of {@link TextmodeCharacter} objects in the font. */
1414
- get characters() {
1415
- return this._characters;
1416
- }
1417
- /** Returns the number of columns in the texture atlas. */
1418
- get textureColumns() {
1419
- return this._textureColumns;
1420
- }
1421
- /** Returns the number of rows in the texture atlas. */
1422
- get textureRows() {
1423
- return this._textureRows;
1424
- }
1425
- /** Returns the maximum dimensions of a glyph in the font. */
1426
- get maxGlyphDimensions() {
1427
- return this._maxGlyphDimensions;
1428
- }
1429
- /**
1430
- * Dispose of all resources used by this font manager.
1431
- * This method is idempotent and safe to call multiple times.
1432
- */
1433
- dispose() {
1434
- this._fontFramebuffer.dispose(), document.fonts.delete(this._fontFace), this._fontFramebuffer = null, this._fontFace = null, this._font = null, this._characters = [], this._maxGlyphDimensions = { width: 0, height: 0 }, this._textureColumns = 0, this._textureRows = 0;
1435
- }
1436
- /** Returns the font size used for rendering. */
1437
- get fontSize() {
1438
- return this._fontSize;
1439
- }
1440
- /** Returns the Typr.js font object. @ignore */
1441
- get font() {
1442
- return this._font;
1443
- }
1444
- }
1445
- class de {
1446
- /**
1447
- * Create a new grid instance.
1448
- * @param canvas The canvas element used to determine the grid dimensions.
1449
- * @param cellWidth The width of each cell in the grid.
1450
- * @param cellHeight The height of each cell in the grid.
1451
- * @ignore
1452
- */
1453
- constructor(e, t, r) {
1454
- /** The number of columns in the grid. */
1455
- o(this, "_cols");
1456
- /** The number of rows in the grid. */
1457
- o(this, "_rows");
1458
- /** The total width of the grid in pixels. */
1459
- o(this, "_width");
1460
- /** The total height of the grid in pixels. */
1461
- o(this, "_height");
1462
- /** The offset to the outer canvas on the x-axis when centering the grid. */
1463
- o(this, "_offsetX");
1464
- /** The offset to the outer canvas on the y-axis when centering the grid. */
1465
- o(this, "_offsetY");
1466
- /** Whether the grid dimensions are fixed, or responsive based on the canvas dimensions. */
1467
- o(this, "_fixedDimensions", !1);
1468
- /** The canvas element used to determine the grid dimensions. */
1469
- o(this, "_canvas");
1470
- /** The width of each cell in the grid. */
1471
- o(this, "_cellWidth");
1472
- /** The height of each cell in the grid. */
1473
- o(this, "_cellHeight");
1474
- this._canvas = e, this._cellWidth = t, this._cellHeight = r, this.reset();
1475
- }
1476
- /**
1477
- * Reset the grid to the default number of columns and rows based on the current canvas dimensions, and the grid cell dimensions.
1478
- * @ignore
1479
- */
1480
- reset() {
1481
- if (!this._fixedDimensions) {
1482
- const e = this._canvas.width, t = this._canvas.height;
1483
- [this._cols, this._rows] = [Math.floor(e / this._cellWidth), Math.floor(t / this._cellHeight)];
1484
- }
1485
- this._resizeGrid();
1486
- }
1487
- /**
1488
- * Reset the total grid width & height, and the offset to the outer canvas.
1489
- */
1490
- _resizeGrid() {
1491
- const e = this._canvas.width, t = this._canvas.height;
1492
- this._width = this._cols * this._cellWidth, this._height = this._rows * this._cellHeight, this._offsetX = Math.floor((e - this._width) / 2), this._offsetY = Math.floor((t - this._height) / 2);
1493
- }
1494
- /**
1495
- * Re-assign the grid cell dimensions and `reset()` the grid.
1496
- * @param newCellWidth The new cell width.
1497
- * @param newCellHeight The new cell height.
1498
- * @ignore
1499
- */
1500
- resizeCellPixelDimensions(e, t) {
1501
- [this._cellWidth, this._cellHeight] = [e, t], this.reset();
1502
- }
1503
- /**
1504
- * Re-assign the grid dimensions and resize the grid.
1505
- *
1506
- * Calling this method makes the grid dimensions fixed, meaning they will not automatically resize based on the canvas dimensions.
1507
- * @param newCols The new number of columns.
1508
- * @param newRows The new number of rows.
1509
- * @ignore
1510
- */
1511
- resizeGridDimensions(e, t) {
1512
- this._fixedDimensions = !0, [this._cols, this._rows] = [e, t], this._resizeGrid();
1513
- }
1514
- /**
1515
- * Make the grid dimensions flexible again, and `reset()` the grid.
1516
- * @ignore
1517
- */
1518
- resetGridDimensions() {
1519
- this._fixedDimensions = !1, this.reset();
1520
- }
1521
- /**
1522
- * Update the canvas used by the grid, and reset the grid dimensions.
1523
- * @param canvas The new canvas element to use for the grid.
1524
- * @ignore
1525
- */
1526
- resize() {
1527
- this._fixedDimensions ? this._resizeGrid() : this.reset();
1528
- }
1529
- /**
1530
- * Gets or sets whether the grid dimensions *(columns and rows)* are fixed or responsive based on the canvas dimensions.
1531
- * @param value Optional. `true` to make the grid dimensions fixed, or `false` to make them responsive. If not provided, returns the current state.
1532
- * @returns If no parameter is provided, returns `true` if the grid dimensions are fixed, or `false` if they are responsive.
1533
- * @ignore
1534
- */
1535
- fixedDimensions(e) {
1536
- if (e === void 0)
1537
- return this._fixedDimensions;
1538
- this._fixedDimensions = e;
1539
- }
1540
- /** Returns the width of each cell in the grid. */
1541
- get cellWidth() {
1542
- return this._cellWidth;
1543
- }
1544
- /** Returns the height of each cell in the grid. */
1545
- get cellHeight() {
1546
- return this._cellHeight;
1547
- }
1548
- /**
1549
- * Dispose of this TextmodeGrid and clean up references.
1550
- * This method is idempotent and safe to call multiple times.
1551
- */
1552
- dispose() {
1553
- this._canvas = null, this._cols = 0, this._rows = 0, this._width = 0, this._height = 0, this._offsetX = 0, this._offsetY = 0, this._cellWidth = 0, this._cellHeight = 0;
1554
- }
1555
- /** Returns the number of columns in the grid. */
1556
- get cols() {
1557
- return this._cols;
1558
- }
1559
- /** Returns the number of rows in the grid. */
1560
- get rows() {
1561
- return this._rows;
1562
- }
1563
- /** Returns the total width of the grid. */
1564
- get width() {
1565
- return this._width;
1566
- }
1567
- /** Returns the total height of the grid. */
1568
- get height() {
1569
- return this._height;
1570
- }
1571
- /** Returns the offset to the outer canvas borders on the x-axis when centering the grid. */
1572
- get offsetX() {
1573
- return this._offsetX;
1574
- }
1575
- /** Returns the offset to the outer canvas borders on the y-axis when centering the grid. */
1576
- get offsetY() {
1577
- return this._offsetY;
1578
- }
1579
- }
1580
- class fe {
1581
- constructor(e, t = !1, r = {}) {
1582
- o(this, "_canvas");
1583
- o(this, "captureSource");
1584
- o(this, "_isStandalone");
1585
- o(this, "resizeObserver");
1586
- o(this, "onTransformChange");
1587
- this.captureSource = e, this._isStandalone = t, this._canvas = this.createCanvas(r.width, r.height), t && this.setupTransformObserver();
1588
- }
1589
- createCanvas(e, t) {
1590
- var i;
1591
- const r = document.createElement("canvas");
1592
- if (r.className = "textmodeCanvas", r.style.imageRendering = "pixelated", this._isStandalone)
1593
- r.width = e || 800, r.height = t || 600, document.body.appendChild(r);
1594
- else {
1595
- const s = this.captureSource.getBoundingClientRect();
1596
- let a = Math.round(s.width), n = Math.round(s.height);
1597
- if (this.captureSource instanceof HTMLVideoElement) {
1598
- const u = this.captureSource;
1599
- (a === 0 || n === 0) && u.videoWidth > 0 && u.videoHeight > 0 && (a = u.videoWidth, n = u.videoHeight);
1600
- }
1601
- r.width = a, r.height = n, r.style.position = "absolute", r.style.pointerEvents = "none";
1602
- const l = window.getComputedStyle(this.captureSource);
1603
- let c = parseInt(l.zIndex || "0", 10);
1604
- isNaN(c) && (c = 0), r.style.zIndex = (c + 1).toString(), this.positionOverlayCanvas(r), (i = this.captureSource.parentNode) == null || i.insertBefore(r, this.captureSource.nextSibling);
1605
- }
1606
- return r;
1607
- }
1608
- positionOverlayCanvas(e) {
1609
- const t = this.captureSource.getBoundingClientRect();
1610
- let r = this.captureSource.offsetParent;
1611
- if (r && r !== document.body) {
1612
- const i = r.getBoundingClientRect();
1613
- e.style.top = t.top - i.top + "px", e.style.left = t.left - i.left + "px";
1614
- } else
1615
- e.style.top = t.top + window.scrollY + "px", e.style.left = t.left + window.scrollX + "px";
1616
- }
1617
- resize(e, t) {
1618
- if (this._isStandalone)
1619
- this._canvas.width = e ?? this._canvas.width, this._canvas.height = t ?? this._canvas.height;
1620
- else {
1621
- const r = this.captureSource.getBoundingClientRect();
1622
- let i = Math.round(r.width), s = Math.round(r.height);
1623
- if (this.captureSource instanceof HTMLVideoElement) {
1624
- const a = this.captureSource;
1625
- (i === 0 || s === 0) && a.videoWidth > 0 && a.videoHeight > 0 && (i = a.videoWidth, s = a.videoHeight);
1626
- }
1627
- this._canvas.width = i, this._canvas.height = s, this.positionOverlayCanvas(this._canvas);
1628
- }
1629
- }
1630
- /**
1631
- * Get the WebGL context for the overlay canvas
1632
- */
1633
- getWebGLContext() {
1634
- const e = {
1635
- alpha: !1,
1636
- premultipliedAlpha: !1,
1637
- preserveDrawingBuffer: !0,
1638
- antialias: !1,
1639
- depth: !1,
1640
- stencil: !1,
1641
- powerPreference: "high-performance"
1642
- }, t = this._canvas.getContext("webgl2", e) || this._canvas.getContext("webgl", e);
1643
- if (!t)
1644
- throw new x("WebGL context could not be created. Ensure your browser supports WebGL.");
1645
- return t;
1646
- }
1647
- /**
1648
- * Get the effective rendering dimensions accounting for CSS transforms
1649
- */
1650
- getEffectiveRenderingDimensions() {
1651
- return this._canvas ? { width: this._canvas.width, height: this._canvas.height } : { width: 0, height: 0 };
1652
- }
1653
- /**
1654
- * Check if the canvas is affected by CSS transforms
1655
- */
1656
- isTransformed() {
1657
- if (!this._canvas || !this._isStandalone) return !1;
1658
- const e = this._canvas.getBoundingClientRect(), t = Math.round(e.width), r = Math.round(e.height);
1659
- return t !== this._canvas.width || r !== this._canvas.height;
1660
- }
1661
- /**
1662
- * Set up ResizeObserver to monitor for CSS transform changes
1663
- */
1664
- setupTransformObserver() {
1665
- typeof ResizeObserver > "u" || (this.resizeObserver = new ResizeObserver((e) => {
1666
- for (const t of e) {
1667
- const r = t.contentRect, i = Math.round(r.width), s = Math.round(r.height);
1668
- this.onTransformChange && (i !== this._canvas.width || s !== this._canvas.height) && this.onTransformChange();
1669
- }
1670
- }), this.resizeObserver.observe(this._canvas));
1671
- }
1672
- /**
1673
- * Dispose of this TextmodeCanvas and clean up all resources.
1674
- * This method is idempotent and safe to call multiple times.
1675
- */
1676
- dispose() {
1677
- if (this.resizeObserver && (this.resizeObserver.disconnect(), this.resizeObserver = void 0), this._canvas) {
1678
- const e = this._canvas.getContext("webgl") || this._canvas.getContext("webgl2");
1679
- if (e) {
1680
- const t = e.getExtension("WEBGL_lose_context");
1681
- t && t.loseContext();
1682
- }
1683
- this._canvas.parentNode && this._canvas.parentNode.removeChild(this._canvas), this._canvas = null;
1684
- }
1685
- this.captureSource = null;
1686
- }
1687
- get canvas() {
1688
- return this._canvas;
1689
- }
1690
- get width() {
1691
- return this._canvas.width;
1692
- }
1693
- get height() {
1694
- return this._canvas.height;
1695
- }
1696
- }
1697
- class D {
1698
- /**
1699
- * Creates a new TextmodeConverter instance.
1700
- * @param renderer Renderer instance for texture creation
1701
- * @param fontManager Font manager for character extraction and color mapping
1702
- * @param grid Grid instance for managing textmode layout
1703
- * @param options Additional options for the converter
1704
- * @ignore
1705
- */
1706
- constructor(e, t, r, i = {}) {
1707
- o(this, "renderer");
1708
- o(this, "fontManager");
1709
- o(this, "grid");
1710
- o(this, "_characterFramebuffer");
1711
- o(this, "_primaryColorFramebuffer");
1712
- o(this, "_secondaryColorFramebuffer");
1713
- o(this, "_rotationFramebuffer");
1714
- o(this, "_transformFramebuffer");
1715
- o(this, "_options");
1716
- this.renderer = e, this.fontManager = t, this.grid = r, this._options = i, this._characterFramebuffer = this.renderer.createFramebuffer(this.grid.cols, this.grid.rows), this._primaryColorFramebuffer = this.renderer.createFramebuffer(this.grid.cols, this.grid.rows), this._secondaryColorFramebuffer = this.renderer.createFramebuffer(this.grid.cols, this.grid.rows), this._rotationFramebuffer = this.renderer.createFramebuffer(this.grid.cols, this.grid.rows), this._transformFramebuffer = this.renderer.createFramebuffer(this.grid.cols, this.grid.rows);
1717
- }
1718
- /**
1719
- * Resizes all internal framebuffers to match the grid dimensions.
1720
- * @ignore
1721
- */
1722
- resize() {
1723
- this._characterFramebuffer.resize(this.grid.cols, this.grid.rows), this._primaryColorFramebuffer.resize(this.grid.cols, this.grid.rows), this._secondaryColorFramebuffer.resize(this.grid.cols, this.grid.rows), this._rotationFramebuffer.resize(this.grid.cols, this.grid.rows), this._transformFramebuffer.resize(this.grid.cols, this.grid.rows);
1724
- }
1725
- /**
1726
- * Enables or disables the converter.
1727
- * @param enabled Whether to enable or disable the converter.
1728
- */
1729
- enabled(e) {
1730
- this._options.enabled = e;
1731
- }
1732
- /**
1733
- * Enables the converter.
1734
- */
1735
- enable() {
1736
- this.enabled(!0);
1737
- }
1738
- /**
1739
- * Disables the converter.
1740
- */
1741
- disable() {
1742
- this.enabled(!1);
1743
- }
1744
- /**
1745
- * Dispose of all framebuffers used by this converter.
1746
- * This method is idempotent and safe to call multiple times.
1747
- */
1748
- dispose() {
1749
- this._characterFramebuffer.dispose(), this._primaryColorFramebuffer.dispose(), this._secondaryColorFramebuffer.dispose(), this._rotationFramebuffer.dispose(), this._transformFramebuffer.dispose(), this._characterFramebuffer = null, this._primaryColorFramebuffer = null, this._secondaryColorFramebuffer = null, this._rotationFramebuffer = null, this._transformFramebuffer = null;
1750
- }
1751
- /** Returns the framebuffer containing character data. */
1752
- get characterFramebuffer() {
1753
- return this._characterFramebuffer;
1754
- }
1755
- /** Returns the framebuffer containing primary color data. */
1756
- get primaryColorFramebuffer() {
1757
- return this._primaryColorFramebuffer;
1758
- }
1759
- /** Returns the framebuffer containing secondary color data. */
1760
- get secondaryColorFramebuffer() {
1761
- return this._secondaryColorFramebuffer;
1762
- }
1763
- /** Returns the framebuffer containing rotation data. */
1764
- get rotationFramebuffer() {
1765
- return this._rotationFramebuffer;
1766
- }
1767
- /** Returns the framebuffer containing transformation data. */
1768
- get transformFramebuffer() {
1769
- return this._transformFramebuffer;
1770
- }
1771
- /** Returns the renderer used by this converter. */
1772
- get options() {
1773
- return this._options;
1774
- }
1775
- }
1776
- class me {
1777
- /**
1778
- * Create a new color palette instance.
1779
- * @param renderer The renderer instance.
1780
- * @param colors The RGB colors to store as [r, g, b] arrays where values are 0-255.
1781
- */
1782
- constructor(e, t) {
1783
- /** The framebuffer used to store the color palette. */
1784
- o(this, "_framebuffer");
1785
- o(this, "_renderer");
1786
- o(this, "_colors");
1787
- this._renderer = e, this._colors = t;
1788
- const r = Math.max(this._colors.length, 1);
1789
- this._framebuffer = this._renderer.createFramebuffer(r, 1), this._updateFramebuffer();
1790
- }
1791
- /**
1792
- * Update the framebuffer with the currently selected colors.
1793
- */
1794
- _updateFramebuffer() {
1795
- if (!this._framebuffer) return;
1796
- const e = Math.max(this._colors.length, 1), t = 1;
1797
- this._framebuffer.width !== e && this._framebuffer.resize(e, t);
1798
- const r = new Uint8Array(e * t * 4);
1799
- for (let i = 0; i < e; i++) {
1800
- const s = i < this._colors.length ? this._colors[i] : [0, 0, 0], a = i * 4;
1801
- r[a] = s[0], r[a + 1] = s[1], r[a + 2] = s[2], r[a + 3] = 255;
1802
- }
1803
- this._framebuffer.updatePixels(r, e, t);
1804
- }
1805
- /**
1806
- * Sets the colors of the palette and updates the framebuffer.
1807
- * @param newColors The new RGB colors to set as [r, g, b] arrays.
1808
- */
1809
- setColors(e) {
1810
- this._colors = e, this._updateFramebuffer();
1811
- }
1812
- /**
1813
- * Get the colors of the palette.
1814
- */
1815
- get colors() {
1816
- return this._colors;
1817
- }
1818
- /**
1819
- * Get the framebuffer containing the colors of the palette.
1820
- */
1821
- get framebuffer() {
1822
- return this._framebuffer;
1823
- }
1824
- /**
1825
- * Get the texture from the framebuffer for use in shaders.
1826
- */
1827
- get texture() {
1828
- return this._framebuffer.texture;
1829
- }
1830
- }
1831
- class N extends D {
1832
- constructor(t, r, i, s = {}) {
1833
- super(t, r, i, s);
1834
- o(this, "palette");
1835
- this.palette = new me(this.renderer, this.fontManager.getCharacterColors(" .:-=+*%@#"));
1836
- }
1837
- /**
1838
- * Sets the characters used for mapping.
1839
- * @param characters The characters to use for mapping, usually ordered from darkest to brightest.
1840
- */
1841
- characters(t) {
1842
- _.validate(
1843
- this.fontManager.hasAllCharacters(t),
1844
- "One or more characters do not exist in the current font.",
1845
- { method: "characters", providedValue: t }
1846
- ) && (this._options.characters = t, this.palette.setColors(this.fontManager.getCharacterColors(t)));
1847
- }
1848
- /**
1849
- * Sets the color of the characters affected by the converter.
1850
- * This is only used when `characterColorMode` is set to `'fixed'`.
1851
- * @param r Red component (0-255) or hex string (e.g., '#FF0000', '#F00', 'FF0000', 'F00').
1852
- * @param g Green component (0-255).
1853
- * @param b Blue component (0-255).
1854
- * @param a Alpha component (0-255).
1855
- */
1856
- characterColor(t, r, i, s = 255) {
1857
- let a, n, l, c;
1858
- if (typeof t == "string") {
1859
- const u = this.parseHexColor(t);
1860
- if (!u) {
1861
- _.validate(
1862
- !1,
1863
- "Invalid hex color format. Use '#FF0000', '#F00', 'FF0000', or 'F00'.",
1864
- { method: "characterColor", providedValue: t }
1865
- );
1866
- return;
1867
- }
1868
- [a, n, l, c] = u;
1869
- } else if (a = t, n = r !== void 0 ? r : t, l = i !== void 0 ? i : t, c = s, !_.validate(
1870
- [a, n, l, c].every((u) => u >= 0 && u <= 255),
1871
- "Character color values must be between 0 and 255",
1872
- { method: "characterColor", providedValues: { r: a, g: n, b: l, a: c } }
1873
- ))
1874
- return;
1875
- this._options.characterColor = [a / 255, n / 255, l / 255, c / 255];
1876
- }
1877
- /**
1878
- * Sets the character color mode.
1879
- * - `'sampled'`: Uses sampled colors from the source texture.
1880
- * - `'fixed'`: Uses a fixed color set by `characterColor()`.
1881
- * @param mode The color mode to use for characters.
1882
- */
1883
- characterColorMode(t) {
1884
- _.validate(
1885
- ["sampled", "fixed"].includes(t),
1886
- "Invalid character color mode. Must be 'sampled' or 'fixed'.",
1887
- { method: "characterColorMode", providedValue: t }
1888
- ) && (this._options.characterColorMode = t);
1889
- }
1890
- /**
1891
- * Sets the cell color for all cells affected by the converter.
1892
- * This is only used when `cellColorMode` is set to `'fixed'`.
1893
- * @param r Red component (0-255) or hex string (e.g., '#FF0000', '#F00', 'FF0000', 'F00').
1894
- * @param g Green component (0-255).
1895
- * @param b Blue component (0-255).
1896
- * @param a Alpha component (0-255).
1897
- */
1898
- cellColor(t, r, i, s = 255) {
1899
- let a, n, l, c;
1900
- if (typeof t == "string") {
1901
- const u = this.parseHexColor(t);
1902
- if (!u) {
1903
- _.validate(
1904
- !1,
1905
- "Invalid hex color format. Use '#FF0000', '#F00', 'FF0000', or 'F00'.",
1906
- { method: "cellColor", providedValue: t }
1907
- );
1908
- return;
1909
- }
1910
- [a, n, l, c] = u;
1911
- } else if (a = t, n = r !== void 0 ? r : t, l = i !== void 0 ? i : t, c = s, !_.validate(
1912
- [a, n, l, c].every((u) => u >= 0 && u <= 255),
1913
- "Cell color values must be between 0 and 255",
1914
- { method: "cellColor", providedValues: { r: a, g: n, b: l, a: c } }
1915
- ))
1916
- return;
1917
- this._options.cellColor = [a / 255, n / 255, l / 255, c / 255];
1918
- }
1919
- /**
1920
- * Sets the cell color mode.
1921
- * - `'sampled'`: Uses sampled colors from the source texture.
1922
- * - `'fixed'`: Uses a fixed color set via {@link cellColor}.
1923
- * @param mode The color mode to use for background cells.
1924
- */
1925
- cellColorMode(t) {
1926
- _.validate(
1927
- ["sampled", "fixed"].includes(t),
1928
- "Invalid cell color mode. Must be 'sampled' or 'fixed'.",
1929
- { method: "cellColorMode", providedValue: t }
1930
- ) && (this._options.cellColorMode = t);
1931
- }
1932
- /**
1933
- * Swaps the character and cell color.
1934
- * @param invert If `true`, the character color becomes the cell color and vice versa.
1935
- */
1936
- invert(t) {
1937
- _.validate(
1938
- typeof t == "boolean" || typeof t == "number" && Number.isInteger(t),
1939
- "Invert must be a boolean value or an integer (0 for false, any other number for true).",
1940
- { method: "invert", providedValue: t }
1941
- ) && (this._options.invert = !!t);
1942
- }
1943
- /**
1944
- * Sets the rotation angle for the characters.
1945
- * @param angle The rotation angle in degrees.
1946
- */
1947
- rotation(t) {
1948
- if (!_.validate(
1949
- typeof t == "number",
1950
- "Rotation angle must be a number.",
1951
- { method: "rotation", providedValue: t }
1952
- ))
1953
- return;
1954
- t = t % 360, t < 0 && (t += 360);
1955
- const r = t * 255 / 360, i = Math.floor(r) / 255, s = Math.round(r - i);
1956
- this._options.rotation = [i, s, 0, 1];
1957
- }
1958
- /**
1959
- * Flips the characters horizontally.
1960
- * @param flip If `true`, characters are flipped horizontally. If `false`, no flip is applied.
1961
- */
1962
- flipHorizontally(t) {
1963
- _.validate(
1964
- typeof t == "boolean" || typeof t == "number" && Number.isInteger(t),
1965
- "Flip horizontally must be a boolean value or an integer (0 for false, any other number for true).",
1966
- { method: "flipHorizontally", providedValue: t }
1967
- ) && (this._options.flipHorizontally = !!t);
1968
- }
1969
- /**
1970
- * Flips the characters vertically.
1971
- * @param flip If `true`, characters are flipped vertically. If `false`, no flip is applied.
1972
- */
1973
- flipVertically(t) {
1974
- _.validate(
1975
- typeof t == "boolean" || typeof t == "number" && Number.isInteger(t),
1976
- "Flip vertically must be a boolean value or an integer (0 for false, any other number for true).",
1977
- { method: "flipVertically", providedValue: t }
1978
- ) && (this._options.flipVertically = !!t);
1979
- }
1980
- /**
1981
- * Parses a hex color string and returns RGBA values.
1982
- * @param hex Hex color string (e.g., '#FF0000', '#F00', 'FF0000', 'F00').
1983
- * @returns RGBA array [r, g, b, a] or null if invalid.
1984
- */
1985
- parseHexColor(t) {
1986
- if (t = t.replace(/^#/, ""), !/^[0-9A-Fa-f]{3}$|^[0-9A-Fa-f]{6}$/.test(t))
1987
- return null;
1988
- t.length === 3 && (t = t.split("").map((a) => a + a).join(""));
1989
- const r = parseInt(t.slice(0, 2), 16), i = parseInt(t.slice(2, 4), 16), s = parseInt(t.slice(4, 6), 16);
1990
- return [r, i, s, 255];
1991
- }
1992
- }
1993
- var ge = "precision lowp float;uniform sampler2D u_sketchTexture;uniform vec2 u_gridCellDimensions;uniform vec2 u_brightnessRange;varying vec2 v_uv;void main(){vec2 cellCenter=(floor(v_uv*u_gridCellDimensions)+vec2(0.5))/u_gridCellDimensions;vec4 color=texture2D(u_sketchTexture,cellCenter);float brightness=dot(color.rgb,vec3(0.299,0.587,0.114));float brightnessValue=brightness*255.0;if(brightnessValue>=u_brightnessRange.x&&brightnessValue<=u_brightnessRange.y){gl_FragColor=color;}else{gl_FragColor=vec4(0.0);}}", pe = "precision lowp float;uniform sampler2D u_sampleTexture;uniform vec4 u_fillColor;uniform bool u_useFixedColor;varying vec2 v_uv;void main(){vec4 sampleColor=texture2D(u_sampleTexture,v_uv);if(sampleColor.a>0.0){if(u_useFixedColor){gl_FragColor=u_fillColor;}else{gl_FragColor=sampleColor;}}else{gl_FragColor=vec4(0.0);}}", _e = "precision lowp float;uniform sampler2D u_sampleTexture;uniform bool u_invert;uniform bool u_flipHorizontally;uniform bool u_flipVertically;varying vec2 v_uv;void main(){vec4 sampleColor=texture2D(u_sampleTexture,v_uv);if(sampleColor.a>0.0){float invertValue=u_invert ? 1.0 : 0.0;float flipHValue=u_flipHorizontally ? 1.0 : 0.0;float flipVValue=u_flipVertically ? 1.0 : 0.0;gl_FragColor=vec4(invertValue,flipHValue,flipVValue,1.0);}else{gl_FragColor=vec4(0.0);}}", ve = "precision lowp float;uniform sampler2D u_sampleTexture;uniform vec4 u_rotationColor;varying vec2 v_uv;void main(){vec4 sampleColor=texture2D(u_sampleTexture,v_uv);if(sampleColor.a>0.0){gl_FragColor=u_rotationColor;}else{gl_FragColor=vec4(0.0);}}", be = "precision lowp float;uniform sampler2D u_colorSampleFramebuffer;uniform sampler2D u_charPaletteTexture;uniform vec2 u_charPaletteSize;uniform vec2 u_brightnessRange;varying vec2 v_uv;void main(){vec4 color=texture2D(u_colorSampleFramebuffer,v_uv);if(color.a==0.0){gl_FragColor=vec4(0.0);return;}float brightness=dot(color.rgb,vec3(0.299,0.587,0.114))*255.0;vec2 range=u_brightnessRange;if(brightness<range.x||brightness>range.y){gl_FragColor=vec4(0.0);return;}float t=(brightness-range.x)/(range.y-range.x);float idx=clamp(floor(t*u_charPaletteSize.x),0.0,u_charPaletteSize.x-1.0);vec3 charColor=texture2D(u_charPaletteTexture,vec2((idx+0.5)/u_charPaletteSize.x,0.0)).rgb;gl_FragColor=vec4(charColor,1.0);}";
1994
- const xe = {
1995
- /** Enable/disable the renderer */
1996
- enabled: !0,
1997
- /** Characters used for brightness mapping (from darkest to brightest) */
1998
- characters: " .:-=+*%@#",
1999
- /** Color of the ASCII characters. Only used when `characterColorMode` is set to `fixed` */
2000
- characterColor: [1, 1, 1, 1],
2001
- // White
2002
- /** Character color mode */
2003
- characterColorMode: "sampled",
2004
- /** Cell background color. Only used when `characterColorMode` is set to `fixed` */
2005
- cellColor: [0, 0, 0, 1],
2006
- // Black
2007
- /** Background color mode */
2008
- cellColorMode: "fixed",
2009
- /** Swap the cells ASCII character colors with it's cell background colors */
2010
- invert: !1,
2011
- /** Rotation angle of all characters in the grid in degrees */
2012
- rotation: [0, 0, 0, 255],
2013
- /** Flip the ASCII characters horizontally */
2014
- flipHorizontally: !1,
2015
- /** Flip the ASCII characters vertically */
2016
- flipVertically: !1,
2017
- /** Range of brightness values to map to ASCII characters */
2018
- brightnessRange: [0, 255]
2019
- };
2020
- class L extends N {
2021
- /**
2022
- * Creates a new TextmodeBrightnessConverter instance.
2023
- * @param renderer Renderer instance for texture creation
2024
- * @param fontManager Font manager for character extraction and color mapping
2025
- * @param grid Grid manager for layout and positioning
2026
- * @ignore
2027
- */
2028
- constructor(t, r, i) {
2029
- super(t, r, i, { ...xe });
2030
- o(this, "sampleShader");
2031
- o(this, "colorFillShader");
2032
- o(this, "charMappingShader");
2033
- o(this, "transformFillShader");
2034
- o(this, "rotationFillShader");
2035
- o(this, "sampleFramebuffer");
2036
- this.sampleShader = new y(t.context, T, ge), this.colorFillShader = new y(t.context, T, pe), this.transformFillShader = new y(t.context, T, _e), this.rotationFillShader = new y(t.context, T, ve), this.charMappingShader = new y(t.context, T, be), this.sampleFramebuffer = this.renderer.createFramebuffer(this.grid.cols, this.grid.rows);
2037
- }
2038
- convert(t) {
2039
- this.sampleFramebuffer.begin(), this.renderer.clear(), this.renderer.shader(this.sampleShader), this.renderer.setUniform("u_sketchTexture", t), this.renderer.setUniform("u_gridCellDimensions", [this.grid.cols, this.grid.rows]), this.renderer.setUniform("u_brightnessRange", this._options.brightnessRange), this.renderer.rect(0, 0, this.grid.cols, this.grid.rows), this.sampleFramebuffer.end(), this._primaryColorFramebuffer.begin(), this.renderer.clear(), this.renderer.shader(this.colorFillShader), this.renderer.setUniform("u_sampleTexture", this.sampleFramebuffer.texture), this.renderer.setUniform("u_fillColor", this._options.characterColor), this.renderer.setUniform("u_useFixedColor", this._options.characterColorMode === "fixed"), this.renderer.rect(0, 0, this.grid.cols, this.grid.rows), this._primaryColorFramebuffer.end(), this._secondaryColorFramebuffer.begin(), this.renderer.clear(), this.renderer.shader(this.colorFillShader), this.renderer.setUniform("u_sampleTexture", this.sampleFramebuffer.texture), this.renderer.setUniform("u_fillColor", this._options.cellColor), this.renderer.setUniform("u_useFixedColor", this._options.cellColorMode === "fixed"), this.renderer.rect(0, 0, this.grid.cols, this.grid.rows), this._secondaryColorFramebuffer.end(), this._transformFramebuffer.begin(), this.renderer.clear(), this.renderer.shader(this.transformFillShader), this.renderer.setUniform("u_sampleTexture", this.sampleFramebuffer.texture), this.renderer.setUniform("u_invert", this._options.invert), this.renderer.setUniform("u_flipHorizontally", this._options.flipHorizontally), this.renderer.setUniform("u_flipVertically", this._options.flipVertically), this.renderer.rect(0, 0, this.grid.cols, this.grid.rows), this._transformFramebuffer.end(), this._rotationFramebuffer.begin(), this.renderer.clear(), this.renderer.shader(this.rotationFillShader), this.renderer.setUniform("u_sampleTexture", this.sampleFramebuffer.texture), this.renderer.setUniform("u_rotationColor", this._options.rotation), this.renderer.rect(0, 0, this.grid.cols, this.grid.rows), this._rotationFramebuffer.end(), this._characterFramebuffer.begin(), this.renderer.clear(0, 0, 0, 0), this.renderer.shader(this.charMappingShader), this.renderer.setUniform("u_colorSampleFramebuffer", this.sampleFramebuffer.texture), this.renderer.setUniform("u_charPaletteTexture", this.palette.texture), this.renderer.setUniform("u_charPaletteSize", [this.palette.colors.length, 1]), this.renderer.setUniform("u_brightnessRange", this._options.brightnessRange), this.renderer.rect(0, 0, this.grid.cols, this.grid.rows), this._characterFramebuffer.end();
2040
- }
2041
- resize() {
2042
- super.resize(), this.sampleFramebuffer.resize(this.grid.cols, this.grid.rows);
2043
- }
2044
- /**
2045
- * Sets the brightness range for ASCII character mapping.
2046
- *
2047
- * Cells that sample outside this range are rendered as transparent.
2048
- *
2049
- * @param range Array of two numbers `[min, max]`, where `min` is darkest and `max` is brightest.
2050
- */
2051
- brightnessRange(t) {
2052
- _.validate(
2053
- Array.isArray(t) && t.length === 2 && t.every((r) => typeof r == "number" && r >= 0 && r <= 255),
2054
- "Brightness range must be an array of two numbers between 0 and 255.",
2055
- { method: "brightnessRange", providedValue: t }
2056
- ) && (this._options.brightnessRange = t);
2057
- }
2058
- }
2059
- const Pe = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
2060
- __proto__: null,
2061
- TextmodeBrightnessConverter: L,
2062
- TextmodeConverter: D,
2063
- TextmodeFeatureConverter: N
2064
- }, Symbol.toStringTag, { value: "Module" }));
2065
- var Ce = "precision mediump float;uniform sampler2D u_characterTexture;uniform vec2 u_charsetDimensions;uniform sampler2D u_primaryColorTexture;uniform sampler2D u_secondaryColorTexture;uniform sampler2D u_transformTexture;uniform sampler2D u_asciiCharacterTexture;uniform sampler2D u_rotationTexture;uniform sampler2D u_captureTexture;uniform vec2 u_captureDimensions;uniform int u_backgroundMode;uniform vec2 u_gridCellDimensions;uniform vec2 u_gridPixelDimensions;mat2 rotate2D(float angle){float s=sin(angle);float c=cos(angle);return mat2(c,-s,s,c);}void main(){vec2 adjustedCoord=gl_FragCoord.xy/u_gridPixelDimensions;vec2 gridCoord=adjustedCoord*u_gridCellDimensions;vec2 cellCoord=floor(gridCoord);vec2 charIndexTexCoord=(cellCoord+0.5)/u_gridCellDimensions;vec4 primaryColor=texture2D(u_primaryColorTexture,charIndexTexCoord);vec4 secondaryColor=texture2D(u_secondaryColorTexture,charIndexTexCoord);vec4 transformColor=texture2D(u_transformTexture,charIndexTexCoord);bool isInverted=transformColor.r>0.5;bool flipHorizontal=transformColor.g>0.5;bool flipVertical=transformColor.b>0.5;vec4 encodedIndexVec=texture2D(u_asciiCharacterTexture,charIndexTexCoord);if(encodedIndexVec.a<0.01){gl_FragColor=(u_backgroundMode==0)? vec4(0.0):texture2D(u_captureTexture,gl_FragCoord.xy/u_captureDimensions);return;}int charIndex=int(encodedIndexVec.r*255.0+0.5)+int(encodedIndexVec.g*255.0+0.5)*256;int charCol=int(mod(float(charIndex),u_charsetDimensions.x));int charRow=charIndex/int(u_charsetDimensions.x);float flippedRow=(u_charsetDimensions.y-1.0)-float(charRow);vec2 charCoord=vec2(float(charCol),flippedRow)/u_charsetDimensions;vec4 rotationColor=texture2D(u_rotationTexture,charIndexTexCoord);float scaledAngle=rotationColor.r*255.0+rotationColor.g;float rotationAngle=(scaledAngle*360.0/255.0)*0.017453292;vec2 fractionalPart=fract(gridCoord)-0.5;if(flipHorizontal)fractionalPart.x=-fractionalPart.x;if(flipVertical)fractionalPart.y=-fractionalPart.y;fractionalPart=rotate2D(rotationAngle)*fractionalPart+0.5;vec2 cellSize=1.0/u_charsetDimensions;vec2 texCoord=charCoord+fractionalPart*cellSize;vec2 cellMax=charCoord+cellSize;if(any(lessThan(texCoord,charCoord))||any(greaterThan(texCoord,cellMax))){gl_FragColor=isInverted ? primaryColor : secondaryColor;return;}vec4 charTexel=texture2D(u_characterTexture,texCoord);if(isInverted)charTexel.rgb=1.0-charTexel.rgb;gl_FragColor=mix(secondaryColor,primaryColor,charTexel);}";
2066
- class Fe {
2067
- /**
2068
- * Creates an instance of TextmodeConversionPipeline.
2069
- * @param renderer The renderer to use for the pipeline.
2070
- * @param font The textmode font to use.
2071
- * @param grid The textmode grid to use.
2072
- * @ignore
2073
- */
2074
- constructor(e, t, r) {
2075
- o(this, "renderer");
2076
- o(this, "font");
2077
- o(this, "grid");
2078
- o(this, "converters");
2079
- o(this, "_resultFramebuffer");
2080
- o(this, "_asciiShader");
2081
- o(this, "_characterFramebuffer");
2082
- o(this, "_primaryColorFramebuffer");
2083
- o(this, "_secondaryColorFramebuffer");
2084
- o(this, "_rotationFramebuffer");
2085
- o(this, "_transformFramebuffer");
2086
- this.renderer = e, this.font = t, this.grid = r, this._asciiShader = this.renderer.createShader(T, Ce), this.converters = [
2087
- { name: "brightness", converter: new L(e, t, r) },
2088
- { name: "custom", converter: new D(e, t, r) }
2089
- ], this._characterFramebuffer = this.renderer.createFramebuffer(r.cols, r.rows), this._primaryColorFramebuffer = this.renderer.createFramebuffer(r.cols, r.rows), this._secondaryColorFramebuffer = this.renderer.createFramebuffer(r.cols, r.rows), this._rotationFramebuffer = this.renderer.createFramebuffer(r.cols, r.rows), this._transformFramebuffer = this.renderer.createFramebuffer(r.cols, r.rows), this._resultFramebuffer = this.renderer.createFramebuffer(this.grid.width, this.grid.height);
2090
- }
2091
- /**
2092
- * Performs the conversion process by applying all converters in the pipeline.
2093
- * @param sourceFramebuffer The source framebuffer to convert.
2094
- * @ignore
2095
- */
2096
- render(e) {
2097
- for (const r of this.converters) {
2098
- const i = r.converter;
2099
- i.options.enabled && i instanceof N && i.convert(e);
2100
- }
2101
- const t = (r, i) => {
2102
- r.begin(), this.renderer.clear();
2103
- for (const s of this.converters) {
2104
- const a = s.converter;
2105
- a.options.enabled && this.renderer.image(i(a), 0, 0);
2106
- }
2107
- r.end();
2108
- };
2109
- t(this._characterFramebuffer, (r) => r.characterFramebuffer), t(this._primaryColorFramebuffer, (r) => r.primaryColorFramebuffer), t(this._secondaryColorFramebuffer, (r) => r.secondaryColorFramebuffer), t(this._rotationFramebuffer, (r) => r.rotationFramebuffer), t(this._transformFramebuffer, (r) => r.transformFramebuffer), this._resultFramebuffer.begin(), this.renderer.clear(), this.renderer.shader(this._asciiShader), this.renderer.setUniform("u_characterTexture", this.font.fontFramebuffer), this.renderer.setUniform("u_charsetDimensions", [this.font.textureColumns, this.font.textureRows]), this.renderer.setUniform("u_asciiCharacterTexture", this._characterFramebuffer.texture), this.renderer.setUniform("u_primaryColorTexture", this._primaryColorFramebuffer.texture), this.renderer.setUniform("u_secondaryColorTexture", this._secondaryColorFramebuffer.texture), this.renderer.setUniform("u_transformTexture", this._transformFramebuffer.texture), this.renderer.setUniform("u_rotationTexture", this._rotationFramebuffer.texture), this.renderer.setUniform("u_captureTexture", e.texture), this.renderer.setUniform("u_backgroundMode", !1), this.renderer.setUniform("u_captureDimensions", [e.width, e.height]), this.renderer.setUniform("u_gridCellDimensions", [this.grid.cols, this.grid.rows]), this.renderer.setUniform("u_gridPixelDimensions", [this.grid.width, this.grid.height]), this.renderer.rect(0, 0, this._resultFramebuffer.width, this._resultFramebuffer.height), this._resultFramebuffer.end();
2110
- }
2111
- /**
2112
- * Get a specific converter by name.
2113
- * @param name The name of the converter to retrieve.
2114
- * @returns The requested `TextmodeConverter` instance.
2115
- */
2116
- get(e) {
2117
- if (!_.validate(
2118
- typeof e == "string" && e.length > 0,
2119
- "Converter name must be a non-empty string.",
2120
- { method: "converter", providedValue: e }
2121
- ))
2122
- return;
2123
- const t = this.converters.find((i) => i.name === e), r = t == null ? void 0 : t.converter;
2124
- if (_.validate(
2125
- r instanceof D,
2126
- `Converter "${e}" is not a valid TextmodeConverter.`,
2127
- { method: "converter", providedValue: e, converterInstance: r }
2128
- ))
2129
- return r;
2130
- }
2131
- /**
2132
- * Adds a new converter to the pipeline.
2133
- * @param name A unique name for the converter.
2134
- * @param type The type of converter to add. Can be either "brightness" or "custom".
2135
- * @returns The newly created {@link TextmodeConverter} instance or `void` if the addition failed.
2136
- */
2137
- add(e, t) {
2138
- if (!_.validate(
2139
- typeof e == "string" && e.length > 0,
2140
- "Converter name must be a non-empty string.",
2141
- { method: "add", providedValue: e }
2142
- ) || !_.validate(
2143
- t === "brightness" || t === "custom",
2144
- `Converter type must be either "brightness" or "custom". Provided: ${t}`,
2145
- { method: "add", providedValue: t }
2146
- ))
2147
- return;
2148
- let r;
2149
- return t === "brightness" ? r = new L(this.renderer, this.font, this.grid) : r = new D(this.renderer, this.font, this.grid), this.converters.push({ name: e, converter: r }), r;
2150
- }
2151
- /**
2152
- * Removes a converter from the pipeline by name or instance.
2153
- * @param nameOrInstance The unique name of the converter or the converter instance to remove.
2154
- */
2155
- remove(e) {
2156
- if (!_.validate(
2157
- typeof e == "string" || e instanceof D,
2158
- "Parameter must be either a string (converter name) or a TextmodeConverter instance.",
2159
- { method: "remove", providedValue: e }
2160
- ))
2161
- return;
2162
- let t = -1;
2163
- if (typeof e == "string") {
2164
- if (!_.validate(
2165
- e.length > 0,
2166
- "Converter name must be a non-empty string.",
2167
- { method: "remove", providedValue: e }
2168
- ))
2169
- return;
2170
- t = this.converters.findIndex((r) => r.name === e);
2171
- } else
2172
- t = this.converters.findIndex((r) => r.converter === e);
2173
- _.validate(
2174
- t !== -1,
2175
- typeof e == "string" ? `Converter with name "${e}" not found in pipeline.` : "Converter instance not found in pipeline.",
2176
- { method: "remove", providedValue: e, convertersCount: this.converters.length }
2177
- ) && this.converters.splice(t, 1);
2178
- }
2179
- /**
2180
- * Returns the framebuffer containing the textmode conversion result.
2181
- */
2182
- get texture() {
2183
- return this._resultFramebuffer;
2184
- }
2185
- /**
2186
- * Resizes all internal framebuffers.
2187
- * @ignore
2188
- */
2189
- resize() {
2190
- this._resultFramebuffer.resize(this.grid.width, this.grid.height), this._characterFramebuffer.resize(this.grid.cols, this.grid.rows), this._primaryColorFramebuffer.resize(this.grid.cols, this.grid.rows), this._secondaryColorFramebuffer.resize(this.grid.cols, this.grid.rows), this._rotationFramebuffer.resize(this.grid.cols, this.grid.rows), this._transformFramebuffer.resize(this.grid.cols, this.grid.rows);
2191
- for (const e of this.converters)
2192
- e.converter.resize();
2193
- }
2194
- /**
2195
- * Checks if any converter in the pipeline is enabled.
2196
- * @returns `true` if any converter is enabled, `false` otherwise.
2197
- */
2198
- hasEnabledConverters() {
2199
- return this.converters.some((e) => e.converter.options.enabled);
2200
- }
2201
- /**
2202
- * Disables all converters in the pipeline.
2203
- */
2204
- disable() {
2205
- for (const e of this.converters)
2206
- e.converter.disable();
2207
- }
2208
- /**
2209
- * Enables all converters in the pipeline.
2210
- */
2211
- enable() {
2212
- for (const e of this.converters)
2213
- e.converter.enable();
2214
- }
2215
- /**
2216
- * Dispose of all resources used by this conversion pipeline.
2217
- * This method is idempotent and safe to call multiple times.
2218
- */
2219
- dispose() {
2220
- for (const e of this.converters)
2221
- e.converter.dispose();
2222
- this._characterFramebuffer.dispose(), this._primaryColorFramebuffer.dispose(), this._secondaryColorFramebuffer.dispose(), this._rotationFramebuffer.dispose(), this._transformFramebuffer.dispose(), this._resultFramebuffer.dispose(), this._asciiShader.dispose(), this.converters = [], this._characterFramebuffer = null, this._primaryColorFramebuffer = null, this._secondaryColorFramebuffer = null, this._rotationFramebuffer = null, this._transformFramebuffer = null, this._resultFramebuffer = null, this._asciiShader = null;
2223
- }
2224
- /** Returns the character framebuffer containing the combined result of all converters. */
2225
- get characterFramebuffer() {
2226
- return this._characterFramebuffer;
2227
- }
2228
- /** Returns the primary color framebuffer containing the combined result of all converters. */
2229
- get primaryColorFramebuffer() {
2230
- return this._primaryColorFramebuffer;
2231
- }
2232
- /** Returns the secondary color framebuffer containing the combined result of all converters. */
2233
- get secondaryColorFramebuffer() {
2234
- return this._secondaryColorFramebuffer;
2235
- }
2236
- /** Returns the rotation framebuffer containing the combined result of all converters. */
2237
- get rotationFramebuffer() {
2238
- return this._rotationFramebuffer;
2239
- }
2240
- /** Returns the transform framebuffer containing the combined result of all converters. */
2241
- get transformFramebuffer() {
2242
- return this._transformFramebuffer;
2243
- }
2244
- }
2245
- class H {
2246
- /**
2247
- * Extracts pixel data from all framebuffers needed for export
2248
- * @param pipeline The conversion pipeline containing framebuffers
2249
- * @returns Object containing all pixel data arrays
2250
- */
2251
- extractFramebufferData(e) {
2252
- const t = e.get("brightness"), r = t == null ? void 0 : t.characterFramebuffer, i = t == null ? void 0 : t.primaryColorFramebuffer, s = t == null ? void 0 : t.secondaryColorFramebuffer, a = t == null ? void 0 : t.transformFramebuffer, n = t == null ? void 0 : t.rotationFramebuffer;
2253
- return r == null || r.loadPixels(), i == null || i.loadPixels(), s == null || s.loadPixels(), a == null || a.loadPixels(), n == null || n.loadPixels(), {
2254
- characterPixels: (r == null ? void 0 : r.pixels) || new Uint8Array(0),
2255
- primaryColorPixels: (i == null ? void 0 : i.pixels) || new Uint8Array(0),
2256
- secondaryColorPixels: (s == null ? void 0 : s.pixels) || new Uint8Array(0),
2257
- transformPixels: (a == null ? void 0 : a.pixels) || new Uint8Array(0),
2258
- rotationPixels: (n == null ? void 0 : n.pixels) || new Uint8Array(0)
2259
- };
2260
- }
2261
- /**
2262
- * Gets character index from character framebuffer pixels
2263
- * @param characterPixels Character framebuffer pixel data
2264
- * @param pixelIndex Index in the pixel array (already multiplied by 4 for RGBA)
2265
- * @param charactersLength Total number of available characters
2266
- * @returns Character index
2267
- */
2268
- getCharacterIndex(e, t) {
2269
- const r = e[t], i = e[t + 1];
2270
- return r + (i << 8);
2271
- }
2272
- /**
2273
- * Converts raw pixel data to RGBA color object
2274
- * @param pixels Pixel data array
2275
- * @param index Pixel index (already multiplied by 4 for RGBA)
2276
- * @returns RGBA color object with r, g, b, a properties
2277
- */
2278
- pixelsToRGBA(e, t) {
2279
- return {
2280
- r: e[t],
2281
- g: e[t + 1],
2282
- b: e[t + 2],
2283
- a: e[t + 3]
2284
- };
2285
- }
2286
- }
2287
- class X {
2288
- /**
2289
- * Creates a downloadable blob from content
2290
- * @param content The content to include in the blob
2291
- * @param mimeType The MIME type for the blob
2292
- * @returns Blob object containing the content
2293
- */
2294
- createBlob(e, t) {
2295
- return new Blob([e], { type: t });
2296
- }
2297
- /**
2298
- * Downloads content as a file
2299
- * @param content The content to download
2300
- * @param filename The filename (with extension)
2301
- * @param mimeType The MIME type for the content
2302
- */
2303
- downloadFile(e, t, r) {
2304
- try {
2305
- const i = this.createBlob(e, r), s = URL.createObjectURL(i), a = document.createElement("a");
2306
- a.href = s, a.download = t, a.style.display = "none", a.rel = "noopener", document.body.appendChild(a), a.click(), document.body.removeChild(a), URL.revokeObjectURL(s);
2307
- } catch (i) {
2308
- throw console.error("Failed to download file:", i), new Error(`File download failed: ${i instanceof Error ? i.message : "Unknown error"}`);
2309
- }
2310
- }
2311
- /**
2312
- * Generates a timestamp string for filenames
2313
- * @returns Formatted timestamp string
2314
- */
2315
- generateTimestamp() {
2316
- return (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace(/:/g, "-");
2317
- }
2318
- /**
2319
- * Generates a date-time string for filenames (alternative format)
2320
- * @returns Formatted date and time string
2321
- */
2322
- generateDateTimeString() {
2323
- const e = /* @__PURE__ */ new Date(), t = e.toISOString().split("T")[0], r = e.toTimeString().split(" ")[0].replace(/:/g, "-");
2324
- return { date: t, time: r };
2325
- }
2326
- /**
2327
- * Validates and sanitizes filename for safety and compatibility
2328
- * @param filename The filename to validate
2329
- * @returns Sanitized filename
2330
- */
2331
- sanitizeFilename(e) {
2332
- return e.replace(/[<>:"/\\|?*]/g, "_").replace(/\s+/g, "_").replace(/_{2,}/g, "_").replace(/^_+|_+$/g, "").substring(0, 255);
2333
- }
2334
- /**
2335
- * Generates a default filename with prefix and timestamp
2336
- * @param prefix The prefix for the filename
2337
- * @param extension The file extension (with dot)
2338
- * @returns Generated filename
2339
- */
2340
- generateDefaultFilename() {
2341
- return `'textmode-export'-${this.generateTimestamp()}`;
2342
- }
2343
- }
2344
- class we extends H {
2345
- /**
2346
- * Extracts transform data from transform pixels
2347
- * @param transformPixels Transform framebuffer pixels
2348
- * @param rotationPixels Rotation framebuffer pixels
2349
- * @param pixelIndex Pixel index in the array
2350
- * @returns Transform data object
2351
- */
2352
- extractTransformData(e, t, r) {
2353
- const i = e[r], s = e[r + 1], a = e[r + 2], n = i === 255, l = s === 255, c = a === 255, u = t[r], d = t[r + 1], f = u + d / 255, g = Math.round(f * 360 / 255 * 100) / 100;
2354
- return {
2355
- isInverted: n,
2356
- flipHorizontal: l,
2357
- flipVertical: c,
2358
- rotation: g
2359
- };
2360
- }
2361
- /**
2362
- * Calculates cell position information
2363
- * @param x Grid X coordinate
2364
- * @param y Grid Y coordinate
2365
- * @param gridInfo Grid information
2366
- * @returns Position data object
2367
- */
2368
- calculateCellPosition(e, t, r) {
2369
- return {
2370
- x: e,
2371
- y: t,
2372
- cellX: e * r.cellWidth,
2373
- cellY: t * r.cellHeight
2374
- };
2375
- }
2376
- /**
2377
- * Processes all grid cells and extracts SVG cell data
2378
- * @param framebufferData Raw pixel data from framebuffers
2379
- * @param grid Grid information
2380
- * @param font Font information
2381
- * @returns Array of SVG cell data objects
2382
- */
2383
- extractSVGCellData(e, t) {
2384
- const r = [];
2385
- let i = 0;
2386
- for (let s = 0; s < t.rows; s++)
2387
- for (let a = 0; a < t.cols; a++) {
2388
- const n = i * 4, l = this.getCharacterIndex(
2389
- e.characterPixels,
2390
- n
2391
- );
2392
- let c = this.pixelsToRGBA(e.primaryColorPixels, n), u = this.pixelsToRGBA(e.secondaryColorPixels, n);
2393
- const d = this.extractTransformData(
2394
- e.transformPixels,
2395
- e.rotationPixels,
2396
- n
2397
- );
2398
- if (d.isInverted) {
2399
- const g = c;
2400
- c = u, u = g;
2401
- }
2402
- const f = this.calculateCellPosition(a, s, t);
2403
- r.push({
2404
- charIndex: l,
2405
- primaryColor: c,
2406
- secondaryColor: u,
2407
- transform: d,
2408
- position: f
2409
- }), i++;
2410
- }
2411
- return r;
2412
- }
2413
- }
2414
- class ye {
2415
- /**
2416
- * Gets the glyph index for a given Unicode code point in a Typr.js font
2417
- * @param fontData The Typr.js font data
2418
- * @param codePoint The Unicode code point to look up
2419
- * @returns The glyph index, or 0 if not found
2420
- */
2421
- getGlyphIndex(e, t) {
2422
- const r = e.cmap;
2423
- if (!r || !r.tables) return 0;
2424
- for (const i of r.tables)
2425
- if (i.format === 4) {
2426
- for (let s = 0; s < i.startCount.length; s++)
2427
- if (t >= i.startCount[s] && t <= i.endCount[s]) {
2428
- if (i.idRangeOffset[s] === 0)
2429
- return t + i.idDelta[s] & 65535;
2430
- {
2431
- const a = i.idRangeOffset[s] / 2 + (t - i.startCount[s]) - (i.startCount.length - s);
2432
- if (a >= 0 && a < i.glyphIdArray.length) {
2433
- const n = i.glyphIdArray[a];
2434
- if (n !== 0)
2435
- return n + i.idDelta[s] & 65535;
2436
- }
2437
- }
2438
- }
2439
- }
2440
- return 0;
2441
- }
2442
- /**
2443
- * Creates an empty path object for characters with no glyph data
2444
- * @returns Empty path object
2445
- */
2446
- createEmptyPath() {
2447
- return {
2448
- getBoundingBox: () => ({ x1: 0, y1: 0, x2: 0, y2: 0 }),
2449
- toSVG: () => ""
2450
- };
2451
- }
2452
- /**
2453
- * Creates a path object for a glyph
2454
- * @param fontData Font data object
2455
- * @param glyphData Glyph data from font
2456
- * @param x X position
2457
- * @param y Y position
2458
- * @param fontSize Font size
2459
- * @returns Path object with bounding box and SVG methods
2460
- */
2461
- createGlyphPath(e, t, r, i, s) {
2462
- if (!t || !t.xs || t.xs.length === 0)
2463
- return this.createEmptyPath();
2464
- const a = s / e.head.unitsPerEm;
2465
- return {
2466
- getBoundingBox: () => ({
2467
- x1: r + t.xMin * a,
2468
- y1: i + -t.yMax * a,
2469
- x2: r + t.xMax * a,
2470
- y2: i + -t.yMin * a
2471
- }),
2472
- toSVG: () => this.glyphToSVGPath(t, r, i, a)
2473
- };
2474
- }
2475
- /**
2476
- * Converts glyph data to SVG path string
2477
- * @param glyphData Glyph data from font
2478
- * @param x X position
2479
- * @param y Y position
2480
- * @param scale Scale factor
2481
- * @returns SVG path data string
2482
- */
2483
- glyphToSVGPath(e, t, r, i) {
2484
- if (!e || !e.xs) return "";
2485
- const { xs: s, ys: a, endPts: n, flags: l } = e;
2486
- if (!s || !a || !n || !l) return "";
2487
- let c = "", u = 0;
2488
- for (let d = 0; d < n.length; d++) {
2489
- const f = n[d];
2490
- if (!(f < u)) {
2491
- if (f >= u) {
2492
- const g = t + s[u] * i, v = r - a[u] * i;
2493
- c += `M${g.toFixed(2)},${v.toFixed(2)}`;
2494
- let p = u + 1;
2495
- for (; p <= f; )
2496
- if ((l[p] & 1) !== 0) {
2497
- const w = t + s[p] * i, F = r - a[p] * i;
2498
- c += `L${w.toFixed(2)},${F.toFixed(2)}`, p++;
2499
- } else {
2500
- const w = t + s[p] * i, F = r - a[p] * i;
2501
- let C = p + 1 > f ? u : p + 1;
2502
- if ((l[C] & 1) !== 0) {
2503
- const U = t + s[C] * i, S = r - a[C] * i;
2504
- c += `Q${w.toFixed(2)},${F.toFixed(2)} ${U.toFixed(2)},${S.toFixed(2)}`, p = C + 1;
2505
- } else {
2506
- const U = t + s[C] * i, S = r - a[C] * i, R = (w + U) / 2, E = (F + S) / 2;
2507
- c += `Q${w.toFixed(2)},${F.toFixed(2)} ${R.toFixed(2)},${E.toFixed(2)}`, p = C;
2508
- }
2509
- }
2510
- c += "Z";
2511
- }
2512
- u = f + 1;
2513
- }
2514
- }
2515
- return c;
2516
- }
2517
- /**
2518
- * Generates an SVG path for a character glyph
2519
- * @param character The character to generate a path for
2520
- * @param fontData The font data object
2521
- * @param x X position
2522
- * @param y Y position
2523
- * @param fontSize Font size
2524
- * @returns Path object with SVG generation methods
2525
- */
2526
- generateCharacterPath(e, t, r, i, s) {
2527
- try {
2528
- const a = e.codePointAt(0) || 0, n = this.getGlyphIndex(t, a);
2529
- if (n === 0)
2530
- return this.createEmptyPath();
2531
- let l = null;
2532
- try {
2533
- t.glyf && t.glyf[n] !== null ? l = t.glyf[n] : m && m.T && m.T.glyf && m.T.glyf._parseGlyf && (l = m.T.glyf._parseGlyf(t, n), t.glyf && l && (t.glyf[n] = l));
2534
- } catch (c) {
2535
- console.warn(`Failed to parse glyph ${n}:`, c);
2536
- }
2537
- return l ? this.createGlyphPath(t, l, r, i, s) : this.createEmptyPath();
2538
- } catch (a) {
2539
- return console.warn(`Failed to generate path for character "${e}":`, a), this.createEmptyPath();
2540
- }
2541
- }
2542
- /**
2543
- * Generates SVG path data for a character with positioning calculations
2544
- * @param character The character to render
2545
- * @param fontData The font data
2546
- * @param cellX Cell X position
2547
- * @param cellY Cell Y position
2548
- * @param cellWidth Cell width
2549
- * @param cellHeight Cell height
2550
- * @param fontSize Font size
2551
- * @param advanceWidth Character advance width
2552
- * @returns SVG path data string or null if generation fails
2553
- */
2554
- generatePositionedCharacterPath(e, t, r, i, s, a, n, l) {
2555
- try {
2556
- const c = n / t.head.unitsPerEm, u = l * c, d = r + (s - u) / 2, f = i + (a + n * 0.7) / 2;
2557
- return this.generateCharacterPath(e, t, d, f, n).toSVG() || null;
2558
- } catch (c) {
2559
- return console.warn(`Failed to generate positioned character path for "${e}":`, c), null;
2560
- }
2561
- }
2562
- }
2563
- class Te {
2564
- constructor() {
2565
- o(this, "pathGenerator");
2566
- this.pathGenerator = new ye();
2567
- }
2568
- /**
2569
- * Generates the SVG header with metadata
2570
- * @param gridInfo Grid dimensions
2571
- * @returns SVG header string
2572
- */
2573
- generateSVGHeader(e) {
2574
- return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
2575
- <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
2576
- <svg width="${e.width}" height="${e.height}" viewBox="0 0 ${e.width} ${e.height}"
2577
- xmlns="http://www.w3.org/2000/svg" version="1.1">
2578
- <title>textmode art generated via textmode.js</title>
2579
- <desc>textmode art visualization generated by textmode.js library</desc>`;
2580
- }
2581
- /**
2582
- * Generates the SVG footer
2583
- * @returns SVG footer string
2584
- */
2585
- generateSVGFooter() {
2586
- return `
2587
- </g>
2588
- </svg>`;
2589
- }
2590
- /**
2591
- * Generates background rectangle if needed
2592
- * @param gridInfo Grid information
2593
- * @param options SVG generation options
2594
- * @returns Background rectangle SVG string or empty string
2595
- */
2596
- generateBackground(e, t) {
2597
- if (!t.includeBackgroundRectangles)
2598
- return "";
2599
- const r = t.backgroundColor, i = `rgba(${r[0]},${r[1]},${r[2]},${r[3] / 255})`;
2600
- return `
2601
- <rect width="${e.width}" height="${e.height}" fill="${i}" />`;
2602
- }
2603
- /**
2604
- * Converts RGBA object to CSS color string
2605
- * @param color RGBA color object
2606
- * @returns CSS color string
2607
- */
2608
- rgbaToColorString(e) {
2609
- return `rgba(${e.r},${e.g},${e.b},${e.a / 255})`;
2610
- }
2611
- /**
2612
- * Generates SVG transform attribute string
2613
- * @param cellData Cell data with transform information
2614
- * @param gridInfo Grid information for center calculations
2615
- * @returns Transform attribute string or empty string
2616
- */
2617
- generateTransformAttribute(e, t) {
2618
- const { transform: r, position: i } = e, s = i.cellX + t.cellWidth / 2, a = i.cellY + t.cellHeight / 2, n = [];
2619
- if (r.flipHorizontal || r.flipVertical) {
2620
- const l = r.flipHorizontal ? -1 : 1, c = r.flipVertical ? -1 : 1;
2621
- n.push(`translate(${s} ${a})`), n.push(`scale(${l} ${c})`), n.push(`translate(${-s} ${-a})`);
2622
- }
2623
- return r.rotation && n.push(`rotate(${r.rotation} ${s} ${a})`), n.length ? ` transform="${n.join(" ")}"` : "";
2624
- }
2625
- /**
2626
- * Generates background rectangle for a cell
2627
- * @param cellData Cell data
2628
- * @param gridInfo Grid information
2629
- * @param options SVG generation options
2630
- * @returns Background rectangle SVG string or empty string
2631
- */
2632
- generateCellBackground(e, t, r) {
2633
- if (!r.includeBackgroundRectangles || e.secondaryColor.a === 0)
2634
- return "";
2635
- const { position: i } = e, s = this.rgbaToColorString(e.secondaryColor);
2636
- return r.drawMode === "stroke" ? `
2637
- <rect x="${i.cellX}" y="${i.cellY}" width="${t.cellWidth}" height="${t.cellHeight}" stroke="${s}" fill="none" stroke-width="${r.strokeWidth}" />` : `
2638
- <rect x="${i.cellX}" y="${i.cellY}" width="${t.cellWidth}" height="${t.cellHeight}" fill="${s}" />`;
2639
- }
2640
- /**
2641
- * Generates character path element for a cell
2642
- * @param cellData Cell data
2643
- * @param gridInfo Grid information
2644
- * @param fontInfo Font information
2645
- * @param options SVG generation options
2646
- * @returns Character path SVG string
2647
- */
2648
- generateCharacterPath(e, t, r, i) {
2649
- const s = r.characters[e.charIndex];
2650
- if (!s)
2651
- return "";
2652
- const a = this.pathGenerator.generatePositionedCharacterPath(
2653
- s.character,
2654
- r.font,
2655
- e.position.cellX,
2656
- e.position.cellY,
2657
- t.cellWidth,
2658
- t.cellHeight,
2659
- r.fontSize,
2660
- s.advanceWidth
2661
- );
2662
- if (!a)
2663
- return "";
2664
- const n = this.rgbaToColorString(e.primaryColor);
2665
- return i.drawMode === "stroke" ? `
2666
- <path id="${`path-${e.charIndex}-${e.position.cellX}-${e.position.cellY}`.replace(/\./g, "-")}" d="${a}" stroke="${n}" stroke-width="${i.strokeWidth}" fill="none" />` : `
2667
- <path d="${a}" fill="${n}" />`;
2668
- }
2669
- /**
2670
- * Generates complete SVG content for a single cell
2671
- * @param cellData Cell data
2672
- * @param gridInfo Grid information
2673
- * @param fontInfo Font information
2674
- * @param options SVG generation options
2675
- * @returns Complete cell SVG content
2676
- */
2677
- generateCellContent(e, t, r, i) {
2678
- let s = "";
2679
- s += this.generateCellBackground(e, t, i);
2680
- const a = this.generateTransformAttribute(e, t), n = this.generateCharacterPath(e, t, r, i);
2681
- return n && (a ? (s += `
2682
- <g${a}>`, s += n, s += `
2683
- </g>`) : s += n), s;
2684
- }
2685
- /**
2686
- * Generates the complete SVG content from cell data
2687
- * @param cellDataArray Array of cell data
2688
- * @param grid Grid information
2689
- * @param fontInfo Font information
2690
- * @param options SVG generation options
2691
- * @returns Complete SVG string
2692
- */
2693
- generateSVGContent(e, t, r, i) {
2694
- let s = this.generateSVGHeader(t);
2695
- s += this.generateBackground(t, i), s += `
2696
- <g id="ascii-cells">`;
2697
- for (const a of e)
2698
- s += this.generateCellContent(a, t, r, i);
2699
- return s += this.generateSVGFooter(), s;
2700
- }
2701
- /**
2702
- * Optimizes SVG content by removing empty elements and unnecessary whitespace
2703
- * @param svgContent Raw SVG content
2704
- * @returns Optimized SVG content
2705
- */
2706
- optimizeSVGContent(e) {
2707
- return e.replace(/<path[^>]*d=""[^>]*\/>/g, "").replace(/\n\s*\n/g, `
2708
- `).replace(/[ \t]+$/gm, "");
2709
- }
2710
- }
2711
- class Se extends X {
2712
- /**
2713
- * Creates a downloadable blob from SVG content
2714
- * @param svgContent The SVG content string
2715
- * @returns Blob object containing the SVG data
2716
- */
2717
- createSVGBlob(e) {
2718
- return this.createBlob(e, "image/svg+xml;charset=utf-8");
2719
- }
2720
- /**
2721
- * Downloads SVG content as a file
2722
- * @param svgContent The SVG content to download
2723
- * @param filename The filename (without extension)
2724
- */
2725
- downloadSVG(e, t) {
2726
- try {
2727
- this.downloadFile(e, this.sanitizeFilename(t) + ".svg", "image/svg+xml;charset=utf-8");
2728
- } catch (r) {
2729
- throw console.error("Failed to download SVG file:", r), new Error(`SVG download failed: ${r instanceof Error ? r.message : "Unknown error"}`);
2730
- }
2731
- }
2732
- /**
2733
- * Saves SVG content with automatic filename generation if not provided
2734
- * @param svgContent The SVG content to save
2735
- * @param filename Optional filename (will generate if not provided)
2736
- */
2737
- saveSVG(e, t) {
2738
- this.downloadSVG(e, t || this.generateDefaultFilename());
2739
- }
2740
- }
2741
- class q {
2742
- constructor() {
2743
- o(this, "dataExtractor");
2744
- o(this, "contentGenerator");
2745
- o(this, "fileHandler");
2746
- this.dataExtractor = new we(), this.contentGenerator = new Te(), this.fileHandler = new Se();
2747
- }
2748
- /**
2749
- * Applies default values to SVG export options
2750
- * @param options User-provided options
2751
- * @returns Complete options with defaults applied
2752
- */
2753
- applyDefaultOptions(e) {
2754
- return {
2755
- includeBackgroundRectangles: e.includeBackgroundRectangles ?? !0,
2756
- drawMode: e.drawMode ?? "fill",
2757
- strokeWidth: e.strokeWidth ?? 1,
2758
- backgroundColor: e.backgroundColor ?? [0, 0, 0, 0]
2759
- };
2760
- }
2761
- /**
2762
- * Generates SVG content from textmode rendering data without saving to file
2763
- * @param textmodifier The textmodifier instance containing rendering data
2764
- * @param options Export options (excluding filename)
2765
- * @returns SVG content as string
2766
- */
2767
- generateSVG(e, t = {}) {
2768
- const r = this.applyDefaultOptions(t), i = this.dataExtractor.extractFramebufferData(e.pipeline), s = this.dataExtractor.extractSVGCellData(
2769
- i,
2770
- e.grid
2771
- ), a = this.contentGenerator.generateSVGContent(
2772
- s,
2773
- e.grid,
2774
- e.font,
2775
- r
2776
- );
2777
- return this.contentGenerator.optimizeSVGContent(a);
2778
- }
2779
- /**
2780
- * Exports SVG content to a downloadable file
2781
- * @param textmodifier The textmodifier instance containing rendering data
2782
- * @param options Export options including filename
2783
- */
2784
- saveSVG(e, t = {}) {
2785
- try {
2786
- const r = this.generateSVG(e, t), i = t.filename || this.fileHandler.generateDefaultFilename();
2787
- this.fileHandler.saveSVG(r, i);
2788
- } catch (r) {
2789
- throw console.error("Failed to save SVG:", r), new Error(`SVG save failed: ${r instanceof Error ? r.message : "Unknown error"}`);
2790
- }
2791
- }
2792
- }
2793
- class Re extends H {
2794
- /**
2795
- * Extracts character data for TXT generation
2796
- * @param framebufferData Framebuffer pixel data
2797
- * @param grid Grid information
2798
- * @param font Font information
2799
- * @param emptyCharacter Character to use for empty cells
2800
- * @returns 2D array of characters (rows x columns)
2801
- */
2802
- extractCharacterGrid(e, t, r, i = " ") {
2803
- var n;
2804
- const s = [];
2805
- let a = 0;
2806
- for (let l = 0; l < t.rows; l++) {
2807
- const c = [];
2808
- for (let u = 0; u < t.cols; u++) {
2809
- const d = a * 4, f = this.getCharacterIndex(
2810
- e.characterPixels,
2811
- d
2812
- ), g = ((n = r.characters[f]) == null ? void 0 : n.character) || i;
2813
- c.push(g), a++;
2814
- }
2815
- s.push(c);
2816
- }
2817
- return s;
2818
- }
2819
- }
2820
- class Ee {
2821
- /**
2822
- * Generates TXT content from a 2D character array
2823
- * @param characterGrid 2D array of characters (rows x columns)
2824
- * @param options Generation options
2825
- * @returns TXT content as string
2826
- */
2827
- generateTXTContent(e, t) {
2828
- const r = [];
2829
- for (const s of e) {
2830
- let a = s.join("");
2831
- t.preserveTrailingSpaces || (a = a.replace(/\s+$/, "")), r.push(a);
2832
- }
2833
- const i = t.lineEnding === "crlf" ? `\r
2834
- ` : `
2835
- `;
2836
- return r.join(i);
2837
- }
2838
- }
2839
- class Ue extends X {
2840
- /**
2841
- * Saves TXT content as a downloadable file
2842
- * @param content The TXT content to save
2843
- * @param filename The filename to use for the download
2844
- */
2845
- saveTXT(e, t) {
2846
- try {
2847
- const r = this.ensureValidFilename(t);
2848
- this.downloadFile(e, r, "text/plain;charset=utf-8");
2849
- } catch (r) {
2850
- throw console.error("Failed to save TXT file:", r), new Error(`TXT file save failed: ${r instanceof Error ? r.message : "Unknown error"}`);
2851
- }
2852
- }
2853
- /**
2854
- * Ensures filename has proper extension and is valid
2855
- * @param filename The filename to validate and fix
2856
- * @returns Valid filename with .txt extension
2857
- */
2858
- ensureValidFilename(e) {
2859
- let t = this.sanitizeFilename(e);
2860
- return t === ".txt" || t.length <= 4 ? this.generateDefaultFilename() : t;
2861
- }
2862
- }
2863
- class K {
2864
- constructor() {
2865
- o(this, "dataExtractor");
2866
- o(this, "contentGenerator");
2867
- o(this, "fileHandler");
2868
- this.dataExtractor = new Re(), this.contentGenerator = new Ee(), this.fileHandler = new Ue();
2869
- }
2870
- /**
2871
- * Applies default values to TXT export options
2872
- * @param options User-provided options
2873
- * @returns Complete options with defaults applied
2874
- */
2875
- applyDefaultOptions(e) {
2876
- return {
2877
- preserveTrailingSpaces: e.preserveTrailingSpaces ?? !1,
2878
- lineEnding: e.lineEnding ?? "lf",
2879
- emptyCharacter: e.emptyCharacter ?? " "
2880
- };
2881
- }
2882
- /**
2883
- * Generates TXT content from textmode rendering data without saving to file
2884
- * @param textmodifier The textmodifier instance containing rendering data
2885
- * @param options Export options (excluding filename)
2886
- * @returns TXT content as string
2887
- */
2888
- generateTXT(e, t = {}) {
2889
- const r = this.applyDefaultOptions(t), i = this.dataExtractor.extractFramebufferData(e.pipeline), s = this.dataExtractor.extractCharacterGrid(
2890
- i,
2891
- e.grid,
2892
- e.font,
2893
- r.emptyCharacter
2894
- );
2895
- return this.contentGenerator.generateTXTContent(
2896
- s,
2897
- r
2898
- );
2899
- }
2900
- /**
2901
- * Exports TXT content to a downloadable file
2902
- * @param textmodifier The textmodifier instance containing rendering data
2903
- * @param options Export options including filename
2904
- */
2905
- saveTXT(e, t = {}) {
2906
- try {
2907
- const r = this.generateTXT(e, t), i = t.filename || this.fileHandler.generateDefaultFilename();
2908
- this.fileHandler.saveTXT(r, i);
2909
- } catch (r) {
2910
- throw console.error("Failed to save TXT:", r), new Error(`TXT save failed: ${r instanceof Error ? r.message : "Unknown error"}`);
2911
- }
2912
- }
2913
- }
2914
- class Me extends H {
2915
- /**
2916
- * Captures the current state of the textmode canvas as image data
2917
- * @param canvas The canvas data containing the rendered textmode graphics
2918
- * @param scale Scale factor for the output image
2919
- * @param backgroundColor Background color for formats that don't support transparency
2920
- * @returns Canvas element containing the captured image data
2921
- */
2922
- captureCanvasData(e, t = 1, r = "transparent") {
2923
- const i = e.canvas;
2924
- if (t === 1 && r === "transparent")
2925
- return i;
2926
- const s = document.createElement("canvas"), a = s.getContext("2d"), n = Math.round(i.width * t), l = Math.round(i.height * t);
2927
- return s.width = n, s.height = l, r !== "transparent" && (a.fillStyle = r, a.fillRect(0, 0, n, l)), a.imageSmoothingEnabled = !1, a.drawImage(
2928
- i,
2929
- 0,
2930
- 0,
2931
- i.width,
2932
- i.height,
2933
- 0,
2934
- 0,
2935
- n,
2936
- l
2937
- ), s;
2938
- }
2939
- }
2940
- class Ae {
2941
- /**
2942
- * Generates image data from canvas
2943
- * @param canvas The canvas containing the image data
2944
- * @param options Generation options with format, quality, etc.
2945
- * @returns Data URL string containing the image data
2946
- */
2947
- generateImageData(e, t) {
2948
- const r = this.getMimeType(t.format);
2949
- return t.format === "png" ? e.toDataURL(r) : e.toDataURL(r, t.quality);
2950
- }
2951
- /**
2952
- * Generates image blob from canvas
2953
- * @param canvas The canvas containing the image data
2954
- * @param options Generation options with format, quality, etc.
2955
- * @returns Promise that resolves to a Blob containing the image data
2956
- */
2957
- async generateImageBlob(e, t) {
2958
- return new Promise((r, i) => {
2959
- const s = this.getMimeType(t.format), a = (n) => {
2960
- n ? r(n) : i(new Error(`Failed to generate ${t.format.toUpperCase()} blob`));
2961
- };
2962
- t.format === "png" ? e.toBlob(a, s) : e.toBlob(a, s, t.quality);
2963
- });
2964
- }
2965
- /**
2966
- * Gets the MIME type for a given image format
2967
- * @param format The image format
2968
- * @returns The corresponding MIME type
2969
- */
2970
- getMimeType(e) {
2971
- switch (e) {
2972
- case "png":
2973
- return "image/png";
2974
- case "jpg":
2975
- return "image/jpeg";
2976
- case "webp":
2977
- return "image/webp";
2978
- default:
2979
- throw new Error(`Unsupported image format: ${e}`);
2980
- }
2981
- }
2982
- }
2983
- const Q = {
2984
- png: "image/png",
2985
- jpg: "image/jpeg",
2986
- webp: "image/webp"
2987
- }, V = {
2988
- png: ".png",
2989
- jpg: ".jpg",
2990
- webp: ".webp"
2991
- };
2992
- class De extends X {
2993
- /**
2994
- * Saves image content as a downloadable file
2995
- * @param content The image content (data URL or blob)
2996
- * @param filename The filename (without extension)
2997
- * @param format The image format
2998
- */
2999
- saveImage(e, t, r) {
3000
- try {
3001
- const i = V[r];
3002
- typeof e == "string" ? this.saveImageFromDataURL(e, this.sanitizeFilename(t) + i) : this.saveImageFromBlob(e, this.sanitizeFilename(t) + i);
3003
- } catch (i) {
3004
- throw console.error(`Failed to save ${r.toUpperCase()} image:`, i), new Error(`Image save failed: ${i instanceof Error ? i.message : "Unknown error"}`);
3005
- }
3006
- }
3007
- /**
3008
- * Saves image from data URL
3009
- * @param dataURL The data URL containing image data
3010
- * @param filename The complete filename with extension
3011
- */
3012
- saveImageFromDataURL(e, t) {
3013
- const r = document.createElement("a");
3014
- r.href = e, r.download = t, r.style.display = "none", r.rel = "noopener", document.body.appendChild(r), r.click(), document.body.removeChild(r);
3015
- }
3016
- /**
3017
- * Saves image from blob
3018
- * @param blob The blob containing image data
3019
- * @param filename The complete filename with extension
3020
- */
3021
- saveImageFromBlob(e, t) {
3022
- const r = URL.createObjectURL(e);
3023
- try {
3024
- const i = document.createElement("a");
3025
- i.href = r, i.download = t, i.style.display = "none", i.rel = "noopener", document.body.appendChild(i), i.click(), document.body.removeChild(i);
3026
- } finally {
3027
- URL.revokeObjectURL(r);
3028
- }
3029
- }
3030
- /**
3031
- * Validates if the browser supports saving files in the specified format
3032
- * @param format The image format to validate
3033
- * @returns True if the format is supported for saving
3034
- */
3035
- validateSaveSupport(e) {
3036
- return e in Q && e in V;
3037
- }
3038
- /**
3039
- * Gets the MIME type for the specified image format
3040
- * @param format The image format
3041
- * @returns The MIME type string
3042
- */
3043
- getMimeType(e) {
3044
- return Q[e];
3045
- }
3046
- /**
3047
- * Gets the file extension for the specified image format
3048
- * @param format The image format
3049
- * @returns The file extension (including the dot)
3050
- */
3051
- getFileExtension(e) {
3052
- return V[e];
3053
- }
3054
- }
3055
- class Ie {
3056
- constructor() {
3057
- o(this, "dataExtractor");
3058
- o(this, "contentGenerator");
3059
- o(this, "fileHandler");
3060
- this.dataExtractor = new Me(), this.contentGenerator = new Ae(), this.fileHandler = new De();
3061
- }
3062
- /**
3063
- * Applies default values to image export options
3064
- * @param options User-provided options
3065
- * @returns Complete options with defaults applied
3066
- */
3067
- applyDefaultOptions(e) {
3068
- return {
3069
- format: e.format ?? "png",
3070
- quality: e.quality ?? 1,
3071
- scale: e.scale ?? 1,
3072
- backgroundColor: e.backgroundColor ?? "transparent"
3073
- };
3074
- }
3075
- /**
3076
- * Validates export options and browser support
3077
- * @param options The options to validate
3078
- * @throws Error if options are invalid or format is not supported
3079
- */
3080
- validateOptions(e) {
3081
- if (console.log("Validating image export options:", e), !this.fileHandler.validateSaveSupport(e.format))
3082
- throw new Error(`Saving '${e.format}' files is not supported`);
3083
- if (e.quality < 0 || e.quality > 1)
3084
- throw new Error("Image quality must be between 0.0 and 1.0");
3085
- if (e.scale <= 0)
3086
- throw new Error("Scale factor must be greater than 0");
3087
- e.scale > 10 && console.warn("Large scale factors may result in very large files and slow performance"), e.format === "jpg" && e.backgroundColor === "transparent" && (e.backgroundColor = "black");
3088
- }
3089
- /**
3090
- * Generates image data from textmode rendering without saving to file
3091
- * @param canvas The canvas data containing the rendered textmode graphics
3092
- * @param options Export options (excluding filename)
3093
- * @returns Data URL string containing the image data
3094
- */
3095
- generateImage(e, t = {}) {
3096
- const r = this.applyDefaultOptions(t);
3097
- if (this.validateOptions(r), r.scale === 1 && r.backgroundColor === "transparent")
3098
- return this.contentGenerator.generateImageData(e.canvas, r);
3099
- const i = this.dataExtractor.captureCanvasData(
3100
- e,
3101
- r.scale,
3102
- r.backgroundColor
3103
- );
3104
- return this.contentGenerator.generateImageData(
3105
- i,
3106
- r
3107
- );
3108
- }
3109
- /**
3110
- * Generates image blob from textmode rendering without saving to file
3111
- * @param canvasData The canvas data containing the rendered textmode graphics
3112
- * @param options Export options (excluding filename)
3113
- * @returns Promise that resolves to a Blob containing the image data
3114
- */
3115
- async generateImageBlob(e, t = {}) {
3116
- const r = this.applyDefaultOptions(t);
3117
- if (this.validateOptions(r), r.scale === 1 && r.backgroundColor === "transparent")
3118
- return await this.contentGenerator.generateImageBlob(e.canvas, r);
3119
- const i = this.dataExtractor.captureCanvasData(
3120
- e,
3121
- r.scale,
3122
- r.backgroundColor
3123
- );
3124
- return await this.contentGenerator.generateImageBlob(
3125
- i,
3126
- r
3127
- );
3128
- }
3129
- /**
3130
- * Exports image to a downloadable file
3131
- * @param canvas The canvas data containing the rendered textmode graphics
3132
- * @param options Export options including filename
3133
- */
3134
- async saveImage(e, t = {}) {
3135
- try {
3136
- const r = await this.generateImageBlob(e, t), i = t.format ?? "png", s = t.filename || this.fileHandler.generateDefaultFilename();
3137
- this.fileHandler.saveImage(r, s, i);
3138
- } catch (r) {
3139
- throw console.error("Failed to save image:", r), new Error(`Image save failed: ${r instanceof Error ? r.message : "Unknown error"}`);
3140
- }
3141
- }
3142
- }
3143
- class k {
3144
- constructor(e = null, t = {}) {
3145
- /** The element to capture content from (optional for standalone mode) */
3146
- o(this, "captureSource");
3147
- /** Our WebGL overlay canvas manager */
3148
- o(this, "textmodeCanvas");
3149
- /** Core WebGL renderer */
3150
- o(this, "_renderer");
3151
- o(this, "_canvasFramebuffer");
3152
- o(this, "_font");
3153
- o(this, "_grid");
3154
- o(this, "resizeObserver");
3155
- // Auto-rendering properties
3156
- o(this, "_mode");
3157
- o(this, "_frameRateLimit");
3158
- o(this, "animationFrameId", null);
3159
- o(this, "lastFrameTime", 0);
3160
- o(this, "frameInterval");
3161
- o(this, "_isLooping", !0);
3162
- o(this, "_frameRate", 0);
3163
- o(this, "lastRenderTime", 0);
3164
- o(this, "_frameCount", 0);
3165
- // Frame rate measurement smoothing
3166
- o(this, "frameTimeHistory", []);
3167
- o(this, "frameTimeHistorySize", 10);
3168
- o(this, "_pipeline");
3169
- o(this, "_isDisposed", !1);
3170
- // Standalone canvas properties
3171
- o(this, "_standalone", !1);
3172
- o(this, "_drawCallback", () => {
3173
- });
3174
- o(this, "_resizedCallback", () => {
3175
- });
3176
- o(this, "_windowResizeListener", null);
3177
- o(this, "_printDebug", !1);
3178
- this.captureSource = e, this._standalone = e === null, this._mode = t.renderMode ?? "auto", this._frameRateLimit = t.frameRate ?? 60, this.frameInterval = 1e3 / this._frameRateLimit;
3179
- }
3180
- /**
3181
- * Static factory method for creating and initializing a Textmodifier instance.
3182
- * @param source The HTML canvas or video element to capture content from. Pass `null` for standalone mode.
3183
- * @param opts Optional configuration options for the `Textmodifier` instance.
3184
- * @ignore
3185
- */
3186
- static async create(e = null, t = {}) {
3187
- const r = new k(e, t), i = r._standalone ? t : void 0;
3188
- r.textmodeCanvas = new fe(r.captureSource, r._standalone, i), r._renderer = new ne(r.textmodeCanvas.getWebGLContext());
3189
- let s, a;
3190
- r._standalone ? (s = t.width || 800, a = t.height || 600) : (s = r.textmodeCanvas.width || 800, a = r.textmodeCanvas.height || 600), r._canvasFramebuffer = r._renderer.createFramebuffer(s, a), r._font = new ue(r._renderer, t.fontSize ?? 16), await r._font.initialize(t.fontSource);
3191
- const n = r._font.maxGlyphDimensions;
3192
- return r._grid = new de(r.textmodeCanvas.canvas, n.width, n.height), r._pipeline = new Fe(r._renderer, r._font, r._grid), r.setupEventListeners(), r.startAutoRendering(), r;
3193
- }
3194
- setupEventListeners() {
3195
- this._windowResizeListener = () => {
3196
- this._standalone ? this._resizedCallback() : this.resize();
3197
- }, window.addEventListener("resize", this._windowResizeListener), window.ResizeObserver && this.captureSource && !this._standalone && (this.resizeObserver = new ResizeObserver(() => {
3198
- this.resize();
3199
- }), this.resizeObserver.observe(this.captureSource));
3200
- }
3201
- /**
3202
- * Generate the current textmode rendering as a text string.
3203
- * @param options Options for text generation *(excluding filename)*
3204
- * @returns Textmode grid content as a string.
3205
- *
3206
- * @example
3207
- * ```javascript
3208
- * // Fetch a canvas element to apply textmode rendering to
3209
- * const canvas = document.querySelector('canvas#myCanvas');
3210
- *
3211
- * // Create a Textmodifier instance
3212
- * const textmodifier = await textmode.create(canvas, {renderMode: 'manual'});
3213
- *
3214
- * // Render a single frame
3215
- * textmodifier.render();
3216
- *
3217
- * // Get the current rendering as a text string
3218
- * const textString = textmodifier.toString({
3219
- * preserveTrailingSpaces: false,
3220
- * lineEnding: 'lf'
3221
- * });
3222
- *
3223
- * // Print to console or use otherwise
3224
- * console.log(textString);
3225
- *
3226
- * ////////
3227
- *
3228
- * // Example with video element
3229
- * const video = document.querySelector('video#myVideo');
3230
- * const videoTextmodifier = await textmode.create(video);
3231
- *
3232
- * // The textmode overlay will automatically update as the video plays
3233
- * video.play();
3234
- *
3235
- * // Get current frame as ASCII
3236
- * const videoFrame = videoTextmodifier.toString();
3237
- * ```
3238
- */
3239
- toString(e = {}) {
3240
- return new K().generateTXT(this, e);
3241
- }
3242
- /**
3243
- * Export the current textmode rendering to a TXT file.
3244
- * @param options Options for TXT export
3245
- *
3246
- * @example
3247
- * ```javascript
3248
- * // Fetch a canvas element to apply textmode rendering to
3249
- * const canvas = document.querySelector('canvas#myCanvas');
3250
- *
3251
- * // Create a Textmodifier instance
3252
- * const textmodifier = await textmode.create(canvas, {renderMode: 'manual'});
3253
- *
3254
- * // Render a single frame
3255
- * textmodifier.render();
3256
- *
3257
- * // Export the current rendering to a TXT file
3258
- * textmodifier.saveStrings({
3259
- * filename: 'my_textmode_rendering',
3260
- * preserveTrailingSpaces: false
3261
- * });
3262
- * ```
3263
- */
3264
- saveStrings(e = {}) {
3265
- new K().saveTXT(this, e);
3266
- }
3267
- /**
3268
- * Generate the current textmode rendering as an SVG string.
3269
- * @param options Options for SVG generation *(excluding filename)*
3270
- * @returns SVG content as a string.
3271
- *
3272
- * @example
3273
- * ```javascript
3274
- * // Fetch a canvas element to apply textmode rendering to
3275
- * const canvas = document.querySelector('canvas#myCanvas');
3276
- *
3277
- * // Create a Textmodifier instance
3278
- * const textmodifier = await textmode.create(canvas, {renderMode: 'manual'});
3279
- *
3280
- * // Render a single frame
3281
- * textmodifier.render();
3282
- *
3283
- * // Get the current rendering as an SVG string
3284
- * const svgString = textmodifier.toSVG({
3285
- * includeBackgroundRectangles: true,
3286
- * drawMode: 'fill'
3287
- * });
3288
- *
3289
- * // Print to console or use otherwise
3290
- * console.log(svgString);
3291
- * ```
3292
- */
3293
- toSVG(e = {}) {
3294
- return new q().generateSVG(this, e);
3295
- }
3296
- /**
3297
- * Export the current textmode rendering to an SVG file.
3298
- * @param options Options for SVG export
3299
- *
3300
- * @example
3301
- * ```javascript
3302
- * // Fetch a canvas element to apply textmode rendering to
3303
- * const canvas = document.querySelector('canvas#myCanvas');
3304
- *
3305
- * // Create a Textmodifier instance
3306
- * const textmodifier = await textmode.create(canvas, {renderMode: 'manual'});
3307
- *
3308
- * // Render a single frame
3309
- * textmodifier.render();
3310
- *
3311
- * // Export the current rendering to an SVG file
3312
- * textmodifier.saveSVG({
3313
- * filename: 'my_textmode_rendering',
3314
- * });
3315
- * ```
3316
- */
3317
- saveSVG(e = {}) {
3318
- new q().saveSVG(this, e);
3319
- }
3320
- /**
3321
- * Export the current textmode rendering to an image file.
3322
- * @param filename The filename (without extension) to save the image as
3323
- * @param format The image format ('png', 'jpg', or 'webp')
3324
- * @param options Additional options for image export
3325
- *
3326
- * @example
3327
- * ```javascript
3328
- * // Fetch a canvas element to apply textmode rendering to
3329
- * const canvas = document.querySelector('canvas#myCanvas');
3330
- *
3331
- * // Create a Textmodifier instance
3332
- * const textmodifier = await textmode.create(canvas, {renderMode: 'manual'});
3333
- *
3334
- * // Render a single frame
3335
- * textmodifier.render();
3336
- *
3337
- * // Export the current rendering to a PNG file
3338
- * textmodifier.saveCanvas('my_textmode_rendering', 'png');
3339
- *
3340
- * // Export with custom options
3341
- * textmodifier.saveCanvas('my_textmode_rendering', 'jpg', {
3342
- * quality: 0.8,
3343
- * scale: 2.0,
3344
- * backgroundColor: 'white'
3345
- * });
3346
- * ```
3347
- */
3348
- async saveCanvas(e, t = "png", r = {}) {
3349
- await new Ie().saveImage(this.textmodeCanvas, {
3350
- ...r,
3351
- filename: e,
3352
- format: t
3353
- });
3354
- }
3355
- /**
3356
- * Update the font used for rendering.
3357
- * @param fontSource The URL of the font to load.
3358
- *
3359
- * @example
3360
- * ```javascript
3361
- * // Fetch a canvas element to apply textmode rendering to
3362
- * const canvas = document.querySelector('canvas#myCanvas');
3363
- *
3364
- * // Create a Textmodifier instance
3365
- * const textmodifier = await textmode.create(canvas);
3366
- *
3367
- * // Load a custom font from a URL
3368
- * await textmodifier.loadFont('https://example.com/fonts/myfont.ttf');
3369
- *
3370
- * // Local font example
3371
- * // await textmodifier.loadFont('./fonts/myfont.ttf');
3372
- * ```
3373
- */
3374
- async loadFont(e) {
3375
- return this._font.loadFont(e).then(() => {
3376
- const t = this._font.maxGlyphDimensions;
3377
- this._grid.resizeCellPixelDimensions(t.width, t.height), this._pipeline.resize();
3378
- });
3379
- }
3380
- /**
3381
- * Apply textmode rendering to the canvas.
3382
- *
3383
- * **Note:** In `'auto'` mode, this is called automatically.
3384
- * In `'manual'` mode, you need to call this method when you want to update the textmode rendering.
3385
- *
3386
- * @example
3387
- * ```javascript
3388
- * // p5.js example
3389
- *
3390
- * let textmodifier;
3391
- *
3392
- * // p5.js setup function
3393
- * async function setup() {
3394
- *
3395
- * // Create a p5.js canvas
3396
- * const canvas = createCanvas(800, 600);
3397
- *
3398
- * // Create a Textmodifier instance
3399
- * textmodifier = await textmode.create(canvas.elt);
3400
- *
3401
- * // Update the rendering mode to 'manual'
3402
- * textmodifier.renderMode('manual');
3403
- * }
3404
- *
3405
- * // p5.js draw function
3406
- * function draw() {
3407
- *
3408
- * // Draw something on the p5.js canvas
3409
- * background(220);
3410
- * fill(255, 0, 0);
3411
- * rect(50, 50, 100, 100);
3412
- *
3413
- * // Apply textmode rendering
3414
- * textmodifier.render();
3415
- * }
3416
- * ```
3417
- */
3418
- render() {
3419
- if (this.measureFrameRate(), this._frameCount++, this._isDisposed) {
3420
- console.warn("Cannot render: Required resources have been disposed");
3421
- return;
3422
- }
3423
- if (this._standalone ? this._canvasFramebuffer && (this._canvasFramebuffer.begin(), this._drawCallback(), this._renderer && this._renderer.reset(), this._canvasFramebuffer && this._canvasFramebuffer.end()) : this.captureSource && this._canvasFramebuffer && this._canvasFramebuffer.update(this.captureSource), !this._pipeline || !this._renderer) {
3424
- console.warn("Cannot complete render: Pipeline or renderer has been disposed");
3425
- return;
3426
- }
3427
- if (this._pipeline.render(this._canvasFramebuffer), this._pipeline.hasEnabledConverters() ? (this._renderer.background(0), this._renderer.image(this._pipeline.texture, this._grid.offsetX, this._grid.offsetY, this._pipeline.texture.width, this._pipeline.texture.height)) : (this._renderer.clear(), this._renderer.image(this._canvasFramebuffer, this._grid.offsetX, this._grid.offsetY, this._canvasFramebuffer.width, this._canvasFramebuffer.height)), this._printDebug) {
3428
- let e = 0;
3429
- this._renderer.image(this._pipeline.characterFramebuffer, e, 0, this._pipeline.characterFramebuffer.width, this._pipeline.characterFramebuffer.height), e += this._pipeline.characterFramebuffer.width, this._renderer.image(this._pipeline.primaryColorFramebuffer, e, 0, this._pipeline.primaryColorFramebuffer.width, this._pipeline.primaryColorFramebuffer.height), e += this._pipeline.primaryColorFramebuffer.width, this._renderer.image(this._pipeline.secondaryColorFramebuffer, e, 0, this._pipeline.secondaryColorFramebuffer.width, this._pipeline.secondaryColorFramebuffer.height), e += this._pipeline.secondaryColorFramebuffer.width, this._renderer.image(this._pipeline.transformFramebuffer, e, 0, this._pipeline.transformFramebuffer.width, this._pipeline.transformFramebuffer.height), e += this._pipeline.transformFramebuffer.width, this._renderer.image(this._pipeline.rotationFramebuffer, e, 0, this._pipeline.rotationFramebuffer.width, this._pipeline.rotationFramebuffer.height);
3430
- }
3431
- }
3432
- resize() {
3433
- this.textmodeCanvas.resize(), this._canvasFramebuffer.resize(this.textmodeCanvas.width, this.textmodeCanvas.height), this._grid.resize(), this._pipeline.resize(), this._renderer.resetViewport(), this._mode !== "manual" && this.render();
3434
- }
3435
- /**
3436
- * Start automatic rendering
3437
- */
3438
- startAutoRendering() {
3439
- if (this._mode !== "auto" || !this._isLooping) return;
3440
- this.lastFrameTime = performance.now();
3441
- const e = (t) => {
3442
- if (!this._isLooping) {
3443
- this.animationFrameId = null;
3444
- return;
3445
- }
3446
- const r = t - this.lastFrameTime;
3447
- r >= this.frameInterval && (this.render(), this.lastFrameTime = t - r % this.frameInterval), this._isLooping && (this.animationFrameId = requestAnimationFrame(e));
3448
- };
3449
- this.animationFrameId = requestAnimationFrame(e);
3450
- }
3451
- /**
3452
- * Update FPS measurement - works for both auto and manual modes
3453
- * Uses a rolling average for smoother frame rate reporting
3454
- */
3455
- measureFrameRate() {
3456
- const e = performance.now();
3457
- if (this.lastRenderTime > 0) {
3458
- const t = e - this.lastRenderTime;
3459
- this.frameTimeHistory.push(t), this.frameTimeHistory.length > this.frameTimeHistorySize && this.frameTimeHistory.shift();
3460
- const r = this.frameTimeHistory.reduce((i, s) => i + s, 0) / this.frameTimeHistory.length;
3461
- this._frameRate = 1e3 / r;
3462
- }
3463
- this.lastRenderTime = e;
3464
- }
3465
- /**
3466
- * Stop automatic rendering
3467
- */
3468
- stopAutoRendering() {
3469
- this.animationFrameId && (cancelAnimationFrame(this.animationFrameId), this.animationFrameId = null);
3470
- }
3471
- /**
3472
- * Update the rendering mode.
3473
- *
3474
- * If called without arguments, returns the current mode.
3475
- *
3476
- * - `'manual'`: Requires manual [render](#render) calls
3477
- * - `'auto'`: Automatically renders using [requestAnimationFrame](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame)
3478
- *
3479
- * @param mode The new rendering mode to set.
3480
- *
3481
- * @example
3482
- * ```javascript
3483
- * // Fetch a canvas element to apply textmode rendering to
3484
- * const canvas = document.querySelector('canvas#myCanvas');
3485
- *
3486
- * // Create a Textmodifier instance
3487
- * const textmodifier = await textmode.create(canvas);
3488
- *
3489
- * // Update the rendering mode to 'manual'
3490
- * textmodifier.renderMode('manual');
3491
- *
3492
- * // Now you need to call textmodifier.render() manually in your animation loop
3493
- * ```
3494
- */
3495
- renderMode(e) {
3496
- this._mode !== e && (this.stopAutoRendering(), this._mode = e, e === "auto" && this._isLooping && this.startAutoRendering());
3497
- }
3498
- /**
3499
- * Set the maximum frame rate for auto rendering. If called without arguments, returns the current measured frame rate.
3500
- * @param fps The maximum frames per second for auto rendering.
3501
- *
3502
- * @example
3503
- * ```javascript
3504
- * // Fetch a canvas element to apply textmode rendering to
3505
- * const canvas = document.querySelector('canvas#myCanvas');
3506
- *
3507
- * // Create a Textmodifier instance
3508
- * const textmodifier = await textmode.create(canvas);
3509
- *
3510
- * // Set the maximum frame rate to 30 FPS
3511
- * textmodifier.frameRate(30);
3512
- * ```
3513
- */
3514
- frameRate(e) {
3515
- if (e === void 0)
3516
- return this._frameRate;
3517
- this._frameRateLimit = e, this.frameInterval = 1e3 / e, this._mode === "auto" && this._isLooping && (this.stopAutoRendering(), this.startAutoRendering());
3518
- }
3519
- /**
3520
- * Stop the automatic rendering loop while keeping the render mode as 'auto'.
3521
- *
3522
- * This method pauses the render loop without changing the render mode, allowing
3523
- * it to be resumed later with {@link loop}. This is useful for temporarily pausing
3524
- * animation while maintaining the ability to restart it.
3525
- *
3526
- * **Note:** This only affects rendering when in `'auto'` mode. In `'manual'` mode,
3527
- * this method has no effect since rendering is already controlled manually.
3528
- *
3529
- * @example
3530
- * ```javascript
3531
- * // Create a textmodifier instance in auto mode
3532
- * const textmodifier = await textmode.create(canvas);
3533
- *
3534
- * // The render loop is running automatically
3535
- * console.log(textmodifier.isLooping()); // true
3536
- *
3537
- * // Stop the automatic rendering loop
3538
- * textmodifier.noLoop();
3539
- * console.log(textmodifier.isLooping()); // false
3540
- *
3541
- * // Resume the automatic rendering loop
3542
- * textmodifier.loop();
3543
- * console.log(textmodifier.isLooping()); // true
3544
- * ```
3545
- */
3546
- noLoop() {
3547
- this._isLooping && (this._isLooping = !1, this.animationFrameId && (cancelAnimationFrame(this.animationFrameId), this.animationFrameId = null));
3548
- }
3549
- /**
3550
- * Resume the automatic rendering loop if it was stopped by {@link noLoop}.
3551
- *
3552
- * This method restarts the render loop when in `'auto'` mode. If the render mode
3553
- * is `'manual'`, the loop state will be set but automatic rendering will not start
3554
- * until the mode is changed back to `'auto'`.
3555
- *
3556
- * @example
3557
- * ```javascript
3558
- * // Create a textmodifier instance
3559
- * const textmodifier = await textmode.create(canvas);
3560
- *
3561
- * // Stop the loop
3562
- * textmodifier.noLoop();
3563
- *
3564
- * // Resume the loop
3565
- * textmodifier.loop();
3566
- *
3567
- * // You can also use this pattern for conditional animation
3568
- * if (someCondition) {
3569
- * textmodifier.loop();
3570
- * } else {
3571
- * textmodifier.noLoop();
3572
- * }
3573
- * ```
3574
- */
3575
- loop() {
3576
- this._isLooping || (this._isLooping = !0, this._mode === "auto" && this.startAutoRendering());
3577
- }
3578
- /**
3579
- * Execute the render function a specified number of times.
3580
- *
3581
- * This method is useful when the render loop has been stopped with {@link noLoop}
3582
- * or when in `'manual'` mode, allowing you to trigger rendering on demand.
3583
- * Similar to p5.js's `redraw()` function.
3584
- *
3585
- * @param n The number of times to execute the render function. Defaults to 1.
3586
- *
3587
- * @example
3588
- * ```javascript
3589
- * // Create a textmodifier instance
3590
- * const textmodifier = await textmode.create(canvas, { renderMode: 'manual' });
3591
- *
3592
- * // Set up drawing
3593
- * textmodifier.draw(() => {
3594
- * textmodifier.background(0);
3595
- * textmodifier.fill(255, 0, 0);
3596
- * textmodifier.rect(100, 100, 200, 150);
3597
- * });
3598
- *
3599
- * // Render once manually
3600
- * textmodifier.redraw();
3601
- *
3602
- * // Render 5 times
3603
- * textmodifier.redraw(5);
3604
- *
3605
- * // Also useful when loop is stopped
3606
- * textmodifier.noLoop();
3607
- * textmodifier.redraw(3); // Render 3 times despite loop being stopped
3608
- * ```
3609
- */
3610
- redraw(e = 1) {
3611
- if (_.validate(
3612
- typeof e == "number" && e > 0 && Number.isInteger(e),
3613
- "Redraw count must be a positive integer.",
3614
- { method: "redraw", providedValue: e }
3615
- ))
3616
- for (let t = 0; t < e; t++)
3617
- this.render();
3618
- }
3619
- /**
3620
- * Check whether the textmodifier is currently running the automatic render loop.
3621
- *
3622
- * Returns `true` when both the render mode is `'auto'` AND the loop is active.
3623
- * Returns `false` when in `'manual'` mode or when {@link noLoop} has been called.
3624
- *
3625
- * @returns True if the automatic render loop is currently active, false otherwise.
3626
- *
3627
- * @example
3628
- * ```javascript
3629
- * const textmodifier = await textmode.create(canvas);
3630
- *
3631
- * // Check loop status in different states
3632
- * console.log(textmodifier.isLooping()); // true (auto mode, looping)
3633
- *
3634
- * textmodifier.noLoop();
3635
- * console.log(textmodifier.isLooping()); // false (auto mode, not looping)
3636
- *
3637
- * textmodifier.renderMode('manual');
3638
- * console.log(textmodifier.isLooping()); // false (manual mode)
3639
- *
3640
- * textmodifier.renderMode('auto');
3641
- * console.log(textmodifier.isLooping()); // false (auto mode, but loop was stopped)
3642
- *
3643
- * textmodifier.loop();
3644
- * console.log(textmodifier.isLooping()); // true (auto mode, looping)
3645
- * ```
3646
- */
3647
- isLooping() {
3648
- return this._mode === "auto" && this._isLooping;
3649
- }
3650
- /**
3651
- * Set the font size used for rendering.
3652
- * @param size The font size to set.
3653
- *
3654
- * @example
3655
- * ```javascript
3656
- * // Fetch a canvas element to apply textmode rendering to
3657
- * const canvas = document.querySelector('canvas#myCanvas');
3658
- *
3659
- * // Create a Textmodifier instance
3660
- * const textmodifier = await textmode.create(canvas);
3661
- *
3662
- * // Set the font size to 24
3663
- * textmodifier.fontSize(24);
3664
- * ```
3665
- */
3666
- fontSize(e) {
3667
- _.validate(
3668
- typeof e == "number" && e > 0,
3669
- "Font size must be a positive number greater than 0.",
3670
- { method: "fontSize", providedValue: e }
3671
- ) && this._font.fontSize !== e && (this._font.setFontSize(e), this._grid.resizeCellPixelDimensions(this._font.maxGlyphDimensions.width, this._font.maxGlyphDimensions.height), this._pipeline.resize(), this._renderer.resetViewport());
3672
- }
3673
- /**
3674
- * Set a draw callback function that will be executed before each render.
3675
- * This method is primarily useful for standalone textmodifier instances.
3676
- * @param callback The function to call before each render
3677
- *
3678
- * @example
3679
- * ```javascript
3680
- * // Create a standalone textmodifier instance
3681
- * const t = await textmode.create({
3682
- * width: 800,
3683
- * height: 600,
3684
- * });
3685
- *
3686
- * // Set up draw callback
3687
- * t.draw(() => {
3688
- * // Set background color
3689
- * t.background(128);
3690
- *
3691
- * // Draw some content
3692
- * t.fill(255, 0, 0); // Set fill color to red
3693
- * t.rect(50, 50, 100, 100);
3694
- * });
3695
- * ```
3696
- */
3697
- draw(e) {
3698
- this._drawCallback = e;
3699
- }
3700
- /**
3701
- * Set a callback function that will be called when the window is resized.
3702
- * @param callback The function to call when the window is resized.
3703
- *
3704
- * @example
3705
- * ```javascript
3706
- * // Create a standalone textmodifier instance
3707
- * const t = await textmode.create({
3708
- * width: window.innerWidth,
3709
- * height: window.innerHeight,
3710
- * });
3711
- *
3712
- * // Draw callback to update content
3713
- * t.draw(() => {
3714
- * // Set background color
3715
- * t.background(128);
3716
- *
3717
- * // Draw some content
3718
- * t.fill(255, 0, 0); // Set fill color to red
3719
- * t.rect(50, 50, 100, 100);
3720
- * });
3721
- *
3722
- * // Set up window resize callback
3723
- * t.windowResized(() => {
3724
- * // Resize the canvas to match window size
3725
- * t.resizeCanvas(window.innerWidth, window.innerHeight);
3726
- * });
3727
- *
3728
- */
3729
- windowResized(e) {
3730
- this._resizedCallback = e;
3731
- }
3732
- /**
3733
- * Resize the `textmode.js` canvas.
3734
- * @param width The new width of the canvas.
3735
- * @param height The new height of the canvas.
3736
- */
3737
- resizeCanvas(e, t) {
3738
- this.textmodeCanvas.resize(e, t), this._canvasFramebuffer.resize(this.textmodeCanvas.width, this.textmodeCanvas.height), this._grid.resize(), this._pipeline.resize(), this._renderer.resetViewport(), this._mode !== "manual" && this.render();
3739
- }
3740
- /**
3741
- * @inheritDoc TextmodeConversionPipeline.get
3742
- *
3743
- * @example
3744
- * ```javascript
3745
- * // Fetch a canvas element to apply textmode rendering to
3746
- * const canvas = document.querySelector('canvas#myCanvas');
3747
- *
3748
- * // Create a Textmodifier instance
3749
- * const textmodifier = await textmode.create(canvas);
3750
- *
3751
- * // Get the pre-defined brightness converter from the pipeline
3752
- * const brightnessConverter = textmodifier.converter('brightness');
3753
- *
3754
- * // Update properties of the brightness converter
3755
- * brightnessConverter.invert(true);
3756
- * brightnessConverter.characters(" .,;:*");
3757
- * ```
3758
- */
3759
- converter(e) {
3760
- return this._pipeline.get(e);
3761
- }
3762
- /**
3763
- * Sets the fill color for subsequent rendering operations
3764
- * @param r Red component (0-255)
3765
- * @param g Green component (0-255, optional)
3766
- * @param b Blue component (0-255, optional)
3767
- * @param a Alpha component (0-255, optional)
3768
- *
3769
- * @example
3770
- * ```javascript
3771
- * const t = await textmode.create({
3772
- * width: 800,
3773
- * height: 600,
3774
- * })
3775
- *
3776
- * t.draw(() => {
3777
- * // Set the background color to black
3778
- * t.background(0);
3779
- *
3780
- * const centerX = t.width / 2;
3781
- * const centerY = t.height / 2;
3782
- * const radius = Math.min(t.width, t .height) / 3;
3783
- * const speed = 0.02; // Adjust speed of rotation
3784
- *
3785
- * const angle = t.frameCount * speed;
3786
- * const x = centerX + Math.cos(angle) * radius - 100;
3787
- * const y = centerY + Math.sin(angle) * radius - 50;
3788
- *
3789
- * // Set the fill color to white
3790
- * t.fill(255);
3791
- *
3792
- * // Draw a rectangle with the fill color
3793
- * t.rect(x, y, 200, 150);
3794
- * });
3795
- * ```
3796
- */
3797
- fill(e, t, r, i) {
3798
- this._renderer.fill(e, t, r, i);
3799
- }
3800
- /**
3801
- * Sets the stroke color for subsequent rendering operations
3802
- * @param r Red component (0-255)
3803
- * @param g Green component (0-255, optional)
3804
- * @param b Blue component (0-255, optional)
3805
- * @param a Alpha component (0-255, optional)
3806
- *
3807
- * @example
3808
- * ```javascript
3809
- * const t = await textmode.create({
3810
- * width: 800,
3811
- * height: 600,
3812
- * })
3813
- *
3814
- * t.draw(() => {
3815
- * // Set the background color to black
3816
- * t.background(0);
3817
- *
3818
- * // Set stroke color to red and stroke weight to 4 pixels
3819
- * t.stroke(255, 0, 0);
3820
- * t.strokeWeight(4);
3821
- *
3822
- * // Draw a rectangle with red stroke
3823
- * t.rect(100, 100, 200, 150);
3824
- *
3825
- * // Rectangle with both fill and stroke
3826
- * t.fill(0, 255, 0);
3827
- * t.stroke(0, 0, 255);
3828
- * t.strokeWeight(2);
3829
- * t.rect(350, 100, 200, 150);
3830
- * });
3831
- * ```
3832
- */
3833
- stroke(e, t, r, i) {
3834
- this._renderer.stroke(e, t, r, i);
3835
- }
3836
- /**
3837
- * Sets the stroke weight (thickness) for subsequent stroke operations
3838
- * @param weight The stroke thickness in pixels
3839
- *
3840
- * @example
3841
- * ```javascript
3842
- * const t = await textmode.create({
3843
- * width: 800,
3844
- * height: 600,
3845
- * })
3846
- *
3847
- * t.draw(() => {
3848
- * t.background(0);
3849
- *
3850
- * // Thin stroke
3851
- * t.stroke(255);
3852
- * t.strokeWeight(1);
3853
- * t.rect(50, 50, 100, 100);
3854
- *
3855
- * // Thick stroke
3856
- * t.strokeWeight(8);
3857
- * t.rect(200, 50, 100, 100);
3858
- * });
3859
- * ```
3860
- */
3861
- strokeWeight(e) {
3862
- this._renderer.strokeWeight(e);
3863
- }
3864
- /**
3865
- * Disables stroke rendering for subsequent operations
3866
- *
3867
- * @example
3868
- * ```javascript
3869
- * const t = await textmode.create({
3870
- * width: 800,
3871
- * height: 600,
3872
- * })
3873
- *
3874
- * t.draw(() => {
3875
- * t.background(0);
3876
- *
3877
- * // Rectangle with stroke
3878
- * t.fill(255, 0, 0);
3879
- * t.stroke(0, 255, 0);
3880
- * t.strokeWeight(4);
3881
- * t.rect(100, 100, 150, 100);
3882
- *
3883
- * // Rectangle without stroke (fill only)
3884
- * t.noStroke();
3885
- * t.rect(300, 100, 150, 100);
3886
- * });
3887
- * ```
3888
- */
3889
- noStroke() {
3890
- this._renderer.noStroke();
3891
- }
3892
- /**
3893
- * Disables fill rendering for subsequent operations
3894
- *
3895
- * @example
3896
- * ```javascript
3897
- * const t = await textmode.create({
3898
- * width: 800,
3899
- * height: 600,
3900
- * })
3901
- *
3902
- * t.draw(() => {
3903
- * t.background(0);
3904
- *
3905
- * // Rectangle with fill
3906
- * t.fill(255, 0, 0);
3907
- * t.stroke(0, 255, 0);
3908
- * t.strokeWeight(4);
3909
- * t.rect(100, 100, 150, 100);
3910
- *
3911
- * // Rectangle without fill (stroke only)
3912
- * t.noFill();
3913
- * t.rect(300, 100, 150, 100);
3914
- * });
3915
- * ```
3916
- */
3917
- noFill() {
3918
- this._renderer.noFill();
3919
- }
3920
- /**
3921
- * Sets the rotation angle for subsequent rendering operations
3922
- * @param degrees The rotation angle in degrees
3923
- *
3924
- * @example
3925
- * ```javascript
3926
- * const t = await textmode.create({
3927
- * width: 800,
3928
- * height: 600,
3929
- * })
3930
- *
3931
- * t.draw(() => {
3932
- * t.background(0);
3933
- *
3934
- * // Normal rectangle
3935
- * t.fill(255, 0, 0);
3936
- * t.rect(100, 100, 150, 100);
3937
- *
3938
- * // Rotated rectangle
3939
- * t.push(); // Save current state
3940
- * t.rotate(45); // Rotate 45 degrees
3941
- * t.fill(0, 255, 0);
3942
- * t.rect(300, 100, 150, 100);
3943
- * t.pop(); // Restore state (no rotation)
3944
- *
3945
- * // Back to normal (no rotation)
3946
- * t.fill(0, 0, 255);
3947
- * t.rect(500, 100, 150, 100);
3948
- * });
3949
- * ```
3950
- */
3951
- rotate(e) {
3952
- this._renderer.rotate(e);
3953
- }
3954
- /**
3955
- * Save the current rendering state (fill, stroke, etc.) to the state stack.
3956
- * Use with {@link pop} to isolate style changes within a block.
3957
- *
3958
- * @example
3959
- * ```javascript
3960
- * const t = await textmode.create({
3961
- * width: 800,
3962
- * height: 600,
3963
- * })
3964
- *
3965
- * t.draw(() => {
3966
- * t.background(0);
3967
- *
3968
- * // Set initial styles
3969
- * t.fill(255, 0, 0); // Red fill
3970
- * t.stroke(0, 255, 0); // Green stroke
3971
- * t.strokeWeight(4); // Thick stroke
3972
- *
3973
- * t.push(); // Save current state
3974
- *
3975
- * // Change styles temporarily
3976
- * t.fill(0, 0, 255); // Blue fill
3977
- * t.stroke(255, 255, 0); // Yellow stroke
3978
- * t.strokeWeight(2); // Thin stroke
3979
- * t.rect(100, 100, 150, 100);
3980
- *
3981
- * t.pop(); // Restore previous state
3982
- *
3983
- * // Back to red fill, green stroke, thick stroke
3984
- * t.rect(300, 100, 150, 100);
3985
- * });
3986
- * ```
3987
- */
3988
- push() {
3989
- this._renderer.push();
3990
- }
3991
- /**
3992
- * Restore the most recently saved rendering state from the state stack.
3993
- * Use with {@link push} to isolate style changes within a block.
3994
- *
3995
- * @example
3996
- * ```javascript
3997
- * const t = await textmode.create({
3998
- * width: 800,
3999
- * height: 600,
4000
- * })
4001
- *
4002
- * t.draw(() => {
4003
- * t.background(0);
4004
- *
4005
- * // Default styles
4006
- * t.fill(255); // White fill
4007
- * t.stroke(0); // Black stroke
4008
- * t.strokeWeight(1); // Thin stroke
4009
- *
4010
- * t.push(); // Save current state
4011
- *
4012
- * // Temporary style changes
4013
- * t.fill(255, 0, 0); // Red fill
4014
- * t.strokeWeight(8); // Thick stroke
4015
- * t.rect(50, 50, 100, 100);
4016
- *
4017
- * t.push(); // Save red style state
4018
- *
4019
- * t.fill(0, 255, 0); // Green fill
4020
- * t.noStroke(); // No stroke
4021
- * t.rect(200, 50, 100, 100);
4022
- *
4023
- * t.pop(); // Back to red fill, thick stroke
4024
- * t.rect(350, 50, 100, 100);
4025
- *
4026
- * t.pop(); // Back to white fill, black stroke, thin stroke
4027
- * t.rect(500, 50, 100, 100);
4028
- * });
4029
- * ```
4030
- */
4031
- pop() {
4032
- this._renderer.pop();
4033
- }
4034
- /**
4035
- * Draw a rectangle with the current shader or fill color.
4036
- * @param x X-coordinate of the rectangle
4037
- * @param y Y-coordinate of the rectangle
4038
- * @param width Width of the rectangle
4039
- * @param height Height of the rectangle
4040
- *
4041
- * @example
4042
- * ```javascript
4043
- * const t = await textmode.create({
4044
- * width: 800,
4045
- * height: 600,
4046
- * })
4047
- *
4048
- * t.draw(() => {
4049
- * // Set the background color to black
4050
- * t.background(0);
4051
- *
4052
- * const centerX = t.width / 2;
4053
- * const centerY = t.height / 2;
4054
- * const radius = Math.min(t.width, t .height) / 3;
4055
- * const speed = 0.02; // Adjust speed of rotation
4056
- *
4057
- * const angle = t.frameCount * speed;
4058
- * const x = centerX + Math.cos(angle) * radius - 100;
4059
- * const y = centerY + Math.sin(angle) * radius - 50;
4060
- *
4061
- * // Set the fill color to white
4062
- * t.fill(255);
4063
- *
4064
- * // Draw a rectangle with the fill color
4065
- * t.rect(x, y, 200, 150);
4066
- * });
4067
- * ```
4068
- */
4069
- rect(e, t, r = 1, i = 1) {
4070
- this._renderer.rect(e, t, r, i);
4071
- }
4072
- /**
4073
- * Draw a line from point (x1, y1) to point (x2, y2) with the current stroke settings.
4074
- * Lines respect stroke color, stroke weight, and rotation, but ignore fill properties.
4075
- * @param x1 X-coordinate of the line start point
4076
- * @param y1 Y-coordinate of the line start point
4077
- * @param x2 X-coordinate of the line end point
4078
- * @param y2 Y-coordinate of the line end point
4079
- *
4080
- * @example
4081
- * ```javascript
4082
- * const t = await textmode.create({
4083
- * width: 800,
4084
- * height: 600,
4085
- * })
4086
- *
4087
- * t.draw(() => {
4088
- * // Set the background color to black
4089
- * t.background(0);
4090
- *
4091
- * // Draw a simple line
4092
- * t.stroke(255, 0, 0); // Red stroke
4093
- * t.strokeWeight(2); // 2px thick
4094
- * t.line(100, 100, 300, 200);
4095
- *
4096
- * // Draw an animated rotating line
4097
- * t.push();
4098
- * t.stroke(0, 255, 0); // Green stroke
4099
- * t.strokeWeight(4); // 4px thick
4100
- * t.rotate(t.frameCount * 2); // Rotate based on frame count
4101
- * t.line(400, 300, 600, 300);
4102
- * t.pop();
4103
- *
4104
- * // Draw a thick yellow line
4105
- * t.stroke(255, 255, 0); // Yellow stroke
4106
- * t.strokeWeight(8); // 8px thick
4107
- * t.line(200, 400, 400, 500);
4108
- *
4109
- * // Line with no stroke won't be visible
4110
- * t.noStroke();
4111
- * t.line(500, 100, 700, 200); // This won't render
4112
- * });
4113
- * ```
4114
- */
4115
- line(e, t, r, i) {
4116
- this._renderer.line(e, t, r, i);
4117
- }
4118
- /**
4119
- * Set the background color for the canvas.
4120
- * @param r Red component (0-255)
4121
- * @param g Green component (0-255, optional)
4122
- * @param b Blue component (0-255, optional)
4123
- * @param a Alpha component (0-255, optional)
4124
- *
4125
- * @example
4126
- * ```javascript
4127
- * const t = await textmode.create({
4128
- * width: 800,
4129
- * height: 600,
4130
- * })
4131
- *
4132
- * t.draw(() => {
4133
- * // Set the background color to black
4134
- * t.background(0);
4135
- *
4136
- * const centerX = t.width / 2;
4137
- * const centerY = t.height / 2;
4138
- * const radius = Math.min(t.width, t .height) / 3;
4139
- * const speed = 0.02; // Adjust speed of rotation
4140
- *
4141
- * const angle = t.frameCount * speed;
4142
- * const x = centerX + Math.cos(angle) * radius - 100;
4143
- * const y = centerY + Math.sin(angle) * radius - 50;
4144
- *
4145
- * // Set the fill color to white
4146
- * t.fill(255);
4147
- *
4148
- * // Draw a rectangle with the fill color
4149
- * t.rect(x, y, 200, 150);
4150
- * });
4151
- * ```
4152
- */
4153
- background(e, t = e, r = e, i = 255) {
4154
- this._renderer.background(e, t, r, i);
4155
- }
4156
- /**
4157
- * Create a shader program from vertex and fragment source code.
4158
- * @param vertexSource The GLSL source code for the vertex shader.
4159
- * @param fragmentSource The GLSL source code for the fragment shader.
4160
- * @returns The created shader program for use in `textmode.js`.
4161
- */
4162
- createShader(e, t) {
4163
- return this._renderer.createShader(e, t);
4164
- }
4165
- /**
4166
- * Create a filter shader program from a fragment source code.
4167
- * @param fragmentSource The GLSL source code for the fragment shader.
4168
- * @returns The created filter shader program for use in `textmode.js`.
4169
- */
4170
- createFilterShader(e) {
4171
- return this._renderer.createFilterShader(e);
4172
- }
4173
- /**
4174
- * Set the current shader for rendering.
4175
- * @param shader The shader program to use for rendering.
4176
- */
4177
- shader(e) {
4178
- this._renderer.shader(e);
4179
- }
4180
- /**
4181
- * Set a uniform variable for the current shader.
4182
- * @param name The name of the uniform variable to set.
4183
- * @param value The value to set for the uniform variable.
4184
- */
4185
- setUniform(e, t) {
4186
- this._renderer.setUniform(e, t);
4187
- }
4188
- /**
4189
- * Completely destroy this Textmodifier instance and free all associated resources.
4190
- *
4191
- * This method performs comprehensive cleanup of:
4192
- * - WebGL resources (framebuffers, textures, shaders)
4193
- * - Animation frames and timers
4194
- * - Event listeners
4195
- * - DOM elements
4196
- * - Font resources
4197
- *
4198
- * After calling this method, the instance should not be used and will be eligible for garbage collection.
4199
- * This method is idempotent and safe to call multiple times.
4200
- *
4201
- * @example
4202
- * ```javascript
4203
- * // Create a textmodifier instance
4204
- * const textmodifier = await textmode.create(canvas);
4205
- *
4206
- * // Use it for rendering
4207
- * textmodifier.render();
4208
- *
4209
- * // When done, completely clean up
4210
- * textmodifier.destroy();
4211
- *
4212
- * // Instance is now safely disposed and ready for garbage collection
4213
- * ```
4214
- */
4215
- destroy() {
4216
- this._isDisposed = !0, this.stopAutoRendering(), this._windowResizeListener && (window.removeEventListener("resize", this._windowResizeListener), this._windowResizeListener = null), this.resizeObserver && (this.resizeObserver.disconnect(), this.resizeObserver = null), this._pipeline.dispose(), this._font.dispose(), this._grid.dispose(), this._canvasFramebuffer.dispose(), this._renderer.dispose(), this.textmodeCanvas.dispose(), this.captureSource = null, this.textmodeCanvas = null, this._renderer = null, this._canvasFramebuffer = null, this._font = null, this._grid = null, this._pipeline = null, this.resizeObserver = null, this._drawCallback = () => {
4217
- }, this._resizedCallback = () => {
4218
- }, this.animationFrameId = null, this.lastFrameTime = 0, this.lastRenderTime = 0, this._frameCount = 0, this._frameRate = 0, this.frameTimeHistory = [], this._isLooping = !1;
4219
- }
4220
- debug(e) {
4221
- this._printDebug = e;
4222
- }
4223
- /** Get the current grid object used for rendering. */
4224
- get grid() {
4225
- return this._grid;
4226
- }
4227
- /** Get the current font object used for rendering. */
4228
- get font() {
4229
- return this._font;
4230
- }
4231
- /** Get the current rendering mode.*/
4232
- get mode() {
4233
- return this._mode;
4234
- }
4235
- /** Get the current textmode conversion pipeline. */
4236
- get pipeline() {
4237
- return this._pipeline;
4238
- }
4239
- /** Get the current frame count. */
4240
- get frameCount() {
4241
- return this._frameCount;
4242
- }
4243
- set frameCount(e) {
4244
- this._frameCount = e;
4245
- }
4246
- /** Get the width of the canvas. */
4247
- get width() {
4248
- return this.textmodeCanvas.width;
4249
- }
4250
- /** Get the height of the canvas. */
4251
- get height() {
4252
- return this.textmodeCanvas.height;
4253
- }
4254
- /** Check if the instance has been disposed/destroyed. */
4255
- get isDisposed() {
4256
- return this._isDisposed;
4257
- }
4258
- }
4259
- class O {
4260
- /**
4261
- * Create a {@link Textmodifier} instance for textmode rendering.
4262
- *
4263
- * @param sourceOrOptions - Either an HTML canvas/video element for capturing content, or options for standalone mode.
4264
- * @param opts - Optional configuration options *(only used when first parameter is a canvas/video element)*.
4265
- * @returns A Promise that resolves to a Textmodifier instance.
4266
- *
4267
- * @example
4268
- * ```javascript
4269
- * // Create a Textmodifier for an existing canvas
4270
- * const canvas = document.querySelector('canvas#myCanvas');
4271
- * const textmodifier = await textmode.create(canvas);
4272
- *
4273
- * ////////
4274
- *
4275
- * // Create a Textmodifier for a video element
4276
- * const video = document.querySelector('video#myVideo');
4277
- * const textmodifier = await textmode.create(video);
4278
- *
4279
- * ////////
4280
- *
4281
- * // Create a standalone Textmodifier
4282
- * const t = await textmode.create({ width: 800, height: 600 });
4283
- *
4284
- * // Set up a draw loop for standalone usage
4285
- * t.draw(() => {
4286
- * t.background(0);
4287
- *
4288
- * const centerX = t.width / 2;
4289
- * const centerY = t.height / 2;
4290
- * const radius = Math.min(t.width, t .height) / 3;
4291
- * const speed = 0.02; // Adjust speed of rotation
4292
- *
4293
- * const angle = t.frameCount * speed;
4294
- * const x = centerX + Math.cos(angle) * radius - 100;
4295
- * const y = centerY + Math.sin(angle) * radius - 50;
4296
- *
4297
- * // Set the fill color to white
4298
- * t.fill(255);
4299
- *
4300
- * // Draw a rectangle with the fill color
4301
- * t.rect(x, y, 200, 150);
4302
- * });
4303
- *
4304
- * ////////
4305
- *
4306
- * // Resource management example
4307
- * const textmodifier = await textmode.create(canvas);
4308
- *
4309
- * // Use the textmodifier...
4310
- * textmodifier.render();
4311
- *
4312
- * // When done, completely clean up all resources
4313
- * textmodifier.destroy();
4314
- *
4315
- * // The instance is now safely disposed and ready for garbage collection
4316
- * console.log(textmodifier.isDestroyed); // true
4317
- * ```
4318
- */
4319
- static async create(e, t = {}) {
4320
- if (e instanceof HTMLCanvasElement || e instanceof HTMLVideoElement)
4321
- return k.create(e, t);
4322
- {
4323
- const r = e || {};
4324
- return k.create(null, r);
4325
- }
4326
- }
4327
- /**
4328
- * Set the global error handling level for the library. This applies to all `Textmodifier` instances.
4329
- * @param level The error handling level to set.
4330
- *
4331
- * @example
4332
- * ```javascript
4333
- * // Set error level to WARNING
4334
- * textmode.setErrorLevel(TextmodeErrorLevel.WARNING);
4335
- * ```
4336
- */
4337
- static setErrorLevel(e) {
4338
- _.setGlobalLevel(e);
4339
- }
4340
- /**
4341
- * Returns the current version of the `textmode.js` library.
4342
- *
4343
- * @example
4344
- * ```javascript
4345
- * console.log(textmode.version); // "1.0.0"
4346
- * ```
4347
- */
4348
- static get version() {
4349
- return "0.1.6-beta.4";
4350
- }
4351
- constructor() {
4352
- throw new Error("Textmode is a static class and cannot be instantiated.");
4353
- }
4354
- }
4355
- const Be = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
4356
- __proto__: null
4357
- }, Symbol.toStringTag, { value: "Module" })), ke = O.create, Ve = O.setErrorLevel, ze = O.version;
4358
- export {
4359
- fe as TextmodeCanvas,
4360
- Fe as TextmodeConversionPipeline,
4361
- re as TextmodeErrorLevel,
4362
- ue as TextmodeFont,
4363
- de as TextmodeGrid,
4364
- k as Textmodifier,
4365
- Pe as converters,
4366
- ke as create,
4367
- O as default,
4368
- Be as export,
4369
- Ve as setErrorLevel,
4370
- O as textmode,
4371
- ze as version
4372
- };