UNPKG

textmode.js

Version:

Apply real-time ASCII conversion to any HTML canvas.

1,365 lines (1,363 loc) 151 kB
var se = Object.defineProperty; var ae = (h, e, t) => e in h ? se(h, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : h[e] = t; var o = (h, e, t) => ae(h, typeof e != "symbol" ? e + "" : e, t); class F extends Error { constructor(t, r, i = {}) { const s = F.createFormattedMessage(t, i); super(s); o(this, "originalError"); o(this, "context"); this.name = "TextmodeError", this.originalError = r, this.context = i; } /** * Create a formatted error message that includes context */ static createFormattedMessage(t, r) { let i = t; if (r && Object.keys(r).length > 0) { i += ` 📋 Context:`; for (const [s, a] of Object.entries(r)) { const n = F.formatValue(a); i += ` - ${s}: ${n}`; } } return i += ` `, i += "↓".repeat(24) + ` `, i; } /** * Format values for better display in error messages */ static formatValue(t) { if (t === null) return "null"; if (t === void 0) return "undefined"; if (typeof t == "string") return `"${t}"`; if (typeof t == "number" || typeof t == "boolean") return String(t); if (Array.isArray(t)) return t.length === 0 ? "[]" : t.length <= 5 ? `[${t.map((r) => F.formatValue(r)).join(", ")}]` : `[${t.slice(0, 3).map((r) => F.formatValue(r)).join(", ")}, ... +${t.length - 3} more]`; if (typeof t == "object") { const r = Object.keys(t); return r.length === 0 ? "{}" : r.length <= 3 ? `{ ${r.map((a) => `${a}: ${F.formatValue(t[a])}`).join(", ")} }` : `{ ${r.slice(0, 2).map((s) => `${s}: ${F.formatValue(t[s])}`).join(", ")}, ... +${r.length - 2} more }`; } return String(t); } } var ne = /* @__PURE__ */ ((h) => (h[h.SILENT = 0] = "SILENT", h[h.WARNING = 1] = "WARNING", h[h.ERROR = 2] = "ERROR", h[h.THROW = 3] = "THROW", h))(ne || {}); const E = class E { constructor() { o(this, "_options", { globalLevel: 3 /* THROW */ }); } static getInstance() { return E._instance || (E._instance = new E()), E._instance; } /** * Handle an error based on the configured settings * @returns true if execution should continue, false if error was handled */ _handle(e, t, r) { const i = "[textmode.js]"; switch (this._options.globalLevel) { case 0: return !1; // Validation failed, handled silently case 1: return console.group( `%c${i} Oops! (╯°□°)╯︵ Something went wrong in your code.`, "color: #f44336; font-weight: bold; background: #ffebee; padding: 2px 6px; border-radius: 3px;" ), console.warn(F.createFormattedMessage(e, t)), console.groupEnd(), !1; case 2: return console.group( `%c${i} Oops! (╯°□°)╯︵ Something went wrong in your code.`, "color: #f44336; font-weight: bold; background: #ffebee; padding: 2px 6px; border-radius: 3px;" ), console.error(F.createFormattedMessage(e, t)), console.groupEnd(), !1; case 3: default: const s = new F(e, r, t); throw console.group( `%c${i} Oops! (╯°□°)╯︵ Something went wrong in your code.`, "color: #d32f2f; font-weight: bold; background: #ffcdd2; padding: 2px 6px; border-radius: 3px;" ), s; } } /** * Validate a condition and handle errors if validation fails * @param condition The condition to validate * @param message Error message if validation fails * @param context Additional context for debugging * @returns true if validation passed, false if validation failed and was handled */ validate(e, t, r) { return e ? !0 : (this._handle(t, r), !1); } /** * Set global error level */ setGlobalLevel(e) { this._options.globalLevel = e; } }; o(E, "_instance", null); let z = E; const x = z.getInstance(); class oe { constructor(e, t, r = t, i = {}) { o(this, "gl"); o(this, "_framebuffer"); o(this, "_texture"); o(this, "_width"); o(this, "_height"); o(this, "options"); o(this, "previousState", null); o(this, "_pixels", null); this.gl = e, this._width = t, this._height = r, this.options = { filter: "nearest", wrap: "clamp", format: "rgba", type: "unsigned_byte", ...i }, this._texture = this.createTexture(), this._framebuffer = e.createFramebuffer(), this.attachTexture(); } createTexture() { const { gl: e } = this, t = e.createTexture(); e.bindTexture(e.TEXTURE_2D, t); const r = this.options.filter === "linear" ? e.LINEAR : e.NEAREST, i = this.options.wrap === "repeat" ? e.REPEAT : e.CLAMP_TO_EDGE; 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; } updateTextureSize() { const { gl: e } = this, t = e.RGBA, r = e.RGBA, i = this.options.type === "float" ? e.FLOAT : e.UNSIGNED_BYTE; e.texImage2D(e.TEXTURE_2D, 0, t, this._width, this._height, 0, r, i, null); } attachTexture() { const { gl: e } = this; e.bindFramebuffer(e.FRAMEBUFFER, this._framebuffer), e.framebufferTexture2D(e.FRAMEBUFFER, e.COLOR_ATTACHMENT0, e.TEXTURE_2D, this._texture, 0), e.bindFramebuffer(e.FRAMEBUFFER, null); } /** * Update the framebuffer texture with canvas or video content */ update(e) { const { gl: t } = this; e instanceof HTMLVideoElement && e.readyState < 2 || (t.bindTexture(t.TEXTURE_2D, this._texture), t.texImage2D(t.TEXTURE_2D, 0, t.RGBA, t.RGBA, t.UNSIGNED_BYTE, e), t.bindTexture(t.TEXTURE_2D, null)); } /** * Update the framebuffer texture with pixel data */ updatePixels(e, t, r) { const { gl: i } = this; 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); } /** * Resize the framebuffer */ resize(e, t) { const { gl: r } = this; this._width = e, this._height = t, r.bindTexture(r.TEXTURE_2D, this._texture), this.updateTextureSize(), r.bindTexture(r.TEXTURE_2D, null); } /** * Begin rendering to this framebuffer */ begin() { const { gl: e } = this; this.previousState = { framebuffer: e.getParameter(e.FRAMEBUFFER_BINDING), viewport: e.getParameter(e.VIEWPORT) }, e.bindFramebuffer(e.FRAMEBUFFER, this._framebuffer), e.viewport(0, 0, this._width, this._height); } /** * End rendering to this framebuffer and restore previous state */ end() { if (!this.previousState) return; const { gl: e } = this; e.bindFramebuffer(e.FRAMEBUFFER, this.previousState.framebuffer), e.viewport(...this.previousState.viewport), this.previousState = null; } /** * Load pixel data from the framebuffer into the pixels array */ loadPixels() { const { gl: e } = this; this._pixels || (this._pixels = new Uint8Array(this._width * this._height * 4)); const t = e.getParameter(e.FRAMEBUFFER_BINDING); 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); } get(e, t, r, i) { const { gl: s } = this; if (e === void 0 && t === void 0) { const a = new Uint8Array(this._width * this._height * 4), n = s.getParameter(s.FRAMEBUFFER_BINDING); 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; } else if (r === void 0 && i === void 0) { (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))); const a = new Uint8Array(4), n = s.getParameter(s.FRAMEBUFFER_BINDING); 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]]; } else { 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)); const a = new Uint8Array(r * i * 4), n = s.getParameter(s.FRAMEBUFFER_BINDING); 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; } } get framebuffer() { return this._framebuffer; } get texture() { return this._texture; } get width() { return this._width; } get height() { return this._height; } get pixels() { return this._pixels; } } class Q { constructor(e, t, r) { o(this, "gl"); o(this, "x"); o(this, "y"); this.gl = e, this.x = t, this.y = r; } } class M { constructor(e, t, r, i, s) { /** The WebGL rendering context */ o(this, "gl"); /** The vertex buffer containing position and texture coordinates */ o(this, "vertexBuffer"); /** The number of vertices in this geometry (always 6 for two triangles) */ o(this, "vertexCount", 6); /** Bytes per vertex: depends on position format (vec2 vs vec3) */ o(this, "bytesPerVertex"); this.gl = e, this.bytesPerVertex = 16; const a = e.getParameter(e.VIEWPORT), n = a[2], l = a[3], c = e.getParameter(e.FRAMEBUFFER_BINDING) !== null, u = t / n * 2 - 1, d = (t + i) / n * 2 - 1; let f, g; c ? (f = r / l * 2 - 1, g = (r + s) / l * 2 - 1) : (f = 1 - r / l * 2, g = 1 - (r + s) / l * 2); let _, p, C, b; _ = u, C = d, p = f, b = g; const v = this.generateVertices(_, p, C, b); this.vertexBuffer = e.createBuffer(), e.bindBuffer(e.ARRAY_BUFFER, this.vertexBuffer), e.bufferData(e.ARRAY_BUFFER, v, e.STATIC_DRAW); } /** * Generate vertex data for the rectangle with texture coordinates * @private */ generateVertices(e, t, r, i) { return new Float32Array([ e, i, 0, 1, // bottom-left r, i, 1, 1, // bottom-right e, t, 0, 0, // top-left e, t, 0, 0, // top-left r, i, 1, 1, // bottom-right r, t, 1, 0 // top-right ]); } /** * Render the rectangle using position and texture coordinate attributes */ render() { this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer); const e = this.gl.getParameter(this.gl.CURRENT_PROGRAM); let t = this.gl.getAttribLocation(e, "a_position"), r = this.gl.getAttribLocation(e, "a_texCoord"); this.gl.enableVertexAttribArray(t), this.gl.vertexAttribPointer(t, 2, this.gl.FLOAT, !1, this.bytesPerVertex, 0), this.gl.enableVertexAttribArray(r), this.gl.vertexAttribPointer(r, 2, this.gl.FLOAT, !1, this.bytesPerVertex, 8), this.gl.drawArrays(this.gl.TRIANGLES, 0, this.vertexCount), this.gl.disableVertexAttribArray(t), this.gl.disableVertexAttribArray(r); } } class he extends Q { constructor(t, r, i, s, a) { super(t, r, i); o(this, "width"); o(this, "height"); this.width = s, this.height = a; } /** * Render the filled rectangle using the existing Rectangle geometry. */ renderFill() { new M(this.gl, this.x, this.y, this.width, this.height).render(); } /** * Render the stroke rectangle as four separate Rectangle instances for each edge. * This approach ensures clean corners with proper overlap and leverages existing geometry. * @param weight The stroke thickness in pixels */ renderStroke(t) { if (t <= 0) return; const r = new M(this.gl, this.x, this.y, this.width, t), i = new M(this.gl, this.x + this.width - t, this.y, t, this.height), s = new M(this.gl, this.x, this.y + this.height - t, this.width, t), a = new M(this.gl, this.x, this.y, t, this.height); r.render(), i.render(), s.render(), a.render(); } } class le { constructor(e, t, r, i, s, a) { /** The WebGL rendering context */ o(this, "gl"); /** The vertex buffer containing position and texture coordinates */ o(this, "vertexBuffer"); /** The number of vertices in this geometry (always 6 for two triangles) */ o(this, "vertexCount", 6); /** Bytes per vertex: vec2+vec2 = 16 bytes */ o(this, "bytesPerVertex"); this.gl = e, this.bytesPerVertex = 16; const n = e.getParameter(e.VIEWPORT), l = n[2], c = n[3], u = e.getParameter(e.FRAMEBUFFER_BINDING) !== null, d = i - t, f = s - r, g = Math.sqrt(d * d + f * f); if (g === 0) { const ie = this.generateVertices(0, 0, 0, 0); this.vertexBuffer = e.createBuffer(), e.bindBuffer(e.ARRAY_BUFFER, this.vertexBuffer), e.bufferData(e.ARRAY_BUFFER, ie, e.STATIC_DRAW); return; } const _ = d / g, C = -(f / g), b = _, v = a / 2, w = t + C * v, A = r + b * v, R = t - C * v, y = r - b * v, P = i + C * v, D = s + b * v, Z = i - C * v, W = s - b * v, J = w / l * 2 - 1, K = R / l * 2 - 1, ee = P / l * 2 - 1, te = Z / l * 2 - 1; let B, G, V, k; u ? (B = A / c * 2 - 1, G = y / c * 2 - 1, V = D / c * 2 - 1, k = W / c * 2 - 1) : (B = 1 - A / c * 2, G = 1 - y / c * 2, V = 1 - D / c * 2, k = 1 - W / c * 2); const re = this.generateLineVertices( J, B, K, G, ee, V, te, k ); this.vertexBuffer = e.createBuffer(), e.bindBuffer(e.ARRAY_BUFFER, this.vertexBuffer), e.bufferData(e.ARRAY_BUFFER, re, e.STATIC_DRAW); } /** * Generate vertex data for a rectangle representing the line with texture coordinates * @private */ generateVertices(e, t, r, i) { return new Float32Array([ e, i, 0, 1, // bottom-left r, i, 1, 1, // bottom-right e, t, 0, 0, // top-left e, t, 0, 0, // top-left r, i, 1, 1, // bottom-right r, t, 1, 0 // top-right ]); } /** * Generate vertex data for the line rectangle with texture coordinates * Uses the four corners calculated based on line direction and thickness * @private */ generateLineVertices(e, t, r, i, s, a, n, l) { return new Float32Array([ e, t, 0, 0, // corner1 (start + perpendicular) r, i, 0, 1, // corner2 (start - perpendicular) s, a, 1, 0, // corner3 (end + perpendicular) r, i, 0, 1, // corner2 (start - perpendicular) n, l, 1, 1, // corner4 (end - perpendicular) s, a, 1, 0 // corner3 (end + perpendicular) ]); } /** * Render the line using position and texture coordinate attributes */ render() { this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer); const e = this.gl.getParameter(this.gl.CURRENT_PROGRAM); let t = this.gl.getAttribLocation(e, "a_position"), r = this.gl.getAttribLocation(e, "a_texCoord"); this.gl.enableVertexAttribArray(t), this.gl.vertexAttribPointer(t, 2, this.gl.FLOAT, !1, this.bytesPerVertex, 0), this.gl.enableVertexAttribArray(r), this.gl.vertexAttribPointer(r, 2, this.gl.FLOAT, !1, this.bytesPerVertex, 8), this.gl.drawArrays(this.gl.TRIANGLES, 0, this.vertexCount), this.gl.disableVertexAttribArray(t), this.gl.disableVertexAttribArray(r); } } class ce extends Q { constructor(t, r, i, s, a) { super(t, r, i); o(this, "x2"); o(this, "y2"); this.x2 = s, this.y2 = a; } /** * Lines don't support fill rendering - this method does nothing. * Lines are rendered only with stroke properties. */ renderFill() { } /** * Render the line with the specified stroke weight. * @param weight The stroke thickness in pixels */ renderStroke(t) { if (t <= 0) return; new le(this.gl, this.x, this.y, this.x2, this.y2, t).render(); } } class T { constructor(e, t, r) { o(this, "gl"); o(this, "program"); o(this, "uniformLocations", /* @__PURE__ */ new Map()); o(this, "attributeLocations", /* @__PURE__ */ new Map()); o(this, "textureUnitCounter", 0); this.gl = e, this.program = this.createProgram(t, r), this.cacheLocations(); } createProgram(e, t) { const r = this.createShader(this.gl.VERTEX_SHADER, e), i = this.createShader(this.gl.FRAGMENT_SHADER, t), s = this.gl.createProgram(); if (this.gl.attachShader(s, r), this.gl.attachShader(s, i), this.gl.linkProgram(s), !this.gl.getProgramParameter(s, this.gl.LINK_STATUS)) { const a = this.gl.getProgramInfoLog(s); throw new Error(`Shader program link error: ${a}`); } return this.gl.deleteShader(r), this.gl.deleteShader(i), s; } createShader(e, t) { const r = this.gl.createShader(e); if (this.gl.shaderSource(r, t), this.gl.compileShader(r), !this.gl.getShaderParameter(r, this.gl.COMPILE_STATUS)) { const i = this.gl.getShaderInfoLog(r); throw this.gl.deleteShader(r), new Error(`Shader compilation error: ${i}`); } return r; } cacheLocations() { const e = this.gl.getProgramParameter(this.program, this.gl.ACTIVE_UNIFORMS); for (let r = 0; r < e; r++) { const i = this.gl.getActiveUniform(this.program, r); if (i) { const s = this.gl.getUniformLocation(this.program, i.name); s && this.uniformLocations.set(i.name, s); } } const t = this.gl.getProgramParameter(this.program, this.gl.ACTIVE_ATTRIBUTES); for (let r = 0; r < t; r++) { const i = this.gl.getActiveAttrib(this.program, r); if (i) { const s = this.gl.getAttribLocation(this.program, i.name); this.attributeLocations.set(i.name, s); } } } /** * Use this shader program */ use() { this.gl.useProgram(this.program), this.resetTextureUnits(); } /** * Set a single uniform value with automatic texture unit management */ setUniform(e, t) { const r = this.uniformLocations.get(e); if (!r) return; const i = this.getUniformInfo(e); if (typeof t == "number") i && i.type === this.gl.INT ? this.gl.uniform1i(r, Math.floor(t)) : this.gl.uniform1f(r, t); else if (typeof t == "boolean") this.gl.uniform1i(r, t ? 1 : 0); else if (Array.isArray(t)) if (i && (i.type === this.gl.INT_VEC2 || i.type === this.gl.INT_VEC3 || i.type === this.gl.INT_VEC4)) { const s = t.map((a) => Math.floor(a)); switch (s.length) { case 2: this.gl.uniform2iv(r, s); break; case 3: this.gl.uniform3iv(r, s); break; case 4: this.gl.uniform4iv(r, s); break; default: console.warn(`Unsupported array length ${s.length} for uniform '${e}'`); } } else switch (t.length) { case 2: this.gl.uniform2f(r, t[0], t[1]); break; case 3: this.gl.uniform3f(r, t[0], t[1], t[2]); break; case 4: this.gl.uniform4f(r, t[0], t[1], t[2], t[3]); break; default: console.warn(`Unsupported array length ${t.length} for uniform '${e}'`); } else if (t instanceof WebGLTexture) { const s = this.getNextTextureUnit(); this.gl.uniform1i(r, s), this.gl.activeTexture(this.gl.TEXTURE0 + s), this.gl.bindTexture(this.gl.TEXTURE_2D, t); } else if (t && typeof t == "object" && "texture" in t) { const s = this.getNextTextureUnit(); this.gl.uniform1i(r, s), this.gl.activeTexture(this.gl.TEXTURE0 + s), this.gl.bindTexture(this.gl.TEXTURE_2D, t.texture); } else console.warn(`Unsupported uniform type for '${e}':`, typeof t); } /** * Get uniform info to determine the correct WebGL type */ getUniformInfo(e) { const t = this.gl.getProgramParameter(this.program, this.gl.ACTIVE_UNIFORMS); for (let r = 0; r < t; r++) { const i = this.gl.getActiveUniform(this.program, r); if (i && i.name === e) return i; } return null; } getNextTextureUnit() { return this.textureUnitCounter++; } /** * Check if this shader has a specific uniform */ hasUniform(e) { return this.uniformLocations.has(e); } /** * Check if this shader has a specific attribute */ hasAttribute(e) { return this.attributeLocations.has(e); } /** * Get the WebGL program */ get glProgram() { return this.program; } /** * Reset texture unit counter (useful when starting a new frame) */ resetTextureUnits() { this.textureUnitCounter = 0; } } var S = "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);}", ue = "precision lowp float;uniform sampler2D u_texture;varying vec2 v_uv;void main(){gl_FragColor=texture2D(u_texture,v_uv);}", de = "precision lowp float;uniform vec4 u_color;void main(){gl_FragColor=u_color;}"; class fe { constructor(e) { o(this, "gl"); o(this, "imageShader"); o(this, "solidColorShader"); o(this, "currentShader", null); // Fill state management - default: white fill enabled o(this, "currentFillColor", [1, 1, 1, 1]); o(this, "fillMode", !0); // Stroke state management - default: black stroke enabled, weight 1 o(this, "currentStrokeColor", [0, 0, 0, 1]); o(this, "currentStrokeWeight", 1); o(this, "strokeMode", !0); // Transformation state management o(this, "currentRotation", 0); // in degrees // State stack for push/pop functionality o(this, "stateStack", []); this.gl = e, this.imageShader = new T(this.gl, S, ue), this.solidColorShader = new T(this.gl, S, de), 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); } /** * Set the current shader */ shader(e) { this.currentShader = e, e.use(); } /** * Sets the fill color for subsequent rendering operations * @param r Red component *(0-255)* * @param g Green component *(0-255, optional)* * @param b Blue component *(0-255, optional)* * @param a Alpha component *(0-255, optional)* */ fill(e, t, r, i) { if (this.fillMode = !0, t === void 0 && r === void 0 && i === void 0) { const s = e / 255; this.currentFillColor = [s, s, s, 1]; } else if (r !== void 0 && i === void 0) this.currentFillColor = [e / 255, t / 255, r / 255, 1]; else if (r !== void 0 && i !== void 0) this.currentFillColor = [e / 255, t / 255, r / 255, i / 255]; else throw new Error("Invalid fill parameters. Use fill(gray), fill(r,g,b), or fill(r,g,b,a)"); } /** * Sets the stroke color for subsequent rendering operations * @param r Red component *(0-255)* * @param g Green component *(0-255, optional)* * @param b Blue component *(0-255, optional)* * @param a Alpha component *(0-255, optional)* */ stroke(e, t, r, i) { if (this.strokeMode = !0, t === void 0 && r === void 0 && i === void 0) { const s = e / 255; this.currentStrokeColor = [s, s, s, 1]; } else if (r !== void 0 && i === void 0) this.currentStrokeColor = [e / 255, t / 255, r / 255, 1]; else if (r !== void 0 && i !== void 0) this.currentStrokeColor = [e / 255, t / 255, r / 255, i / 255]; else throw new Error("Invalid stroke parameters. Use stroke(gray), stroke(r,g,b), or stroke(r,g,b,a)"); } /** * Sets the stroke weight (thickness) for subsequent stroke operations * @param weight The stroke thickness in pixels */ strokeWeight(e) { if (e < 0) throw new Error("Stroke weight must be non-negative"); this.currentStrokeWeight = e; } /** * Disables stroke rendering for subsequent operations */ noStroke() { this.strokeMode = !1; } /** * Disables fill rendering for subsequent operations */ noFill() { this.fillMode = !1; } /** * Sets the rotation angle for subsequent rendering operations * @param degrees The rotation angle in degrees */ rotate(e) { this.currentRotation = e; } /** * Save the current rendering state (fill, stroke, etc.) to the state stack */ push() { this.stateStack.push({ fillColor: [...this.currentFillColor], fillMode: this.fillMode, strokeColor: [...this.currentStrokeColor], strokeWeight: this.currentStrokeWeight, strokeMode: this.strokeMode, rotation: this.currentRotation }); } /** * Restore the most recently saved rendering state from the state stack */ pop() { const e = this.stateStack.pop(); 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()"); } /** * Reset frame-specific state - called automatically after each frame. * Note: This does not reset fill/stroke state as that should persist * across frames and be managed by push/pop or explicit calls. */ reset() { this.currentShader = null, this.stateStack = [], this.currentRotation = 0, this.fillMode = !0, this.strokeMode = !0, this.currentFillColor = [1, 1, 1, 1], this.currentStrokeColor = [0, 0, 0, 1], this.currentStrokeWeight = 1; } createShader(e, t) { return new T(this.gl, e, t); } /** * Set a uniform value for the current shader */ setUniform(e, t) { this.currentShader.setUniform(e, t); } /** * Draw a rectangle with the current fill and/or stroke settings */ rect(e, t, r, i) { const s = new he(this.gl, e, t, r, i); if (this.currentShader !== null) { if (this.currentRotation !== 0) { const { centerX: d, centerY: f, radians: g, aspectRatio: _ } = this.calculateRotationParams(e, t, r, i); this.setUniform("u_rotation", g), this.setUniform("u_center", [d, f]), this.setUniform("u_aspectRatio", _); } else this.setUniform("u_rotation", 0), this.setUniform("u_center", [0, 0]), this.setUniform("u_aspectRatio", 1); s.renderFill(), this.currentShader = null; return; } const a = this.solidColorShader, { centerX: n, centerY: l, radians: c, aspectRatio: u } = this.calculateRotationParams(e, t, r, i); this.fillMode && (this.shader(a), this.setUniform("u_color", this.currentFillColor), this.setUniform("u_rotation", c), this.setUniform("u_center", [n, l]), this.setUniform("u_aspectRatio", u), s.renderFill()), this.strokeMode && (this.shader(a), this.setUniform("u_color", this.currentStrokeColor), this.setUniform("u_rotation", c), this.setUniform("u_center", [n, l]), this.setUniform("u_aspectRatio", u), s.renderStroke(this.currentStrokeWeight)), this.currentShader = null; } /** * Draw a line from (x1, y1) to (x2, y2) with the current stroke settings. * Lines only support stroke rendering - fill properties are ignored. * @param x1 X-coordinate of the line start point * @param y1 Y-coordinate of the line start point * @param x2 X-coordinate of the line end point * @param y2 Y-coordinate of the line end point */ line(e, t, r, i) { if (!this.strokeMode) return; const s = new ce(this.gl, e, t, r, i); if (this.currentShader !== null) { if (this.currentRotation !== 0) { const p = (e + r) / 2, C = (t + i) / 2, b = Math.abs(r - e), v = Math.abs(i - t), { centerX: w, centerY: A, radians: R, aspectRatio: y } = this.calculateRotationParams(p - b / 2, C - v / 2, b, v); this.setUniform("u_rotation", R), this.setUniform("u_center", [w, A]), this.setUniform("u_aspectRatio", y); } else this.setUniform("u_rotation", 0), this.setUniform("u_center", [0, 0]), this.setUniform("u_aspectRatio", 1); s.renderStroke(this.currentStrokeWeight), this.currentShader = null; return; } const a = this.solidColorShader, n = (e + r) / 2, l = (t + i) / 2, c = Math.abs(r - e), u = Math.abs(i - t), { centerX: d, centerY: f, radians: g, aspectRatio: _ } = this.calculateRotationParams(n - c / 2, l - u / 2, c, u); this.shader(a), this.setUniform("u_color", this.currentStrokeColor), this.setUniform("u_rotation", g), this.setUniform("u_center", [d, f]), this.setUniform("u_aspectRatio", _), s.renderStroke(this.currentStrokeWeight), this.currentShader = null; } /** * Calculate rotation parameters for built-in shaders (NDC coordinates) */ calculateRotationParams(e, t, r, i) { const s = this.gl.getParameter(this.gl.VIEWPORT), a = s[2], n = s[3], l = a / n, c = this.gl.getParameter(this.gl.FRAMEBUFFER_BINDING) !== null, u = e + r / 2, d = t + i / 2, f = u / a * 2 - 1; let g; c ? g = d / n * 2 - 1 : g = 1 - d / n * 2; const _ = this.currentRotation * Math.PI / 180; return { centerX: f, centerY: g, radians: _, aspectRatio: l }; } /** * Create a new framebuffer */ createFramebuffer(e, t, r = {}) { return new oe(this.gl, e, t, r); } /** * Fill the current framebuffer with a solid color */ background(e, t = e, r = e, i = 255) { this.clear(e / 255, t / 255, r / 255, i / 255); } /** * Clear the current framebuffer */ clear(e = 0, t = 0, r = 0, i = 0) { this.gl.clearColor(e, t, r, i), this.gl.clear(this.gl.COLOR_BUFFER_BIT); } /** * Ensure viewport matches canvas dimensions */ resetViewport() { this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height); } /** * Get the WebGL context */ get context() { return this.gl; } /** * Render a framebuffer at a specific position with optional scaling */ image(e, t, r, i, s) { this.shader(this.imageShader), this.setUniform("u_texture", e.texture); const { centerX: a, centerY: n, radians: l, aspectRatio: c } = this.calculateRotationParams( t, r, i ?? e.width, s ?? e.height ); this.setUniform("u_rotation", l), this.setUniform("u_center", [a, n]), this.setUniform("u_aspectRatio", c), this.rect(t, r, i ?? e.width, s ?? e.height); } } var m = {}; m.parse = function(h) { var e = function(s, a, n, l) { var c = m.T, u = { cmap: c.cmap, head: c.head, hhea: c.hhea, maxp: c.maxp, hmtx: c.hmtx, loca: c.loca, glyf: c.glyf }, d = { _data: s, _index: a, _offset: n }; for (var f in u) { var g = m.findTable(s, f, n); if (g) { var _ = g[0], p = l[_]; p == null && (p = u[f].parseTab(s, _, g[1], d)), d[f] = l[_] = p; } } return d; }, t = new Uint8Array(h), r = {}, i = e(t, 0, 0, r); return [i]; }; m.findTable = function(h, e, t) { for (var r = m.B, i = r.readUshort(h, t + 4), s = t + 12, a = 0; a < i; a++) { var n = r.readASCII(h, s, 4); r.readUint(h, s + 4); var l = r.readUint(h, s + 8), c = r.readUint(h, s + 12); if (n == e) return [l, c]; s += 16; } return null; }; m.T = {}; m.B = { readShort: function(h, e) { var t = m.B.t.uint16; return t[0] = h[e] << 8 | h[e + 1], m.B.t.int16[0]; }, readUshort: function(h, e) { return h[e] << 8 | h[e + 1]; }, readUshorts: function(h, e, t) { for (var r = [], i = 0; i < t; i++) r.push(m.B.readUshort(h, e + i * 2)); return r; }, readUint: function(h, e) { var t = m.B.t.uint8; 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]; }, readASCII: function(h, e, t) { for (var r = "", i = 0; i < t; i++) r += String.fromCharCode(h[e + i]); return r; }, // Simplified typed array buffer - only what's needed t: function() { var h = new ArrayBuffer(8); return { uint8: new Uint8Array(h), int16: new Int16Array(h), uint16: new Uint16Array(h), uint32: new Uint32Array(h) }; }() }; m.T.cmap = { parseTab: function(h, e, t) { var r = { tables: [], ids: {}, off: e }; h = new Uint8Array(h.buffer, e, t), e = 0; var i = m.B, s = i.readUshort, a = m.T.cmap; s(h, e), e += 2; var n = s(h, e); e += 2; for (var l = [], c = 0; c < n; c++) { var u = s(h, e); e += 2; var d = s(h, e); e += 2; var f = i.readUint(h, e); e += 4; var g = "p" + u + "e" + d, _ = l.indexOf(f); if (_ == -1) { _ = r.tables.length; var p = {}; l.push(f); var C = p.format = s(h, f); C == 4 ? p = a.parse4(h, f, p) : C == 12 && (p = a.parse12(h, f, p)), r.tables.push(p); } r.ids[g] != null && console.log("multiple tables for one platform+encoding: " + g), r.ids[g] = _; } return r; }, parse4: function(h, e, t) { var r = m.B, i = r.readUshort, s = r.readUshorts, a = e; e += 2; var n = i(h, e); e += 2, i(h, e), e += 2; var l = i(h, e); e += 2; var c = l >>> 1; 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 = []; for (var u = 0; u < c; u++) t.idDelta.push(r.readShort(h, e)), e += 2; return t.idRangeOffset = s(h, e, c), e += c * 2, t.glyphIdArray = s(h, e, a + n - e >> 1), t; }, parse12: function(h, e, t) { var r = m.B, i = r.readUint; e += 4, i(h, e), e += 4, i(h, e), e += 4; var s = i(h, e) * 3; e += 4; for (var a = t.groups = new Uint32Array(s), n = 0; n < s; n += 3) 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); return t; } }; m.T.head = { parseTab: function(h, e, t) { var r = m.B, i = {}; 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; } }; m.T.hhea = { parseTab: function(h, e, t) { var r = m.B, i = {}; e += 4; for (var s = [ "ascender", "descender", "lineGap", "advanceWidthMax", "minLeftSideBearing", "minRightSideBearing", "xMaxExtent", "caretSlopeRise", "caretSlopeRun", "caretOffset", "res0", "res1", "res2", "res3", "metricDataFormat", "numberOfHMetrics" ], a = 0; a < s.length; a++) { var n = s[a], l = n == "advanceWidthMax" || n == "numberOfHMetrics" ? r.readUshort : r.readShort; i[n] = l(h, e + a * 2); } return i; } }; m.T.hmtx = { parseTab: function(h, e, t, r) { for (var i = m.B, s = [], a = [], n = r.maxp.numGlyphs, l = r.hhea.numberOfHMetrics, c = 0, u = 0, d = 0; d < l; ) c = i.readUshort(h, e + (d << 2)), u = i.readShort(h, e + (d << 2) + 2), s.push(c), a.push(u), d++; for (; d < n; ) s.push(c), a.push(u), d++; return { aWidth: s, lsBearing: a }; } }; m.T.maxp = { parseTab: function(h, e, t) { var r = m.B, i = r.readUshort, s = {}; return r.readUint(h, e), e += 4, s.numGlyphs = i(h, e), e += 2, s; } }; m.T.loca = { parseTab: function(h, e, t, r) { var i = m.B, s = [], a = r.head.indexToLocFormat, n = r.maxp.numGlyphs + 1; if (a == 0) for (var l = 0; l < n; l++) s.push(i.readUshort(h, e + (l << 1)) << 1); if (a == 1) for (var l = 0; l < n; l++) s.push(i.readUint(h, e + (l << 2))); return s; } }; m.T.glyf = { parseTab: function(h, e, t, r) { for (var i = [], s = r.maxp.numGlyphs, a = 0; a < s; a++) i.push(null); return i; }, _parseGlyf: function(h, e) { var t = m.B, r = h._data, i = h.loca; if (i[e] == i[e + 1]) return null; var s = m.findTable(r, "glyf", h._offset)[0] + i[e], a = {}; 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; if (a.noc > 0) { a.endPts = []; for (var n = 0; n < a.noc; n++) a.endPts.push(t.readUshort(r, s)), s += 2; var l = t.readUshort(r, s); if (s += 2, r.length - s < l) return null; s += l; var c = a.endPts[a.noc - 1] + 1; a.flags = []; for (var n = 0; n < c; n++) { var u = r[s]; if (s++, a.flags.push(u), u & 8) { var d = r[s]; s++; for (var f = 0; f < d; f++) a.flags.push(u), n++; } } a.xs = []; for (var n = 0; n < c; n++) { var g = (a.flags[n] & 2) != 0, _ = (a.flags[n] & 16) != 0; g ? (a.xs.push(_ ? r[s] : -r[s]), s++) : _ ? a.xs.push(0) : (a.xs.push(t.readShort(r, s)), s += 2); } a.ys = []; for (var n = 0; n < c; n++) { var g = (a.flags[n] & 4) != 0, _ = (a.flags[n] & 32) != 0; g ? (a.ys.push(_ ? r[s] : -r[s]), s++) : _ ? a.ys.push(0) : (a.ys.push(t.readShort(r, s)), s += 2); } for (var p = 0, C = 0, n = 0; n < c; n++) p += a.xs[n], C += a.ys[n], a.xs[n] = p, a.ys[n] = C; } else a.parts = []; return a; } }; typeof module < "u" && module.exports ? module.exports = m : typeof window < "u" && (window.Typr = m); class me { /** * Extracts all available characters from a font's cmap tables. * @param font The parsed font object from Typr * @returns Array of unique character strings */ extractCharacters(e) { var r; const t = []; return (r = e == null ? void 0 : e.cmap) != null && r.tables ? (e.cmap.tables.forEach((i) => { if (i.format === 4) { const s = this._extractCharactersFromFormat4Table(i); t.push(...s); } else if (i.format === 12) { const s = this._extractCharactersFromFormat12Table(i); t.push(...s); } }), [...new Set(t)]) : []; } /** * Extracts characters from a Format 4 cmap table (Basic Multilingual Plane). * @param table The Format 4 cmap table * @returns Array of character strings */ _extractCharactersFromFormat4Table(e) { const t = []; if (!e.startCount || !e.endCount || !e.idRangeOffset || !e.idDelta) return t; for (let r = 0; r < e.startCount.length; r++) { const i = e.startCount[r], s = e.endCount[r]; if (!(i === 65535 && s === 65535)) { for (let a = i; a <= s; a++) if (this._calculateGlyphIndexFormat4(e, a, r) > 0) { const l = String.fromCodePoint(a); t.push(l); } } } return t; } /** * Extracts characters from a Format 12 cmap table (Extended Unicode ranges). * @param table The Format 12 cmap table * @returns Array of character strings */ _extractCharactersFromFormat12Table(e) { const t = []; if (!e.groups) return t; for (let r = 0; r < e.groups.length; r += 3) { const i = e.groups[r], s = e.groups[r + 1], a = e.groups[r + 2]; for (let n = i; n <= s; n++) if (a + (n - i) > 0) { const c = String.fromCodePoint(n); t.push(c); } } return t; } /** * Calculates the glyph index for a character in a Format 4 cmap table. * @param table The Format 4 cmap table * @param codePoint The Unicode code point * @param rangeIndex The index of the character range * @returns The glyph index, or 0 if not found */ _calculateGlyphIndexFormat4(e, t, r) { if (e.idRangeOffset[r] === 0) return t + e.idDelta[r] & 65535; { const i = e.idRangeOffset[r] / 2 + (t - e.startCount[r]) - (e.startCount.length - r); if (i >= 0 && e.glyphIdArray && i < e.glyphIdArray.length) { const s = e.glyphIdArray[i]; if (s !== 0) return s + e.idDelta[r] & 65535; } } return 0; } /** * Filters out problematic characters that might cause rendering issues. * @param characters Array of character strings to filter * @returns Filtered array of character strings */ filterProblematicCharacters(e) { return e.filter((t) => this._isValidCharacter(t)); } /** * Checks if a character is valid for rendering. * @param char The character to check * @returns True if the character is valid, false otherwise */ _isValidCharacter(e) { const t = e.codePointAt(0) || 0; return !(t >= 0 && t <= 31 && t !== 9 && t !== 10 && t !== 13 || t >= 127 && t <= 159); } } class ge { /** * Creates a new TextureAtlasCreation instance. * @param renderer The WebGL renderer instance */ constructor(e) { o(this, "_textureCanvas"); o(this, "_textureContext"); o(this, "_renderer"); this._renderer = e, this._textureCanvas = document.createElement("canvas"), this._textureContext = this._textureCanvas.getContext("2d", { willReadFrequently: !0, alpha: !1 }); } /** * Creates a texture atlas from the given characters. * @param characters Array of TextmodeCharacter objects * @param maxGlyphDimensions Maximum dimensions of glyphs * @param fontSize Font size for rendering * @param fontFamilyName Font family name to use * @returns Object containing framebuffer, columns, and rows */ createTextureAtlas(e, t, r, i) { const s = e.length, a = Math.ceil(Math.sqrt(s)), n = Math.ceil(s / a), l = t.width * a, c = t.height * n; this._setupCanvas(l, c, r, i), this._renderCharactersToCanvas(e, t, a, r), this._applyBlackWhiteThreshold(); const u = this._renderer.createFramebuffer(l, c, { filter: "nearest" }); return u.update(this._textureCanvas), { framebuffer: u, columns: a, rows: n }; } /** * Sets up the canvas for rendering. * @param width Canvas buffer width * @param height Canvas buffer height * @param fontSize Font size * @param fontFamilyName Font family name * @param logicalWidth Logical width for scaling context * @param logicalHeight Logical height for scaling context */ _setupCanvas(e, t, r, i) { 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"; } /** * Renders all characters to the canvas in a grid layout. * @param characters Array of characters to render * @param maxGlyphDimensions Maximum glyph dimensions * @param textureColumns Number of columns in the texture * @param fontSize Font size */ _renderCharactersToCanvas(e, t, r, i) { for (let s = 0; s < e.length; s++) { 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); this._textureContext.fillText(e[s].character, u, d); } } /** * Applies a black and white threshold filter to the canvas. * This converts antialiased grayscale pixels to pure black or white, * ensuring crisp text rendering suitable for NEAREST texture filtering. * @param threshold Threshold value (0-255) for black/white conversion */ _applyBlackWhiteThreshold(e = 128) { const t = this._textureContext.getImageData(0, 0, this._textureCanvas.width, this._textureCanvas.height), r = t.data; for (let i = 0; i < r.length; i += 4) { const s = 0.299 * r[i] + 0.587 * r[i + 1] + 0.114 * r[i + 2], a = e + 32, n = s > a ? 255 : 0; r[i] = n, r[i + 1] = n, r[i + 2] = n; } this._textureContext.putImageData(t, 0, 0); } } class pe { /** * Creates a new MetricsCalculation instance. */ constructor() { o(this, "_tempCanvas"); o(this, "_tempContext"); this._tempCanvas = document.createElement("canvas"), this._tempContext = this._tempCanvas.getContext("2d"); } /** * Calculates the maximum glyph dimensions for a given set of characters. * @param characters Array of character strings * @param fontSize Font size to use for measurement * @param fontFamilyName Font family name * @param fontFace FontFace object (optional, for validation) * @returns Object containing width and height dimensions */ calculateMaxGlyphDimensions(e, t, r) { this._tempContext.font = `${t}px ${r}`; let i = 0, s = 0; for (const a of e) { const n = this._tempContext.measureText(a), l = n.width, c = n.actualBoundingBoxAscent + n.actualBoundingBoxDescent; l > 0 && (i = Math.max(i, l), s = Math.max(s, c)); } return { width: Math.ceil(i), height: Math.ceil(s) }; } } class _e { /** * Creates TextmodeCharacter objects with unique color assignments. * @param characters Array of character strings * @param font The parsed font object from Typr * @returns Array of TextmodeCharacter objects with colors */ createCharacterObjects(e, t) { return e.map((r, i) => { const s = r.codePointAt(0) || 0, a = this._generateCharacterColor(i); let n = 0; if (t.hmtx && t.hmtx.aWidth) { const l = this._getGlyphIndex(t, s); l > 0 && t.hmtx.aWidth[l] !== void 0 && (n = t.hmtx.aWidth[l]); } return { character: r, unicode: s, color: a, advanceWidth: n }; }); } /** * Gets the glyph index for a given Unicode code point in a Typr.js font * This is a simplified version for advance width lookup only * @param fontData The Typr.js font data * @param codePoint The Unicode code point to look up * @returns The glyph index, or 0 if not found */ _getGlyphIndex(e, t) { const r = e.cmap; if (!r || !r.tables) return 0; for (const i of r.tables) if (i.format === 4) { for (let s = 0; s < i.startCount.length; s++) if (t >= i.startCount[s] && t <= i.endCount[s]) { if (i.idRangeOffset[s] === 0) return t + i.idDelta[s] & 65535; { const a = i.idRangeOffset[s] / 2 + (t - i.startCount[s]) - (i.startCount.length - s); if (a >= 0 && a < i.glyphIdArray.length) { const n = i.glyphIdArray[a]; if (n !== 0) return n + i.idDelta[s] & 65535; } } } } return 0; } /** * Generates a unique RGB color for a character based on its index. * @param index The index of the character * @returns RGB color as a tuple [r, g, b] */ _generateCharacterColor(e) { const t = e % 256, r = Math.floor(e / 256) % 256, i = Math.floor(e / 65536) % 256; return [t, r, i]; } /** * Gets the color for a specific character. * @param character The character to get the color for * @param characters Array of TextmodeCharacter objects * @returns RGB color as a tuple [r, g, b], or [0, 0, 0] if not found */ getCharacterColor(e, t) { if (!x.validate( typeof e == "string" && e.length === 1, "Character must be a single character string.", { method: "getCharacterColor", providedValue: e } )) return [0, 0, 0]; const r = t.find((i) => i.character === e); return r ? r.color : [0, 0, 0]; } /** * Gets colors for multiple characters. * @param characterString String of characters to get colors for * @param characters Array of TextmodeCharacter objects * @returns Array of RGB colors for each character */ getCharacterColors(e, t) { return x.validate( typeof e == "string" && e.length > 0, "Characters must be a string with at least one character.", { method: "getCharacterColors", providedValue: e } ) ? e.split("").map((r) => this.getCharacterColor(r, t) || [0, 0, 0]) : [[0, 0, 0]]; } } class xe { /** * Creates a new TextmodeFont instance. * @param renderer Renderer instance for texture creation * @param fontSize Font size to use for the texture atlas * @ignore */ constructor(e, t = 16) { o(this, "_font"); o(this, "_characters", []); o(this, "_fontFramebuffer"); o(this, "_fontSize", 16); o(this, "_textureColumns", 0); o(this, "_textureRows", 0); o(this, "_maxGlyphDimensions", { width: 0, height: 0 }); o(this, "_fontFace"); o(this, "_fontFamilyName", "UrsaFont"); // Component classes o(this, "_characterExtractor"); o(this, "_textureAtlas"); o(this, "_metricsCalculator"); o(this, "_characterColorMapper"); this._fontSize = t, this._characterExtractor = new me(), this._textureAtlas = new ge(e), this._metricsCalculator = new pe(), this._characterColorMapper = new _e(); } /** * Initializes the font manager by loading the font and creating the texture atlas. * @param fontSource Optional URL to load a custom font. If not provided, uses embedded font (full builds only). * @returns Promise that resolves when initialization is complete * @ignore */ async initialize(e) { let t; if (e) { const r = await fetch(e); if (!r.ok) throw new F(`Failed to load font file: ${r.status} ${r.statusText}`); t = await r.arrayBuffer(); } else throw new F("Embedded font not available. This appears to be a minified buil