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.
- package/dist/textmode.js +26 -0
- package/dist/textmode.min.js +25 -0
- package/dist/types/rendering/index.d.ts +0 -5
- package/dist/types/rendering/webgl/Framebuffer.d.ts +2 -9
- package/dist/types/rendering/webgl/Renderer.d.ts +2 -12
- package/dist/types/rendering/webgl/Shader.d.ts +5 -12
- package/dist/types/rendering/webgl/StateCache.d.ts +0 -2
- package/dist/types/rendering/webgl/geometries/BaseGeometry.d.ts +40 -0
- package/dist/types/rendering/webgl/geometries/Line.d.ts +6 -26
- package/dist/types/rendering/webgl/geometries/Rectangle.d.ts +8 -18
- package/dist/types/rendering/webgl/geometries/index.d.ts +3 -0
- package/dist/types/textmode/Textmodifier.d.ts +27 -589
- package/dist/types/textmode/mixins/ExportMixin.d.ts +44 -0
- package/dist/types/textmode/mixins/FontMixin.d.ts +49 -0
- package/dist/types/textmode/mixins/MixinBase.d.ts +39 -0
- package/dist/types/textmode/mixins/RenderingMixin.d.ts +106 -0
- package/dist/types/textmode/mixins/index.d.ts +4 -0
- package/package.json +4 -16
- package/dist/textmode.esm.js +0 -4374
- package/dist/textmode.esm.min.js +0 -4372
- package/dist/textmode.umd.js +0 -26
- package/dist/textmode.umd.min.js +0 -25
- package/dist/types/rendering/webgl/shapes/LineShape.d.ts +0 -20
- package/dist/types/rendering/webgl/shapes/RectangleShape.d.ts +0 -20
- package/dist/types/rendering/webgl/shapes/Shape.d.ts +0 -21
package/dist/textmode.esm.min.js
DELETED
|
@@ -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
|
-
};
|