From 221e69aaa4eca7abedbd3617edc093d8bbce6820 Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Mon, 18 May 2026 02:04:46 +0300 Subject: [PATCH] fix(layout): preserve derived text bounds --- packages/core/src/kiwi/node-change/convert.ts | 7 ++ packages/core/src/layout.ts | 7 ++ .../auto-layout/text/measurement.test.ts | 77 +++++++++++++++++++ 3 files changed, 91 insertions(+) diff --git a/packages/core/src/kiwi/node-change/convert.ts b/packages/core/src/kiwi/node-change/convert.ts index c204b57c9..8694e997b 100644 --- a/packages/core/src/kiwi/node-change/convert.ts +++ b/packages/core/src/kiwi/node-change/convert.ts @@ -280,6 +280,7 @@ function convertTextProps( | 'styleRuns' | 'textTruncation' | 'textDirection' + | 'figmaDerivedLayout' | 'figmaDerivedTextGlyphs' > { return { @@ -306,6 +307,12 @@ function convertTextProps( (getOpenPencilPluginValue(nc, TEXT_DIRECTION_PLUGIN_KEY) as | SceneNode['textDirection'] | null) || 'AUTO', + figmaDerivedLayout: nc.derivedTextData?.layoutSize + ? { + width: nc.derivedTextData.layoutSize.x, + height: nc.derivedTextData.layoutSize.y + } + : null, figmaDerivedTextGlyphs: convertFigmaDerivedTextGlyphs(nc.derivedTextData, blobs) } } diff --git a/packages/core/src/layout.ts b/packages/core/src/layout.ts index d5fa35883..c4d6ddbdb 100644 --- a/packages/core/src/layout.ts +++ b/packages/core/src/layout.ts @@ -337,6 +337,13 @@ function configureTextLeaf(yogaChild: YogaNode, child: SceneNode, parent: SceneN const UNCONSTRAINED_KEY = -1 if (autoResize === 'WIDTH_AND_HEIGHT') { + const importedSize = child.figmaDerivedLayout + if (importedSize?.width !== undefined && importedSize.height !== undefined) { + yogaChild.setWidth(child.width) + yogaChild.setHeight(child.height) + return + } + yogaChild.setMeasureFunc((width, widthMode, _height, _heightMode) => { const maxW = widthMode === MeasureMode.Undefined ? undefined : width const cacheKey = maxW === undefined ? UNCONSTRAINED_KEY : Math.round(maxW) diff --git a/tests/engine/layout/auto-layout/text/measurement.test.ts b/tests/engine/layout/auto-layout/text/measurement.test.ts index 86846b558..ccfebc086 100644 --- a/tests/engine/layout/auto-layout/text/measurement.test.ts +++ b/tests/engine/layout/auto-layout/text/measurement.test.ts @@ -8,6 +8,83 @@ import { getNodeOrThrow } from '#tests/helpers/assert' import { autoFrame, loadFixtureGraph, pageId, rect } from '#tests/helpers/layout' describe('text measurement', () => { + test('derived text layout preserves imported auto-layout text bounds during measurement', () => { + const graph = new SceneGraph() + const page = pageId(graph) + const tabs = autoFrame(graph, page, { + width: 180, + height: 42, + primaryAxisSizing: 'FIXED', + counterAxisSizing: 'HUG', + paddingTop: 5, + paddingBottom: 5, + paddingLeft: 5, + paddingRight: 5 + }) + const tab = autoFrame(graph, tabs.id, { + width: 80, + height: 32, + primaryAxisSizing: 'FIXED', + counterAxisSizing: 'HUG', + paddingTop: 6, + paddingBottom: 6, + paddingLeft: 12, + paddingRight: 12 + }) + graph.createNode('TEXT', tab.id, { + text: 'Account', + width: 56, + height: 20, + textAutoResize: 'WIDTH_AND_HEIGHT', + figmaDerivedLayout: { width: 56, height: 20 } + }) + + setTextMeasurer(() => ({ width: 56, height: 40 })) + computeAllLayouts(graph, page) + setTextMeasurer(null) + + expect(graph.getNode(tab.id)?.height).toBe(32) + expect(graph.getNode(tabs.id)?.height).toBe(42) + }) + + test('live text without derived glyphs still uses CanvasKit measurement', () => { + const graph = new SceneGraph() + const page = pageId(graph) + const tabs = autoFrame(graph, page, { + width: 180, + height: 42, + primaryAxisSizing: 'FIXED', + counterAxisSizing: 'HUG', + paddingTop: 5, + paddingBottom: 5, + paddingLeft: 5, + paddingRight: 5 + }) + const tab = autoFrame(graph, tabs.id, { + width: 80, + height: 32, + primaryAxisSizing: 'FIXED', + counterAxisSizing: 'HUG', + paddingTop: 6, + paddingBottom: 6, + paddingLeft: 12, + paddingRight: 12 + }) + graph.createNode('TEXT', tab.id, { + text: 'Account', + width: 56, + height: 20, + textAutoResize: 'WIDTH_AND_HEIGHT' + }) + + setTextMeasurer(() => ({ width: 56, height: 40 })) + computeAllLayouts(graph, page) + setTextMeasurer(null) + + expect(graph.getNode(tab.id)?.height).toBe(52) + expect(graph.getNode(tabs.id)?.height).toBe(62) + }) + test('opening imported fig keeps stored text bounds before CanvasKit measurement', async () => { const graph = await loadFixtureGraph('gold-preview.fig') const store = createEditorStore(graph)