diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index fdacebd81..60f86986f 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -48,7 +48,8 @@ export { FigmaAPI, FigmaNodeProxy, type FigmaFontName } from './figma-api' export { ALL_TOOLS, defineTool, toolsToAI } from './tools' export type { ToolDef, ParamDef, ParamType } from './tools' export { SkiaRenderer, type RenderOverlays } from './renderer' -export { computeLayout, computeAllLayouts } from './layout' +export { computeLayout, computeAllLayouts, setTextMeasurer } from './layout' +export type { TextMeasurer } from './layout' export { getCanvasKit, getGpuBackend, type CanvasKitOptions, type GpuBackend } from './canvaskit' export { loadFont, diff --git a/packages/core/src/layout.ts b/packages/core/src/layout.ts index cfde4f34d..48f7d83b7 100644 --- a/packages/core/src/layout.ts +++ b/packages/core/src/layout.ts @@ -11,6 +11,14 @@ import Yoga, { import type { SceneGraph, SceneNode } from './scene-graph' +export type TextMeasurer = (node: SceneNode) => { width: number; height: number } | null + +let globalTextMeasurer: TextMeasurer | null = null + +export function setTextMeasurer(measurer: TextMeasurer | null): void { + globalTextMeasurer = measurer +} + export function computeLayout(graph: SceneGraph, frameId: string): void { const frame = graph.getNode(frameId) if (!frame || frame.layoutMode === 'NONE') return @@ -155,19 +163,25 @@ function configureChildAsLeaf(yogaChild: YogaNode, child: SceneNode, parent: Sce const isRow = parent.layoutMode === 'HORIZONTAL' const stretchCross = child.layoutAlignSelf === 'STRETCH' || parent.counterAxisAlign === 'STRETCH' + const measured = child.type === 'TEXT' && child.textAutoResize === 'WIDTH_AND_HEIGHT' + ? measureTextSize(child) + : null + const w = measured ? measured.width : child.width + const h = child.height + if (child.layoutGrow > 0) { yogaChild.setFlexGrow(child.layoutGrow) if (!stretchCross) { - if (isRow) yogaChild.setHeight(child.height) - else yogaChild.setWidth(child.width) + if (isRow) yogaChild.setHeight(h) + else yogaChild.setWidth(w) } } else { if (isRow) { - yogaChild.setWidth(child.width) - if (!stretchCross) yogaChild.setHeight(child.height) + yogaChild.setWidth(w) + if (!stretchCross) yogaChild.setHeight(h) } else { - yogaChild.setHeight(child.height) - if (!stretchCross) yogaChild.setWidth(child.width) + yogaChild.setHeight(h) + if (!stretchCross) yogaChild.setWidth(w) } } @@ -176,6 +190,11 @@ function configureChildAsLeaf(yogaChild: YogaNode, child: SceneNode, parent: Sce } } +function measureTextSize(node: SceneNode): { width: number; height: number } | null { + if (!globalTextMeasurer) return null + return globalTextMeasurer(node) +} + function setSizing( yogaNode: YogaNode, axis: 'width' | 'height', diff --git a/packages/core/src/renderer.ts b/packages/core/src/renderer.ts index 45cb3e934..9c2055e65 100644 --- a/packages/core/src/renderer.ts +++ b/packages/core/src/renderer.ts @@ -1963,6 +1963,18 @@ export class SkiaRenderer { } } + measureTextNode(node: SceneNode): { width: number; height: number } | null { + if (!this.fontsLoaded || !this.fontProvider || !this.isNodeFontLoaded(node)) return null + if (node.type !== 'TEXT' || !node.text) return null + + const paragraph = this.buildParagraph(node) + paragraph.layout(node.textAutoResize === 'WIDTH_AND_HEIGHT' ? 1e6 : node.width || 1e6) + const width = paragraph.getLongestLine() + const height = paragraph.getHeight() + paragraph.delete() + return { width: Math.ceil(width), height: Math.ceil(height) } + } + isNodeFontLoaded(node: SceneNode): boolean { const families = new Set() families.add(node.fontFamily || DEFAULT_FONT_FAMILY) diff --git a/src/engine/layout.ts b/src/engine/layout.ts index 0101322c1..38b292185 100644 --- a/src/engine/layout.ts +++ b/src/engine/layout.ts @@ -1 +1 @@ -export { computeLayout, computeAllLayouts } from '@open-pencil/core' +export { computeLayout, computeAllLayouts, setTextMeasurer } from '@open-pencil/core' diff --git a/src/stores/editor.ts b/src/stores/editor.ts index f119f9612..39429cec2 100644 --- a/src/stores/editor.ts +++ b/src/stores/editor.ts @@ -22,7 +22,7 @@ import { prefetchFigmaSchema } from '@/engine/clipboard' import { exportFigFile } from '@/engine/fig-export' -import { computeLayout, computeAllLayouts } from '@/engine/layout' +import { computeLayout, computeAllLayouts, setTextMeasurer } from '@/engine/layout' import { renderNodesToImage } from '@/engine/render-image' import { SceneGraph } from '@/engine/scene-graph' import { TextEditor } from '@/engine/text-editor' @@ -603,6 +603,7 @@ export function createEditorStore() { _ck = ck _renderer = renderer _textEditor = new TextEditor(ck) + setTextMeasurer((node) => renderer.measureTextNode(node)) } function buildFigFile() {