UNPKG

asciiground

Version:

Library for creating animated ASCII canvas backgrounds supporting generative patterns and extensive configuration options.

839 lines (838 loc) • 37 kB
//#region \0@oxc-project+runtime@0.153.0/helpers/esm/typeof.js function e(t) { "@babel/helpers - typeof"; return e = typeof Symbol == "function" && typeof Symbol.iterator == "symbol" ? function(e) { return typeof e; } : function(e) { return e && typeof Symbol == "function" && e.constructor === Symbol && e !== Symbol.prototype ? "symbol" : typeof e; }, e(t); } //#endregion //#region \0@oxc-project+runtime@0.153.0/helpers/esm/toPrimitive.js function t(t, n) { if (e(t) != "object" || !t) return t; var r = t[Symbol.toPrimitive]; if (r !== void 0) { var i = r.call(t, n || "default"); if (e(i) != "object") return i; throw TypeError("@@toPrimitive must return a primitive value."); } return (n === "string" ? String : Number)(t); } //#endregion //#region \0@oxc-project+runtime@0.153.0/helpers/esm/toPropertyKey.js function n(n) { var r = t(n, "string"); return e(r) == "symbol" ? r : r + ""; } //#endregion //#region \0@oxc-project+runtime@0.153.0/helpers/esm/defineProperty.js function r(e, t, r) { return (t = n(t)) in e ? Object.defineProperty(e, t, { value: r, enumerable: !0, configurable: !0, writable: !0 }) : e[t] = r, e; } //#endregion //#region src/patterns/pattern.ts var i = { characters: [ "█", "▓", "▒", "░", " " ] }, a = class { get id() { return this.constructor.ID; } get options() { return this._options; } get isDirty() { return this._isDirty; } set isDirty(e) { this._isDirty = e; } constructor(e = {}) { r(this, "_options", void 0), r(this, "_isDirty", !1), this._options = { ...i, ...e }; } setOptions(e) { this._isDirty = this._hasOptionsChanged(e), this._options = { ...this._options, ...e }; } initialize(e) {} destroy() {} onMouseInteraction(e, t, n) {} _hasOptionsChanged(e) { return Object.keys(e).some((t) => this._options[t] !== e[t]); } }; r(a, "ID", void 0); //#endregion //#region src/patterns/dummy-pattern.ts var o = class extends a { constructor(e = {}) { super(e); } update(e) { return this; } generate(e) { return []; } }; r(o, "ID", "dummy"); //#endregion //#region src/rendering/webgl/glyph-atlas.ts var s = class { constructor(e, t, n) { r(this, "size", void 0), r(this, "_gl", void 0), r(this, "_canvas", void 0), r(this, "_context", void 0), r(this, "_pages", []), r(this, "_glyphs", /* @__PURE__ */ new Map()), this._gl = e, this._canvas = t, this._context = n, this.size = Math.min(1024, e.getParameter(e.MAX_TEXTURE_SIZE)); } getFirstPage() { return this._pages[0] ?? this._createPage(); } destroy() { this.reset(), this._canvas.width = this._canvas.height = 1; } reset() { this._pages.forEach((e) => this._gl.deleteTexture(e.texture)), this._pages = [], this._glyphs.clear(); } getGlyph(e, t, n) { let r = this._glyphs.get(e); if (r) return r; let i = this._context; i.font = `${t}px ${n}`, i.textBaseline = "top"; let a = i.measureText(e), o = Math.floor(-(a.actualBoundingBoxLeft ?? 0)) - 1, s = Math.floor(-(a.actualBoundingBoxAscent ?? 0)) - 1, c = Math.max(1, Math.ceil(a.actualBoundingBoxRight ?? a.width) - o + 1), l = Math.max(1, Math.ceil(a.actualBoundingBoxDescent ?? t) - s + 1); if (c > this.size || l > this.size) throw Error("Character exceeds the WebGL glyph atlas size."); let u = this._pages.at(-1) ?? this._createPage(); u.x + c > this.size && (u.x = 0, u.y += u.rowHeight, u.rowHeight = 0), u.y + l > this.size && (u = this._createPage()); let d = { page: u, x: u.x, y: u.y, width: c, height: l, left: o, top: s }; u.x += c, u.rowHeight = Math.max(u.rowHeight, l), this._canvas.width = c, this._canvas.height = l, i.font = `${t}px ${n}`, i.textBaseline = "top", i.fillStyle = "#ffffff", i.fillText(e, -o, -s); let f = this._gl; return f.bindTexture(f.TEXTURE_2D, u.texture), f.texSubImage2D(f.TEXTURE_2D, 0, d.x, d.y, f.RGBA, f.UNSIGNED_BYTE, this._canvas), this._glyphs.set(e, d), d; } _createPage() { let e = this._gl, t = e.createTexture(); if (!t) throw Error("Could not create WebGL glyph atlas."); let n = { texture: t, x: 0, y: 0, rowHeight: 0 }; return this._pages.push(n), e.bindTexture(e.TEXTURE_2D, t), e.texImage2D(e.TEXTURE_2D, 0, e.RGBA, this.size, this.size, 0, e.RGBA, e.UNSIGNED_BYTE, null), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MIN_FILTER, e.LINEAR), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_MAG_FILTER, e.LINEAR), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_S, e.CLAMP_TO_EDGE), e.texParameteri(e.TEXTURE_2D, e.TEXTURE_WRAP_T, e.CLAMP_TO_EDGE), n; } }, c = class { constructor(e, t) { r(this, "_canvas", void 0), r(this, "_context", void 0), r(this, "_colors", /* @__PURE__ */ new Map()), this._canvas = e, this._context = t; } destroy() { this._colors.clear(), this._canvas.width = this._canvas.height = 1; } parse(e) { let t = this._colors.get(e); if (t) return t; let n = this._context; n.clearRect(0, 0, 1, 1), n.fillStyle = "#000000", n.fillStyle = e, n.fillRect(0, 0, 1, 1); let r = n.getImageData(0, 0, 1, 1).data, i = [ r[0] / 255, r[1] / 255, r[2] / 255, r[3] / 255 ]; return this._colors.size >= 1024 && this._colors.clear(), this._colors.set(e, i), i; } }, l = "#version 300 es\n\nprecision highp float;\nin vec4 a_rect;\nin vec4 a_uv;\nin vec4 a_color;\nin vec4 a_transform;\nuniform vec2 u_resolution;\nout vec2 v_uv;\nout vec4 v_color;\n\nconst vec2 corners[6] = vec2[6](\n vec2(0, 0), vec2(1, 0), vec2(0, 1),\n vec2(0, 1), vec2(1, 0), vec2(1, 1)\n);\n\nvoid main() {\n vec2 corner = corners[gl_VertexID];\n vec2 local = a_rect.xy + corner * a_rect.zw - a_transform.xy;\n\n vec2 position = a_transform.xy + vec2(\n local.x * a_transform.z - local.y * a_transform.w,\n local.x * a_transform.w + local.y * a_transform.z\n );\n\n gl_Position = vec4((position / u_resolution * 2.0 - 1.0) * vec2(1, -1), 0, 1);\n v_uv = a_uv.xy + corner * a_uv.zw;\n v_color = a_color;\n}\n", u = "#version 300 es\n\nprecision highp float;\nin vec2 v_uv;\nin vec4 v_color;\nuniform sampler2D u_texture;\nuniform bool u_solid;\nuniform vec4 u_clip;\nout vec4 fragColor;\n\nvoid main() {\n float coverage = u_solid ? 1.0 : texture(u_texture, v_uv).a;\n\n vec2 clippedSize = clamp(\n min(gl_FragCoord.xy + 0.5, u_clip.zw) - max(gl_FragCoord.xy - 0.5, u_clip.xy),\n 0.0, 1.0\n );\n\n fragColor = vec4(v_color.rgb, v_color.a * coverage * clippedSize.x * clippedSize.y);\n}\n", d = 4096, f = class { constructor(e, t) { r(this, "instances", new Float32Array(d * 16)), r(this, "_gl", void 0), r(this, "_canvas", void 0), r(this, "_program", null), r(this, "_buffer", null), r(this, "_vertexArray", null), r(this, "_shaders", []), r(this, "_resolution", null), r(this, "_solid", null), r(this, "_clip", null), this._gl = e, this._canvas = t; } destroy() { let e = this._gl; this._shaders.forEach((t) => e.deleteShader(t)), e.deleteBuffer(this._buffer), e.deleteVertexArray(this._vertexArray), e.deleteProgram(this._program), this._buffer = this._vertexArray = this._program = null, this._resolution = this._solid = this._clip = null, this._shaders = []; } setClip(e, t, n, r) { this._gl.uniform4f(this._clip, e, t, n, r); } initialize() { let e = this._gl, t = this._createShader(e.VERTEX_SHADER, l), n = this._createShader(e.FRAGMENT_SHADER, u); if (this._program = e.createProgram(), !this._program) throw Error("Could not create WebGL program."); if (e.attachShader(this._program, t), e.attachShader(this._program, n), e.linkProgram(this._program), !e.getProgramParameter(this._program, e.LINK_STATUS)) throw Error("Failed to link WebGL program: " + e.getProgramInfoLog(this._program)); if (this._shaders.forEach((t) => e.deleteShader(t)), this._shaders = [], this._buffer = e.createBuffer(), this._vertexArray = e.createVertexArray(), !this._buffer || !this._vertexArray) throw Error("Could not create WebGL character buffers."); e.bindVertexArray(this._vertexArray), e.bindBuffer(e.ARRAY_BUFFER, this._buffer), e.bufferData(e.ARRAY_BUFFER, this.instances.byteLength, e.DYNAMIC_DRAW); for (let [t, n] of [ "a_rect", "a_uv", "a_color", "a_transform" ].entries()) { let r = e.getAttribLocation(this._program, n); e.enableVertexAttribArray(r), e.vertexAttribPointer(r, 4, e.FLOAT, !1, 64, t * 16), e.vertexAttribDivisor(r, 1); } this._resolution = e.getUniformLocation(this._program, "u_resolution"), this._solid = e.getUniformLocation(this._program, "u_solid"), this._clip = e.getUniformLocation(this._program, "u_clip"), e.useProgram(this._program), e.uniform1i(e.getUniformLocation(this._program, "u_texture"), 0), e.enable(e.BLEND), e.blendFuncSeparate(e.SRC_ALPHA, e.ONE_MINUS_SRC_ALPHA, e.ONE, e.ONE_MINUS_SRC_ALPHA), e.pixelStorei(e.UNPACK_PREMULTIPLY_ALPHA_WEBGL, !1), e.pixelStorei(e.UNPACK_FLIP_Y_WEBGL, !1), e.activeTexture(e.TEXTURE0), e.viewport(0, 0, this._canvas.width, this._canvas.height); } prepareDraw(e) { let t = this._gl; t.useProgram(this._program), t.bindVertexArray(this._vertexArray), t.bindBuffer(t.ARRAY_BUFFER, this._buffer), t.uniform2f(this._resolution, this._canvas.width, this._canvas.height), t.uniform1i(this._solid, +!!e), t.uniform4f(this._clip, 0, 0, this._canvas.width, this._canvas.height); } drawBatch(e, t) { if (t === 0) return; let n = this._gl; n.bindTexture(n.TEXTURE_2D, e.texture), n.bufferSubData(n.ARRAY_BUFFER, 0, this.instances.subarray(0, t * 16)), n.drawArraysInstanced(n.TRIANGLES, 0, 6, t); } _createShader(e, t) { let n = this._gl, r = n.createShader(e); if (!r) throw Error("Could not create WebGL shader."); if (this._shaders.push(r), n.shaderSource(r, t), n.compileShader(r), !n.getShaderParameter(r, n.COMPILE_STATUS)) throw Error("Failed to compile WebGL shader: " + n.getShaderInfoLog(r)); return r; } }, p = class { constructor() { r(this, "_gl", null), r(this, "_canvas", void 0), r(this, "_options", void 0), r(this, "_atlas", null), r(this, "_colors", null), r(this, "_pipeline", null), r(this, "_initialized", !1); } get options() { return this._options; } set options(e) { let t = e.fontSize !== this._options.fontSize || e.fontFamily !== this._options.fontFamily; this._options = e, t && this._atlas?.reset(); } initialize(e, t) { this.destroy(), this._canvas = e, this._options = t; let n = e.getContext("webgl2", { alpha: !0, preserveDrawingBuffer: !0 }); if (!n) throw Error("Could not get WebGL2 context from canvas."); this._gl = n; try { let t = document.createElement("canvas"), r = document.createElement("canvas"); r.width = r.height = 1; let i = t.getContext("2d"), a = r.getContext("2d", { willReadFrequently: !0 }); if (!i || !a) throw Error("Could not get 2D context for WebGL glyphs and colors."); this._atlas = new s(n, t, i), this._colors = new c(r, a), this._pipeline = new f(n, e), this._pipeline.initialize(), this._atlas.getFirstPage(), this._initialized = !0; } catch (e) { throw this.destroy(), e; } } clear(e) { if (!this._initialized || !this._gl) return; let t = this._gl; if (t.isContextLost()) throw Error("WebGL context was lost."); let n = this._colors.parse(e); t.disable(t.SCISSOR_TEST), n[3] === 1 ? (t.clearColor(...n), t.clear(t.COLOR_BUFFER_BIT)) : n[3] > 0 && (this._pipeline.prepareDraw(!0), this._pipeline.instances.set([ 0, 0, this._canvas.width, this._canvas.height, 0, 0, 0, 0, ...n, 0, 0, 1, 0 ]), this._pipeline.drawBatch(this._atlas.getFirstPage(), 1)); } render(e, t) { if (!this._initialized || !this._gl || e.length === 0) return; let n = this._gl; if (this._pipeline.prepareDraw(!1), t.startColumn !== 0 || t.startRow !== 0 || t.endColumn !== t.columns || t.endRow !== t.rows) { let e = Math.max(0, t.startColumn * t.charSpacingX), r = Math.max(0, t.startRow * t.charSpacingY), i = Math.min(this._canvas.width, t.endColumn * t.charSpacingX), a = Math.min(this._canvas.height, t.endRow * t.charSpacingY); n.enable(n.SCISSOR_TEST), n.scissor(Math.floor(e), this._canvas.height - Math.ceil(a), Math.max(0, Math.ceil(i) - Math.floor(e)), Math.max(0, Math.ceil(a) - Math.floor(r))), this._pipeline.setClip(e, this._canvas.height - a, i, this._canvas.height - r); } let r = 0, i = this._atlas.getFirstPage(); try { for (let n of e) { if (n.x < 0 || n.x >= t.canvasWidth || n.y < 0 || n.y >= t.canvasHeight) continue; let e = this._atlas.getGlyph(n.char, this._options.fontSize, this._options.fontFamily); (e.page !== i || r === 4096) && (this._pipeline.drawBatch(i, r), i = e.page, r = 0); let a = this._colors.parse(n.color || this._options.colorMap[n.char] || this._options.color), o = this._options.enableOpacity === !1 ? 1 : n.opacity ?? 1, s = n.scale ?? 1, c = n.rotation ?? 0; this._pipeline.instances.set([ n.x + e.left, n.y + e.top, e.width, e.height, e.x / this._atlas.size, e.y / this._atlas.size, e.width / this._atlas.size, e.height / this._atlas.size, a[0], a[1], a[2], a[3] * o, n.x + t.charWidth / 2, n.y + t.charHeight / 2, Math.cos(c) * s, Math.sin(c) * s ], r * 16), r++; } this._pipeline.drawBatch(i, r); } finally { n.disable(n.SCISSOR_TEST); } } resize(e, t) { this._initialized && this._gl && (this._canvas.width = e, this._canvas.height = t, this._gl.viewport(0, 0, e, t), this._gl.disable(this._gl.SCISSOR_TEST), this._gl.clearColor(0, 0, 0, 0), this._gl.clear(this._gl.COLOR_BUFFER_BIT)); } destroy() { this._atlas?.destroy(), this._pipeline?.destroy(), this._colors?.destroy(), this._gl?.getExtension("WEBGL_lose_context")?.loseContext(), this._atlas = this._pipeline = this._colors = null, this._gl = null, this._initialized = !1; } }, m = class { constructor() { r(this, "_canvas", void 0), r(this, "_context", void 0), r(this, "_options", void 0); } get options() { return this._options; } set options(e) { this._options = e, this._setupContext(); } initialize(e, t) { this._canvas = e, this._options = t; let n = e.getContext("2d"); if (!n) throw Error("Could not get 2D context from canvas"); this._context = n, this._setupContext(); } clear(e) { this._context.fillStyle = e, this._context.fillRect(0, 0, this._canvas.width, this._canvas.height), this._context.fillStyle = this._options.color; } render(e, t) { let n = t.startColumn !== 0 || t.startRow !== 0 || t.endColumn !== t.columns || t.endRow !== t.rows; n && (this._context.save(), this._context.beginPath(), this._context.rect(t.startColumn * t.charSpacingX, t.startRow * t.charSpacingY, (t.endColumn - t.startColumn) * t.charSpacingX, (t.endRow - t.startRow) * t.charSpacingY), this._context.clip()); let r = this._options.color, i = this._options.colorMap, a = r, o = 1; this._context.fillStyle = r; for (let n of e) { if (n.x < 0 || n.x >= t.canvasWidth || n.y < 0 || n.y >= t.canvasHeight) continue; let e = this._options.enableOpacity === !1 ? 1 : n.opacity ?? 1; e !== o && (this._context.globalAlpha = e, o = e); let s = n.color || i[n.char] || r; s !== a && (this._context.fillStyle = s, a = s), n.scale !== void 0 || n.rotation !== void 0 ? (this._context.save(), this._context.translate(n.x + t.charWidth / 2, n.y + t.charHeight / 2), n.rotation !== void 0 && this._context.rotate(n.rotation), n.scale !== void 0 && this._context.scale(n.scale, n.scale), this._context.fillText(n.char, -t.charWidth / 2, -t.charHeight / 2), this._context.restore()) : this._context.fillText(n.char, n.x, n.y); } o !== 1 && (this._context.globalAlpha = 1), a !== r && (this._context.fillStyle = r), n && this._context.restore(); } resize(e, t) { this._canvas.width = e, this._canvas.height = t, this._setupContext(); } destroy() {} _setupContext() { this._context.font = `${this._options.fontSize}px ${this._options.fontFamily}`, this._context.textBaseline = "top", this._context.fillStyle = this._options.color; } }, h = (e) => { switch (e) { case "WebGL": return new p(); case "2D": return new m(); default: throw Error("Unknown renderer type given!"); } }, g = { color: "#3e3e80ff", colorMap: {}, enableOpacity: !0, fontSize: 32, characterDensity: 1, fontFamily: "monospace", backgroundColor: "#181818ff", padding: 0, rendererType: "2D", enableMouseInteraction: !1, animated: !1, animationSpeed: 1, charSpacingX: void 0, charSpacingY: void 0 }, _ = () => ({ canvas: null, renderer: null, pattern: null, region: null, options: { ...g, resizeTo: window }, lastTime: 0, animationId: null, animationTime: 0, mouseX: 0, mouseY: 0, mouseClicked: !1, tempCanvas: null, tempContext: null, lastHash: 0, isDirty: !0, resizeObserver: null, frameCount: 0, lastFpsTime: 0, currentFps: 0, fpsUpdateInterval: 1e3 }), v = class { get options() { return this._state.options; } get renderInfo() { return { fps: this._state.currentFps, rows: this._state.region?.rows || 0, columns: this._state.region?.columns || 0, frameCount: this._state.frameCount }; } get mouseInfo() { return { x: this._state.mouseX, y: this._state.mouseY, clicked: this._state.mouseClicked }; } get region() { if (!this._state.region) throw Error("Renderer is not initialized! Make sure to call the constructor first."); return this._state.region; } get pattern() { if (!this._state.pattern) throw Error("Pattern not initialized"); return this._state.pattern; } set pattern(e) { this._state.pattern && this._state.pattern.destroy(), this._state.pattern = e, this._state.region && this._state.pattern.initialize(this._state.region), this._resetAnimationTime(); } get isAnimating() { return this._state.options.animated; } get canvas() { if (!this._state.canvas) throw Error("Canvas not initialized."); return this._state.canvas; } get realCanvasSize() { let e = this._state.options.resizeTo; if (e instanceof HTMLElement) { let t = window.getComputedStyle(e), n = parseFloat(t.paddingLeft) + parseFloat(t.paddingRight), r = parseFloat(t.paddingTop) + parseFloat(t.paddingBottom); return [e.clientWidth - n, e.clientHeight - r]; } return [e.innerWidth, e.innerHeight]; } get renderer() { if (!this._state.renderer) throw Error("Renderer not initialized."); return this._state.renderer; } get tempContext() { if (!this._state.tempCanvas && (this._state.tempCanvas = document.createElement("canvas"), this._state.tempContext = this._state.tempCanvas.getContext("2d", { alpha: !1 }), !this._state.tempContext)) throw Error("Failed to create 2D context for temp canvas"); if (!this._state.tempContext) throw Error("Temp context not initialized."); return this._state.tempContext; } constructor({ canvas: e, pattern: t, options: n }) { r(this, "_state", _()), r(this, "_handleResize", void 0), r(this, "_contextLostHandler", () => { this._state.isDirty = !0, this.render(); }), r(this, "_mouseMoveHandler", (e) => { let t = this.canvas.getBoundingClientRect(), n = this.canvas.width / this.region.columns, r = this.canvas.height / this.region.rows; this._state.mouseX = Math.floor((e.clientX - t.left) / n), this._state.mouseY = Math.floor((e.clientY - t.top) / r); }), r(this, "_mouseClickHandler", () => { this._state.mouseClicked = !0; }), n || (n = {}), this._state.canvas = e, this._state.pattern = t || new o(), this._handleResize = this.resize.bind(this), this._state.options = { ...g, resizeTo: window, ...n }, this._setupRenderer(), this._state.options.enableMouseInteraction && this._setupMouseEvents(); } render(e = performance.now()) { let t = e - this._state.lastTime; this._state.lastTime = e, this._state.options.animated && (this._state.animationTime += t / 1e3 * this._state.options.animationSpeed); let n = { time: this._state.animationTime, deltaTime: t / 1e3, animationTime: this._state.animationTime, region: this.region, mouseX: this._state.mouseX, mouseY: this._state.mouseY, clicked: this._state.mouseClicked, isAnimating: this._state.options.animated, animationSpeed: this._state.options.animationSpeed }; this._state.mouseClicked = !1; let r = this.pattern.update(n).generate(n), i = this._hash(r); if (i !== this._state.lastHash || this._state.isDirty || this.pattern.isDirty) { this._updateFps(e), this.pattern.isDirty = !1, this._state.isDirty = !1, this._state.lastHash = i; try { this.renderer.clear(this._state.options.backgroundColor), this.renderer.render(r, this.region); } catch (e) { if (this._state.options.rendererType !== "WebGL") throw e; this._fallbackToCanvas(e), this.renderer.clear(this._state.options.backgroundColor), this.renderer.render(r, this.region); } } } startAnimation() { if (this._state.animationId) throw Error("Animation is already running!"); this._state.options.animated = !0, this._state.lastTime = performance.now(), this._state.frameCount = 0, this._state.lastFpsTime = 0, this._state.currentFps = 0; let e = (t) => { this.isAnimating && (this.render(t), this._state.animationId = requestAnimationFrame(e)); }; this._state.animationId = requestAnimationFrame(e); } stopAnimation() { this._state.options.animated = !1, this._state.animationId !== null && (cancelAnimationFrame(this._state.animationId), this._state.animationId = null); } setOptions(e) { let t = this._state.options; this._state.options = { ...t, ...e }, this._state.isDirty = this._state.isDirty || this._hasOptionsChanged(t), t.rendererType !== this._state.options.rendererType && (this._removeMouseEvents(), this.renderer.destroy(), this._replaceCanvas(), this._initializeBackend()), this._state.region = this._calculateRegion(), this.pattern.initialize(this._state.region), this.renderer.options = this._state.options, this._syncAnimationState(), this._state.options.enableMouseInteraction ? (this._removeMouseEvents(), this._setupMouseEvents()) : this._removeMouseEvents(); } resize() { [this.canvas.width, this.canvas.height] = this.realCanvasSize, this._state.region = this._calculateRegion(), this.renderer.resize(this.canvas.width, this.canvas.height), this.pattern.initialize(this._state.region), this._state.isDirty = !0, this.isAnimating || this.render(); } destroy() { this.stopAnimation(), this._state.pattern?.destroy(), this._state.canvas?.removeEventListener("webglcontextlost", this._contextLostHandler), this._state.renderer?.destroy(), this._state.canvas?.removeEventListener("mousemove", this._mouseMoveHandler), this._state.canvas?.removeEventListener("click", this._mouseClickHandler), this._state.options.resizeTo.removeEventListener("resize", this._handleResize), this._state.resizeObserver?.disconnect(), this._state = _(); } _calculateSpacing() { let e = 0; for (let t of this.pattern.options.characters) { let n = this.tempContext.measureText(t); e = Math.max(e, n.width); } let t = e, n = this.tempContext.measureText(this.pattern.options.characters.join("")), r = n.actualBoundingBoxAscent + n.actualBoundingBoxDescent, i = Math.max(r, this._state.options.fontSize), a = this._state.options.charSpacingX && this._state.options.charSpacingX > 0 ? this._state.options.charSpacingX : t, o = this._state.options.charSpacingY && this._state.options.charSpacingY > 0 ? this._state.options.charSpacingY : Math.max(i, this._state.options.fontSize * 1.2), s = this._state.options.characterDensity ?? 1, c = Number.isFinite(s) && s > 0 ? Math.sqrt(s) : 1; return [ t, i, a / c, o / c ]; } _calculateRegion() { this.tempContext.font = `${this._state.options.fontSize}px ${this._state.options.fontFamily}`; let [e, t, n, r] = this._calculateSpacing(), i = Math.floor(this.canvas.width / n), a = Math.floor(this.canvas.height / r); return { startColumn: this._state.options.padding, endColumn: i - this._state.options.padding, startRow: this._state.options.padding, endRow: a - this._state.options.padding, columns: i, rows: a, charWidth: e, charHeight: t, charSpacingX: n, charSpacingY: r, canvasWidth: this.canvas.width, canvasHeight: this.canvas.height }; } _setupRenderer() { [this.canvas.width, this.canvas.height] = this.realCanvasSize, this._initializeBackend(), this._state.region = this._calculateRegion(), this.pattern.initialize(this.region), this._state.options.resizeTo instanceof HTMLElement ? (this._state.resizeObserver = new ResizeObserver(this._handleResize), this._state.resizeObserver.observe(this._state.options.resizeTo)) : this._state.options.resizeTo.addEventListener("resize", this._handleResize); } _replaceCanvas() { let e = this.canvas; e.removeEventListener("webglcontextlost", this._contextLostHandler); let t = e.cloneNode(!0); e.replaceWith(t), this._state.canvas = t, this._state.options.resizeTo === e && (this._state.options = { ...this._state.options, resizeTo: t }, this._state.resizeObserver?.disconnect(), this._state.resizeObserver?.observe(t)); } _initializeBackend() { this._state.renderer = h(this._state.options.rendererType); try { this.renderer.initialize(this.canvas, this._state.options); } catch (e) { if (this._state.options.rendererType !== "WebGL") throw this.renderer.destroy(), e; this._fallbackToCanvas(e); } this._state.options.rendererType === "WebGL" && this.canvas.addEventListener("webglcontextlost", this._contextLostHandler); } _fallbackToCanvas(e) { console.warn("WebGL failed, falling back to Canvas 2D:", e), this._removeMouseEvents(), this.renderer.destroy(), this._replaceCanvas(), this._state.options = { ...this._state.options, rendererType: "2D" }, this._state.renderer = h("2D"), this.renderer.initialize(this.canvas, this._state.options), this._state.options.enableMouseInteraction && this._setupMouseEvents(); } _setupMouseEvents() { this.canvas.addEventListener("mousemove", this._mouseMoveHandler), this.canvas.addEventListener("click", this._mouseClickHandler); } _removeMouseEvents() { this.canvas.removeEventListener("mousemove", this._mouseMoveHandler), this.canvas.removeEventListener("click", this._mouseClickHandler); } _resetAnimationTime() { this._state.animationTime = 0; } _syncAnimationState() { this._state.options.animated && this._state.animationId === null ? this.startAnimation() : !this._state.options.animated && this._state.animationId !== null && this.stopAnimation(); } _hash(e) { let t = 0; for (let { x: n, y: r, char: i, color: a = "", opacity: o = 1, scale: s = 1, rotation: c = 0 } of e) t ^= (n * 31 + r * 17 ^ i.charCodeAt(0)) + (a.charCodeAt(0) || 0) * 13 + Math.floor(o * 100) * 7 + Math.floor(s * 100) * 5 + Math.floor(c * 100) * 3; return t; } _hasOptionsChanged(e) { return Object.keys(e).some((t) => this._state.options[t] !== e[t]); } _updateFps(e) { if (this._state.frameCount++, this._state.lastFpsTime === 0) { this._state.lastFpsTime = e; return; } let t = e - this._state.lastFpsTime; t >= this._state.fpsUpdateInterval && (this._state.currentFps = Math.round(this._state.frameCount * 1e3 / t), this._state.frameCount = 0, this._state.lastFpsTime = e); } }, y = (e) => { let t = Math.floor(e) % 2147483647; return t <= 0 && (t += 2147483646), () => (t = t * 16807 % 2147483647, t / 2147483647); }, b = { frequency: .01, octaves: 4, persistence: .5, lacunarity: 2, seed: 0 }, x = class extends a { get _octaves() { return this._options.octaves; } get _persistence() { return this._options.persistence; } get _lacunarity() { return this._options.lacunarity; } constructor(e = {}) { super({ ...b, ...e }), r(this, "_permutations", void 0), this._permutations = this._generatePermutations(this._options.seed); } setOptions(e) { let t = this._options.seed; super.setOptions(e), e.seed !== void 0 && e.seed !== t && (this._permutations = this._generatePermutations(this._options.seed)); } update(e) { return this; } generate({ animationTime: e, region: t }) { if (this.options.characters.length === 0) return []; let n = []; for (let r = t.startRow; r <= t.endRow; r++) for (let i = t.startColumn; i <= t.endColumn; i++) { let a = this._fractalNoise(i * this._options.frequency, r * this._options.frequency, e * .001), o = Math.max(0, Math.min(1, (a + 1) / 2)), s = Math.floor(o * this._options.characters.length), c = Math.max(0, Math.min(s, this._options.characters.length - 1)); n.push({ char: this._options.characters[c], x: i * t.charSpacingX, y: r * t.charSpacingY, opacity: o }); } return n; } _generatePermutations(e) { let t = y(e), n = Array.from({ length: 256 }, (e, t) => t); for (let e = 255; e > 0; e--) { let r = Math.floor(t() * (e + 1)), i = n[e]; n[e] = n[r], n[r] = i; } return n.concat(n); } _fade(e) { return e * e * e * (e * (e * 6 - 15) + 10); } _lerp(e, t, n) { return e + n * (t - e); } _gradient3D(e, t, n, r) { let i = e & 15, a = i < 8 ? t : n, o = i < 4 ? n : i === 12 || i === 14 ? t : r; return (i & 1 ? -a : a) + (i & 2 ? -o : o); } _noise3D(e, t, n) { let r = Math.floor(e) & 255, i = Math.floor(t) & 255, a = Math.floor(n) & 255; e -= Math.floor(e), t -= Math.floor(t), n -= Math.floor(n); let o = this._fade(e), s = this._fade(t), c = this._fade(n), l = this._permutations, u = l[r] + i, d = l[u] + a, f = l[u + 1] + a, p = l[r + 1] + i, m = l[p] + a, h = l[p + 1] + a; return this._lerp(this._lerp(this._lerp(this._gradient3D(l[d], e, t, n), this._gradient3D(l[m], e - 1, t, n), o), this._lerp(this._gradient3D(l[f], e, t - 1, n), this._gradient3D(l[h], e - 1, t - 1, n), o), s), this._lerp(this._lerp(this._gradient3D(l[d + 1], e, t, n - 1), this._gradient3D(l[m + 1], e - 1, t, n - 1), o), this._lerp(this._gradient3D(l[f + 1], e, t - 1, n - 1), this._gradient3D(l[h + 1], e - 1, t - 1, n - 1), o), s), c); } _fractalNoise(e, t, n = 0) { let r = 0, i = 0, a = 1, o = 1; for (let s = 0; s < this._octaves; s++) r += this._noise3D(e * o, t * o, n * o) * a, i += a, a *= this._persistence, o *= this._lacunarity; return r / i; } }; r(x, "ID", "perlin-noise"); //#endregion //#region src/patterns/rain-pattern.ts var S = { rainDensity: .8, minDropLength: 8, maxDropLength: 25, minSpeed: .5, maxSpeed: 1.5, mutationRate: .04, fadeOpacity: .2, headColor: "#FFFFFF" }, C = class extends a { constructor(e = {}) { super({ ...S, ...e }), r(this, "_rainDrops", []), r(this, "_region", null), r(this, "_lastFrameCharacters", []); } initialize(e) { let t = this._rainDrops.length > 0, n = this._region; this._region = e, !t || !n || Math.abs(n.columns - e.columns) > 2 || Math.abs(n.rows - e.rows) > 2 ? (this._rainDrops = [], this._lastFrameCharacters = [], this._initializeRainDrops()) : (this._adjustDropsToNewRegion(n, e), this._maintainRainDensity()); } update(e) { return this._region && (this._updateRainDrops(e), this._maintainRainDensity()), this; } generate(e) { if ((!this._region || this._rainDrops.length === 0) && (this._region && this._rainDrops.length === 0 && this._initializeRainDrops(), this._rainDrops.length === 0)) return []; let t = []; if (this._options.fadeOpacity > 0) for (let e of this._lastFrameCharacters) t.push({ ...e, opacity: this._options.fadeOpacity }); for (let n of this._rainDrops) this._renderRainDrop(n, t, e); return this._lastFrameCharacters = t.filter((e) => e.opacity !== this._options.fadeOpacity), t; } _initializeRainDrops() { if (!this._region) return; let e = Math.floor(this._region.columns * this._options.rainDensity), t = Math.max(1, e); for (let e = 0; e < t; e++) { let n = Math.floor(Math.random() * this._region.columns) + this._region.startColumn, r = this._createRainDrop(n, 0); e < Math.max(1, Math.floor(t * .3)) && (r.y = Math.random() * this._region.rows), this._rainDrops.push(r); } } _createRainDrop(e, t = 0) { let n = Math.floor(Math.random() * (this._options.maxDropLength - this._options.minDropLength)) + this._options.minDropLength, r = Array.from({ length: n }, () => this._options.characters[Math.floor(Math.random() * this._options.characters.length)]); return { y: -Math.floor(Math.random() * n) - Math.random() * 10, column: e, length: n, characters: r, lastMutationTime: t, speed: Math.random() * (this._options.maxSpeed - this._options.minSpeed) + this._options.minSpeed }; } _updateRainDrops(e) { if (this._region) for (let t of this._rainDrops) { if (e.isAnimating && (t.y += t.speed * e.animationSpeed * e.deltaTime), e.animationTime - t.lastMutationTime > 1 / this._options.mutationRate && Math.random() < this._options.mutationRate) { let n = Math.floor(Math.random() * t.length); t.characters[n] = this._options.characters[Math.floor(Math.random() * this._options.characters.length)], t.lastMutationTime = e.animationTime; } t.y - t.length > this._region.endRow && this._resetRainDrop(t, e.animationTime); } } _resetRainDrop(e, t) { this._region && (e.lastMutationTime = t, e.y = -Math.floor(Math.random() * 8) - e.length, e.length = Math.floor(Math.random() * (this._options.maxDropLength - this._options.minDropLength)) + this._options.minDropLength, e.characters = Array.from({ length: e.length }, () => this._options.characters[Math.floor(Math.random() * this._options.characters.length)]), e.speed = Math.random() * (this._options.maxSpeed - this._options.minSpeed) + this._options.minSpeed, e.column = Math.floor(Math.random() * this._region.columns) + this._region.startColumn); } _maintainRainDensity() { if (!this._region) return; let e = Math.floor(this._region.columns * this._options.rainDensity); for (; this._rainDrops.length < e;) { let e = this._rainDrops.length % this._region.columns + this._region.startColumn, t = this._createRainDrop(e, 0); Math.random() < .4 && (t.y = Math.random() * this._region.rows + this._region.startRow), this._rainDrops.push(t); } this._rainDrops.length > e && (this._rainDrops.length = e); } _adjustDropsToNewRegion(e, t) { for (let e of this._rainDrops) e.column >= t.startColumn + t.columns ? e.column = e.column % t.columns + t.startColumn : e.column < t.startColumn && (e.column = t.startColumn); } _renderRainDrop(e, t, n) { if (this._region) for (let n = 0; n < e.length; n++) { let r = Math.floor(e.y) - n; if (r < this._region.startRow || r > this._region.endRow || e.column < this._region.startColumn || e.column > this._region.endColumn) continue; let i = e.column * this._region.charSpacingX, a = r * this._region.charSpacingY, o, s = 1; n === 0 ? o = this._options.headColor : n >= 3 && (s = 1 - n / e.length), t.push({ x: i, y: a, color: o, opacity: s, char: e.characters[n] }); } } destroy() { this._rainDrops = [], this._lastFrameCharacters = [], this._region = null; } setOptions(e) { if (super.setOptions(e), e.rainDensity !== void 0 && this._region && this._maintainRainDensity(), e.characters !== void 0) for (let e of this._rainDrops) e.characters = Array.from({ length: e.length }, () => this._options.characters[Math.floor(Math.random() * this._options.characters.length)]); } }; r(C, "ID", "rain"); //#endregion //#region src/patterns/static-noise-pattern.ts var w = { seed: 0 }, T = class extends a { constructor(e = {}) { super({ ...w, ...e }); } update(e) { return this; } generate({ region: e, animationTime: t }) { let n = [], r = y(this._options.seed + Math.floor(t * 10)); for (let t = e.startRow; t <= e.endRow; t++) for (let i = e.startColumn; i <= e.endColumn; i++) { let a = Math.floor(r() * this._options.characters.length), o = this._options.characters[a] || " "; n.push({ char: o, x: i * e.charSpacingX, y: t * e.charSpacingY }); } return n; } }; r(T, "ID", "static"); //#endregion //#region src/index.ts var E = class { constructor() { r(this, "_renderer", null); } get pattern() { return this.renderer.pattern; } get options() { return Object.freeze({ ...this.renderer.options }); } get canvas() { return this.renderer.canvas; } get renderer() { if (!this._renderer) throw Error("Renderer is not initialized - call init() first."); return this._renderer; } set renderer(e) { this._renderer = e; } init(e, t, n) { return this.renderer = new v({ canvas: e, pattern: t, options: n }), this; } startAnimation() { return this.renderer.startAnimation(), this; } stopAnimation() { return this.renderer.stopAnimation(), this; } setPattern(e) { return this.renderer.pattern = e, this; } setOptions(e) { return this.renderer.setOptions(e), this; } destroy() { this.renderer.destroy(), this.renderer = null; } }; //#endregion export { E as ASCIIGround, E as default, v as ASCIIRenderer, m as Canvas2DRenderer, o as DummyPattern, a as Pattern, x as PerlinNoisePattern, C as RainPattern, T as StaticNoisePattern, p as WebGLRenderer, h as createRenderer, y as createSeededRandom };