From 9c82cba672a446a217eb95ea702f788abef05d2d Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Wed, 4 Mar 2026 00:16:15 +0300 Subject: [PATCH] Fix font loading not registering in core cache, add layout text measurement tests Tauri's loadFont registered fonts with the CanvasKit provider but not in core's loadedFamilies cache, so isFontLoaded returned false and measureTextNode returned null during layout recomputation. Add markFontLoaded() to core and call it from Tauri font loader. Add tests verifying text measurement integrates with auto-layout centering. --- packages/core/src/fonts.ts | 6 +++ packages/core/src/index.ts | 1 + src/engine/fonts.ts | 5 +-- tests/engine/layout.test.ts | 82 ++++++++++++++++++++++++++++++++++++- 4 files changed, 90 insertions(+), 4 deletions(-) diff --git a/packages/core/src/fonts.ts b/packages/core/src/fonts.ts index bc4594849..6c45b94a7 100644 --- a/packages/core/src/fonts.ts +++ b/packages/core/src/fonts.ts @@ -133,6 +133,12 @@ export async function ensureNodeFont(family: string, weight: number): Promise k.startsWith(`${family}|`)) } diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 60f86986f..e3b8859b3 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -57,6 +57,7 @@ export { initFontService, getFontProvider, isFontLoaded, + markFontLoaded, ensureNodeFont, styleToWeight, weightToStyle diff --git a/src/engine/fonts.ts b/src/engine/fonts.ts index 9af480382..a6e121911 100644 --- a/src/engine/fonts.ts +++ b/src/engine/fonts.ts @@ -1,6 +1,6 @@ export { initFontService, getFontProvider, ensureNodeFont } from '@open-pencil/core' -import { loadFont as loadFontCore, getFontProvider, styleToWeight } from '@open-pencil/core' +import { loadFont as loadFontCore, markFontLoaded, styleToWeight } from '@open-pencil/core' interface TauriFontFamily { family: string @@ -60,8 +60,7 @@ export async function loadFont(family: string, style = 'Regular'): Promise('load_system_font', { family, style }) const buffer = new Uint8Array(data).buffer - const provider = getFontProvider() - if (provider) provider.registerFont(buffer, family) + markFontLoaded(family, style, buffer) const weight = styleToWeight(style) const italic = style.toLowerCase().includes('italic') ? 'italic' : 'normal' diff --git a/tests/engine/layout.test.ts b/tests/engine/layout.test.ts index 561f5e432..96599ca7b 100644 --- a/tests/engine/layout.test.ts +++ b/tests/engine/layout.test.ts @@ -1,7 +1,7 @@ import { describe, test, expect } from 'bun:test' import { SceneGraph, type SceneNode } from '../../src/engine/scene-graph' -import { computeLayout, computeAllLayouts } from '../../src/engine/layout' +import { computeLayout, computeAllLayouts, setTextMeasurer } from '../../src/engine/layout' function pageId(graph: SceneGraph) { return graph.getPages()[0].id @@ -885,4 +885,84 @@ describe('Auto Layout', () => { expect(children[3].y).toBe(90) }) }) + + describe('text measurement', () => { + test('WIDTH_AND_HEIGHT text uses measured width in centered layout', () => { + const graph = new SceneGraph() + const pid = pageId(graph) + + const frame = autoFrame(graph, pid, { + width: 300, + height: 40, + layoutMode: 'HORIZONTAL', + primaryAxisSizing: 'FIXED', + counterAxisSizing: 'FIXED', + primaryAxisAlign: 'CENTER', + paddingLeft: 10, + paddingRight: 10, + itemSpacing: 10, + }) + + const arrow1 = graph.createNode('FRAME', frame.id, { width: 20, height: 20 }) + const text = graph.createNode('TEXT', frame.id, { + width: 200, + height: 20, + text: 'Test', + fontSize: 14, + textAutoResize: 'WIDTH_AND_HEIGHT' as const, + }) + const arrow2 = graph.createNode('FRAME', frame.id, { width: 20, height: 20 }) + + setTextMeasurer((node) => { + if (node.type === 'TEXT' && node.textAutoResize === 'WIDTH_AND_HEIGHT') { + return { width: 60, height: 20 } + } + return null + }) + + computeAllLayouts(graph) + + setTextMeasurer(null) + + const updatedText = graph.getNode(text.id)! + const updatedArrow1 = graph.getNode(arrow1.id)! + const updatedArrow2 = graph.getNode(arrow2.id)! + + expect(updatedText.width).toBe(60) + + // Total content: 10 + 20 + 10 + 60 + 10 + 20 + 10 = 140 + // Free space: 300 - 140 = 160, centered offset = 80 + expect(updatedArrow1.x).toBe(90) + expect(updatedText.x).toBe(120) + expect(updatedArrow2.x).toBe(190) + }) + + test('without measurer, text keeps its existing width', () => { + const graph = new SceneGraph() + const pid = pageId(graph) + + const frame = autoFrame(graph, pid, { + width: 300, + height: 40, + layoutMode: 'HORIZONTAL', + primaryAxisSizing: 'FIXED', + counterAxisSizing: 'FIXED', + primaryAxisAlign: 'CENTER', + }) + + const text = graph.createNode('TEXT', frame.id, { + width: 200, + height: 20, + text: 'Test', + fontSize: 14, + textAutoResize: 'WIDTH_AND_HEIGHT' as const, + }) + + setTextMeasurer(null) + computeAllLayouts(graph) + + const updatedText = graph.getNode(text.id)! + expect(updatedText.width).toBe(200) + }) + }) })