From 945560b5a9c90cb632998470100d252a58a4d6fa Mon Sep 17 00:00:00 2001 From: Danila Poyarkov Date: Mon, 18 May 2026 13:52:45 +0300 Subject: [PATCH] fix(io): ignore inside stroke geometry bounds --- packages/core/src/geometry.ts | 5 ++- scripts/visual-bisect.ts | 3 +- tests/engine/geometry/visual-bounds.test.ts | 42 +++++++++++++++------ 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/packages/core/src/geometry.ts b/packages/core/src/geometry.ts index ebbcc50ae..65bca22a4 100644 --- a/packages/core/src/geometry.ts +++ b/packages/core/src/geometry.ts @@ -327,9 +327,12 @@ export function nodeVisualBounds( maxY: base.y + base.height } + const hasNonInsideStroke = node.strokes?.some( + (stroke) => stroke.visible && stroke.align !== 'INSIDE' + ) const localGeometry = geometryBlobBounds([ ...(node.fillGeometry ?? []), - ...(node.type === 'COMPONENT_SET' ? [] : (node.strokeGeometry ?? [])) + ...(hasNonInsideStroke ? (node.strokeGeometry ?? []) : []) ]) if (localGeometry) { bounds = unionVisualBounds(bounds, transformedLocalBounds(node, localGeometry, abs)) ?? bounds diff --git a/scripts/visual-bisect.ts b/scripts/visual-bisect.ts index 1a72df093..4c14065bd 100644 --- a/scripts/visual-bisect.ts +++ b/scripts/visual-bisect.ts @@ -176,7 +176,8 @@ async function exportOpenPencilSubset(indices: number[], path: string): Promise< } try { - const nodeIds = indices.map((index) => childIds[index]) + const nodeIds = + rootNode.type === 'CANVAS' ? indices.map((index) => childIds[index]) : [rootNode.id] const data = await headlessRenderNodes(graph, page.id, nodeIds, { scale, format: 'PNG', diff --git a/tests/engine/geometry/visual-bounds.test.ts b/tests/engine/geometry/visual-bounds.test.ts index a6964e55a..5d8283bee 100644 --- a/tests/engine/geometry/visual-bounds.test.ts +++ b/tests/engine/geometry/visual-bounds.test.ts @@ -337,7 +337,7 @@ describe('computeVisualBounds', () => { expect(multiStroke.height).toBe(noEffects.height + 8) }) - test('component set stroke geometry does not expand export bounds', () => { + test('inside stroke geometry does not expand export bounds', () => { const strokeGeometry = [ { commandsBlob: commandsBlobFromPoints([ @@ -347,39 +347,57 @@ describe('computeVisualBounds', () => { } ] const nodes = { - set: { - id: 'set', - type: 'COMPONENT_SET', + inside: { + id: 'inside', + type: 'COMPONENT', width: 100, height: 50, visible: true, + strokes: [ + { + weight: 1, + visible: true, + align: 'INSIDE' as const, + color: { r: 0, g: 0, b: 0, a: 1 }, + opacity: 1 + } + ], strokeGeometry, childIds: [] }, - frame: { - id: 'frame', + outside: { + id: 'outside', type: 'FRAME', width: 100, height: 50, visible: true, + strokes: [ + { + weight: 1, + visible: true, + align: 'OUTSIDE' as const, + color: { r: 0, g: 0, b: 0, a: 1 }, + opacity: 1 + } + ], strokeGeometry, childIds: [] } } - const componentSetBounds = computeDescendantVisualBounds( - ['set'], + const insideBounds = computeDescendantVisualBounds( + ['inside'], (id) => nodes[id as keyof typeof nodes], () => ({ x: 10, y: 20 }) ) - const frameBounds = computeDescendantVisualBounds( - ['frame'], + const outsideBounds = computeDescendantVisualBounds( + ['outside'], (id) => nodes[id as keyof typeof nodes], () => ({ x: 10, y: 20 }) ) - expect(componentSetBounds).toEqual({ minX: 10, minY: 20, maxX: 110, maxY: 70 }) - expect(frameBounds).toEqual({ minX: 9, minY: 19, maxX: 111, maxY: 71 }) + expect(insideBounds).toEqual({ minX: 10, minY: 20, maxX: 110, maxY: 70 }) + expect(outsideBounds).toEqual({ minX: 9, minY: 19, maxX: 111, maxY: 71 }) }) test('multiple effects accumulate directional overflow', () => {