openpencil/packages/fig/src/instance-overrides/derived-symbol-data/layout.ts
Danila Poyarkov 2bd2304bcb refactor(fig): own instance interpretation
- Move component properties, symbol overrides, derived symbol data, and clone synchronization into @open-pencil/fig\n- Add the public instance-overrides subpath with package tests and packed smoke coverage\n- Remove cross-package forwarding modules and enforce direct owner imports in architecture checks
2026-07-18 02:06:34 +03:00

100 lines
3.4 KiB
TypeScript

import {
convertFigmaDerivedTextGlyphs,
convertLetterSpacing,
convertLineHeight
} from '@open-pencil/fig/node-change'
import type { SceneNode } from '@open-pencil/scene-graph'
import type { DerivedSymbolOverride, OverrideContext } from '../types'
import { resolveDsdGeometry } from './geometry'
function getVisibleSiblingCount(
ctx: OverrideContext,
cache: Map<string, number>,
parentId: string
): number {
const cached = cache.get(parentId)
if (cached !== undefined) return cached
const count = ctx.graph.getChildren(parentId).filter((child) => child.visible).length
cache.set(parentId, count)
return count
}
function resolveSizeOnlyPosition(
ctx: OverrideContext,
visibleSiblingCount: Map<string, number>,
node: SceneNode
): Pick<SceneNode, 'x' | 'y'> | null {
if (
!node.parentId ||
getVisibleSiblingCount(ctx, visibleSiblingCount, node.parentId) !== 1 ||
!node.componentId
)
return null
const source = ctx.graph.getNode(node.componentId)
if (!source) return null
const sourceParent = source.parentId ? ctx.graph.getNode(source.parentId) : null
if (!sourceParent) return { x: source.x, y: source.y }
const withinParent =
source.x >= 0 &&
source.y >= 0 &&
source.x + source.width <= sourceParent.width + 0.01 &&
source.y + source.height <= sourceParent.height + 0.01
return withinParent ? { x: source.x, y: source.y } : { x: 0, y: 0 }
}
function buildDsdTextUpdates(
d: DerivedSymbolOverride,
blobs: Uint8Array[],
target: SceneNode
): Partial<SceneNode> {
const updates: Partial<SceneNode> = {}
if (d.fontSize !== undefined) updates.fontSize = d.fontSize
if (d.lineHeight !== undefined) updates.lineHeight = convertLineHeight(d.lineHeight, d.fontSize)
if (d.letterSpacing !== undefined)
updates.letterSpacing = convertLetterSpacing(d.letterSpacing, d.fontSize)
if (d.strokeWeight !== undefined && target.strokes.length > 0) {
updates.strokes = target.strokes.map((stroke) => ({
...stroke,
weight: d.strokeWeight as number
}))
}
const figmaDerivedTextGlyphs = convertFigmaDerivedTextGlyphs(d.derivedTextData, blobs)
if (figmaDerivedTextGlyphs.length > 0) updates.figmaDerivedTextGlyphs = figmaDerivedTextGlyphs
return updates
}
export function buildDsdLayoutUpdates(
ctx: OverrideContext,
visibleSiblingCount: Map<string, number>,
d: DerivedSymbolOverride,
target: SceneNode
): { updates: Partial<SceneNode>; hasSize: boolean } {
const updates: Partial<SceneNode> = buildDsdTextUpdates(d, ctx.blobs, target)
const figmaDerivedLayout: NonNullable<SceneNode['figmaDerivedLayout']> = {}
if (d.size) {
updates.width = d.size.x
updates.height = d.size.y
figmaDerivedLayout.width = d.size.x
figmaDerivedLayout.height = d.size.y
}
if (d.transform) {
updates.x = d.transform.m02
updates.y = d.transform.m12
figmaDerivedLayout.x = d.transform.m02
figmaDerivedLayout.y = d.transform.m12
} else if (d.size) {
const position = resolveSizeOnlyPosition(ctx, visibleSiblingCount, target)
if (position) {
updates.x = position.x
updates.y = position.y
figmaDerivedLayout.x = position.x
figmaDerivedLayout.y = position.y
}
}
if (Object.keys(figmaDerivedLayout).length > 0) updates.figmaDerivedLayout = figmaDerivedLayout
Object.assign(updates, resolveDsdGeometry(d, target, ctx.blobs))
return { updates, hasSize: d.size !== undefined }
}