import { Texture } from "../../graphics/texture/Texture" ; import * as Attributes from "../../core/Attributes" ; import { FontShadow , DefaultFontShadow } from "./FontShadow" ; import { WebGL } from "../../graphics/WebGL" ; import { TextAlignment } from "../Alignment" ; import { TextureFiltering } from "../../graphics/GraphicTypes" ; namespace Private { export function wordWrap ( context , text , maxWidth ) { let finalLines = [ ] ; var words = text . split ( " " ) ; var line = "" ; for ( let n = 0 ; n < words . length ; n ++ ) { let testLine = line + words [ n ] + " " ; let metrics = context . measureText ( testLine ) ; let testWidth = metrics . width ; if ( testWidth > maxWidth && n > 0 ) { finalLines . push ( line ) ; line = words [ n ] + " " ; } else { line = testLine ; } } finalLines . push ( line ) ; return finalLines ; } } @ Attributes . displayName ( "Font Texture" ) export class FontTexture extends Texture { set scaleFactor ( scale ) { if ( scale !== this . _scaleFactor ) { this . _isDirty = true ; this . _scaleFactor = scale ; } } set maxWidth ( maxWidth ) { if ( maxWidth !== this . _maxWidth ) { this . _isDirty = true ; this . _maxWidth = maxWidth ; } } set family ( family ) { if ( family !== this . _fontFamily ) { this . _isDirty = true ; this . _fontFamily = family ; } } set text ( text ) { if ( text !== this . _text ) { this . _text = text ; if ( this . tryUpdateTextureInEditor ( ) ) { return ; } this . _isDirty = true ; } } set size ( size ) { if ( size !== this . _fontSize ) { this . _fontSize = size ; if ( this . tryUpdateTextureInEditor ( ) ) { return ; } this . _isDirty = true ; } } set bold ( bold ) { if ( bold !== this . _bold ) { this . _bold = bold ; if ( this . tryUpdateTextureInEditor ( ) ) { return ; } this . _isDirty = true ; } } set italic ( italic ) { if ( italic !== this . _italic ) { this . _italic = italic ; if ( this . tryUpdateTextureInEditor ( ) ) { return ; } this . _isDirty = true ; } } set alignment ( alignment ) { if ( this . _alignment !== alignment ) { this . _alignment = alignment ; if ( this . tryUpdateTextureInEditor ( ) ) { return ; } this . _isDirty = true ; } } set shadow ( shadow ) { if ( this . _shadow !== shadow ) { this . _shadow = shadow ; if ( this . tryUpdateTextureInEditor ( ) ) { return ; } this . _isDirty = true ; } } set filtering ( filtering ) { if ( this . _filtering !== filtering ) { this . _filtering = filtering ; if ( this . tryUpdateTextureInEditor ( ) ) { return ; } this . _isDirty = true ; } } private _canvas ? : HTMLCanvasElement ; private _isDirty = true ; private _text ! : string ; private _fontSize ! : number ; private _bold ! : boolean ; private _italic ! : boolean ; private _shadow ? : FontShadow ; private _fontFamily ! : string ; private _maxWidth = 0 ; private _alignment ! : number ; private _scaleFactor = 1 ; private _filtering = TextureFiltering . Nearest ; dirtify ( ) { this . _isDirty = true ; } getWidth ( ) { return ( this . _canvas ?. width ?? 0 ) / this . _scaleFactor ; } getHeight ( ) { return ( this . _canvas ?. height ?? 0 ) / this . _scaleFactor ; } begin ( stage ) { const gl = WebGL . context ; let textureValid = true ; if ( ! this . _textureId ) { if ( ! this . _canvas ) { this . _canvas = document . createElement ( "canvas" ) ; } const context = this . _canvas . getContext ( "2d" ) ; if ( context ) { this . _textureId = gl . createTexture ( ) ; textureValid = this . updateTexture ( context ) ; } } else if ( this . _isDirty ) { const context = this . _canvas ?. getContext ( "2d" ) ; if ( context ) { textureValid = this . updateTexture ( context ) ; } } if ( textureValid ) { gl . activeTexture ( gl . TEXTURE0 + stage ) ; gl . bindTexture ( gl . TEXTURE_2D , this . _textureId ) ; return true ; } return false ; } destroy ( ) { delete this . _canvas ; super . destroy ( ) ; } private tryUpdateTextureInEditor ( ) { if ( process . env . CONFIG === "editor" ) { if ( ! this . _isDirty ) { this . updateTexture ( this . _canvas ?. getContext ( "2d" ) as CanvasRenderingContext2D ) ; return true ; } } return false ; } private updateTexture ( context ) { let gl = WebGL . context ; let font = `${ this . _italic ? "italic " : "" }` ; font += `${ this . _bold ? "bold " : "" }` ; let fontSize = this . _fontSize * this . _scaleFactor ; font += `${ fontSize }px ${ this . _fontFamily }` ; context . font = font ; let globalLines = this . _text . split ( "\\n" ) ; if ( globalLines . length === 0 ) { return false ; } let isMultiline = this . _maxWidth > 0 ; const canvas = this . _canvas as HTMLCanvasElement ; if ( isMultiline ) { let canvasWidth = this . _maxWidth * this . _scaleFactor ; let lines = [ ] ; for ( let globalLine of globalLines ) { lines = lines . concat ( Private . wordWrap ( context , globalLine , canvasWidth ) ) ; } if ( lines . length === 0 ) { return false ; } let lineHeight = this . getTextHeight ( font , lines [ 0 ] ) ; canvas . width = canvasWidth ; canvas . height = lineHeight * lines . length ; this . drawText ( context , font , lines , lineHeight ) ; } else { let textWidth = context . measureText ( globalLines [ 0 ] ) . width ; for ( let i = 1 ; i < globalLines . length ; ++ i ) { textWidth = Math . max ( textWidth , context . measureText ( globalLines [ i ] ) . width ) ; } if ( textWidth === 0 ) { return false ; } let lineHeight = this . getTextHeight ( font , globalLines [ 0 ] ) ; let canvasWidth = textWidth ; let canvasHeight = lineHeight * globalLines . length ; canvas . width = canvasWidth ; canvas . height = canvasHeight ; this . drawText ( context , font , globalLines , lineHeight ) ; } gl . activeTexture ( gl . TEXTURE0 ) ; gl . bindTexture ( gl . TEXTURE_2D , this . _textureId ) ; gl . pixelStorei ( gl . UNPACK_FLIP_Y_WEBGL , 1 ) ; gl . pixelStorei ( gl . UNPACK_ALIGNMENT , 4 ) ; if ( this . _filtering === TextureFiltering . Nearest ) { gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_MAG_FILTER , gl . NEAREST ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_MIN_FILTER , gl . NEAREST ) ; } else { gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_MAG_FILTER , gl . LINEAR ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_MIN_FILTER , gl . LINEAR ) ; } gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_WRAP_S , gl . CLAMP_TO_EDGE ) ; gl . texParameteri ( gl . TEXTURE_2D , gl . TEXTURE_WRAP_T , gl . CLAMP_TO_EDGE ) ; gl . texImage2D ( gl . TEXTURE_2D , 0 , gl . RGBA , gl . RGBA , gl . UNSIGNED_BYTE , canvas ) ; this . _isDirty = false ; return true ; } private getTextHeight ( font , text ) { var d = document . createElement ( "span" ) ; d . setAttribute ( "style" , `font: ${ font };` ) ; d . textContent = text ; document . body . appendChild ( d ) ; let height = d . offsetHeight ; document . body . removeChild ( d ) ; return height ; } private drawText ( context , font , lines , lineHeight ) { context . font = font ; context . textBaseline = "middle" ; context . fillStyle = "white" ; if ( this . _shadow ) { this . _shadow . applyToContext ( context ) ; } else { context . shadowColor = "" ; } const canvas = this . _canvas as HTMLCanvasElement ; context . clearRect ( 0 , 0 , canvas . width , canvas . height ) ; const halfLine = Math . ceil ( lineHeight / 2 ) ; for ( let i = 0 ; i < lines . length ; ++ i ) { let x = 0 ; if ( this . _alignment === TextAlignment . Center ) { x = ( canvas . width - context . measureText ( lines [ i ] ) . width ) / 2 ; } else if ( this . _alignment === TextAlignment . Right ) { x = canvas . width - context . measureText ( lines [ i ] ) . width ; } context . fillText ( lines [ i ] , x , ( i * lineHeight ) + halfLine ) ; } } }