diff --git a/examples/common/MemMonitor.ts b/examples/common/MemMonitor.ts index 4629efdb6..c41ba3014 100644 --- a/examples/common/MemMonitor.ts +++ b/examples/common/MemMonitor.ts @@ -65,6 +65,7 @@ export class MemMonitor extends Component { this.node.color = 0xffffffaa; this.node.w = 400; this.node.h = BAR_HEIGHT + MARGIN * 2; + const fontFamily = 'SDF-Ubuntu'; this.bar = renderer.createNode({ x: this.node.w - BAR_WIDTH - MARGIN, @@ -118,7 +119,7 @@ export class MemMonitor extends Component { x: -15, y: 0, text: 'Critical', - fontFamily: 'Ubuntu', + fontFamily: fontFamily, parent: this.bar, fontSize: 20, color: 0xff0000ff, @@ -140,7 +141,7 @@ export class MemMonitor extends Component { x: -15, y: 0, text: 'Target', - fontFamily: 'Ubuntu', + fontFamily: fontFamily, parent: this.bar, fontSize: 20, color: 0x000000ff, @@ -165,7 +166,7 @@ export class MemMonitor extends Component { x: MARGIN, y: infoTextY, text: '', - fontFamily: 'Ubuntu', + fontFamily: fontFamily, parent: this.node, fontSize: INFO_TEXT_SIZE, lineHeight: INFO_TEXT_LINEHEIGHT, @@ -176,7 +177,7 @@ export class MemMonitor extends Component { x: MARGIN, y: infoTextY + INFO_TEXT_LINEHEIGHT, text: '', - fontFamily: 'Ubuntu', + fontFamily: fontFamily, parent: this.node, fontSize: INFO_TEXT_SIZE, lineHeight: INFO_TEXT_LINEHEIGHT, @@ -187,7 +188,7 @@ export class MemMonitor extends Component { x: MARGIN, y: infoTextY + INFO_TEXT_LINEHEIGHT * 2, text: 'Base:', - fontFamily: 'Ubuntu', + fontFamily: fontFamily, parent: this.node, fontSize: INFO_TEXT_SIZE, lineHeight: INFO_TEXT_LINEHEIGHT, @@ -198,7 +199,7 @@ export class MemMonitor extends Component { x: MARGIN, y: infoTextY + INFO_TEXT_LINEHEIGHT * 3, text: '', - fontFamily: 'Ubuntu', + fontFamily: fontFamily, parent: this.node, fontSize: INFO_TEXT_SIZE, lineHeight: INFO_TEXT_LINEHEIGHT, @@ -209,7 +210,7 @@ export class MemMonitor extends Component { x: MARGIN, y: infoTextY + INFO_TEXT_LINEHEIGHT * 6, text: '', - fontFamily: 'Ubuntu', + fontFamily: fontFamily, parent: this.node, fontSize: INFO_TEXT_SIZE, lineHeight: INFO_TEXT_LINEHEIGHT, @@ -220,7 +221,7 @@ export class MemMonitor extends Component { x: MARGIN, y: infoTextY + INFO_TEXT_LINEHEIGHT * 9, text: '', - fontFamily: 'Ubuntu', + fontFamily: fontFamily, parent: this.node, fontSize: INFO_TEXT_SIZE, lineHeight: INFO_TEXT_LINEHEIGHT, diff --git a/examples/common/PageContainer.ts b/examples/common/PageContainer.ts index 6d392c7ec..0798af5cf 100644 --- a/examples/common/PageContainer.ts +++ b/examples/common/PageContainer.ts @@ -61,7 +61,7 @@ export class PageContainer extends Component { }); this.titleNode = renderer.createTextNode({ - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: TITLE_FONT_SIZE, x: PADDING, y: PADDING, @@ -72,7 +72,7 @@ export class PageContainer extends Component { this.settings = settings; this.pageNumberNode = renderer.createTextNode({ - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, x: PADDING, y: this.node.h - 30 - PADDING, diff --git a/examples/common/installFonts.ts b/examples/common/installFonts.ts index bd5bc942f..f28dc2e0c 100644 --- a/examples/common/installFonts.ts +++ b/examples/common/installFonts.ts @@ -22,7 +22,7 @@ import { type Stage } from '@lightningjs/renderer'; export async function installFonts(stage: Stage) { // Load Canvas fonts using the new unified API stage.loadFont('canvas', { - fontFamily: 'NotoSans', + fontFamily: 'Canvas-NotoSans', fontUrl: './fonts/NotoSans-Regular.ttf', metrics: { ascender: 1069, @@ -33,7 +33,7 @@ export async function installFonts(stage: Stage) { }); stage.loadFont('canvas', { - fontFamily: 'Ubuntu', + fontFamily: 'Canvas-Ubuntu', fontUrl: './fonts/Ubuntu-Regular.ttf', metrics: { ascender: 776, @@ -44,7 +44,7 @@ export async function installFonts(stage: Stage) { }); stage.loadFont('canvas', { - fontFamily: 'Ubuntu-No-Metrics', + fontFamily: 'Canvas-Ubuntu-No-Metrics', fontUrl: './fonts/Ubuntu-Regular.ttf', }); @@ -56,7 +56,7 @@ export async function installFonts(stage: Stage) { }; stage.loadFont('canvas', { - fontFamily: 'Ubuntu-Modified-Metrics', + fontFamily: 'Canvas-Ubuntu-Modified-Metrics', fontUrl: './fonts/Ubuntu-Regular.ttf', metrics: ubuntuModifiedMetrics, }); @@ -64,7 +64,7 @@ export async function installFonts(stage: Stage) { // Load SDF fonts for WebGL renderer using the new unified API if (stage.renderer.mode === 'webgl') { stage.loadFont('sdf', { - fontFamily: 'NotoSans', + fontFamily: 'SDF-NotoSans', atlasUrl: './fonts/NotoSans-Regular.ssdf.png', atlasDataUrl: './fonts/NotoSans-Regular.ssdf.json', metrics: { @@ -76,7 +76,7 @@ export async function installFonts(stage: Stage) { }); stage.loadFont('sdf', { - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', atlasUrl: './fonts/Ubuntu-Regular.msdf.png', atlasDataUrl: './fonts/Ubuntu-Regular.msdf.json', // Instead of supplying `metrics` this font will rely on the ones @@ -84,14 +84,14 @@ export async function installFonts(stage: Stage) { }); stage.loadFont('sdf', { - fontFamily: 'Ubuntu-Modified-Metrics', + fontFamily: 'SDF-Ubuntu-Modified-Metrics', atlasUrl: './fonts/Ubuntu-Regular.msdf.png', atlasDataUrl: './fonts/Ubuntu-Regular.msdf.json', metrics: ubuntuModifiedMetrics, }); stage.loadFont('sdf', { - fontFamily: 'Ubuntu-ssdf', + fontFamily: 'SDF-Ubuntu-ssdf', atlasUrl: './fonts/Ubuntu-Regular.ssdf.png', atlasDataUrl: './fonts/Ubuntu-Regular.ssdf.json', metrics: { diff --git a/examples/common/paginateTestRows.ts b/examples/common/paginateTestRows.ts index d236eed78..158f3b640 100644 --- a/examples/common/paginateTestRows.ts +++ b/examples/common/paginateTestRows.ts @@ -81,7 +81,7 @@ export async function paginateTestRows( parent: pageNode, }); const rowHeaderNode = renderer.createTextNode({ - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: HEADER_FONT_SIZE, forceLoad: true, y: PADDING, diff --git a/examples/tests/alignment.ts b/examples/tests/alignment.ts index abf3ca385..5ecdb4e72 100644 --- a/examples/tests/alignment.ts +++ b/examples/tests/alignment.ts @@ -48,7 +48,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { parent: node, x: 200, y: 1080 / 3 - 30, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, text: 'Shows a parent node with 4 aligned children', }); @@ -92,7 +92,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { parent: node, x: 200, y: 1080 / 2 + 30, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, text: 'Shows a empty holder node, with a parent \nnode and 4 aligned children', }); diff --git a/examples/tests/alpha-blending.ts b/examples/tests/alpha-blending.ts index 5ee40d0cc..0d28f8e99 100644 --- a/examples/tests/alpha-blending.ts +++ b/examples/tests/alpha-blending.ts @@ -82,7 +82,7 @@ export default async function test(settings: ExampleSettings) { // Header text for both sides const leftHeader = renderer.createTextNode({ text: 'WebGL -> Browser Alpha', - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: HEADER_FONT_SIZE, color: 0xffffffff, contain: 'width', @@ -94,7 +94,7 @@ export default async function test(settings: ExampleSettings) { const rightHeader = renderer.createTextNode({ text: 'WebGL -> WebGL Alpha', - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: HEADER_FONT_SIZE, color: 0xffffffff, contain: 'width', @@ -106,7 +106,7 @@ export default async function test(settings: ExampleSettings) { }); const pageNumberNode = renderer.createTextNode({ - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, color: 0xffffffff, x: PADDING, @@ -127,7 +127,7 @@ export default async function test(settings: ExampleSettings) { //// Alpha Prop vs RGBA Alpha Component Blending Test const alphaPropVsRGBAHeader = renderer.createTextNode({ text: 'The rectangles below should appear the same', - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, color: 0xffffffff, y: curY, @@ -168,7 +168,7 @@ export default async function test(settings: ExampleSettings) { const sameColorRectHeader = renderer.createTextNode({ text: 'The rectangles below should appear invisible', - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, color: 0xffffffff, y: curY, @@ -234,7 +234,7 @@ export default async function test(settings: ExampleSettings) { renderer.createTextNode({ text: 'The texture rects below should appear invisible', - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, color: 0xffffffff, y: curY, @@ -318,7 +318,7 @@ export default async function test(settings: ExampleSettings) { const sameColorTextHeader = renderer.createTextNode({ text: 'The text below should appear invisible', - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, color: 0xffffffff, y: curY, @@ -332,12 +332,11 @@ export default async function test(settings: ExampleSettings) { // Canvas Text - Same Color - 100% alpha prop / 100% alpha component renderer.createTextNode({ text: CANVAS_TEXT, - fontFamily: 'NotoSans', + fontFamily: 'Canvas-NotoSans', fontSize: 30, alpha: 1, color: bgColor, y: curY, - textRendererOverride: 'canvas', parent: sideContainer, }); @@ -346,12 +345,11 @@ export default async function test(settings: ExampleSettings) { // Canvas Text - Same Color - 50% alpha prop / 100% alpha component renderer.createTextNode({ text: CANVAS_TEXT, - fontFamily: 'NotoSans', + fontFamily: 'Canvas-NotoSans', fontSize: 30, alpha: 0.5, color: bgColor, y: curY, - textRendererOverride: 'canvas', parent: sideContainer, }); @@ -360,12 +358,11 @@ export default async function test(settings: ExampleSettings) { // Canvas Text - Same Color - 100% alpha prop / 50% alpha component renderer.createTextNode({ text: CANVAS_TEXT, - fontFamily: 'NotoSans', + fontFamily: 'Canvas-NotoSans', fontSize: 30, alpha: 1, color: mergeColorAlpha(bgColor, 0.5), y: curY, - textRendererOverride: 'canvas', parent: sideContainer, }); @@ -374,12 +371,11 @@ export default async function test(settings: ExampleSettings) { // Canvas Text - Same Color - 50% alpha prop / 50% alpha component renderer.createTextNode({ text: CANVAS_TEXT, - fontFamily: 'NotoSans', + fontFamily: 'Canvas-NotoSans', fontSize: 30, alpha: 0.5, color: mergeColorAlpha(bgColor, 0.5), y: curY, - textRendererOverride: 'canvas', parent: sideContainer, }); @@ -390,12 +386,11 @@ export default async function test(settings: ExampleSettings) { // SDF Text - Same Color - 100% alpha prop / 100% alpha component renderer.createTextNode({ text: SDF_TEXT, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, alpha: 1, color: bgColor, y: curY, - textRendererOverride: 'sdf', parent: sideContainer, }); @@ -404,12 +399,11 @@ export default async function test(settings: ExampleSettings) { // SDF Text - Same Color - 50% alpha prop / 100% alpha component renderer.createTextNode({ text: SDF_TEXT, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, alpha: 0.5, color: bgColor, y: curY, - textRendererOverride: 'sdf', parent: sideContainer, }); @@ -418,12 +412,11 @@ export default async function test(settings: ExampleSettings) { // SDF Text - Same Color - 100% alpha prop / 50% alpha component renderer.createTextNode({ text: SDF_TEXT, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, alpha: 1, color: mergeColorAlpha(bgColor, 0.5), y: curY, - textRendererOverride: 'sdf', parent: sideContainer, }); @@ -432,12 +425,11 @@ export default async function test(settings: ExampleSettings) { // SDF Text - Same Color - 50% alpha prop / 50% alpha component renderer.createTextNode({ text: SDF_TEXT, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, alpha: 0.5, color: mergeColorAlpha(bgColor, 0.5), y: curY, - textRendererOverride: 'sdf', parent: sideContainer, }); @@ -447,7 +439,7 @@ export default async function test(settings: ExampleSettings) { const textureBlendingHeader = renderer.createTextNode({ text: 'The textures below should have smooth edges', - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, color: 0xffffffff, y: curY, diff --git a/examples/tests/animation-events.ts b/examples/tests/animation-events.ts index 165d78d4b..f3d662655 100644 --- a/examples/tests/animation-events.ts +++ b/examples/tests/animation-events.ts @@ -66,6 +66,8 @@ export default async function test({ testRoot.h = 250; testRoot.color = 0xffffffff; + const FONT_FAMILY = 'SDF-Ubuntu'; + const robot = renderer.createNode({ x: 0, y: 0, @@ -81,7 +83,7 @@ export default async function test({ x: testRoot.w, y: testRoot.h, fontSize: 40, - fontFamily: 'Ubuntu', + fontFamily: FONT_FAMILY, parent: testRoot, color: 0x000000ff, }); diff --git a/examples/tests/animation.ts b/examples/tests/animation.ts index cc8950c55..739e530b3 100644 --- a/examples/tests/animation.ts +++ b/examples/tests/animation.ts @@ -54,7 +54,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { parent: node, x: 40, y: 40, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 40, text: '', }); @@ -63,7 +63,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { parent: node, x: 40, y: 90, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 20, text: 'press left or right arrow key to change easing', }); diff --git a/examples/tests/clear-color-setting.ts b/examples/tests/clear-color-setting.ts index 40c279fe1..8f864a1b0 100644 --- a/examples/tests/clear-color-setting.ts +++ b/examples/tests/clear-color-setting.ts @@ -54,7 +54,7 @@ function createTestCase( w: 400, h: 400, text: `Test passes if the background appears as ${colorName}`, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 50, x: renderer.settings.appWidth / 2, y: renderer.settings.appHeight / 2, diff --git a/examples/tests/clipping.ts b/examples/tests/clipping.ts index cf72bb390..74be2dfce 100644 --- a/examples/tests/clipping.ts +++ b/examples/tests/clipping.ts @@ -443,10 +443,9 @@ export default async function test(settings: ExampleSettings) { w: SQUARE_SIZE, h: SQUARE_SIZE, parent, - fontFamily: 'Ubuntu', + fontFamily: 'Canvas-Ubuntu', fontSize: 40, color: 0x000000ff, - textRendererOverride: 'canvas', text: 'Canvas ancestor clipping', }); @@ -471,10 +470,9 @@ export default async function test(settings: ExampleSettings) { w: SQUARE_SIZE, h: SQUARE_SIZE, parent, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 40, color: 0x000000ff, - textRendererOverride: 'sdf', text: 'SDF ancestor clipping', }); diff --git a/examples/tests/default-fps-animation.ts b/examples/tests/default-fps-animation.ts index fa2688137..cc065df00 100644 --- a/examples/tests/default-fps-animation.ts +++ b/examples/tests/default-fps-animation.ts @@ -47,7 +47,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { parent: backgroundNode, x: 50, y: 50, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 48, text: 'Global FPS Throttling Demo', color: 0xffffffff, @@ -58,7 +58,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { parent: backgroundNode, x: 50, y: 120, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 32, text: `Current FPS Limit: ${ renderer.targetFPS === 0 ? 'Unlimited' : renderer.targetFPS @@ -71,7 +71,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { parent: backgroundNode, x: 50, y: 180, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 20, text: 'Press 1-9 for FPS limits (10-120), 0 for unlimited, Space to toggle animations', color: 0xccccccff, @@ -82,7 +82,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { parent: backgroundNode, x: 50, y: 220, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 18, text: 'Actual FPS: Calculating...', color: 0xffff00ff, @@ -304,7 +304,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { parent: backgroundNode, x: startX, y: startY - 40, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 24, text: 'Animated Grid', color: 0xffffffff, @@ -314,7 +314,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { parent: backgroundNode, x: 1450, y: 350, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 20, text: 'Rotation', color: 0xffffffff, @@ -324,7 +324,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { parent: backgroundNode, x: 1600, y: 350, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 20, text: 'Scaling', color: 0xffffffff, @@ -335,7 +335,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { parent: backgroundNode, x: 50, y: 950, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 16, text: 'Lower FPS = choppier animation, Higher FPS = smoother animation\nGlobal throttling affects ALL animations simultaneously', color: 0x888888ff, diff --git a/examples/tests/inspector.ts b/examples/tests/inspector.ts index c8e0b7789..dd33ab126 100644 --- a/examples/tests/inspector.ts +++ b/examples/tests/inspector.ts @@ -87,7 +87,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { y: 600, h: 100, text: 'Hello World', - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 100, parent: bg, data: { @@ -100,7 +100,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { y: 80, h: 100, text: 'Boxes should turn green if the inspector is enabled', - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 50, parent: bg, }); @@ -110,7 +110,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { y: 800, h: 100, text: 'Please make sure to run this test with ?inspector=true', - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 50, parent: bg, }); diff --git a/examples/tests/quads-rendered.ts b/examples/tests/quads-rendered.ts index 16de77f65..ce666e897 100644 --- a/examples/tests/quads-rendered.ts +++ b/examples/tests/quads-rendered.ts @@ -46,7 +46,7 @@ export default async function test({ }); const quadsNode = renderer.createTextNode({ - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 40, x: 20, y: 20, diff --git a/examples/tests/rtt-dimension.ts b/examples/tests/rtt-dimension.ts index 5b9f215cb..abb4eeac0 100644 --- a/examples/tests/rtt-dimension.ts +++ b/examples/tests/rtt-dimension.ts @@ -12,6 +12,7 @@ export async function automation(settings: ExampleSettings) { } export default async function test({ renderer, testRoot }: ExampleSettings) { + const fontFamily = 'SDF-Ubuntu'; const node = renderer.createNode({ x: 0, y: 0, @@ -51,7 +52,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { parent: rttNode, fontSize: 48, color: 0xffffffff, - fontFamily: 'Ubuntu', + fontFamily: fontFamily, }); renderer.createNode({ @@ -91,7 +92,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { parent: rttNode2, fontSize: 22, color: 0xff00ffff, - fontFamily: 'Ubuntu', + fontFamily: fontFamily, }); renderer.createNode({ @@ -131,7 +132,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { parent: rttNode3, fontSize: 22, color: 0xff00ffff, - fontFamily: 'Ubuntu', + fontFamily: fontFamily, }); renderer.createNode({ @@ -170,7 +171,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { parent: nestedRTTNode1, fontSize: 22, color: 0xff00ffff, - fontFamily: 'Ubuntu', + fontFamily: fontFamily, }); const rocko4 = renderer.createNode({ @@ -220,7 +221,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { parent: node, fontSize: 22, color: 0xffffffff, - fontFamily: 'Ubuntu', + fontFamily: fontFamily, }); renderer.createTextNode({ @@ -230,7 +231,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { parent: node, fontSize: 22, color: 0xffffffff, - fontFamily: 'Ubuntu', + fontFamily: fontFamily, }); renderer.createTextNode({ @@ -240,7 +241,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { parent: node, fontSize: 22, color: 0xffffffff, - fontFamily: 'Ubuntu', + fontFamily: fontFamily, }); let curPage = 0; diff --git a/examples/tests/rtt-nested-clipping.ts b/examples/tests/rtt-nested-clipping.ts index ee13a5212..e8f7bebdd 100644 --- a/examples/tests/rtt-nested-clipping.ts +++ b/examples/tests/rtt-nested-clipping.ts @@ -41,7 +41,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { parent: rttNode, fontSize: 48, color: 0xffffffff, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', }); renderer.createNode({ diff --git a/examples/tests/rtt-reflection.ts b/examples/tests/rtt-reflection.ts index 0c2c814f9..105bf7e89 100644 --- a/examples/tests/rtt-reflection.ts +++ b/examples/tests/rtt-reflection.ts @@ -72,7 +72,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { parent: rootRenderToTextureNode, x: 80, y: 540, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 40, text: 'RTT reflection demo', }); diff --git a/examples/tests/rtt-spritemap.ts b/examples/tests/rtt-spritemap.ts index c991837dc..5a7afbf1b 100644 --- a/examples/tests/rtt-spritemap.ts +++ b/examples/tests/rtt-spritemap.ts @@ -73,7 +73,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { parent: rttNode, fontSize: 48, color: 0xffffffff, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', }); function execTest( diff --git a/examples/tests/shader-animation.ts b/examples/tests/shader-animation.ts index 212e49396..99dea621d 100644 --- a/examples/tests/shader-animation.ts +++ b/examples/tests/shader-animation.ts @@ -55,7 +55,7 @@ export default async function test({ x: testRoot.w - 90, y: 90, fontSize: 40, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', text: 'radius: 100', parent: testRoot, color: 0xffffffff, diff --git a/examples/tests/stress-single-level-text.ts b/examples/tests/stress-single-level-text.ts index 0fd5f5b0a..e98d89dba 100644 --- a/examples/tests/stress-single-level-text.ts +++ b/examples/tests/stress-single-level-text.ts @@ -48,8 +48,7 @@ export default async function ({ const node = renderer.createTextNode({ x: randomIntBetween(startMin, startMax), y: randomIntBetween(startMin, startMax), - fontFamily: 'Ubuntu', - textRendererOverride: 'sdf', + fontFamily: 'SDF-Ubuntu', text: 'Lightning 3.0', // contain: 'both', // w: 237, diff --git a/examples/tests/test.ts b/examples/tests/test.ts index 9f0de9d20..ac7172854 100644 --- a/examples/tests/test.ts +++ b/examples/tests/test.ts @@ -160,7 +160,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { color: 0xffffffff, alpha: 1.0, text: 'Dn', - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 100, textAlign: 'center', parent: elevatorRect, @@ -409,7 +409,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { color: 0xffffffff, alpha: 1.0, text: 'Text Test: 0', - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', textAlign: 'center', fontSize: 100, scale: 1, diff --git a/examples/tests/text-align.ts b/examples/tests/text-align.ts index 4e0839e46..d8b66523d 100644 --- a/examples/tests/text-align.ts +++ b/examples/tests/text-align.ts @@ -54,7 +54,6 @@ export async function automation(settings: ExampleSettings) { * @returns */ export default async function test({ renderer, testRoot }: ExampleSettings) { - const fontFamily = 'Ubuntu'; const fontSize = 20; const yPos = 0; testRoot.w = 500; @@ -67,9 +66,8 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { maxWidth: testRoot.w, contain: 'width', fontSize, - fontFamily, + fontFamily: 'Canvas-Ubuntu', color: 0xff0000ff, - textRendererOverride: 'canvas', parent: testRoot, }); const sdfText = renderer.createTextNode({ @@ -77,7 +75,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { maxWidth: testRoot.w, contain: 'width', fontSize, - fontFamily, + fontFamily: 'SDF-Ubuntu', color: 0x0000ff77, parent: testRoot, zIndex: 3, @@ -87,7 +85,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { y: testRoot.h, mount: 1, color: 0x000000ff, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 20, text: '1', parent: testRoot, diff --git a/examples/tests/text-alpha.ts b/examples/tests/text-alpha.ts index 466bf942f..b97f22fff 100644 --- a/examples/tests/text-alpha.ts +++ b/examples/tests/text-alpha.ts @@ -35,8 +35,6 @@ const NODE_PROPS = { y: containerSize / 2, color: 0x000000ff, text: 'xyz', - fontFamily: 'Ubuntu', - textRendererOverride: 'sdf', fontSize: 50, } satisfies Partial; @@ -72,7 +70,8 @@ function generateAlphaTest( content: async (rowNode) => { const nodeProps = { ...NODE_PROPS, - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', } satisfies Partial; return await constructTestRow({ renderer, rowNode, containerSize }, [ @@ -102,7 +101,8 @@ function generateAlphaTest( content: async (rowNode) => { const nodeProps = { ...NODE_PROPS, - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', } satisfies Partial; return await constructTestRow({ renderer, rowNode, containerSize }, [ @@ -129,7 +129,8 @@ function generateAlphaTest( content: async (rowNode) => { const nodeProps = { ...NODE_PROPS, - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', } satisfies Partial; return await constructTestRow({ renderer, rowNode, containerSize }, [ @@ -158,7 +159,8 @@ function generateAlphaTest( content: async (rowNode) => { const nodeProps = { ...NODE_PROPS, - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', } satisfies Partial; return await constructTestRow({ renderer, rowNode, containerSize }, [ @@ -198,7 +200,7 @@ function createContainedTextNode( }); renderer.createTextNode({ ...NODE_PROPS, - textRendererOverride: textRenderer, + fontFamily: textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', parent: container, // alpha: 0.50, }); diff --git a/examples/tests/text-canvas-font-no-metrics.ts b/examples/tests/text-canvas-font-no-metrics.ts index 1cafe410b..8d7e9b4fd 100644 --- a/examples/tests/text-canvas-font-no-metrics.ts +++ b/examples/tests/text-canvas-font-no-metrics.ts @@ -43,8 +43,8 @@ export async function automation(settings: ExampleSettings) { * @returns */ export default async function test({ renderer, testRoot }: ExampleSettings) { - const fontFamily = 'Ubuntu'; - const fontFamilyNoMetrics = 'Ubuntu-No-Metrics'; + const fontFamily = 'Canvas-Ubuntu'; + const fontFamilyNoMetrics = 'Canvas-Ubuntu-No-Metrics'; const text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; @@ -61,7 +61,6 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { fontSize, fontFamily, color: 0xff0000ff, - textRendererOverride: 'canvas', parent: testRoot, }); const indexInfo = renderer.createTextNode({ @@ -69,7 +68,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { y: testRoot.h, mount: 1, color: 0x000000ff, - fontFamily: 'Ubuntu', + fontFamily, fontSize: 20, text: '1', parent: testRoot, diff --git a/examples/tests/text-contain-adv.ts b/examples/tests/text-contain-adv.ts index 094a514d7..a30d0590b 100644 --- a/examples/tests/text-contain-adv.ts +++ b/examples/tests/text-contain-adv.ts @@ -12,7 +12,7 @@ export async function automation(settings: ExampleSettings) { export default async function test(settings: ExampleSettings) { const { renderer, testRoot } = settings; - + const fontFamily = 'SDF-Ubuntu'; const createTextAlignPage = (align: 'left' | 'center' | 'right'): INode => { const container = renderer.createNode({ x: 0, @@ -35,7 +35,7 @@ export default async function test(settings: ExampleSettings) { y: 20, mountX: 0.5, text: `Text Align: ${align}, no maxWidth, no contain, mount: [0, .5, 1]`, - fontFamily: 'Ubuntu', + fontFamily, fontSize: 40, color: 0x0000ffff, textAlign: 'center', @@ -46,7 +46,7 @@ export default async function test(settings: ExampleSettings) { x: testRoot.w / 2, y: 100, text: `Text align: ${align}`, - fontFamily: 'Ubuntu', + fontFamily, fontSize: 30, color: 0x000000ff, textAlign: align, @@ -58,7 +58,7 @@ export default async function test(settings: ExampleSettings) { y: 150, mountX: 0.5, text: `Text align: ${align}`, - fontFamily: 'Ubuntu', + fontFamily, fontSize: 30, color: 0x000000ff, textAlign: align, @@ -70,7 +70,7 @@ export default async function test(settings: ExampleSettings) { y: 200, mountX: 1, text: `Text align: ${align}`, - fontFamily: 'Ubuntu', + fontFamily, fontSize: 30, color: 0x000000ff, textAlign: align, @@ -82,7 +82,7 @@ export default async function test(settings: ExampleSettings) { y: 300, mountX: 0.5, text: `Text Align: ${align}, maxWidth, no contain, mount: [0, .5, 1]`, - fontFamily: 'Ubuntu', + fontFamily, fontSize: 40, color: 0x0000ffff, textAlign: 'center', @@ -94,7 +94,7 @@ export default async function test(settings: ExampleSettings) { y: 400, maxWidth: 400, text: `Text align: ${align}`, - fontFamily: 'Ubuntu', + fontFamily, fontSize: 30, color: 0x000000ff, textAlign: align, @@ -107,7 +107,7 @@ export default async function test(settings: ExampleSettings) { maxWidth: 400, mountX: 0.5, text: `Text align: ${align}`, - fontFamily: 'Ubuntu', + fontFamily, fontSize: 30, color: 0x000000ff, textAlign: align, @@ -120,7 +120,7 @@ export default async function test(settings: ExampleSettings) { maxWidth: 400, mountX: 1, text: `Text align: ${align}`, - fontFamily: 'Ubuntu', + fontFamily, fontSize: 30, color: 0x000000ff, textAlign: align, @@ -132,7 +132,7 @@ export default async function test(settings: ExampleSettings) { y: 600, mountX: 0.5, text: `Text Align: ${align}, maxWidth, contain, mount: [0, .5, 1]`, - fontFamily: 'Ubuntu', + fontFamily, fontSize: 40, color: 0x0000ffff, textAlign: 'center', @@ -154,7 +154,7 @@ export default async function test(settings: ExampleSettings) { maxWidth: 400, contain: 'width', text: `Text align: ${align}`, - fontFamily: 'Ubuntu', + fontFamily, fontSize: 30, color: 0x000000ff, textAlign: align, @@ -178,7 +178,7 @@ export default async function test(settings: ExampleSettings) { contain: 'width', mountX: 0.5, text: `Text align: ${align}`, - fontFamily: 'Ubuntu', + fontFamily, fontSize: 30, color: 0x000000ff, textAlign: align, @@ -203,7 +203,7 @@ export default async function test(settings: ExampleSettings) { contain: 'width', mountX: 1, text: `Text align: ${align}`, - fontFamily: 'Ubuntu', + fontFamily, fontSize: 30, // rotation: (Math.PI * 2) / 16, color: 0x000000ff, diff --git a/examples/tests/text-contain.ts b/examples/tests/text-contain.ts index 3783dda4e..c4637a216 100644 --- a/examples/tests/text-contain.ts +++ b/examples/tests/text-contain.ts @@ -50,8 +50,7 @@ export default async function test(settings: ExampleSettings) { y: textSizeAfterLoadingBg.y, color: 0x000000ff, forceLoad: true, - fontFamily: 'Ubuntu', - textRendererOverride: 'sdf', + fontFamily: 'SDF-Ubuntu', fontSize: 20, text: `Lorem ipsum dolor sit e Consectetur adipiscing elit. Vivamus id. @@ -65,8 +64,7 @@ Vivamus consectetur ex magna, non mollis.`, y: textSizeAfterLoadingBg.y, color: 0x000000ff, forceLoad: true, - fontFamily: 'Ubuntu', - textRendererOverride: 'canvas', + fontFamily: 'Canvas-Ubuntu', fontSize: 20, text: `Lorem ipsum dolor sit e Consectetur adipiscing elit. Vivamus id. @@ -81,7 +79,7 @@ Vivamus consectetur ex magna, non mollis.`, y: testRoot.h, mount: 1, color: 0x000000ff, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 20, text: '1', parent: testRoot, @@ -92,7 +90,7 @@ Vivamus consectetur ex magna, non mollis.`, y: testRoot.h - 20, mount: 1, color: 0x00ff00ff, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 20, text: '', parent: testRoot, @@ -103,7 +101,7 @@ Vivamus consectetur ex magna, non mollis.`, y: testRoot.h - 40, mount: 1, color: 0xff0000ff, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 20, text: '', parent: testRoot, @@ -114,7 +112,7 @@ Vivamus consectetur ex magna, non mollis.`, y: testRoot.h - 60, mount: 1, color: 0x0000ffff, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 20, text: '', parent: testRoot, @@ -125,7 +123,7 @@ Vivamus consectetur ex magna, non mollis.`, y: testRoot.h - 80, mount: 1, color: 0x000000ff, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 20, text: '', parent: testRoot, diff --git a/examples/tests/text-dimensions.ts b/examples/tests/text-dimensions.ts index 8f66061eb..bd9f64d4a 100644 --- a/examples/tests/text-dimensions.ts +++ b/examples/tests/text-dimensions.ts @@ -62,8 +62,7 @@ export default async function test(settings: ExampleSettings) { h: 0, color: 0x000000ff, forceLoad: true, - fontFamily: 'Ubuntu', - textRendererOverride: 'sdf', + fontFamily: 'SDF-Ubuntu', fontSize: 50, text: '', parent: testRoot, @@ -76,7 +75,7 @@ export default async function test(settings: ExampleSettings) { w: 0, h: 0, color: 0x000000ff, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 20, text: '1', parent: testRoot, @@ -86,14 +85,14 @@ export default async function test(settings: ExampleSettings) { const mutations = [ () => { text1.text = 'SDF'; - text1.textRendererOverride = 'sdf'; + text1.fontFamily = 'SDF-Ubuntu'; }, () => { text1.text = 'SDF\ngyqpj'; }, () => { text1.text = 'Canvas'; - text1.textRendererOverride = 'canvas'; + text1.fontFamily = 'Canvas-Ubuntu'; }, () => { text1.text = 'Canvas\ngyqpj'; @@ -101,17 +100,17 @@ export default async function test(settings: ExampleSettings) { () => { // Test one more time with SDF to make sure Canvas text1.text = 'SDF 2nd'; - text1.textRendererOverride = 'sdf'; + text1.fontFamily = 'SDF-Ubuntu'; }, () => { // Test when text ends with space for correct width text1.text = 'Canvas '; - text1.textRendererOverride = 'canvas'; + text1.fontFamily = 'Canvas-Ubuntu'; }, () => { // Test when text ends with space for correct width text1.text = 'SDF '; - text1.textRendererOverride = 'sdf'; + text1.fontFamily = 'SDF-Ubuntu'; }, ]; /** diff --git a/examples/tests/text-events.ts b/examples/tests/text-events.ts index a2361574b..5faf103f0 100644 --- a/examples/tests/text-events.ts +++ b/examples/tests/text-events.ts @@ -57,7 +57,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { boxColor1: 0x9f6dffff, boxColor2: 0x00aaaaff, textColor: 0xffffffff, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', parent: testRoot, }, renderer, @@ -152,7 +152,6 @@ Uniting two lands, a powerful stream.`; const textFailedEventTest = renderer.createTextNode({ y: 50, fontFamily: '$$SDF_FAILURE_TEST$$', - textRendererOverride: 'sdf', parent: testRoot, fontSize: 50, }); @@ -164,7 +163,7 @@ Uniting two lands, a powerful stream.`; // Nothing } - textFailedEventTest.fontFamily = 'Ubuntu'; + textFailedEventTest.fontFamily = 'SDF-Ubuntu'; if (textError) { textFailedEventTest.text = 'Failure Event Test Passed!'; textFailedEventTest.color = 0x00ff00ff; diff --git a/examples/tests/text-layout-consistency-modified-metrics.ts b/examples/tests/text-layout-consistency-modified-metrics.ts index e5dfa3341..9b0b6fce7 100644 --- a/examples/tests/text-layout-consistency-modified-metrics.ts +++ b/examples/tests/text-layout-consistency-modified-metrics.ts @@ -45,8 +45,8 @@ export async function automation(settings: ExampleSettings) { * @returns */ export default async function test({ renderer, testRoot }: ExampleSettings) { - const fontFamily = 'Ubuntu'; - const fontFamilyModified = 'Ubuntu-Modified-Metrics'; + const fontFamily = 'SDF-Ubuntu'; + const fontFamilyModified = 'SDF-Ubuntu-Modified-Metrics'; const text = getLoremIpsum(1200); const fontSize = 20; @@ -61,9 +61,8 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { maxWidth: testRoot.w, text, fontSize, - fontFamily, + fontFamily: 'Canvas-Ubuntu', color: 0xff0000ff, - textRendererOverride: 'canvas', parent: testRoot, }); const sdfText = renderer.createTextNode({ @@ -71,7 +70,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { maxWidth: testRoot.w, text, fontSize, - fontFamily, + fontFamily: 'SDF-Ubuntu', color: 0x0000ff77, parent: testRoot, zIndex: 3, @@ -83,7 +82,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { w: 0, h: 0, color: 0x000000ff, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 20, text: '1', parent: testRoot, diff --git a/examples/tests/text-layout-consistency.ts b/examples/tests/text-layout-consistency.ts index f3981c405..eff767605 100644 --- a/examples/tests/text-layout-consistency.ts +++ b/examples/tests/text-layout-consistency.ts @@ -52,7 +52,6 @@ export async function automation(settings: ExampleSettings) { * @returns */ export default async function test({ renderer, testRoot }: ExampleSettings) { - const fontFamily = 'Ubuntu'; const text = getLoremIpsum(1200); const fontSize = 20; const yPos = 0; @@ -77,9 +76,8 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { maxWidth: testRoot.w, text, fontSize, - fontFamily, + fontFamily: 'Canvas-Ubuntu', color: 0xff0000ff, - textRendererOverride: 'canvas', parent: testRoot, }); const sdfText = renderer.createTextNode({ @@ -87,7 +85,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { maxWidth: testRoot.w, text, fontSize, - fontFamily, + fontFamily: 'SDF-Ubuntu', color: 0x0000ff77, parent: testRoot, zIndex: 3, @@ -97,7 +95,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { y: testRoot.h, mount: 1, color: 0x000000ff, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 20, text: '1', parent: testRoot, diff --git a/examples/tests/text-line-height.ts b/examples/tests/text-line-height.ts index 537be497e..ebb7e9df2 100644 --- a/examples/tests/text-line-height.ts +++ b/examples/tests/text-line-height.ts @@ -50,8 +50,7 @@ const NODE_PROPS = { mount: 0.5, color: 0x000000ff, text: 'abcd\ntxyz', - fontFamily: 'Ubuntu', - textRendererOverride: 'sdf', + fontFamily: 'SDF-Ubuntu', fontSize: 50, } satisfies Partial; @@ -67,7 +66,8 @@ function generateLineHeightTest( content: async (rowNode) => { const nodeProps = { ...NODE_PROPS, - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', } satisfies Partial; const baselineNode = renderer.createTextNode({ diff --git a/examples/tests/text-max-lines.ts b/examples/tests/text-max-lines.ts index 396517c66..85ebecf04 100644 --- a/examples/tests/text-max-lines.ts +++ b/examples/tests/text-max-lines.ts @@ -51,8 +51,6 @@ const BASE_NODE_PROPS = { y: 100, maxWidth: 200, color: 0x000000ff, - fontFamily: 'Ubuntu', - textRendererOverride: 'sdf', fontSize: 20, lineHeight: 28, } satisfies Partial; @@ -68,7 +66,8 @@ function generateMaxLinesTest( const nodeProps = { ...BASE_NODE_PROPS, text: 'Line1 Line1_Line1_Line1\nLine2 Line2____Line2\nLine 3\nLine 4', - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', } satisfies Partial; const baselineNode = renderer.createTextNode({ @@ -124,7 +123,8 @@ function generateMaxLinesTest( const nodeProps = { ...BASE_NODE_PROPS, text: getLoremIpsum(100), - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', } satisfies Partial; const baselineNode = renderer.createTextNode({ diff --git a/examples/tests/text-offscreen-move.ts b/examples/tests/text-offscreen-move.ts index 0ff5a9459..b72ff417b 100644 --- a/examples/tests/text-offscreen-move.ts +++ b/examples/tests/text-offscreen-move.ts @@ -63,8 +63,7 @@ export default async function test(settings: ExampleSettings) { const commonTextProps = { mount: 0.5, text: 'Test passes if this text appears only as green', - fontFamily: 'Ubuntu', - textRendererOverride: 'canvas', + fontFamily: 'Canvas-Ubuntu', fontSize: 50, } satisfies Partial; @@ -79,8 +78,7 @@ function createTestCase( x: 0, y: 10, text: '', - fontFamily: 'Ubuntu', - textRendererOverride: 'sdf', + fontFamily: 'SDF-Ubuntu', fontSize: 30, parent: page, }); @@ -91,7 +89,7 @@ function createTestCase( color: 0xff0000ff, x: renderer.settings.appWidth / 2, y: renderer.settings.appHeight / 2, - textRendererOverride: textRenderer, + fontFamily: textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', maxHeight, maxWidth, parent: page, @@ -102,7 +100,7 @@ function createTestCase( color: 0x00ff00ff, x: -1000, y: -1000, - textRendererOverride: textRenderer, + fontFamily: textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', maxHeight, maxWidth, parent: page, diff --git a/examples/tests/text-overflow-suffix.ts b/examples/tests/text-overflow-suffix.ts index cc981597c..32da3863c 100644 --- a/examples/tests/text-overflow-suffix.ts +++ b/examples/tests/text-overflow-suffix.ts @@ -51,8 +51,7 @@ const NODE_PROPS = { maxWidth: 200, color: 0x000000ff, text: getLoremIpsum(100), - fontFamily: 'Ubuntu', - textRendererOverride: 'sdf', + fontFamily: 'SDF-Ubuntu', fontSize: 20, lineHeight: 28, } satisfies Partial; @@ -67,7 +66,8 @@ function generateOverflowSuffixTest( content: async (rowNode) => { const nodeProps = { ...NODE_PROPS, - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', } satisfies Partial; const baselineNode = renderer.createTextNode({ diff --git a/examples/tests/text-rotation.ts b/examples/tests/text-rotation.ts index 8bc46e8b7..c23a02595 100644 --- a/examples/tests/text-rotation.ts +++ b/examples/tests/text-rotation.ts @@ -49,8 +49,7 @@ const NODE_PROPS = { y: 100, color: 0x000000ff, text: 'xyz', - fontFamily: 'Ubuntu', - textRendererOverride: 'sdf', + fontFamily: 'SDF-Ubuntu', fontSize: 50, } satisfies Partial; @@ -64,7 +63,8 @@ function generateRotationTest( content: async (rowNode) => { const nodeProps = { ...NODE_PROPS, - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', } satisfies Partial; const baselineNode = renderer.createTextNode({ @@ -113,7 +113,8 @@ function generateRotationTest( mount: 0.5, x: 100, y: 100, - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', } satisfies Partial; return await constructTestRow({ renderer, rowNode }, [ @@ -147,7 +148,8 @@ function generateRotationTest( const nodeProps = { ...NODE_PROPS, mount: 1, - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', } satisfies Partial; const baselineNode = renderer.createTextNode({ diff --git a/examples/tests/text-scaling.ts b/examples/tests/text-scaling.ts index 37111dbce..44d81e30c 100644 --- a/examples/tests/text-scaling.ts +++ b/examples/tests/text-scaling.ts @@ -54,8 +54,6 @@ const NODE_PROPS = { y: 100, color: 0x000000ff, text: 'xyz', - fontFamily: 'Ubuntu', - textRendererOverride: 'sdf', fontSize: 50, } satisfies Partial; @@ -70,7 +68,8 @@ function generateScalingTest( content: async (rowNode) => { const nodeProps = { ...NODE_PROPS, - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', } satisfies Partial; const baselineNode = renderer.createTextNode({ @@ -142,7 +141,8 @@ function generateScalingTest( mount: 0.5, x: 100, y: 100, - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', } satisfies Partial; const baselineNode = renderer.createTextNode({ @@ -198,7 +198,8 @@ function generateScalingTest( const nodeProps = { ...NODE_PROPS, mount: 1, - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', } satisfies Partial; const baselineNode = renderer.createTextNode({ diff --git a/examples/tests/text-ssdf.ts b/examples/tests/text-ssdf.ts index 9e6571f20..c75f04683 100644 --- a/examples/tests/text-ssdf.ts +++ b/examples/tests/text-ssdf.ts @@ -26,7 +26,7 @@ export default async function test(settings: ExampleSettings) { renderer.createTextNode({ text: 'SSDF', color: 0x000000ff, - fontFamily: 'Ubuntu-ssdf', + fontFamily: 'SDF-Ubuntu-ssdf', parent: testRoot, fontSize: 80, lineHeight: 80 * 1.2, diff --git a/examples/tests/text-vertical-align.ts b/examples/tests/text-vertical-align.ts index de640880d..6ef8f6052 100644 --- a/examples/tests/text-vertical-align.ts +++ b/examples/tests/text-vertical-align.ts @@ -51,8 +51,6 @@ export default async function test(settings: ExampleSettings) { const NODE_PROPS = { color: 0x000000ff, - fontFamily: 'Ubuntu', - textRendererOverride: 'sdf', fontSize: 50, lineHeight: 70, } satisfies Partial; @@ -94,7 +92,8 @@ function generateVerticalAlignTest( ...NODE_PROPS, text: 'txyz', contain: 'height', - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', maxHeight: CONTAINER_SIZE, } satisfies Partial; @@ -131,7 +130,8 @@ function generateVerticalAlignTest( const nodeProps = { ...NODE_PROPS, text: 'abcd\ntxyz', - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', contain: 'height', maxHeight: CONTAINER_SIZE, } satisfies Partial; diff --git a/examples/tests/text-wordbreak.ts b/examples/tests/text-wordbreak.ts index 36e7d25af..62f4bd4d8 100644 --- a/examples/tests/text-wordbreak.ts +++ b/examples/tests/text-wordbreak.ts @@ -35,8 +35,6 @@ const NODE_PROPS = { y: 0, color: 0x000000ff, text: `This is a long and Honorificabilitudinitatibus califragilisticexpialidocious Taumatawhakatangihangakoauauotamateaturipukakapikimaungahoronukupokaiwhenuakitanatahu`, - fontFamily: 'Ubuntu', - textRendererOverride: 'sdf', fontSize: 20, maxWidth: containerSize, wordBreak: 'overflow', @@ -57,7 +55,6 @@ function generateWordBreakTest( content: async (rowNode) => { const nodeProps = { ...NODE_PROPS, - textRendererOverride: textRenderer, } satisfies Partial; return await constructTestRow( @@ -73,13 +70,13 @@ function generateWordBreakTest( renderer.createTextNode({ ...nodeProps, maxLines, - textRendererOverride: 'sdf', + fontFamily: 'SDF-Ubuntu', }), 'Renderer: canvas', renderer.createTextNode({ ...nodeProps, maxLines, - textRendererOverride: 'canvas', + fontFamily: 'Canvas-Ubuntu', }), ], ); @@ -90,7 +87,8 @@ function generateWordBreakTest( content: async (rowNode) => { const nodeProps = { ...NODE_PROPS, - textRendererOverride: textRenderer, + fontFamily: + textRenderer === 'canvas' ? 'Canvas-Ubuntu' : 'SDF-Ubuntu', } satisfies Partial; return await constructTestRow( @@ -107,14 +105,14 @@ function generateWordBreakTest( ...nodeProps, maxLines, wordBreak: 'break-all', - textRendererOverride: 'sdf', + fontFamily: 'SDF-Ubuntu', }), 'Renderer: canvas', renderer.createTextNode({ ...nodeProps, maxLines, wordBreak: 'break-all', - textRendererOverride: 'canvas', + fontFamily: 'Canvas-Ubuntu', }), ], ); @@ -125,7 +123,6 @@ function generateWordBreakTest( content: async (rowNode) => { const nodeProps = { ...NODE_PROPS, - textRendererOverride: textRenderer, } satisfies Partial; return await constructTestRow( @@ -142,14 +139,14 @@ function generateWordBreakTest( ...nodeProps, maxLines, wordBreak: 'break-word', - textRendererOverride: 'sdf', + fontFamily: 'SDF-Ubuntu', }), 'Renderer: canvas', renderer.createTextNode({ ...nodeProps, maxLines, wordBreak: 'break-word', - textRendererOverride: 'canvas', + fontFamily: 'Canvas-Ubuntu', }), ], ); diff --git a/examples/tests/text-zwsp.ts b/examples/tests/text-zwsp.ts index d36adfb81..f34704624 100644 --- a/examples/tests/text-zwsp.ts +++ b/examples/tests/text-zwsp.ts @@ -43,7 +43,7 @@ export async function automation(settings: ExampleSettings) { * @returns */ export default async function test({ renderer, testRoot }: ExampleSettings) { - const fontFamily = 'Ubuntu'; + const fontFamily = 'SDF-Ubuntu'; const fontSize = 40; const yPos = 0; testRoot.w = 500; @@ -55,9 +55,8 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { y: yPos, maxWidth: testRoot.w, fontSize, - fontFamily, + fontFamily: 'Canvas-Ubuntu', color: 0xff0000ff, - textRendererOverride: 'canvas', parent: testRoot, }); @@ -65,7 +64,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { y: yPos, maxWidth: testRoot.w, fontSize, - fontFamily, + fontFamily: 'SDF-Ubuntu', color: 0x0000ff77, parent: testRoot, zIndex: 3, @@ -75,7 +74,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { y: testRoot.h, mount: 1, color: 0x000000ff, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 20, text: '1', parent: testRoot, diff --git a/examples/tests/text.ts b/examples/tests/text.ts index 26771c85f..91b10f3c5 100644 --- a/examples/tests/text.ts +++ b/examples/tests/text.ts @@ -29,7 +29,7 @@ import { saveStorage, } from '../common/LocalStorage.js'; -const FONT_FAMILY = 'Ubuntu'; +const FONT_FAMILY = 'SDF-Ubuntu'; const HEADER_SIZE = 45; const FONT_SIZE = 40; @@ -350,24 +350,20 @@ const sdfOffsetY = 0; function getFontProps(fontType: string): { fontFamily: string; offsetY: number; - textRendererOverride: 'sdf' | 'canvas'; } { if (fontType === 'msdf') { return { - fontFamily: `${FONT_FAMILY}`, + fontFamily: `SDF-${FONT_FAMILY}`, offsetY: sdfOffsetY, - textRendererOverride: 'sdf', }; } else if (fontType === 'ssdf') { return { - fontFamily: `${FONT_FAMILY}-ssdf`, + fontFamily: `SDF-${FONT_FAMILY}-ssdf`, offsetY: sdfOffsetY, - textRendererOverride: 'sdf', }; } return { - fontFamily: `${FONT_FAMILY}`, + fontFamily: `Canvas-${FONT_FAMILY}`, offsetY: 0, - textRendererOverride: 'canvas', }; } diff --git a/examples/tests/texture-autosize.ts b/examples/tests/texture-autosize.ts index 8145e00e0..74b6a78be 100644 --- a/examples/tests/texture-autosize.ts +++ b/examples/tests/texture-autosize.ts @@ -64,7 +64,7 @@ export default async function test(settings: ExampleSettings) { text: dimensionsMatch ? 'Autosize\nSuccess' : 'Autosize\nFailure', color: dimensionsMatch ? 0x00ff00ff : 0xff0000ff, fontSize: 50, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', parent: testRoot, }); } diff --git a/examples/tests/texture-cleanup-critical.ts b/examples/tests/texture-cleanup-critical.ts index 4823409c0..0f0753524 100644 --- a/examples/tests/texture-cleanup-critical.ts +++ b/examples/tests/texture-cleanup-critical.ts @@ -52,7 +52,7 @@ export default async function ({ y: 0, text: 'Critical Texture Memory Cleanup Test', parent: screen, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 60, }); @@ -65,7 +65,7 @@ export default async function ({ See docs/ManualRegressionTests.md for more information. `, parent: screen, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 40, }); diff --git a/examples/tests/texture-cleanup-idle.ts b/examples/tests/texture-cleanup-idle.ts index 8a766ba2d..dc9c19c86 100644 --- a/examples/tests/texture-cleanup-idle.ts +++ b/examples/tests/texture-cleanup-idle.ts @@ -56,7 +56,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { y: 0, text: 'Idle Texture Memory Cleanup Test', parent: testRoot, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 60, zIndex: 1, }); @@ -72,7 +72,7 @@ The Texture Memory Manager should perform Idle Texture Cleanup roughly every 5 s See docs/ManualRegressionTests.md for more information. `, parent: testRoot, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 40, zIndex: 1, }); diff --git a/examples/tests/texture-memory-allocation.ts b/examples/tests/texture-memory-allocation.ts index 3bff1a9e4..f027d5beb 100644 --- a/examples/tests/texture-memory-allocation.ts +++ b/examples/tests/texture-memory-allocation.ts @@ -152,7 +152,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { autosize: true, parent: childNode, text: `Card ${id}`, - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 20, color: 0xffffffff, }); diff --git a/examples/tests/texture-reload.ts b/examples/tests/texture-reload.ts index c372a0bac..85aa2b497 100644 --- a/examples/tests/texture-reload.ts +++ b/examples/tests/texture-reload.ts @@ -62,7 +62,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { const nodes: INode[] = []; const header = renderer.createTextNode({ - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', text: `Texture Reload Test`, fontSize: 45, parent: testRoot, @@ -71,7 +71,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { }); const finalStatus = renderer.createTextNode({ - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', text: `Running...`, fontSize: 30, parent: testRoot, @@ -316,7 +316,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { testNodeInstance.h = 128; renderer.createTextNode({ - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', text: `${testIdx}. Test ${status} for: ${name}`, fontSize: 30, parent: testRoot, diff --git a/examples/tests/texture-source.ts b/examples/tests/texture-source.ts index 133138875..b0cee368a 100644 --- a/examples/tests/texture-source.ts +++ b/examples/tests/texture-source.ts @@ -37,7 +37,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { const BEGIN_Y = FONT_SIZE; const header = renderer.createTextNode({ - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', text: `PNG Source Test`, fontSize: FONT_SIZE, parent: testRoot, @@ -97,7 +97,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { expectedHeight: number, ) { const textNode = renderer.createTextNode({ - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', x: curX, text: '', fontSize: FONT_SIZE, diff --git a/examples/tests/texture-svg.ts b/examples/tests/texture-svg.ts index d71f3d8b9..6324f4609 100644 --- a/examples/tests/texture-svg.ts +++ b/examples/tests/texture-svg.ts @@ -39,7 +39,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { const BEGIN_Y = FONT_SIZE; const header = renderer.createTextNode({ - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', text: `SVG Test`, fontSize: FONT_SIZE, parent: testRoot, @@ -129,7 +129,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { expectedHeight: number, ) { const textNode = renderer.createTextNode({ - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', x: curX, text: '', fontSize: FONT_SIZE, @@ -173,7 +173,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { async function execFailureTest(imgNode: INode) { const textNode = renderer.createTextNode({ - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', x: curX, text: '', fontSize: FONT_SIZE, diff --git a/examples/tests/textures.ts b/examples/tests/textures.ts index b5fe33467..30c23b5f9 100644 --- a/examples/tests/textures.ts +++ b/examples/tests/textures.ts @@ -38,7 +38,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { const BEGIN_Y = FONT_SIZE; const header = renderer.createTextNode({ - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', text: `Texture Test`, fontSize: FONT_SIZE, parent: testRoot, @@ -228,7 +228,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { expectedHeight: number, ) { const textNode = renderer.createTextNode({ - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', x: curX, text: '', fontSize: FONT_SIZE, @@ -272,7 +272,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { async function execFailureTest(imgNode: INode) { const textNode = renderer.createTextNode({ - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', x: curX, text: '', fontSize: FONT_SIZE, diff --git a/examples/tests/tx-compression.ts b/examples/tests/tx-compression.ts index 0b8248efc..c81f3c7c6 100644 --- a/examples/tests/tx-compression.ts +++ b/examples/tests/tx-compression.ts @@ -26,7 +26,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { color: 0xffffffff, alpha: 1.0, text: 'etc1 compression in .pvr', - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, parent: testRoot, }); @@ -46,7 +46,7 @@ export default async function ({ renderer, testRoot }: ExampleSettings) { color: 0xffffffff, alpha: 1.0, text: 's3tc compression in .ktx', - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, parent: testRoot, }); diff --git a/examples/tests/zIndex.ts b/examples/tests/zIndex.ts index e25ced9bf..a532c7a74 100644 --- a/examples/tests/zIndex.ts +++ b/examples/tests/zIndex.ts @@ -43,7 +43,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { color: 0xffffffff, alpha: 1.0, text: 'These should neatly stack on top of each other.', - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, textAlign: 'center', parent: testRoot, @@ -122,7 +122,7 @@ export default async function test({ renderer, testRoot }: ExampleSettings) { color: 0xffffffff, alpha: 1.0, text: 'Green box should overlap even though it has a lower zIndex because the parent is locked', - fontFamily: 'Ubuntu', + fontFamily: 'SDF-Ubuntu', fontSize: 30, textAlign: 'center', parent: testRoot, diff --git a/src/core/CoreTextNode.ts b/src/core/CoreTextNode.ts index c813f926e..e01c1c763 100644 --- a/src/core/CoreTextNode.ts +++ b/src/core/CoreTextNode.ts @@ -18,8 +18,6 @@ */ import type { - FontHandler, - TextRenderer, TrProps, TextLayout, TextRenderInfo, @@ -40,11 +38,8 @@ import type { RectWithValid } from './lib/utils.js'; import type { CoreRenderer } from './renderers/CoreRenderer.js'; import type { TextureLoadedEventHandler } from './textures/Texture.js'; import { Matrix3d } from './lib/Matrix3d.js'; +import { FontState, type CoreFont } from './text-rendering/CoreFont.js'; export interface CoreTextNodeProps extends CoreNodeProps, TrProps { - /** - * Force Text Node to use a specific Text Renderer - */ - textRendererOverride?: string | null; forceLoad: boolean; } @@ -56,8 +51,7 @@ export enum TextConstraint { } export class CoreTextNode extends CoreNode implements CoreTextNodeProps { - private textRenderer: TextRenderer; - private fontHandler: FontHandler; + private font?: CoreFont; private _layoutGenerated = false; private _waitingForFont = false; @@ -77,19 +71,13 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps { private _type: 'sdf' | 'canvas' = 'sdf'; // Default to SDF renderer - constructor( - stage: Stage, - props: CoreTextNodeProps, - textRenderer: TextRenderer, - ) { + constructor(stage: Stage, props: CoreTextNodeProps) { super(stage, props); - this.textRenderer = textRenderer; - this.fontHandler = textRenderer.font; - this._type = textRenderer.type; // Initialize text properties from props // Props are guaranteed to have all defaults resolved by Stage.createTextNode this.textProps = props; + this.font = stage.fontManager.getFont(props.fontFamily); this._containType = TextConstraint[props.contain]; this.setUpdateType(UpdateType.All); @@ -110,7 +98,8 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps { dimensions, } satisfies NodeTextureLoadedPayload); } - this.setUpdateType(UpdateType.IsRenderable); + + this.setUpdateType(UpdateType.Local); }; allowTextGeneration() { @@ -118,6 +107,9 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps { if (p === null) { return false; } + if (this.font === undefined) { + return false; + } if (p.worldAlpha > 0 && p.renderState > CoreNodeRenderState.OutOfBounds) { return true; } @@ -201,15 +193,17 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps { this.allowTextGeneration() === true) && this._layoutGenerated === false ) { - if (this.fontHandler.isFontLoaded(this.textProps.fontFamily) === true) { + //font existance is checked in allowTextGeneration method + const font = this.font as CoreFont; + if (font.state === FontState.Loaded) { this._waitingForFont = false; this._cachedLayout = null; // Invalidate cached layout this._lastVertexBuffer = null; // Invalidate last vertex buffer - const resp = this.textRenderer.renderText(this.textProps); + const resp = font.textRenderer.renderText(font, this.textProps); this.handleRenderResult(resp); this._layoutGenerated = true; } else if (this._waitingForFont === false) { - this.fontHandler.waitingForFont(this.textProps.fontFamily, this); + font.waiting(this); this._waitingForFont = true; } } @@ -222,8 +216,12 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps { * Override is renderable check for SDF text nodes */ override updateIsRenderable(): void { + if (this.font === undefined) { + this.setRenderable(false); + return; + } // SDF text nodes are always renderable if they have a valid layout - if (this._type === 'canvas') { + if (this.font.type === 'canvas') { super.updateIsRenderable(); return; } @@ -237,7 +235,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps { */ private handleRenderResult(result: TextRenderInfo): void { // Host paths on top - const textRendererType = this._type; + const textRendererType = this.font!.type; let width = result.width; let height = result.height; @@ -302,25 +300,31 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps { if (rtt === false || this.parentRenderTexture !== renderer.activeRttNode) return; } + const font = this.font; + if (font === undefined) { + console.warn('No font assigned to text node, cannot render'); + return; + } // Canvas renderer: use standard texture rendering via CoreNode - if (this._type === 'canvas') { + if (font.type === 'canvas') { super.renderQuads(renderer); return; } // Early return if no cached data - if (!this._cachedLayout) { + if (this._cachedLayout === null) { return; } if (this._lastVertexBuffer === null) { - this._lastVertexBuffer = this.textRenderer.addQuads(this._cachedLayout); + this._lastVertexBuffer = font.textRenderer.addQuads(this._cachedLayout); } const props = this.textProps; - this.textRenderer.renderQuads( + font.textRenderer.renderQuads( renderer, + font, this._cachedLayout as TextLayout, this._lastVertexBuffer!, { @@ -344,17 +348,18 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps { } override destroy(): void { - if (this._waitingForFont === true && this.fontHandler) { - this.fontHandler.stopWaitingForFont(this.textProps.fontFamily, this); + if (this.font !== undefined) { + if (this._waitingForFont === true) { + this.font.stopWaiting(this); + } + //remove font reference + this.font = undefined; } // Clear cached layout and vertex buffer this._cachedLayout = null; this._lastVertexBuffer = null; - this.fontHandler = null!; // Clear reference to avoid memory leaks - this.textRenderer = null!; // Clear reference to avoid memory leaks - super.destroy(); } @@ -441,9 +446,7 @@ export class CoreTextNode extends CoreNode implements CoreTextNodeProps { set fontFamily(value: string) { if (this.textProps.fontFamily !== value) { - if (this._waitingForFont === true) { - this.fontHandler.stopWaitingForFont(this.textProps.fontFamily, this); - } + this.font = this.stage.fontManager.getFont(value); this.textProps.fontFamily = value; this._layoutGenerated = false; this.setUpdateType(UpdateType.Local); diff --git a/src/core/Stage.ts b/src/core/Stage.ts index d949c4b6a..a5e90c3b1 100644 --- a/src/core/Stage.ts +++ b/src/core/Stage.ts @@ -28,11 +28,8 @@ import { import { CoreTextureManager } from './CoreTextureManager.js'; import { CoreShaderManager } from './CoreShaderManager.js'; import { - type FontHandler, type FontLoadOptions, - type TextRenderer, type TextRenderers, - type TrProps, } from './text-rendering/TextRenderer.js'; import { EventEmitter } from '../common/EventEmitter.js'; @@ -48,6 +45,7 @@ import { } from './TextureMemoryManager.js'; import { CoreRenderer } from './renderers/CoreRenderer.js'; import { CoreTextNode, type CoreTextNodeProps } from './CoreTextNode.js'; +import { CoreFontManager } from './text-rendering/FontManager.js'; import { santizeCustomDataMap } from '../main-api/utils.js'; import { pointInBound } from './lib/utils.js'; import type { CoreShaderNode } from './renderers/CoreShaderNode.js'; @@ -94,8 +92,7 @@ export class Stage { public readonly animationManager: AnimationManager; public readonly txManager: CoreTextureManager; public readonly txMemManager: TextureMemoryManager; - public readonly textRenderers: Record = {}; - public readonly fontHandlers: Record = {}; + public readonly fontManager: CoreFontManager; public readonly shManager: CoreShaderManager; public readonly renderer: CoreRenderer; public readonly root: CoreNode; @@ -144,13 +141,6 @@ export class Stage { private renderRequested = false; private frameEventQueue: [name: string, payload: unknown][] = []; - // Font resolve optimisation flags - private hasOnlyOneFontEngine: boolean; - private hasOnlyCanvasFontEngine: boolean; - private hasCanvasEngine: boolean; - private singleFontEngine: TextRenderer | null = null; - private singleFontHandler: FontHandler | null = null; - // Debug data contextSpy: ContextSpy | null = null; @@ -231,6 +221,7 @@ export class Stage { }); this.shManager = new CoreShaderManager(this); + this.fontManager = new CoreFontManager(this, fontEngines); this.defShaderNode = this.renderer.getDefaultShaderNode(); this.calculateTextureCoord = this.renderer.getTextureCoords !== undefined; @@ -243,84 +234,7 @@ export class Stage { // Must do this after renderer is created this.txManager.renderer = this.renderer; - // Create text renderers - this.hasOnlyOneFontEngine = fontEngines.length === 1; - this.hasOnlyCanvasFontEngine = - fontEngines.length === 1 && fontEngines[0]!.type === 'canvas'; - this.hasCanvasEngine = false; - this.singleFontEngine = this.hasOnlyOneFontEngine - ? (fontEngines[0] as TextRenderer) - : null; - this.singleFontHandler = this.hasOnlyOneFontEngine - ? (fontEngines[0]?.font as FontHandler) - : null; - - if (this.singleFontEngine === null) { - // Multiple font engines case - // Filter out incompatible engines first - const compatibleEngines = fontEngines.filter( - (fontEngine: TextRenderer) => { - const type = fontEngine.type; - - if (type === 'sdf' && renderMode === 'canvas') { - console.warn( - 'MsdfTextRenderer is not compatible with Canvas renderer. Skipping...', - ); - return false; - } - - if (type === 'canvas') { - this.hasCanvasEngine = true; - } - - return true; - }, - ); - - // Sort engines: SDF first, Canvas last, others in between - const sortedEngines = compatibleEngines.sort( - (a: TextRenderer, b: TextRenderer) => { - if (a.type === 'sdf') return -1; - if (b.type === 'sdf') return 1; - if (a.type === 'canvas') return 1; - if (b.type === 'canvas') return -1; - return 0; - }, - ); - - // Initialize engines in sorted order - sortedEngines.forEach((fontEngine: TextRenderer) => { - const type = fontEngine.type; - - // Add to map for type-based access - this.textRenderers[type] = fontEngine; - this.textRenderers[type].init(this); - - this.fontHandlers[type] = fontEngine.font; - }); - } else { - // Single font engine case - initialize it directly - const fontEngine = this.singleFontEngine; - const type = fontEngine.type; - - // Check compatibility - if (type === 'sdf' && renderMode === 'canvas') { - console.warn( - 'MsdfTextRenderer is not compatible with Canvas renderer. Skipping...', - ); - } else { - if (type === 'canvas') { - this.hasCanvasEngine = true; - } - - // Add to map for type-based access - this.textRenderers[type] = fontEngine; - this.fontHandlers[type] = fontEngine.font; - this.textRenderers[type].init(this); - } - } - - if (Object.keys(this.textRenderers).length === 0) { + if (fontEngines.length === 0) { console.warn('No text renderers available. Your text will not render.'); } @@ -616,66 +530,6 @@ export class Stage { this.renderRequested = true; } - /** - * Given a font name, and possible renderer override, return the best compatible text renderer. - * - * @remarks - * Will try to return a canvas renderer if no other suitable renderer can be resolved. - * - * @param fontFamily - * @param textRendererOverride - * @returns - */ - resolveTextRenderer( - trProps: TrProps, - textRendererOverride: keyof TextRenderers | null = null, - ): TextRenderer | null { - // If we have an overide, return it - if (textRendererOverride !== null) { - const overrideKey = String(textRendererOverride); - if (this.textRenderers[overrideKey] === undefined) { - console.warn(`Text renderer override '${overrideKey}' not found.`); - return null; - } - - return this.textRenderers[overrideKey]; - } - - // If we have only one font engine early return it - if (this.singleFontEngine !== null) { - // If we have only one font engine and its the canvas engine, we can just return it - if (this.hasOnlyCanvasFontEngine === true) { - return this.singleFontEngine; - } - - // If we have only one font engine and it can render the font, return it - if (this.singleFontHandler?.canRenderFont(trProps) === true) { - return this.singleFontEngine; - } - - // If we have only one font engine and it cannot render the font, return null - console.warn(`Text renderer cannot render font`, trProps); - - return null; - } - - // Multi font handling - If we have multiple font engines, we need to resolve the best one - - // First check SDF - if (this.fontHandlers['sdf']?.canRenderFont(trProps) === true) { - return this.textRenderers.sdf || null; - } - - // If we have a canvas engine, we can return it (it can render all fonts) - if (this.hasCanvasEngine === true) { - return this.textRenderers.canvas || null; - } - - // If we have no font engines, return null - console.warn('No text renderers available. Your text will not render.'); - return null; - } - createNode(props: Partial) { const resolvedProps = this.resolveNodeDefaults(props); return new CoreNode(this, resolvedProps); @@ -685,7 +539,6 @@ export class Stage { const fontSize = props.fontSize || 16; const resolvedProps = Object.assign(this.resolveNodeDefaults(props), { text: props.text || '', - textRendererOverride: props.textRendererOverride || null, fontSize, fontFamily: props.fontFamily || 'sans-serif', fontStyle: props.fontStyle || 'normal', @@ -703,18 +556,7 @@ export class Stage { forceLoad: props.forceLoad || false, }); - const resolvedTextRenderer = this.resolveTextRenderer( - resolvedProps, - resolvedProps.textRendererOverride as keyof TextRenderers | null, - ); - - if (!resolvedTextRenderer) { - throw new Error( - `No compatible text renderer found for ${resolvedProps.fontFamily}`, - ); - } - - return new CoreTextNode(this, resolvedProps, resolvedTextRenderer); + return new CoreTextNode(this, resolvedProps); } setBoundsMargin(value: number | [number, number, number, number]) { @@ -924,17 +766,6 @@ export class Stage { rendererType: TextRenderers, options: FontLoadOptions, ): Promise { - const rendererTypeKey = String(rendererType); - const fontHandler = this.fontHandlers[rendererTypeKey]; - - if (!fontHandler) { - throw new Error( - `Font handler for renderer type '${rendererTypeKey}' not found. Available types: ${Object.keys( - this.fontHandlers, - ).join(', ')}`, - ); - } - - return fontHandler.loadFont(this, options); + this.fontManager.loadFont(rendererType, options); } } diff --git a/src/core/text-rendering/CanvasFont.ts b/src/core/text-rendering/CanvasFont.ts new file mode 100644 index 000000000..d50fdff93 --- /dev/null +++ b/src/core/text-rendering/CanvasFont.ts @@ -0,0 +1,178 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2025 Comcast Cable Communications Management, LLC. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { CoreFont, FontState, type CoreFontProps } from './CoreFont.js'; +import { normalizeFontMetrics } from './TextLayoutEngine.js'; +import type { NormalizedFontMetrics, TextRenderer } from './TextRenderer.js'; +import { hasZeroWidthSpace } from './Utils.js'; + +/** + * make fontface add not show errors + */ +interface FontFaceSetWithAdd extends FontFaceSet { + add(font: FontFace): void; +} + +export type CanvasFontProps = CoreFontProps & { + fontUrl: string; +}; + +export class CanvasFont extends CoreFont { + public type = 'canvas'; + public url: string; + + constructor( + textRenderer: TextRenderer, + props: CanvasFontProps, + private measureContext: + | CanvasRenderingContext2D + | OffscreenCanvasRenderingContext2D, + ) { + super(textRenderer, props); + this.url = props.fontUrl; + this.metrics = props.metrics; + } + + load() { + if (this.state !== FontState.Created) { + return; + } + if (this.family === 'sans-serif') { + // Default font, skip loading + this.onLoaded(); + return; + } + + this.state = FontState.Loading; + new FontFace(this.family, `url(${this.url})`) + .load() + .then((loadedFont) => { + (document.fonts as FontFaceSetWithAdd).add(loadedFont); + this.onLoaded(); + }) + .catch((error) => { + this.state = FontState.Failed; + console.error(`Failed to load font: ${this.family}`, error); + this.emit('failed'); + throw error; + }); + } + + measureText(text: string, letterSpacing: number): number { + if (letterSpacing === 0) { + return this.measureContext.measureText(text).width; + } + if (hasZeroWidthSpace(text) === false) { + return ( + this.measureContext.measureText(text).width + + letterSpacing * text.length + ); + } + return text.split('').reduce((acc, char) => { + if (hasZeroWidthSpace(char) === true) { + return acc; + } + return acc + this.measureContext.measureText(char).width + letterSpacing; + }, 0); + } + + override getMetrics(fontSize: number): NormalizedFontMetrics { + let m = this.normalizedMetrics![fontSize]; + if (m !== undefined) { + return m; + } + let metrics = this.metrics; + + if (metrics === undefined) { + metrics = calculateCanvasMetrics( + this.family, + fontSize, + this.measureContext, + ); + } + m = this.normalizedMetrics![fontSize] = normalizeFontMetrics( + metrics, + fontSize, + ); + + console.log( + 'normalized metrics for font', + this.family, + 'at size', + fontSize, + 'calculated as', + m, + ); + return m; + } +} + +function calculateCanvasMetrics( + fontFamily: string, + fontSize: number, + measureContext: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, +) { + // If the font face doesn't have metrics defined, we fallback to using the + // browser's measureText method to calculate take a best guess at the font + // actual font's metrics. + // - fontBoundingBox[Ascent|Descent] is the best estimate but only supported + // in Chrome 87+ (2020), Firefox 116+ (2023), and Safari 11.1+ (2018). + // - It is an estimate as it can vary between browsers. + // - actualBoundingBox[Ascent|Descent] is less accurate and supported in + // Chrome 77+ (2019), Firefox 74+ (2020), and Safari 11.1+ (2018). + // - If neither are supported, we'll use some default values which will + // get text on the screen but likely not be great. + // NOTE: It's been decided not to rely on fontBoundingBox[Ascent|Descent] + // as it's browser support is limited and it also tends to produce higher than + // expected values. It is instead HIGHLY RECOMMENDED that developers provide + // explicit metrics in the font face definition. + + // Ensure font is loaded by checking document.fonts + if (!document.fonts.check(`${fontSize}px ${fontFamily}`)) { + console.warn( + `Font ${fontFamily} may not be fully loaded yet when calculating metrics`, + ); + } + + measureContext.font = `${fontSize}px ${fontFamily}`; + + const metrics = measureContext.measureText( + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', + ); + console.warn( + `Font metrics not provided for Canvas Web font ${fontFamily}. ` + + 'Using fallback values. It is HIGHLY recommended you use the latest ' + + 'version of the Lightning 3 `msdf-generator` tool to extract the default ' + + 'metrics for the font and provide them in the Canvas Web font definition.', + ); + const ascender = + metrics.fontBoundingBoxAscent ?? metrics.actualBoundingBoxAscent ?? 0; + const descender = + metrics.fontBoundingBoxDescent ?? metrics.actualBoundingBoxDescent ?? 0; + + const emHeight = + (metrics.emHeightAscent ?? 0) + (metrics.emHeightDescent ?? 0); + const unitsPerEm = emHeight > 0 ? emHeight : ascender + descender; + + return { + ascender, + descender: -descender, + lineGap: emHeight - (ascender + descender), + unitsPerEm, + }; +} diff --git a/src/core/text-rendering/CanvasFontHandler.ts b/src/core/text-rendering/CanvasFontHandler.ts deleted file mode 100644 index 35db304bf..000000000 --- a/src/core/text-rendering/CanvasFontHandler.ts +++ /dev/null @@ -1,304 +0,0 @@ -/* - * If not stated otherwise in this file or this component's LICENSE file the - * following copyright and licenses apply: - * - * Copyright 2025 Comcast Cable Communications Management, LLC. - * - * Licensed under the Apache License, Version 2.0 (the License); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import type { - FontFamilyMap, - FontLoadOptions, - FontMetrics, - NormalizedFontMetrics, -} from './TextRenderer.js'; -import type { Stage } from '../Stage.js'; -import { hasZeroWidthSpace } from './Utils.js'; -import type { CoreTextNode } from '../CoreTextNode.js'; -import { UpdateType } from '../CoreNode.js'; -import { - defaultFontMetrics, - normalizeFontMetrics, -} from './TextLayoutEngine.js'; - -interface CanvasFont { - fontFamily: string; - fontFace?: FontFace; - metrics?: FontMetrics; -} - -/** - * Global font set regardless of if run in the main thread or a web worker - */ -// const globalFontSet: FontFaceSet = (resolvedGlobal.document?.fonts || -// (resolvedGlobal as unknown as { fonts: FontFaceSet }).fonts) as FontFaceSet; - -// Global state variables for fontHandler -const fontFamilies: Record = {}; -const fontLoadPromises = new Map>(); -const normalizedMetrics = new Map(); -const nodesWaitingForFont: Record = Object.create( - null, -) as Record; - -const fontCache = new Map(); - -let initialized = false; -let context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D; -let measureContext: - | CanvasRenderingContext2D - | OffscreenCanvasRenderingContext2D; - -/** - * Check if a font can be rendered - */ -export const canRenderFont = (): boolean => { - // Canvas can always render any font family (assuming the browser supports it) - return true; -}; - -const processFontData = ( - fontFamily: string, - fontFace?: FontFace, - metrics?: FontMetrics, -) => { - metrics = metrics || defaultFontMetrics; - fontCache.set(fontFamily, { - fontFamily, - fontFace, - metrics, - }); -}; - -/** - * Load a font by providing fontFamily, fontUrl, and optional metrics - */ -export const loadFont = async ( - stage: Stage, - options: FontLoadOptions, -): Promise => { - const { fontFamily, fontUrl, metrics } = options; - - // If already loaded, return immediately - if (fontCache.has(fontFamily) === true) { - return; - } - - const existingPromise = fontLoadPromises.get(fontFamily); - // If already loading, return the existing promise - if (existingPromise !== undefined) { - return existingPromise; - } - - const nwff: CoreTextNode[] = (nodesWaitingForFont[fontFamily] = []); - // Create and store the loading promise - const loadPromise = new FontFace(fontFamily, `url(${fontUrl})`) - .load() - .then((loadedFont) => { - stage.platform.addFont(loadedFont); - processFontData(fontFamily, loadedFont, metrics); - fontLoadPromises.delete(fontFamily); - for (let key in nwff) { - nwff[key]!.setUpdateType(UpdateType.Local); - } - delete nodesWaitingForFont[fontFamily]; - }) - .catch((error) => { - fontLoadPromises.delete(fontFamily); - console.error(`Failed to load font: ${fontFamily}`, error); - throw error; - }); - - fontLoadPromises.set(fontFamily, loadPromise); - return loadPromise; -}; - -/** - * Get the font families map for resolving fonts - */ -export const getFontFamilies = (): FontFamilyMap => { - return fontFamilies; -}; - -/** - * Initialize the global font handler - */ -export const init = ( - c: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, - mc: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, -): void => { - if (initialized === true) { - return; - } - - if (c === undefined) { - throw new Error( - 'Canvas context is not provided for font handler initialization', - ); - } - - context = c; - measureContext = mc; - - // Register the default 'sans-serif' font face - const defaultMetrics: FontMetrics = { - ascender: 800, - descender: -200, - lineGap: 200, - unitsPerEm: 1000, - }; - - processFontData('sans-serif', undefined, defaultMetrics); - initialized = true; -}; - -export const type = 'canvas'; - -/** - * Check if a font is already loaded by font family - */ -export const isFontLoaded = (fontFamily: string): boolean => { - return fontCache.has(fontFamily); -}; - -/** - * Wait for a font to load - * - * @param fontFamily - * @param node - */ -export const waitingForFont = (fontFamily: string, node: CoreTextNode) => { - if (nodesWaitingForFont[fontFamily] === undefined) { - return; - } - nodesWaitingForFont[fontFamily]![node.id] = node; -}; - -/** - * Stop waiting for a font to load - * - * @param fontFamily - * @param node - * @returns - */ -export const stopWaitingForFont = (fontFamily: string, node: CoreTextNode) => { - if (nodesWaitingForFont[fontFamily] === undefined) { - return; - } - - delete nodesWaitingForFont[fontFamily][node.id]; -}; - -export const getFontMetrics = ( - fontFamily: string, - fontSize: number, -): NormalizedFontMetrics => { - const out = normalizedMetrics.get(fontFamily + fontSize); - if (out !== undefined) { - return out; - } - let metrics = fontCache.get(fontFamily)!.metrics; - if (metrics === undefined) { - metrics = calculateFontMetrics(fontFamily, fontSize); - } - return processFontMetrics(fontFamily, fontSize, metrics); -}; - -export const processFontMetrics = ( - fontFamily: string, - fontSize: number, - metrics: FontMetrics, -): NormalizedFontMetrics => { - const label = fontFamily + fontSize; - const normalized = normalizeFontMetrics(metrics, fontSize); - normalizedMetrics.set(label, normalized); - return normalized; -}; - -export const measureText = ( - text: string, - fontFamily: string, - letterSpacing: number, -) => { - if (letterSpacing === 0) { - return measureContext.measureText(text).width; - } - if (hasZeroWidthSpace(text) === false) { - return measureContext.measureText(text).width + letterSpacing * text.length; - } - return text.split('').reduce((acc, char) => { - if (hasZeroWidthSpace(char) === true) { - return acc; - } - return acc + measureContext.measureText(char).width + letterSpacing; - }, 0); -}; - -/** - * Get the font metrics for a font face. - * - * @remarks - * This function will attempt to grab the explicitly defined metrics from the - * font face first. If the font face does not have metrics defined, it will - * attempt to calculate the metrics using the browser's measureText method. - * - * If the browser does not support the font metrics API, it will use some - * default values. - * - * @param context - * @param fontFace - * @param fontSize - * @returns - */ -export function calculateFontMetrics( - fontFamily: string, - fontSize: number, -): FontMetrics { - // If the font face doesn't have metrics defined, we fallback to using the - // browser's measureText method to calculate take a best guess at the font - // actual font's metrics. - // - fontBoundingBox[Ascent|Descent] is the best estimate but only supported - // in Chrome 87+ (2020), Firefox 116+ (2023), and Safari 11.1+ (2018). - // - It is an estimate as it can vary between browsers. - // - actualBoundingBox[Ascent|Descent] is less accurate and supported in - // Chrome 77+ (2019), Firefox 74+ (2020), and Safari 11.1+ (2018). - // - If neither are supported, we'll use some default values which will - // get text on the screen but likely not be great. - // NOTE: It's been decided not to rely on fontBoundingBox[Ascent|Descent] - // as it's browser support is limited and it also tends to produce higher than - // expected values. It is instead HIGHLY RECOMMENDED that developers provide - // explicit metrics in the font face definition. - const metrics = measureContext.measureText( - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz', - ); - console.warn( - `Font metrics not provided for Canvas Web font ${fontFamily}. ` + - 'Using fallback values. It is HIGHLY recommended you use the latest ' + - 'version of the Lightning 3 `msdf-generator` tool to extract the default ' + - 'metrics for the font and provide them in the Canvas Web font definition.', - ); - const ascender = - metrics.fontBoundingBoxAscent ?? metrics.actualBoundingBoxAscent ?? 0; - const descender = - metrics.fontBoundingBoxDescent ?? metrics.actualBoundingBoxDescent ?? 0; - return { - ascender, - descender: -descender, - lineGap: - (metrics.emHeightAscent ?? 0) + - (metrics.emHeightDescent ?? 0) - - (ascender + descender), - unitsPerEm: (metrics.emHeightAscent ?? 0) + (metrics.emHeightDescent ?? 0), - }; -} diff --git a/src/core/text-rendering/CanvasTextRenderer.ts b/src/core/text-rendering/CanvasTextRenderer.ts index b2cee3641..7099ccbc1 100644 --- a/src/core/text-rendering/CanvasTextRenderer.ts +++ b/src/core/text-rendering/CanvasTextRenderer.ts @@ -19,25 +19,32 @@ import { assertTruthy } from '../../utils.js'; import type { Stage } from '../Stage.js'; -import type { TextLineStruct, TextRenderInfo } from './TextRenderer.js'; -import * as CanvasFontHandler from './CanvasFontHandler.js'; +import type { + FontLoadOptions, + FontMetrics, + TextLineStruct, + TextRenderer, + TextRenderInfo, +} from './TextRenderer.js'; import type { CoreTextNodeProps } from '../CoreTextNode.js'; import { hasZeroWidthSpace } from './Utils.js'; import { mapTextLayout } from './TextLayoutEngine.js'; +import { CanvasFont, type CanvasFontProps } from './CanvasFont.js'; +import type { CoreFontManager } from './FontManager.js'; const MAX_TEXTURE_DIMENSION = 4096; const type = 'canvas' as const; let canvas: HTMLCanvasElement | OffscreenCanvas | null = null; -let context: +export let context: | CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null = null; // Separate canvas and context for text measurements let measureCanvas: HTMLCanvasElement | OffscreenCanvas | null = null; -let measureContext: +export let measureContext: | CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D | null = null; @@ -55,7 +62,7 @@ const layoutCache = new Map< >(); // Initialize the Text Renderer -const init = (stage: Stage): void => { +const init = (stage: Stage, fontManager: CoreFontManager): void => { const dpr = stage.options.devicePhysicalPixelRatio; // Drawing canvas and context @@ -82,7 +89,30 @@ const init = (stage: Stage): void => { measureCanvas.width = 1; measureCanvas.height = 1; - CanvasFontHandler.init(context, measureContext); + // Register the default 'sans-serif' font face + const defaultMetrics: FontMetrics = { + ascender: 800, + descender: -200, + lineGap: 200, + unitsPerEm: 1000, + }; + + fontManager.loadFont(type, { + fontFamily: 'sans-serif', + metrics: defaultMetrics, + }); +}; + +const createFont = (settings: FontLoadOptions): CanvasFont | undefined => { + if (settings.fontFamily !== 'sans-serif' && settings.fontUrl === undefined) { + console.error('fontUrl is missing'); + return; + } + return new CanvasFont( + CanvasTextRenderer as unknown as TextRenderer, + settings as unknown as CanvasFontProps, + measureContext!, + ); }; /** @@ -92,7 +122,10 @@ const init = (stage: Stage): void => { * @param props - Text rendering properties * @returns Object containing ImageData and dimensions */ -const renderText = (props: CoreTextNodeProps): TextRenderInfo => { +const renderText = ( + font: CanvasFont, + props: CoreTextNodeProps, +): TextRenderInfo => { assertTruthy(canvas, 'Canvas is not initialized'); assertTruthy(context, 'Canvas context is not available'); assertTruthy(measureContext, 'Canvas measureContext is not available'); @@ -112,12 +145,12 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => { wordBreak, } = props; - const font = `${fontStyle} ${fontSize}px Unknown, ${fontFamily}`; + const canvasFont = `${fontStyle} ${fontSize}px Unknown, ${fontFamily}`; // Get font metrics and calculate line height - measureContext.font = font; + measureContext.font = canvasFont; measureContext.textBaseline = 'hanging'; - const metrics = CanvasFontHandler.getFontMetrics(fontFamily, fontSize); + const metrics = font.getMetrics(fontSize); const letterSpacing = props.letterSpacing; @@ -130,7 +163,7 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => { effectiveWidth, effectiveHeight, ] = mapTextLayout( - CanvasFontHandler.measureText, + font, metrics, text, textAlign, @@ -150,7 +183,7 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => { canvas.width = canvasW; canvas.height = canvasH; context.fillStyle = 'white'; - context.font = font; + context.font = canvasFont; context.textBaseline = 'hanging'; // Performance optimization for large fonts @@ -175,11 +208,7 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => { continue; } context.fillText(char, currentX, currentY); - currentX += CanvasFontHandler.measureText( - char, - fontFamily, - letterSpacing, - ); + currentX += font.measureText(char, letterSpacing); } } } @@ -244,7 +273,7 @@ const renderQuads = (): void => { */ const CanvasTextRenderer = { type, - font: CanvasFontHandler, + createFont, renderText, addQuads, renderQuads, diff --git a/src/core/text-rendering/CoreFont.ts b/src/core/text-rendering/CoreFont.ts new file mode 100644 index 000000000..5a259e497 --- /dev/null +++ b/src/core/text-rendering/CoreFont.ts @@ -0,0 +1,91 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2025 Comcast Cable Communications Management, LLC. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import type { CoreTextNode } from '../CoreTextNode.js'; +import type { + FontMetrics, + NormalizedFontMetrics, + TextRenderer, +} from './TextRenderer.js'; +import { UpdateType } from '../CoreNode.js'; +import { EventEmitter } from '../../common/EventEmitter.js'; + +export enum FontState { + Created, + Loading, + Loaded, + Failed, +} + +export interface CoreFontProps { + fontFamily: string; + metrics?: FontMetrics; +} + +/** + * EventEmiter only intended to communicated with FontManager + */ +export abstract class CoreFont extends EventEmitter { + protected waitingNodes?: Record = Object.create( + null, + ) as Record; + protected normalizedMetrics?: Record = + Object.create(null) as Record; + + public textRenderer: TextRenderer; + public state: FontState; + public family: string; + public metrics?: FontMetrics; + + constructor(textRenderer: TextRenderer, props: CoreFontProps) { + super(); + this.family = props.fontFamily; + this.state = FontState.Created; + this.textRenderer = textRenderer; + } + + protected onLoaded() { + const waitingNodes = this.waitingNodes; + for (let key in waitingNodes) { + waitingNodes[key]!.setUpdateType(UpdateType.Local); + delete waitingNodes[key]; + } + this.state = FontState.Loaded; + } + + public waiting(node: CoreTextNode) { + this.waitingNodes![node.id] = node; + } + + public stopWaiting(node: CoreTextNode) { + if (this.waitingNodes![node.id]) { + delete this.waitingNodes![node.id]; + } + } + + public destroy() { + delete this.waitingNodes; + delete this.normalizedMetrics; + delete this.metrics; + } + + abstract type: string; + abstract load(): void; + abstract measureText(text: string, letterSpacing: number): number; + abstract getMetrics(fontSize: number): NormalizedFontMetrics; +} diff --git a/src/core/text-rendering/FontManager.ts b/src/core/text-rendering/FontManager.ts new file mode 100644 index 000000000..1ebe82bc6 --- /dev/null +++ b/src/core/text-rendering/FontManager.ts @@ -0,0 +1,57 @@ +import type { Stage } from '../Stage.js'; +import type { CoreFont } from './CoreFont.js'; +import type { + FontLoadOptions, + TextRenderer, + TextRenderers, +} from './TextRenderer.js'; + +export class CoreFontManager { + private fonts: Record = Object.create(null) as Record< + string, + CoreFont + >; + private renderers: Record = Object.create( + null, + ) as Record; + + constructor(stage: Stage, textRenderers: TextRenderer[]) { + for (let i = 0; i < textRenderers.length; i++) { + const renderer = textRenderers[i]!; + this.renderers[renderer.type] = renderer; + renderer.init(stage, this); + } + } + + loadFont(type: TextRenderers, options: FontLoadOptions) { + const targetRenderer = this.renderers[type]; + if (targetRenderer === undefined) { + console.error('renderer type for this font does not exist'); + return; + } + const font = targetRenderer.createFont(options); + if (font === undefined) { + return; + } + font.load(); + this.fonts[options.fontFamily] = font; + } + + unloadFont(fontFamily: string) { + const targetFont = this.fonts[fontFamily]; + if (targetFont === undefined) { + return; + } + targetFont.destroy(); + delete this.fonts[fontFamily]; + } + + getFont(fontFamily: string) { + const font = this.fonts[fontFamily]; + if (font === undefined) { + console.warn('fontFamily not registered'); + return; + } + return font; + } +} diff --git a/src/core/text-rendering/SdfFont.ts b/src/core/text-rendering/SdfFont.ts new file mode 100644 index 000000000..d8bc74684 --- /dev/null +++ b/src/core/text-rendering/SdfFont.ts @@ -0,0 +1,179 @@ +/* + * If not stated otherwise in this file or this component's LICENSE file the + * following copyright and licenses apply: + * + * Copyright 2025 Comcast Cable Communications Management, LLC. + * + * Licensed under the Apache License, Version 2.0 (the License); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { Stage } from '../Stage.js'; +import type { ImageTexture } from '../textures/ImageTexture.js'; +import { CoreFont, FontState, type CoreFontProps } from './CoreFont.js'; +import { + buildGlyphMap, + buildKerningTable, + type KerningTable, + type SdfFontData, + type SdfGlyphMap, +} from './SdfFontHandler.js'; +import { normalizeFontMetrics } from './TextLayoutEngine.js'; +import type { NormalizedFontMetrics, TextRenderer } from './TextRenderer.js'; +import { hasZeroWidthSpace } from './Utils.js'; + +export type SdfFontProps = CoreFontProps & { + atlasUrl: string; + atlasDataUrl: string; +}; + +export class SdfFont extends CoreFont { + public type = 'sdf'; + public atlasUrl: string; + public atlasDataUrl: string; + public atlasTexture?: ImageTexture; + + private glyphMap?: SdfGlyphMap; + private kerningTable?: KerningTable; + private data?: SdfFontData; + + constructor( + textRenderer: TextRenderer, + props: SdfFontProps, + private stage: Stage, + ) { + super(textRenderer, props); + this.atlasUrl = props.atlasUrl; + this.atlasDataUrl = props.atlasDataUrl; + } + + load() { + new Promise(async () => { + const atlasData = await fetch(this.atlasDataUrl); + if (atlasData.ok === false) { + this.hardFail(`Failed to load font data: ${atlasData.statusText}`); + } + + const fontData = (await atlasData.json()) as SdfFontData; + if (fontData.chars === undefined) { + this.hardFail('Invalid SDF font data format'); + } + + const atlasTexture = this.stage.txManager.createTexture('ImageTexture', { + src: this.atlasUrl, + premultiplyAlpha: false, + }); + + atlasTexture.setRenderableOwner(this.family, true); + atlasTexture.preventCleanup = true; + atlasTexture.on('loaded', () => { + this.onLoaded(); + }); + atlasTexture.on('failed', (error: Error) => { + console.error(`Failed to load SDF font: ${this.family}`, error); + this.emit('failed'); + }); + + this.atlasTexture = atlasTexture; + this.processFontData(fontData); + }); + } + + private hardFail(message: string) { + this.state = FontState.Failed; + throw new Error(message); + } + + private processFontData(fontData: SdfFontData) { + this.glyphMap = buildGlyphMap(fontData.chars); + this.kerningTable = buildKerningTable(fontData.kernings); + this.metrics = this.metrics || + fontData.lightningMetrics || { + ascender: 800, + descender: -200, + lineGap: 200, + unitsPerEm: 1000, + }; + this.data = fontData; + } + + measureText(text: string, letterSpacing: number) { + if (text.length === 1) { + const char = text.charAt(0); + const codepoint = text.codePointAt(0); + if (codepoint === undefined) return 0; + if (hasZeroWidthSpace(char) === true) return 0; + + const glyph = this.getGlyph(codepoint); + if (glyph === null) return 0; + return glyph.xadvance + letterSpacing; + } + let width = 0; + let prevCodepoint = 0; + for (let i = 0; i < text.length; i++) { + const char = text.charAt(i); + const codepoint = text.codePointAt(i); + if (codepoint === undefined) continue; + + // Skip zero-width spaces in width calculations + if (hasZeroWidthSpace(char)) { + continue; + } + + const glyph = this.getGlyph(codepoint); + if (glyph === null) continue; + + let advance = glyph.xadvance; + + // Add kerning if there's a previous character + if (prevCodepoint !== 0) { + const kerning = this.getKerning(prevCodepoint, codepoint); + advance += kerning; + } + + width += advance + letterSpacing; + prevCodepoint = codepoint; + } + + return width; + } + + getMetrics(fontSize: number): NormalizedFontMetrics { + let m = this.normalizedMetrics![fontSize]; + if (m !== undefined) { + return m; + } + m = this.normalizedMetrics![fontSize] = normalizeFontMetrics( + this.metrics!, + fontSize, + ); + return m; + } + + getGlyph(codepoint: number) { + const gm = this.glyphMap as SdfGlyphMap; + return gm[codepoint] || gm[63] || null; + } + + getKerning(firstGlyph: number, secondGlyph: number) { + const seconds = this.kerningTable![secondGlyph]; + return (seconds !== undefined && seconds[firstGlyph]) || 0; + } + + getAtlas() { + return this.atlasTexture!; + } + + getData() { + return this.data!; + } +} diff --git a/src/core/text-rendering/SdfFontHandler.ts b/src/core/text-rendering/SdfFontHandler.ts index c5666c05f..368cb64bf 100644 --- a/src/core/text-rendering/SdfFontHandler.ts +++ b/src/core/text-rendering/SdfFontHandler.ts @@ -17,44 +17,35 @@ * limitations under the License. */ -import type { - FontFamilyMap, - FontMetrics, - NormalizedFontMetrics, - TrProps, - FontLoadOptions, -} from './TextRenderer.js'; -import type { ImageTexture } from '../textures/ImageTexture.js'; -import type { Stage } from '../Stage.js'; -import type { CoreTextNode } from '../CoreTextNode.js'; -import { UpdateType } from '../CoreNode.js'; -import { hasZeroWidthSpace } from './Utils.js'; -import { normalizeFontMetrics } from './TextLayoutEngine.js'; +import type { FontMetrics } from './TextRenderer.js'; +export interface SdfGlyph { + id: number; + char: string; + x: number; + y: number; + width: number; + height: number; + xoffset: number; + yoffset: number; + xadvance: number; + page: number; + chnl: number; +} + +export type SdfGlyphMap = Record; +export interface SdfKerning { + first: number; + second: number; + amount: number; +} /** * SDF Font Data structure matching msdf-bmfont-xml output */ export interface SdfFontData { pages: string[]; - chars: Array<{ - id: number; - char: string; - x: number; - y: number; - width: number; - height: number; - xoffset: number; - yoffset: number; - xadvance: number; - page: number; - chnl: number; - }>; - - kernings: Array<{ - first: number; - second: number; - amount: number; - }>; + chars: SdfGlyph[]; + kernings: SdfKerning[]; info: { face: string; size: number; @@ -108,39 +99,17 @@ export interface SdfFontData { * @typedef {Object} KerningTable * Fast lookup table for kerning values */ -type KerningTable = Record< +export type KerningTable = Record< number, Record | undefined >; -/** - * @typedef {Object} SdfFontCache - * Cached font data for performance - */ -export interface SdfFont { - data: SdfFontData; - glyphMap: Map; - kernings: KerningTable; - atlasTexture: ImageTexture; - metrics: FontMetrics; - maxCharHeight: number; -} - -//global state variables for SdfFontHandler -const fontCache = new Map(); -const fontLoadPromises = new Map>(); -const normalizedMetrics = new Map(); -const nodesWaitingForFont: Record = Object.create( - null, -) as Record; -let initialized = false; - /** * Build kerning lookup table for fast access * @param {Array} kernings - Kerning data from font * @returns {KerningTable} Optimized kerning lookup table */ -const buildKerningTable = (kernings: SdfFontData['kernings']): KerningTable => { +export const buildKerningTable = (kernings: SdfKerning[]): KerningTable => { const kerningTable: KerningTable = {}; let i = 0; @@ -165,16 +134,8 @@ const buildKerningTable = (kernings: SdfFontData['kernings']): KerningTable => { return kerningTable; }; -/** - * Build glyph map from font data for fast character lookup - * @param {Array} chars - Character data from font - * @returns {Map} Glyph map for character to glyph lookup - */ -const buildGlyphMap = ( - chars: SdfFontData['chars'], -): Map => { - const glyphMap = new Map(); - let maxCharHeight = 0; +export const buildGlyphMap = (chars: SdfGlyph[]) => { + const glyphMap = Object.create(null) as SdfGlyphMap; let i = 0; const length = chars.length; @@ -187,405 +148,7 @@ const buildGlyphMap = ( continue; } - glyphMap.set(glyph.id, glyph); - - const charHeight = glyph.yoffset + glyph.height; - if (charHeight > maxCharHeight) { - maxCharHeight = charHeight; - } + glyphMap[glyph.id] = glyph; } - return glyphMap; }; - -/** - * Process font data and create optimized cache entry - * @param {string} fontFamily - Font family name - * @param {SdfFontData} fontData - Raw font data - * @param {ImageTexture} atlasTexture - Atlas texture - * @param {FontMetrics} metrics - Font metrics - */ -const processFontData = ( - fontFamily: string, - fontData: SdfFontData, - atlasTexture: ImageTexture, - metrics?: FontMetrics, -): void => { - // Build optimized data structures - const glyphMap = buildGlyphMap(fontData.chars); - const kernings = buildKerningTable(fontData.kernings); - - // Calculate max char height - let maxCharHeight = 0; - let i = 0; - const length = fontData.chars.length; - - while (i < length) { - const glyph = fontData.chars[i]; - if (glyph !== undefined) { - const charHeight = glyph.yoffset + glyph.height; - if (charHeight > maxCharHeight) { - maxCharHeight = charHeight; - } - } - i++; - } - - if (metrics === undefined && fontData.lightningMetrics === undefined) { - console.warn( - `Font metrics not found for SDF font ${fontFamily}. ` + - 'Make sure you are using the latest version of the Lightning ' + - '3 msdf-generator tool to generate your SDF fonts. Using default metrics.', - ); - } - - metrics = metrics || - fontData.lightningMetrics || { - ascender: 800, - descender: -200, - lineGap: 200, - unitsPerEm: 1000, - }; - - // Cache processed data - fontCache.set(fontFamily, { - data: fontData, - glyphMap, - kernings, - atlasTexture, - metrics, - maxCharHeight, - }); -}; - -/** - * Check if the SDF font handler can render a font - * @param {TrProps} trProps - Text rendering properties - * @returns {boolean} True if the font can be rendered - */ -export const canRenderFont = (trProps: TrProps): boolean => { - return ( - isFontLoaded(trProps.fontFamily) || fontLoadPromises.has(trProps.fontFamily) - ); -}; - -/** - * Load SDF font from JSON + PNG atlas - * @param {Object} options - Font loading options - * @param {string} options.fontFamily - Font family name - * @param {string} options.fontUrl - JSON font data URL (atlasDataUrl) - * @param {string} options.atlasUrl - PNG atlas texture URL - * @param {FontMetrics} options.metrics - Optional font metrics - */ -export const loadFont = async ( - stage: Stage, - options: FontLoadOptions, -): Promise => { - const { fontFamily, atlasUrl, atlasDataUrl, metrics } = options; - // Early return if already loaded - if (fontCache.get(fontFamily) !== undefined) { - return; - } - - // Early return if already loading - const existingPromise = fontLoadPromises.get(fontFamily); - if (existingPromise !== undefined) { - return existingPromise; - } - - if (atlasDataUrl === undefined) { - throw new Error( - `Atlas data URL must be provided for SDF font: ${fontFamily}`, - ); - } - - const nwff: CoreTextNode[] = (nodesWaitingForFont[fontFamily] = []); - // Create loading promise - const loadPromise = (async (): Promise => { - // Load font JSON data - const response = await fetch(atlasDataUrl); - if (!response.ok) { - throw new Error(`Failed to load font data: ${response.statusText}`); - } - - const fontData = (await response.json()) as SdfFontData; - if (!fontData || !fontData.chars) { - throw new Error('Invalid SDF font data format'); - } - - // Atlas texture should be provided externally - if (!atlasUrl) { - throw new Error('Atlas texture must be provided for SDF fonts'); - } - - // Wait for atlas texture to load - return new Promise((resolve, reject) => { - // create new atlas texture using ImageTexture - const atlasTexture = stage.txManager.createTexture('ImageTexture', { - src: atlasUrl, - premultiplyAlpha: false, - }); - - atlasTexture.setRenderableOwner(fontFamily, true); - atlasTexture.preventCleanup = true; // Prevent automatic cleanup - - if (atlasTexture.state === 'loaded') { - // If already loaded, process immediately - processFontData(fontFamily, fontData, atlasTexture, metrics); - fontLoadPromises.delete(fontFamily); - - for (let key in nwff) { - nwff[key]!.setUpdateType(UpdateType.Local); - } - delete nodesWaitingForFont[fontFamily]; - return resolve(); - } - - atlasTexture.on('loaded', () => { - // Process and cache font data - processFontData(fontFamily, fontData, atlasTexture, metrics); - - // remove from promises - fontLoadPromises.delete(fontFamily); - - for (let key in nwff) { - nwff[key]!.setUpdateType(UpdateType.Local); - } - delete nodesWaitingForFont[fontFamily]; - resolve(); - }); - - atlasTexture.on('failed', (error: Error) => { - // Cleanup on error - fontLoadPromises.delete(fontFamily); - if (fontCache[fontFamily]) { - delete fontCache[fontFamily]; - } - console.error(`Failed to load SDF font: ${fontFamily}`, error); - reject(error); - }); - }); - })(); - - fontLoadPromises.set(fontFamily, loadPromise); - return loadPromise; -}; - -/** - * Stop waiting for a font to load - * @param {string} fontFamily - Font family name - * @param {CoreTextNode} node - Node that was waiting for the font - */ -export const waitingForFont = (fontFamily: string, node: CoreTextNode) => { - if (nodesWaitingForFont[fontFamily] === undefined) { - return; - } - nodesWaitingForFont[fontFamily]![node.id] = node; -}; - -/** - * Stop waiting for a font to load - * - * @param fontFamily - * @param node - * @returns - */ -export const stopWaitingForFont = (fontFamily: string, node: CoreTextNode) => { - if (nodesWaitingForFont[fontFamily] === undefined) { - return; - } - delete nodesWaitingForFont[fontFamily][node.id]; -}; - -/** - * Get the font families map for resolving fonts - */ -export const getFontFamilies = (): FontFamilyMap => { - const families: FontFamilyMap = {}; - - // SDF fonts don't use the traditional FontFamilyMap structure - // Return empty map since SDF fonts are handled differently - return families; -}; - -/** - * Initialize the SDF font handler - */ -export const init = ( - c?: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, -): void => { - if (initialized === true) { - return; - } - - initialized = true; -}; - -export const type = 'sdf'; - -/** - * Check if a font is already loaded by font family - */ -export const isFontLoaded = (fontFamily: string): boolean => { - return fontCache.has(fontFamily); -}; - -/** - * Get normalized font metrics for a font family - */ -export const getFontMetrics = ( - fontFamily: string, - - fontSize: number, -): NormalizedFontMetrics => { - const out = normalizedMetrics.get(fontFamily); - if (out !== undefined) { - return out; - } - let metrics = fontCache.get(fontFamily)!.metrics; - return processFontMetrics(fontFamily, fontSize, metrics); -}; - -export const processFontMetrics = ( - fontFamily: string, - fontSize: number, - metrics: FontMetrics, -): NormalizedFontMetrics => { - const label = fontFamily + fontSize; - const normalized = normalizeFontMetrics(metrics, fontSize); - normalizedMetrics.set(label, normalized); - return normalized; -}; - -/** - * Get glyph data for a character in a specific font - * @param {string} fontFamily - Font family name - * @param {number} codepoint - Character codepoint - * @returns {Object|null} Glyph data or null if not found - */ -export const getGlyph = ( - fontFamily: string, - codepoint: number, -): SdfFontData['chars'][0] | null => { - const cache = fontCache.get(fontFamily); - if (cache === undefined) return null; - - return cache.glyphMap.get(codepoint) || cache.glyphMap.get(63) || null; // 63 = '?' -}; - -/** - * Get kerning value between two glyphs - * @param {string} fontFamily - Font family name - * @param {number} firstGlyph - First glyph ID - * @param {number} secondGlyph - Second glyph ID - * @returns {number} Kerning value or 0 - */ -export const getKerning = ( - fontFamily: string, - firstGlyph: number, - secondGlyph: number, -): number => { - const cache = fontCache.get(fontFamily); - if (cache === undefined) return 0; - - const seconds = cache.kernings[secondGlyph]; - return seconds ? seconds[firstGlyph] || 0 : 0; -}; - -/** - * Get atlas texture for a font family - * @param {string} fontFamily - Font family name - * @returns {ImageTexture|null} Atlas texture or null - */ -export const getAtlas = (fontFamily: string): ImageTexture | null => { - const cache = fontCache.get(fontFamily); - return cache !== undefined ? cache.atlasTexture : null; -}; - -/** - * Get font data for a font family - * @param {string} fontFamily - Font family name - * @returns {SdfFontData|null} Font data or null - */ -export const getFontData = (fontFamily: string): SdfFont | undefined => { - return fontCache.get(fontFamily); -}; - -/** - * Get maximum character height for a font family - * @param {string} fontFamily - Font family name - * @returns {number} Max character height or 0 - */ -export const getMaxCharHeight = (fontFamily: string): number => { - const cache = fontCache.get(fontFamily); - return cache !== undefined ? cache.maxCharHeight : 0; -}; - -/** - * Get all loaded font families - * @returns {string[]} Array of font family names - */ -export const getLoadedFonts = (): string[] => { - return Array.from(fontCache.keys()); -}; - -/** - * Unload a font and free resources - * @param {string} fontFamily - Font family name - */ -export const unloadFont = (fontFamily: string): void => { - const cache = fontCache.get(fontFamily); - if (cache !== undefined) { - // Free texture if needed - if (typeof cache.atlasTexture.free === 'function') { - cache.atlasTexture.free(); - } - - fontCache.delete(fontFamily); - } -}; - -export const measureText = ( - text: string, - fontFamily: string, - letterSpacing: number, -): number => { - if (text.length === 1) { - const char = text.charAt(0); - const codepoint = text.codePointAt(0); - if (codepoint === undefined) return 0; - if (hasZeroWidthSpace(char) === true) return 0; - - const glyph = getGlyph(fontFamily, codepoint); - if (glyph === null) return 0; - return glyph.xadvance + letterSpacing; - } - let width = 0; - let prevCodepoint = 0; - for (let i = 0; i < text.length; i++) { - const char = text.charAt(i); - const codepoint = text.codePointAt(i); - if (codepoint === undefined) continue; - - // Skip zero-width spaces in width calculations - if (hasZeroWidthSpace(char)) { - continue; - } - - const glyph = getGlyph(fontFamily, codepoint); - if (glyph === null) continue; - - let advance = glyph.xadvance; - - // Add kerning if there's a previous character - if (prevCodepoint !== 0) { - const kerning = getKerning(fontFamily, prevCodepoint, codepoint); - advance += kerning; - } - - width += advance + letterSpacing; - prevCodepoint = codepoint; - } - - return width; -}; diff --git a/src/core/text-rendering/SdfTextRenderer.ts b/src/core/text-rendering/SdfTextRenderer.ts index 834118bf2..de1e14645 100644 --- a/src/core/text-rendering/SdfTextRenderer.ts +++ b/src/core/text-rendering/SdfTextRenderer.ts @@ -19,8 +19,9 @@ import type { Stage } from '../Stage.js'; import type { - FontHandler, + FontLoadOptions, TextLineStruct, + TextRenderer, TextRenderInfo, TextRenderProps, } from './TextRenderer.js'; @@ -37,6 +38,8 @@ import type { WebGlShaderNode } from '../renderers/webgl/WebGlShaderNode.js'; import { mergeColorAlpha } from '../../utils.js'; import type { TextLayout, GlyphLayout } from './TextRenderer.js'; import { mapTextLayout } from './TextLayoutEngine.js'; +import { SdfFont, type SdfFontProps } from './SdfFont.js'; +import type { CoreFont } from './CoreFont.js'; // Each glyph requires 6 vertices (2 triangles) with 4 floats each (x, y, u, v) const FLOATS_PER_VERTEX = 4; @@ -46,18 +49,17 @@ const VERTICES_PER_GLYPH = 6; const type = 'sdf' as const; let sdfShader: WebGlShaderNode | null = null; +let stage: Stage | null = null; // Initialize the SDF text renderer -const init = (stage: Stage): void => { - SdfFontHandler.init(); +const init = (s: Stage): void => { + stage = s; // Register SDF shader with the shader manager stage.shManager.registerShaderType('Sdf', Sdf); sdfShader = stage.shManager.createShader('Sdf') as WebGlShaderNode; }; -const font: FontHandler = SdfFontHandler; - /** * SDF text renderer using MSDF/SDF fonts with WebGL * @@ -65,7 +67,10 @@ const font: FontHandler = SdfFontHandler; * @param props - Text rendering properties * @returns Object containing ImageData and dimensions */ -const renderText = (props: CoreTextNodeProps): TextRenderInfo => { +const renderText = ( + font: SdfFont, + props: CoreTextNodeProps, +): TextRenderInfo => { // Early return if no text if (props.text.length === 0) { return { @@ -75,7 +80,7 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => { } // Get font cache for this font family - const fontData = SdfFontHandler.getFontData(props.fontFamily); + const fontData = font.getData(); if (fontData === undefined) { // Font not loaded, return empty result return { @@ -85,7 +90,7 @@ const renderText = (props: CoreTextNodeProps): TextRenderInfo => { } // Calculate text layout and generate glyph data for caching - const layout = generateTextLayout(props, fontData); + const layout = generateTextLayout(props, font); // For SDF renderer, ImageData is null since we render via WebGL return { @@ -184,6 +189,7 @@ const addQuads = (layout?: TextLayout): Float32Array | null => { */ const renderQuads = ( renderer: CoreRenderer, + font: SdfFont, layout: TextLayout, vertexBuffer: Float32Array, renderProps: TextRenderProps, @@ -193,7 +199,7 @@ const renderQuads = ( const worldAlpha = renderProps.worldAlpha; const globalTransform = renderProps.globalTransform; - const atlasTexture = SdfFontHandler.getAtlas(fontFamily); + const atlasTexture = font.getAtlas(); if (atlasTexture === null) { console.warn(`SDF atlas texture not found for font: ${fontFamily}`); return; @@ -271,15 +277,15 @@ const renderQuads = ( */ const generateTextLayout = ( props: CoreTextNodeProps, - fontCache: SdfFontHandler.SdfFont, + font: SdfFont, ): TextLayout => { const fontSize = props.fontSize; const fontFamily = props.fontFamily; const lineHeight = props.lineHeight; - const metrics = SdfFontHandler.getFontMetrics(fontFamily, fontSize); + const metrics = font.getMetrics(fontSize); const verticalAlign = props.verticalAlign; - const fontData = fontCache.data; + const fontData = font.getData(); const commonFontData = fontData.common; const designFontSize = fontData.info.size; @@ -301,7 +307,7 @@ const generateTextLayout = ( effectiveWidth, effectiveHeight, ] = mapTextLayout( - SdfFontHandler.measureText, + font, metrics, props.text, props.textAlign, @@ -339,7 +345,7 @@ const generateTextLayout = ( continue; } // Get glyph data from font handler - const glyph = SdfFontHandler.getGlyph(fontFamily, codepoint); + const glyph = font.getGlyph(codepoint); if (glyph === null) { continue; } @@ -348,11 +354,7 @@ const generateTextLayout = ( // Add kerning if there's a previous character if (prevCodepoint !== 0) { - const kerning = SdfFontHandler.getKerning( - fontFamily, - prevCodepoint, - codepoint, - ); + const kerning = font.getKerning(prevCodepoint, codepoint); advance += kerning; } @@ -393,12 +395,20 @@ const generateTextLayout = ( }; }; +const createFont = (settings: FontLoadOptions): SdfFont | undefined => { + return new SdfFont( + SdfTextRenderer as unknown as TextRenderer, + settings as unknown as SdfFontProps, + stage!, + ); +}; + /** * SDF Text Renderer - implements TextRenderer interface */ const SdfTextRenderer = { type, - font, + createFont, renderText, addQuads, renderQuads, diff --git a/src/core/text-rendering/TextLayoutEngine.ts b/src/core/text-rendering/TextLayoutEngine.ts index d00c992a5..b6a23f4b1 100644 --- a/src/core/text-rendering/TextLayoutEngine.ts +++ b/src/core/text-rendering/TextLayoutEngine.ts @@ -1,6 +1,6 @@ +import type { CoreFont } from './CoreFont.js'; import type { FontMetrics, - MeasureTextFn, NormalizedFontMetrics, TextLayoutStruct, TextLineStruct, @@ -18,10 +18,9 @@ export const defaultFontMetrics: FontMetrics = { }; type WrapStrategyFn = ( - measureText: MeasureTextFn, + font: CoreFont, word: string, wordWidth: number, - fontFamily: string, letterSpacing: number, wrappedLines: TextLineStruct[], currentLine: string, @@ -48,7 +47,7 @@ export const normalizeFontMetrics = ( }; export const mapTextLayout = ( - measureText: MeasureTextFn, + font: CoreFont, metrics: NormalizedFontMetrics, text: string, textAlign: string, @@ -90,22 +89,15 @@ export const mapTextLayout = ( const [lines, remainingLines, remainingText] = wrappedText === true ? wrapText( - measureText, + font, text, - fontFamily, maxWidth, letterSpacing, overflowSuffix, wordBreak, effectiveMaxLines, ) - : measureLines( - measureText, - text.split('\n'), - fontFamily, - letterSpacing, - effectiveMaxLines, - ); + : measureLines(font, text.split('\n'), letterSpacing, effectiveMaxLines); let effectiveLineAmount = lines.length; let effectiveMaxWidth = 0; @@ -154,9 +146,8 @@ export const mapTextLayout = ( }; export const measureLines = ( - measureText: MeasureTextFn, + font: CoreFont, lines: string[], - fontFamily: string, letterSpacing: number, maxLines: number, ): WrappedLinesStruct => { @@ -171,7 +162,7 @@ export const measureLines = ( if (line === undefined) { continue; } - const width = measureText(line, fontFamily, letterSpacing); + const width = font.measureText(line, letterSpacing); measuredLines.push([line, width, false, 0, 0]); } @@ -183,9 +174,8 @@ export const measureLines = ( }; export const wrapText = ( - measureText: MeasureTextFn, + font: CoreFont, text: string, - fontFamily: string, maxWidth: number, letterSpacing: number, overflowSuffix: string, @@ -196,8 +186,8 @@ export const wrapText = ( const wrappedLines: TextLineStruct[] = []; // Calculate space width for line wrapping - const spaceWidth = measureText(' ', fontFamily, letterSpacing); - const overflowWidth = measureText(overflowSuffix, fontFamily, letterSpacing); + const spaceWidth = font.measureText(' ', letterSpacing); + const overflowWidth = font.measureText(overflowSuffix, letterSpacing); let wrappedLine: TextLineStruct[] = []; let remainingLines = maxLines > 0 ? maxLines : 1000; @@ -213,9 +203,8 @@ export const wrapText = ( [wrappedLine, remainingLines, hasRemainingText] = line.length > 0 ? wrapLine( - measureText, + font, line, - fontFamily, maxWidth, letterSpacing, spaceWidth, @@ -236,8 +225,7 @@ export const wrapText = ( if (lastLine[2] === false) { let remainingText = ''; const [line, lineWidth] = truncateLineEnd( - measureText, - fontFamily, + font, letterSpacing, lastLine[0], lastLine[1], @@ -259,9 +247,8 @@ export const wrapText = ( }; export const wrapLine = ( - measureText: MeasureTextFn, + font: CoreFont, line: string, - fontFamily: string, maxWidth: number, letterSpacing: number, spaceWidth: number, @@ -280,7 +267,7 @@ export const wrapLine = ( const wrapFn = getWrapStrategy(wordBreak); while (words.length > 0 && remainingLines > 0) { let word = words.shift()!; - let wordWidth = measureText(word, fontFamily, letterSpacing); + let wordWidth = font.measureText(word, letterSpacing); let remainingWord = ''; //handle first word of new line separately to avoid empty line issues @@ -293,23 +280,15 @@ export const wrapLine = ( [word, remainingWord, wordWidth] = remainingLines === 0 ? truncateWord( - measureText, + font, word, wordWidth, maxWidth, - fontFamily, letterSpacing, overflowSuffix, overflowWidth, ) - : splitWord( - measureText, - word, - wordWidth, - maxWidth, - fontFamily, - letterSpacing, - ); + : splitWord(font, word, wordWidth, maxWidth, letterSpacing); if (remainingWord.length > 0) { words.unshift(remainingWord); @@ -349,10 +328,9 @@ export const wrapLine = ( } [currentLine, currentLineWidth, remainingWord] = wrapFn( - measureText, + font, word, wordWidth, - fontFamily, letterSpacing, wrappedLines, currentLine, @@ -403,10 +381,9 @@ const getWrapStrategy = (wordBreak: string): WrapStrategyFn => { * @remarks This strategy is similar to 'normal' in html/CSS. However */ export const overflow = ( - measureText: MeasureTextFn, + font: CoreFont, word: string, wordWidth: number, - fontFamily: string, letterSpacing: number, wrappedLines: TextLineStruct[], currentLine: string, @@ -432,10 +409,9 @@ export const overflow = ( }; export const breakWord = ( - measureText: MeasureTextFn, + font: CoreFont, word: string, wordWidth: number, - fontFamily: string, letterSpacing: number, wrappedLines: TextLineStruct[], currentLine: string, @@ -451,8 +427,7 @@ export const breakWord = ( remainingWord = word; if (remainingLines === 0) { [currentLine, currentLineWidth, remainingWord] = truncateLineEnd( - measureText, - fontFamily, + font, letterSpacing, currentLine, currentLineWidth, @@ -471,10 +446,9 @@ export const breakWord = ( }; export const breakAll = ( - measureText: MeasureTextFn, + font: CoreFont, word: string, wordWidth: number, - fontFamily: string, letterSpacing: number, wrappedLines: TextLineStruct[], currentLine: string, @@ -494,23 +468,15 @@ export const breakAll = ( const truncate = remainingLines === 0; [word, remainingWord, wordWidth] = truncate ? truncateWord( - measureText, + font, word, wordWidth, remainingSpace, - fontFamily, letterSpacing, overflowSuffix, overflowWidth, ) - : splitWord( - measureText, - word, - wordWidth, - remainingSpace, - fontFamily, - letterSpacing, - ); + : splitWord(font, word, wordWidth, remainingSpace, letterSpacing); currentLine += space + word; currentLineWidth += spaceWidth + wordWidth; @@ -524,8 +490,7 @@ export const breakAll = ( }; export const truncateLineEnd = ( - measureText: MeasureTextFn, - fontFamily: string, + font: CoreFont, letterSpacing: number, currentLine: string, currentLineWidth: number, @@ -544,7 +509,7 @@ export const truncateLineEnd = ( let truncated = false; for (let i = currentLine.length - 1; i > 0; i--) { const char = currentLine.charAt(i); - const charWidth = measureText(char, fontFamily, letterSpacing); + const charWidth = font.measureText(char, letterSpacing); currentLineWidth -= charWidth; if (currentLineWidth + overflowWidth <= maxWidth) { currentLine = currentLine.substring(0, i) + overflowSuffix; @@ -564,11 +529,10 @@ export const truncateLineEnd = ( }; export const truncateWord = ( - measureText: MeasureTextFn, + font: CoreFont, word: string, wordWidth: number, maxWidth: number, - fontFamily: string, letterSpacing: number, overflowSuffix: string, overflowWidth: number, @@ -589,7 +553,7 @@ export const truncateWord = ( let currentWidth = wordWidth; for (let i = word.length - 1; i > 0; i--) { const char = word.charAt(i); - const charWidth = measureText(char, fontFamily, letterSpacing); + const charWidth = font.measureText(char, letterSpacing); currentWidth -= charWidth; if (currentWidth <= targetWidth) { const remainingWord = word.substring(i); @@ -608,7 +572,7 @@ export const truncateWord = ( let currentWidth = 0; for (let i = 0; i < word.length; i++) { const char = word.charAt(i); - const charWidth = measureText(char, fontFamily, letterSpacing); + const charWidth = font.measureText(char, letterSpacing); if (currentWidth + charWidth > targetWidth) { const remainingWord = word.substring(i); return [ @@ -624,11 +588,10 @@ export const truncateWord = ( }; export const splitWord = ( - measureText: MeasureTextFn, + font: CoreFont, word: string, wordWidth: number, maxWidth: number, - fontFamily: string, letterSpacing: number, ): [string, string, number] => { if (maxWidth <= 0) { @@ -645,7 +608,7 @@ export const splitWord = ( let currentWidth = wordWidth; for (let i = word.length - 1; i > 0; i--) { const char = word.charAt(i); - const charWidth = measureText(char, fontFamily, letterSpacing); + const charWidth = font.measureText(char, letterSpacing); currentWidth -= charWidth; if (currentWidth <= maxWidth) { const remainingWord = word.substring(i); @@ -660,7 +623,7 @@ export const splitWord = ( let currentWidth = 0; for (let i = 0; i < word.length; i++) { const char = word.charAt(i); - const charWidth = measureText(char, fontFamily, letterSpacing); + const charWidth = font.measureText(char, letterSpacing); if (currentWidth + charWidth > maxWidth) { const remainingWord = word.substring(i); return [word.substring(0, i), remainingWord, currentWidth]; diff --git a/src/core/text-rendering/TextRenderer.ts b/src/core/text-rendering/TextRenderer.ts index ad97cada1..c22f5fd84 100644 --- a/src/core/text-rendering/TextRenderer.ts +++ b/src/core/text-rendering/TextRenderer.ts @@ -17,9 +17,11 @@ * limitations under the License. */ -import type { CoreTextNodeProps } from '../CoreTextNode.js'; +import type { CoreTextNode, CoreTextNodeProps } from '../CoreTextNode.js'; import type { CoreRenderer } from '../renderers/CoreRenderer.js'; import type { Stage } from '../Stage.js'; +import type { CoreFont } from './CoreFont.js'; +import type { CoreFontManager } from './FontManager.js'; // Text baseline and vertical align types export type TextBaseline = @@ -348,24 +350,6 @@ export type MeasureTextFn = ( letterSpacing: number, ) => number; -export interface FontHandler { - init: ( - c: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D, - ) => void; - type: 'canvas' | 'sdf'; - isFontLoaded: (fontFamily: string) => boolean; - loadFont: (stage: Stage, options: FontLoadOptions) => Promise; - waitingForFont: (fontFamily: string, CoreTextNode) => void; - stopWaitingForFont: (fontFamily: string, CoreTextNode) => void; - getFontFamilies: () => FontFamilyMap; - canRenderFont: (trProps: TrProps) => boolean; - getFontMetrics: ( - fontFamily: string, - fontSize: number, - ) => NormalizedFontMetrics; - measureText: MeasureTextFn; -} - export interface TextRenderProps { fontFamily: string; fontSize: number; @@ -392,17 +376,18 @@ export interface TextRenderInfo { export interface TextRenderer { type: 'canvas' | 'sdf'; - font: FontHandler; - renderText: (props: CoreTextNodeProps) => TextRenderInfo; + createFont: (settings: FontLoadOptions) => CoreFont | undefined; + renderText: (font: CoreFont, props: CoreTextNodeProps) => TextRenderInfo; // Updated to accept layout data and return vertex buffer for performance addQuads: (layout?: TextLayout) => Float32Array | null; renderQuads: ( renderer: CoreRenderer, + font: CoreFont, layout: TextLayout, vertexBuffer: Float32Array, renderProps: TextRenderProps, ) => void; - init: (stage: Stage) => void; + init: (stage: Stage, fontManager: CoreFontManager) => void; } /** diff --git a/src/core/text-rendering/tests/TextLayoutEngine.test.ts b/src/core/text-rendering/tests/TextLayoutEngine.test.ts index 4f43645d8..1404b1f45 100644 --- a/src/core/text-rendering/tests/TextLayoutEngine.test.ts +++ b/src/core/text-rendering/tests/TextLayoutEngine.test.ts @@ -24,10 +24,11 @@ import { breakWord, truncateLineEnd, } from '../TextLayoutEngine.js'; +import type { CoreFont } from '../CoreFont.js'; // Mock font data for testing // Mock SdfFontHandler functions -const mockGetGlyph = (_fontFamily: string, codepoint: number) => { +const mockGetGlyph = (codepoint: number) => { // Mock glyph data - each character is 10 units wide for easy testing return { id: codepoint, @@ -51,18 +52,14 @@ const mockGetKerning = () => { // Test-specific measureText function that mimics testMeasureText behavior // but works with our mocked getGlyph and getKerning functions -const testMeasureText = ( - text: string, - fontFamily: string, - letterSpacing: number, -): number => { +const testMeasureText = (text: string, letterSpacing: number): number => { if (text.length === 1) { const char = text.charAt(0); const codepoint = text.codePointAt(0); if (codepoint === undefined) return 0; if (char === '\u200B') return 0; // Zero-width space - const glyph = mockGetGlyph(fontFamily, codepoint); + const glyph = mockGetGlyph(codepoint); if (glyph === null) return 0; return glyph.xadvance + letterSpacing; } @@ -78,7 +75,7 @@ const testMeasureText = ( continue; } - const glyph = mockGetGlyph(fontFamily, codepoint); + const glyph = mockGetGlyph(codepoint); if (glyph === null) continue; let advance = glyph.xadvance; @@ -96,26 +93,30 @@ const testMeasureText = ( return width; }; +const font = { + measureText: testMeasureText, +} as unknown as CoreFont; + // Mock measureText function to replace the broken SDF implementation describe('SDF Text Utils', () => { describe('measureText', () => { it('should return correct width for basic text', () => { - const width = testMeasureText('hello', 'Arial', 0); + const width = font.measureText('hello', 0); expect(width).toBeCloseTo(50); // 5 chars * 10 xadvance }); it('should return 0 width for empty text', () => { - const width = testMeasureText('', 'Arial', 0); + const width = font.measureText('', 0); expect(width).toBe(0); }); it('should include letter spacing in width calculation', () => { - const width = testMeasureText('hello', 'Arial', 2); + const width = font.measureText('hello', 2); expect(width).toBeCloseTo(60); // 5 chars * (10 xadvance + 2 letterSpacing) }); it('should skip zero-width spaces in width calculation', () => { - const width = testMeasureText('hel\u200Blo', 'Arial', 0); + const width = font.measureText('hel\u200Blo', 0); expect(width).toBeCloseTo(50); // Should be same as 'hello' }); }); @@ -123,9 +124,8 @@ describe('SDF Text Utils', () => { describe('wrapLine', () => { it('should wrap text that exceeds max width', () => { const result = wrapLine( - testMeasureText, // Add measureText as first parameter + font, // Add measureText as first parameter 'hello world test', - 'Arial', 100, // maxWidth (10 characters at 10 units each) 0, // designLetterSpacing 10, // spaceWidth @@ -143,9 +143,8 @@ describe('SDF Text Utils', () => { it('should handle single word that fits', () => { const result = wrapLine( - testMeasureText, + font, 'hello', - 'Arial', 100, // maxWidth (10 characters at 10 units each) 0, // designLetterSpacing 10, // spaceWidth @@ -159,9 +158,8 @@ describe('SDF Text Utils', () => { it('should break long words', () => { const result = wrapLine( - testMeasureText, + font, 'verylongwordthatdoesnotfit', - 'Arial', 100, // maxWidth (10 characters at 10 units each) 0, // designLetterSpacing 10, // spaceWidth @@ -180,9 +178,8 @@ describe('SDF Text Utils', () => { it('should handle ZWSP as word break opportunity', () => { // Test 1: ZWSP should provide break opportunity when needed const result1 = wrapLine( - testMeasureText, + font, 'hello\u200Bworld test', - 'Arial', 100, // maxWidth (10 characters at 10 units each) 0, // designLetterSpacing 10, // spaceWidth @@ -198,9 +195,8 @@ describe('SDF Text Utils', () => { // Test 2: ZWSP should NOT break when text fits on one line const result2 = wrapLine( - testMeasureText, + font, 'hi\u200Bthere', - 'Arial', 200, // maxWidth 0, // designLetterSpacing 10, // spaceWidth @@ -213,9 +209,8 @@ describe('SDF Text Utils', () => { // Test 3: ZWSP should break when it's the only break opportunity const result3 = wrapLine( - testMeasureText, + font, 'verylongword\u200Bmore', - 'Arial', 100, // 10 characters max - forces break at ZWSP 0, 10, // spaceWidth @@ -230,9 +225,8 @@ describe('SDF Text Utils', () => { it('should truncate with suffix when max lines reached', () => { const result = wrapLine( - testMeasureText, + font, 'hello world test more and even more text that exceeds limits', - 'Arial', 200, // Wide enough to force multiple words on one line 0, 10, // spaceWidth @@ -252,9 +246,8 @@ describe('SDF Text Utils', () => { describe('wrapText', () => { it('should wrap multiple lines', () => { const result = wrapText( - testMeasureText, + font, 'line one\nline two that is longer', - 'Arial', 100, 0, '', @@ -267,9 +260,8 @@ describe('SDF Text Utils', () => { it('should handle empty lines', () => { const result = wrapText( - testMeasureText, + font, 'line one\n\nline three', - 'Arial', 100, 0, '', @@ -281,9 +273,8 @@ describe('SDF Text Utils', () => { it('should respect max lines limit', () => { const result = wrapText( - testMeasureText, + font, 'line one\\nline two\\nline three\\nline four', - 'Arial', 100, 0, '', @@ -298,8 +289,7 @@ describe('SDF Text Utils', () => { describe('truncateLineWithSuffix', () => { it('should truncate line and add suffix', () => { const result = truncateLineEnd( - testMeasureText, - 'Arial', + font, 0, 'this is a very long line', //current line 240, // current line width @@ -314,8 +304,7 @@ describe('SDF Text Utils', () => { it('should return suffix if suffix is too long', () => { const result = truncateLineEnd( - testMeasureText, - 'Arial', + font, 0, 'hello', 50, // current line width @@ -332,8 +321,7 @@ describe('SDF Text Utils', () => { // This is the expected behavior when used in overflow contexts where the suffix // indicates that content was truncated at the line limit. const result = truncateLineEnd( - testMeasureText, - 'Arial', + font, 0, 'short', 50, // 5 characters fit @@ -349,10 +337,9 @@ describe('SDF Text Utils', () => { describe('breakLongWord', () => { it('should break word into multiple lines', () => { const result = breakWord( - testMeasureText, + font, 'verylongword', 'verylongword'.length * 10, - 'Arial', 0, [], '', @@ -371,10 +358,9 @@ describe('SDF Text Utils', () => { it('should handle single character word', () => { const result = breakWord( - testMeasureText, + font, 'a', 10, - 'Arial', 0, [], '', @@ -392,10 +378,9 @@ describe('SDF Text Utils', () => { it('should truncate with suffix when max lines reached', () => { const result = breakWord( - testMeasureText, + font, 'verylongword', 'verylongword'.length * 10, - 'Arial', 0, [], '', @@ -417,9 +402,8 @@ describe('SDF Text Utils', () => { const text = 'This is a test\u200Bwith zero-width\u200Bspaces that should wrap properly'; const result = wrapText( - testMeasureText, + font, text, - 'Arial', 200, // 20 characters max per line 0, '...', @@ -435,9 +419,8 @@ describe('SDF Text Utils', () => { it('should handle mixed content with long words and ZWSP', () => { const text = 'Short\u200Bverylongwordthatmustbebroken\u200Bshort'; const result = wrapText( - testMeasureText, + font, text, - 'Arial', 100, // 10 characters max per line 0, '',