@animech-public/playcanvas
Version:
PlayCanvas WebGL game engine
2 lines (1 loc) • 4.89 kB
JavaScript
import{string as t}from"../../core/string.js";import{EventHandler as e}from"../../core/event-handler.js";import{Color as s}from"../../core/math/color.js";import{PIXELFORMAT_RGBA8 as i,FILTER_LINEAR_MIPMAP_LINEAR as h,FILTER_LINEAR as n,ADDRESS_CLAMP_TO_EDGE as a}from"../../platform/graphics/constants.js";import{Texture as o}from"../../platform/graphics/texture.js";class r{constructor(t,e,s,r){this.canvas=document.createElement("canvas"),this.canvas.width=e,this.canvas.height=s,this.texture=new o(t,{name:r,format:i,width:e,height:s,mipmaps:!0,minFilter:h,magFilter:n,addressU:a,addressV:a,levels:[this.canvas]}),this.ctx=this.canvas.getContext("2d",{alpha:!0})}destroy(){this.texture.destroy()}clear(t){const{width:e,height:s}=this.canvas;this.ctx.clearRect(0,0,e,s),this.ctx.fillStyle=t,this.ctx.fillRect(0,0,e,s)}}class l extends e{constructor(t,e={}){super(),this.type="bitmap",this.app=t,this.intensity=0,this.fontWeight=e.fontWeight||"normal",this.fontSize=parseInt(e.fontSize,10),this.glyphSize=this.fontSize,this.fontName=e.fontName||"Arial",this.color=e.color||new s(1,1,1),this.padding=e.padding||0,this.width=Math.min(4096,e.width||512),this.height=Math.min(4096,e.height||512),this.atlases=[],this.chars="",this.data={}}createTextures(t){const e=this._normalizeCharsSet(t);if(e.length===this.chars.length){for(let t=0;t<e.length;t++)if(e[t]!==this.chars[t])return void this._renderAtlas(e)}else this._renderAtlas(e)}updateTextures(t){const e=this._normalizeCharsSet(t),s=[];for(let t=0;t<e.length;t++){const i=e[t];this.data.chars[i]||s.push(i)}s.length>0&&this._renderAtlas(this.chars.concat(s))}destroy(){this.atlases.forEach((t=>t.destroy())),this.chars=null,this.color=null,this.data=null,this.fontName=null,this.fontSize=null,this.glyphSize=null,this.intensity=null,this.atlases=null,this.type=null,this.fontWeight=null}_colorToRgbString(t,e){let s;const i=Math.round(255*t.r),h=Math.round(255*t.g),n=Math.round(255*t.b);return s=e?`rgba(${i}, ${h}, ${n}, ${t.a})`:`rgb(${i}, ${h}, ${n})`,s}renderCharacter(t,e,s,i,h){t.fillStyle=h,t.fillText(e,s,i)}_getAtlas(t){return t>=this.atlases.length&&(this.atlases[t]=new r(this.app.graphicsDevice,this.width,this.height,`font-atlas-${this.fontName}-${t}`)),this.atlases[t]}_renderAtlas(e){this.chars=e;const s=this.width,i=this.height,h=this._colorToRgbString(this.color,!1),n=this.color.a;this.color.a=1/255;const a=this._colorToRgbString(this.color,!0);this.color.a=n;let o=0,r=this._getAtlas(o++);r.clear(a),this.data=this._createJson(this.chars,this.fontName,s,i);const l=t.getSymbols(this.chars.join(""));let c=0,d=0;const g={};for(let t=0;t<l.length;t++){const e=l[t];g[e]=this._getTextMetrics(e),c=Math.max(c,g[e].height),d=Math.max(d,g[e].descent)}this.glyphSize=Math.max(this.glyphSize,c);const p=this.glyphSize+2*this.padding,f=this.glyphSize+2*this.padding,m=this.glyphSize/2+this.padding,u=f-d-this.padding;let x=0,y=0;for(let e=0;e<l.length;e++){const n=l[e],c=t.getCodePoint(l[e]);let S=this.fontSize;r.ctx.font=`${this.fontWeight} ${S.toString()}px ${this.fontName}`,r.ctx.textAlign="center",r.ctx.textBaseline="alphabetic";let z=r.ctx.measureText(n).width;z>S&&(S=this.fontSize*this.fontSize/z,r.ctx.font=`${this.fontWeight} ${S.toString()}px ${this.fontName}`,z=this.fontSize),this.renderCharacter(r.ctx,n,x+m,y+u,h);const v=this.padding+(this.glyphSize-z)/2,_=-this.padding+g[n].descent-d,b=z;this._addChar(this.data,n,c,x,y,p,f,v,_,b,o-1,s,i),x+=p,x+p>s&&(x=0,y+=f,y+f>i&&(r=this._getAtlas(o++),r.clear(a),y=0))}this.atlases.splice(o).forEach((t=>t.destroy())),this.atlases.forEach((t=>t.texture.upload())),this.fire("render")}_createJson(t,e,s,i){return{version:3,intensity:this.intensity,info:{face:e,width:s,height:i,maps:[{width:s,height:i}]},chars:{}}}_addChar(t,e,s,i,h,n,a,o,r,l,c,d,g){t.info.maps.length<c+1&&t.info.maps.push({width:d,height:g});const p=this.fontSize/32;t.chars[e]={id:s,letter:e,x:i,y:h,width:n,height:a,xadvance:l/p,xoffset:o/p,yoffset:(r+this.padding)/p,scale:p,range:1,map:c,bounds:[0,0,n/p,a/p]}}_normalizeCharsSet(e){const s=this.app.systems.element.getUnicodeConverter();s&&(e=s(e));const i={},h=t.getSymbols(e);for(let t=0;t<h.length;t++){const e=h[t];i[e]||(i[e]=e)}return Object.keys(i).sort()}_getTextMetrics(t){const e=document.createElement("span");e.id="content-span",e.innerHTML=t;const s=document.createElement("div");s.id="content-block",s.style.display="inline-block",s.style.width="1px",s.style.height="0px";const i=document.createElement("div");i.appendChild(e),i.appendChild(s),i.style.font=`${this.fontSize}px ${this.fontName}`;document.body.appendChild(i);let h=-1,n=-1,a=-1;try{s.style["vertical-align"]="baseline",h=s.offsetTop-e.offsetTop,s.style["vertical-align"]="bottom",a=s.offsetTop-e.offsetTop,n=a-h}finally{document.body.removeChild(i)}return{ascent:h,descent:n,height:a}}get textures(){return this.atlases.map((t=>t.texture))}}export{l as CanvasFont};