asciiground
Version:
Library for creating animated ASCII canvas backgrounds supporting generative patterns and extensive configuration options.
50 lines (41 loc) • 31 kB
JavaScript
Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});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)}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(`@ must return a primitive value.`)}return(n===`string`?String:Number)(t)}function n(n){var r=t(n,`string`);return e(r)==`symbol`?r:r+``}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}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);var o=class extends a{constructor(e={}){super(e)}update(e){return this}generate(e){return[]}};r(o,`ID`,`dummy`);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`,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`,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
precision highp float;
in vec4 a_rect;
in vec4 a_uv;
in vec4 a_color;
in vec4 a_transform;
uniform vec2 u_resolution;
out vec2 v_uv;
out vec4 v_color;
const vec2 corners[6] = vec2[6](
vec2(0, 0), vec2(1, 0), vec2(0, 1),
vec2(0, 1), vec2(1, 0), vec2(1, 1)
);
void main() {
vec2 corner = corners[gl_VertexID];
vec2 local = a_rect.xy + corner * a_rect.zw - a_transform.xy;
vec2 position = a_transform.xy + vec2(
local.x * a_transform.z - local.y * a_transform.w,
local.x * a_transform.w + local.y * a_transform.z
);
gl_Position = vec4((position / u_resolution * 2.0 - 1.0) * vec2(1, -1), 0, 1);
v_uv = a_uv.xy + corner * a_uv.zw;
v_color = a_color;
}
`,u=`#version 300 es
precision highp float;
in vec2 v_uv;
in vec4 v_color;
uniform sampler2D u_texture;
uniform bool u_solid;
uniform vec4 u_clip;
out vec4 fragColor;
void main() {
float coverage = u_solid ? 1.0 : texture(u_texture, v_uv).a;
vec2 clippedSize = clamp(
min(gl_FragCoord.xy + 0.5, u_clip.zw) - max(gl_FragCoord.xy - 0.5, u_clip.xy),
0.0, 1.0
);
fragColor = vec4(v_color.rgb, v_color.a * coverage * clippedSize.x * clippedSize.y);
}
`,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`);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`);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`);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}};exports.ASCIIGround=E,exports.default=E,exports.ASCIIRenderer=v,exports.Canvas2DRenderer=m,exports.DummyPattern=o,exports.Pattern=a,exports.PerlinNoisePattern=x,exports.RainPattern=C,exports.StaticNoisePattern=T,exports.WebGLRenderer=p,exports.createRenderer=h,exports.createSeededRandom=y;