import { assert , dirname , log , logIf , LogLevel } from '../auxiliaries' ; import { GLfloat2 , GLfloat4 } from '../tuples' ; import { FontFace } from './fontface' ; import { Glyph } from './glyph' ; type StringPairs = Map < string , string > ; /**
 * Transforms input raw data of a text-based font file to a font face (@see  FontFace ) specification. All
 * referenced pages/glyph atlases will be loaded and setup as well. This is intended to be used directly by the
 * FontFace and is not required to be exposed to webgl-operates public API.
  export class FontFaceLoader { param stream param fontFace protected static processInfo ( stream , fontFace ) { const pairs = new Map < string , string > ( ) ; const success = this . readKeyValuePairs ( stream , [ 'size' , 'padding' ] , pairs ) ; if ( ! success ) { return false ; } fontFace . size = parseFloat ( pairs . get ( 'size' ) ! ) ; const values = pairs . get ( 'padding' ) ! . split ( ',' ) ; if ( values . length !== 4 ) { log ( LogLevel . Warning , `expected 4 values for padding, given ${ values } (${ values . length })` ) ; return false ; } const padding = [ parseFloat ( values [ 0 ] ) , parseFloat ( values [ 1 ] ) , parseFloat ( values [ 2 ] ) , parseFloat ( values [ 3 ] ) , ] ; fontFace . glyphTexturePadding = padding ; return true ; } param stream param fontFace protected static processCommon ( stream , fontFace ) { const pairs = new Map < string , string > ( ) ; const success = this . readKeyValuePairs ( stream , [ 'lineHeight' , 'base' , 'scaleW' , 'scaleH' ] , pairs ) ; if ( ! success ) { return false ; } fontFace . base = parseFloat ( pairs . get ( 'base' ) ! ) ; if ( pairs . has ( 'ascent' ) ) { fontFace . ascent = parseFloat ( pairs . get ( 'ascent' ) ! ) ; } if ( pairs . has ( 'descent' ) ) { fontFace . descent = parseFloat ( pairs . get ( 'descent' ) ! ) ; } fontFace . lineHeight = parseFloat ( pairs . get ( 'lineHeight' ) ! ) ; fontFace . glyphTextureExtent = [ parseFloat ( pairs . get ( 'scaleW' ) ! ) , parseFloat ( pairs . get ( 'scaleH' ) ! ) , ] ; return true ; } param stream param fontFace param url returns protected static processPage ( stream , fontFace , url ) { const pairs = new Map < string , string > ( ) ; const success = this . readKeyValuePairs ( stream , [ 'file' ] , pairs ) ; if ( ! success ) { return undefined ; } const path = dirname ( url ) ; let page = pairs . get ( 'file' ) ! ; page = page . replace ( /['"]+/g , '' ) ; return fontFace . glyphTexture . fetch ( `${ path }/${ page }` , true , true ) . catch ( ( ) => Promise . reject ( `page '${ page }' referenced in font file '${ url }' was not found` ) ) ; } param fontFace param pageFileUrlsByPageID returns protected static processPages ( fontFace , pageFileUrlsByPageID ) { logIf ( pageFileUrlsByPageID . size !== 1 , LogLevel . Warning , 'glyph atlas supports a single page only (impl. for multiple pages pending)' ) ; assert ( pageFileUrlsByPageID . has ( 0 ) , 'page with page ID 0 expected for glyph atlas (impl. for multiple pages pending)' ) ; const page = 0 ; const url = pageFileUrlsByPageID . get ( page ) ; if ( ! url ) { return undefined ; } return fontFace . glyphTexture . fetch ( `${ url }` , true , true ) . catch ( ( ) => Promise . reject ( `fetching page '${ page }' from '${ url }' failed` ) ) ; } param stream param fontFace protected static processChar ( stream , fontFace ) { const pairs = new Map < string , string > ( ) ; const success = this . readKeyValuePairs ( stream , [ 'id' , 'x' , 'y' , 'width' , 'height' , 'xoffset' , 'yoffset' , 'xadvance' ] , pairs ) ; if ( ! success ) { return false ; } const index = parseInt ( pairs . get ( 'id' ) ! , 10 ) ; logIf ( index <= 0.0 , LogLevel . Warning , `expected glyph index to be greater than 0, given ${ index }` ) ; const glyph = new Glyph ( ) ; glyph . index = index ; const extentScale = [ 1.0 / fontFace . glyphTextureExtent [ 0 ] , 1.0 / fontFace . glyphTextureExtent [ 1 ] , ] ; const extent = [ parseFloat ( pairs . get ( 'width' ) ! ) , parseFloat ( pairs . get ( 'height' ) ! ) , ] ; glyph . subTextureOrigin = [ parseFloat ( pairs . get ( 'x' ) ! ) * extentScale [ 0 ] , 1.0 - ( parseFloat ( pairs . get ( 'y' ) ! ) + extent [ 1 ] ) * extentScale [ 1 ] , ] ; glyph . extent = extent ; glyph . subTextureExtent [ 0 ] = extent [ 0 ] * extentScale [ 0 ] ; glyph . subTextureExtent [ 1 ] = extent [ 1 ] * extentScale [ 1 ] ; glyph . bearingFromFontBaseAndOffset ( fontFace . base , parseFloat ( pairs . get ( 'xoffset' ) ! ) , parseFloat ( pairs . get ( 'yoffset' ) ! ) ) ; glyph . advance = parseFloat ( pairs . get ( 'xadvance' ) ! ) ; fontFace . addGlyph ( glyph ) ; return true ; } param stream param fontFace protected static processKerning ( stream , fontFace ) { const pairs = new Map < string , string > ( ) ; const success = this . readKeyValuePairs ( stream , [ 'first' , 'second' , 'amount' ] , pairs ) ; if ( ! success ) { return false ; } const first = parseInt ( pairs . get ( 'first' ) ! , 10 ) ; if ( first <= 0.0 ) { log ( LogLevel . Warning , `expected kerning's first to be greater than 0, given ${ first }` ) ; return false ; } const second = parseInt ( pairs . get ( 'second' ) ! , 10 ) ; if ( second <= 0.0 ) { log ( LogLevel . Warning , `expected kerning's second to be greater than 0, given ${ second }` ) ; return false ; } const kerning = parseFloat ( pairs . get ( 'amount' ) ! ) ; fontFace . setKerning ( first , second , kerning ) ; return true ; } param stream param mandatoryKeys param result returns protected static readKeyValuePairs ( stream , mandatoryKeys , resultPairs ) { let key ; let value ; for ( const s of stream ) { const pair = s . split ( '=' ) ; key = pair [ 0 ] ; value = pair [ 1 ] ; resultPairs . set ( key , value ) ; } let valid = true ; mandatoryKeys . forEach ( ( key ) => valid = valid && resultPairs . has ( key ) ) ; if ( ! valid ) { log ( LogLevel . Warning , `Not all required keys are provided! Mandatory keys: ${ mandatoryKeys }` ) ; } return valid ; } param fontFace param size protected static findAscentAndDescentIfNoneProvided ( fontFace , size ) { if ( fontFace . ascent > 0.0 && fontFace . descent < 0.0 ) { return ; } if ( fontFace . ascent > 0.0 ) { fontFace . descent = fontFace . ascent - size ; } if ( fontFace . descent < 0.0 ) { fontFace . ascent = fontFace . descent - size ; } let maximumYBearing = Number . MIN_VALUE ; for ( const i of fontFace . arrayOfGlyphIndices ( ) ) { if ( fontFace . glyph ( i ) . extent [ 1 ] === 0.0 ) { continue ; } maximumYBearing = Math . max ( fontFace . glyph ( i ) . bearing [ 1 ] , maximumYBearing ) ; } fontFace . ascent = maximumYBearing ; fontFace . descent = fontFace . ascent - size ; log ( LogLevel . Debug , `ascent not specified, derived ${ fontFace . ascent } from maximum y-offset` ) ; } param fontFace param data param url param headless static process ( fontFace , data , fontFileUrl , pageFileUrlsByPageID , headless = false ) { const lines = data . split ( '\n' ) ; const promises = new Array < Promise < void > > ( ) ; let status = true ; if ( ! headless && pageFileUrlsByPageID ) { const promise = this . processPages ( fontFace , pageFileUrlsByPageID ) ; if ( promise !== undefined ) { promises . push ( promise ) ; } } for ( const line of lines ) { let attributes = line . split ( ' ' ) ; const identifier = attributes [ 0 ] ; attributes = attributes . slice ( 1 ) ; switch ( identifier ) { case 'info' : status = this . processInfo ( attributes , fontFace ) ; break ; case 'common' : status = this . processCommon ( attributes , fontFace ) ; break ; case 'page' : if ( headless || pageFileUrlsByPageID ) { break ; } const promise = this . processPage ( attributes , fontFace , fontFileUrl ) ; if ( promise !== undefined ) { promises . push ( promise ) ; } break ; case 'char' : status = this . processChar ( attributes , fontFace ) ; break ; case 'kerning' : this . processKerning ( attributes , fontFace ) ; break ; default : break ; } if ( status === false ) { break ; } } FontFaceLoader . findAscentAndDescentIfNoneProvided ( fontFace , fontFace . size ) ; if ( fontFace . size <= 0.0 ) { log ( LogLevel . Warning , `expected fontFace.size to be greater than 0, given ${ fontFace . size }` ) ; } return Promise . all ( promises ) . then ( ( ) => fontFace ) ; } }