import * as THREE from "three" ; import { QUAD_VERTEX_MEMORY_FOOTPRINT } from "../rendering/TextGeometry" ; import { FontStyle , FontVariant } from "../rendering/TextStyle" ; import { TypesettingUtils } from "../utils/TypesettingUtils" ; import { UnicodeUtils } from "../utils/UnicodeUtils" ; import { Typesetter , TypesettingParameters } from "./Typesetter" ; hidden export interface PathTypesettingParameters extends TypesettingParameters { /**
     * Path to be followed when arranging glyphs.
     */ path : THREE . Path | THREE . CurvePath < THREE . Vector2 > ; /**
     * If `true`, text on a path will be placed even when its size its bigger than the path's size.
     */ pathOverflow : boolean ; } /**
 * [[Typesetter]] implementation that arranges glyphs alongside a specified path.
 */ export class PathTypesetter implements Typesetter { private readonly m_tempTransform : THREE . Matrix3 ; private readonly m_tempCorners : THREE . Vector3 [ ] ; private m_tempLineDirection : UnicodeUtils . Direction ; private m_tempRunDirection : UnicodeUtils . Direction ; private m_tempPixelSize : number ; private m_tempPixelBgSize : number ; private m_tempScale : number ; private m_tempSmallCaps : boolean ; private readonly m_tempPathPosition : THREE . Vector3 ; private m_tempPathLength : number ; private m_tempPathOffset : number ; private m_currentParams ? : PathTypesettingParameters ; returns constructor ( ) { this . m_tempTransform = new THREE . Matrix3 ( ) ; this . m_tempCorners = [ new THREE . Vector3 ( ) , new THREE . Vector3 ( ) , new THREE . Vector3 ( ) , new THREE . Vector3 ( ) ] ; this . m_tempLineDirection = UnicodeUtils . Direction . LTR ; this . m_tempRunDirection = UnicodeUtils . Direction . LTR ; this . m_tempPixelSize = 1.0 ; this . m_tempPixelBgSize = 1.0 ; this . m_tempScale = 1.0 ; this . m_tempSmallCaps = false ; this . m_tempPathPosition = new THREE . Vector3 ( ) ; this . m_tempPathLength = 0.0 ; this . m_tempPathOffset = 0.0 ; } param params returns arrangeGlyphs ( params ) { this . m_currentParams = params ; this . m_tempLineDirection = TypesettingUtils . getDirection ( this . m_currentParams . glyphs , 0 ) ; this . m_tempRunDirection = this . m_tempLineDirection ; this . m_tempPixelSize = TypesettingUtils . getPixelSize ( this . m_currentParams . textRenderStyle . fontSize . size , this . m_currentParams . textRenderStyle . fontSize . unit , this . m_currentParams . fontCatalog . size ) ; this . m_tempScale = this . m_tempPixelSize / this . m_currentParams . fontCatalog . size ; this . m_tempPixelBgSize = Math . min ( TypesettingUtils . getPixelSize ( this . m_currentParams . textRenderStyle . fontSize . backgroundSize , this . m_currentParams . textRenderStyle . fontSize . unit , this . m_currentParams . fontCatalog . size ) , this . m_currentParams ! . fontCatalog . distanceRange * this . m_tempScale ) ; this . m_tempSmallCaps = this . m_currentParams ! . smallCapsArray !== undefined ; this . m_tempPathLength = this . m_currentParams . path . getLength ( ) ; this . m_tempPathOffset = 0.0 ; const isOnlyMeasured = this . m_currentParams . globalBounds !== undefined && this . m_currentParams . vertexBuffer === undefined ; let isBidirectional = false ; let pathWidth = 0.0 ; for ( let i = 0 ; i < this . m_currentParams . glyphs . length ; ++ i ) { const glyphData = this . m_currentParams . glyphs [ i ] ; if ( ! glyphData . isInCache && ! isOnlyMeasured ) { return false ; } if ( ! UnicodeUtils . isPrintable ( glyphData . codePoint ) ) { continue ; } if ( ! isBidirectional && glyphData . direction === - this . m_tempLineDirection ) { isBidirectional = true ; } pathWidth += ( glyphData . advanceX + this . m_currentParams . textLayoutStyle . tracking ) * this . m_tempScale * ( this . m_tempSmallCaps ? TypesettingUtils . getSmallCapsScale ( this . m_currentParams . glyphs , this . m_currentParams . smallCapsArray ! , i , this . m_currentParams . textRenderStyle . fontVariant ) : 1.0 ) ; } this . m_tempPathOffset = Math . min ( Math . max ( - this . m_currentParams . textLayoutStyle . horizontalAlignment + ( this . m_currentParams . textLayoutStyle . horizontalAlignment * pathWidth ) / this . m_tempPathLength , 0 ) , 1 ) ; return this . placeLine ( this . m_tempLineDirection , isBidirectional ) ; } private placeLine ( direction , isBidirectional ) { if ( ! isBidirectional ) { return this . placeRun ( 0 , this . m_currentParams ! . glyphs . length - 1 , direction ) ; } const glyphDataArray = this . m_currentParams ! . glyphs ; let runStart = 0 ; for ( let i = runStart ; i < glyphDataArray . length ; ++ i ) { const glyphData = glyphDataArray [ i ] ; if ( glyphData . direction === - this . m_tempRunDirection ) { if ( ! this . placeRun ( runStart , i - 1 , this . m_tempRunDirection ) ) { return false ; } runStart = i ; this . m_tempRunDirection *= - 1.0 ; } else if ( glyphData . direction === UnicodeUtils . Direction . Neutral && this . m_tempRunDirection === - direction ) { let neutralIdx = i ; while ( neutralIdx + 1 < glyphDataArray . length && Math . abs ( glyphDataArray [ neutralIdx ] . direction ) !== 1 ) { ++ neutralIdx ; } if ( glyphDataArray [ neutralIdx ] . direction !== this . m_tempRunDirection ) { if ( ! this . placeRun ( runStart , i - 1 , this . m_tempRunDirection ) ) { return false ; } runStart = i ; this . m_tempRunDirection *= - 1.0 ; } } } if ( runStart < glyphDataArray . length ) { if ( ! this . placeRun ( runStart , glyphDataArray . length - 1 , this . m_tempRunDirection ) ) { return false ; } } return true ; } private placeRun ( startIdx , endIdx , direction ) { const glyphDataArray = this . m_currentParams ! . glyphs ; const smallCapsArray = this . m_currentParams ! . smallCapsArray ; const fontCatalog = this . m_currentParams ! . fontCatalog ; const textRenderStyle = this . m_currentParams ! . textRenderStyle ; const textLayoutStyle = this . m_currentParams ! . textLayoutStyle ; const position = this . m_currentParams ! . position ; const geometry = this . m_currentParams ! . geometry ; const globalBounds = this . m_currentParams ! . globalBounds ; const individualBounds = this . m_currentParams ! . individualBounds ; const vertexBuffer = this . m_currentParams ! . vertexBuffer ; const path = this . m_currentParams ! . path ; const defaultGlyphRotation = textRenderStyle . rotation ; const normalDisplacement = textLayoutStyle . verticalAlignment * glyphDataArray [ 0 ] . font . metrics . capHeight * this . m_tempScale ; const start = direction === UnicodeUtils . Direction . LTR ? startIdx : endIdx ; const end = direction === UnicodeUtils . Direction . LTR ? endIdx : startIdx ; for ( let i = start ; direction === UnicodeUtils . Direction . RTL ? i >= end : i <= end ; i += direction ) { const glyphData = glyphDataArray [ i ] ; if ( ! UnicodeUtils . isPrintable ( glyphData . codePoint ) ) { continue ; } if ( startIdx !== endIdx && i !== 0 && direction === UnicodeUtils . Direction . RTL && glyphData . direction === UnicodeUtils . Direction . Weak ) { let weakRunStart = i ; let weakGlyph = glyphDataArray [ weakRunStart - 1 ] ; while ( weakRunStart !== startIdx && ( weakGlyph . direction === UnicodeUtils . Direction . Weak || ( weakGlyph . direction === UnicodeUtils . Direction . Neutral && ! UnicodeUtils . isWhiteSpace ( weakGlyph . codePoint ) ) ) ) { -- weakRunStart ; weakGlyph = glyphDataArray [ weakRunStart - 1 ] ; } this . placeRun ( Math . max ( weakRunStart , startIdx ) , i , UnicodeUtils . Direction . LTR ) ; i = weakRunStart ; continue ; } const glyphFont = glyphData . font ; const glyphFontMetrics = glyphFont . metrics ; const fontStyle = textRenderStyle . fontStyle ; const isBoldEmulated = ( fontStyle === FontStyle . Bold && glyphFont . bold === undefined ) || ( fontStyle === FontStyle . BoldItalic && glyphFont . bold === undefined && glyphFont . boldItalic === undefined ) ; const isItalicEmulated = ( fontStyle === FontStyle . Italic && glyphFont . italic === undefined ) || ( fontStyle === FontStyle . BoldItalic && glyphFont . italic === undefined && glyphFont . boldItalic === undefined ) ; const isSmallCaps = this . m_tempSmallCaps ? smallCapsArray ! [ i ] && textRenderStyle . fontVariant === FontVariant . SmallCaps : false ; const smallCapsScale = isSmallCaps ? glyphFontMetrics . xHeight / glyphFontMetrics . capHeight : 1.0 ; const glyphScale = this . m_tempScale * smallCapsScale ; const emulationWeight = ( ( isBoldEmulated ? 0.02 : 0.0 ) + ( isSmallCaps ? 0.01 : 0.0 ) ) * ( fontCatalog . size / fontCatalog . distanceRange ) ; const bgWeight = ( 0.5 * this . m_tempPixelBgSize ! ) / ( fontCatalog . distanceRange * Math . max ( glyphScale , 1.0 ) ) ; const isMirrored = UnicodeUtils . isRtlMirrored ( glyphData . codePoint ) && direction === UnicodeUtils . Direction . RTL ; const verticalOffset = glyphFontMetrics . lineHeight - glyphFontMetrics . base - glyphFontMetrics . distanceRange * 0.5 ; const textPoint = path . getPoint ( this . m_tempPathOffset ) ; if ( textPoint === null ) { return this . m_currentParams ! . pathOverflow ; } const tangent = path . getTangent ( this . m_tempPathOffset ) ; const normal = new THREE . Vector2 ( - tangent . y , tangent . x ) . multiplyScalar ( normalDisplacement ) ; const angle = Math . atan2 ( tangent . y , tangent . x ) ; this . m_tempPathPosition . set ( normal . x + textPoint . x , normal . y + textPoint . y , position . z ) ; textRenderStyle . rotation = defaultGlyphRotation + angle ; TypesettingUtils . computeGlyphTransform ( this . m_tempTransform , this . m_tempPathPosition , glyphScale , 0.0 , textRenderStyle . rotation ) ; for ( let j = 0 ; j < 4 ; ++ j ) { const glyphVertexPosition = glyphData . positions [ j ] ; const horizontalOffset = isItalicEmulated && j > 1 ? TypesettingUtils . OBLIQUE_OFFSET * glyphFontMetrics . size : 0.0 ; this . m_tempCorners [ j ] . set ( glyphVertexPosition . x + horizontalOffset , glyphVertexPosition . y - verticalOffset , glyphVertexPosition . z ) ; this . m_tempCorners [ j ] . applyMatrix3 ( this . m_tempTransform ) ; this . m_tempCorners [ j ] . x -= position . x ; this . m_tempCorners [ j ] . y -= position . y ; } if ( globalBounds === undefined && vertexBuffer === undefined ) { if ( ! geometry . add ( glyphData , this . m_tempCorners , emulationWeight , emulationWeight + bgWeight , isMirrored , textRenderStyle ) ) { return false ; } } else { if ( globalBounds !== undefined ) { TypesettingUtils . updateBounds ( this . m_tempCorners , globalBounds , individualBounds ) ; } if ( vertexBuffer !== undefined ) { geometry . addToBuffer ( vertexBuffer , i * QUAD_VERTEX_MEMORY_FOOTPRINT , glyphData , this . m_tempCorners , emulationWeight , emulationWeight + bgWeight , isMirrored , textRenderStyle ) ; } } textRenderStyle . rotation = defaultGlyphRotation ; this . m_tempPathOffset += ( ( glyphData . advanceX + textLayoutStyle . tracking ) * glyphScale ) / this . m_tempPathLength ; } return true ; } }