fix(fig): scale instance stroke weights
This commit is contained in:
parent
1de4f65767
commit
2d011580f0
|
|
@ -91,6 +91,15 @@ function scaleVectorNetwork(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function scaledStrokes(source: SceneNode, child: SceneNode, shapeScaleX: number, shapeScaleY: number) {
|
||||||
|
if (source.strokes.length !== child.strokes.length) return undefined
|
||||||
|
if (Math.abs(shapeScaleX - shapeScaleY) >= 0.001) return undefined
|
||||||
|
return child.strokes.map((stroke, strokeIndex) => ({
|
||||||
|
...stroke,
|
||||||
|
weight: source.strokes[strokeIndex].weight
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
function scaleChildren(
|
function scaleChildren(
|
||||||
graph: SceneGraph,
|
graph: SceneGraph,
|
||||||
instance: SceneNode,
|
instance: SceneNode,
|
||||||
|
|
@ -131,6 +140,7 @@ function scaleChildren(
|
||||||
if (source.vectorNetwork) {
|
if (source.vectorNetwork) {
|
||||||
updates.vectorNetwork = scaleVectorNetwork(source.vectorNetwork, shapeScaleX, shapeScaleY)
|
updates.vectorNetwork = scaleVectorNetwork(source.vectorNetwork, shapeScaleX, shapeScaleY)
|
||||||
}
|
}
|
||||||
|
updates.strokes = scaledStrokes(source, child, shapeScaleX, shapeScaleY)
|
||||||
graph.updateNode(child.id, updates)
|
graph.updateNode(child.id, updates)
|
||||||
scaled.add(child.id)
|
scaled.add(child.id)
|
||||||
|
|
||||||
|
|
@ -202,6 +212,12 @@ function propagateScaling(ctx: OverrideContext, scaled: Set<string>): void {
|
||||||
cu.strokeGeometry = copyGeometryPaths(source.strokeGeometry)
|
cu.strokeGeometry = copyGeometryPaths(source.strokeGeometry)
|
||||||
if (source.vectorNetwork) cu.vectorNetwork = structuredClone(source.vectorNetwork)
|
if (source.vectorNetwork) cu.vectorNetwork = structuredClone(source.vectorNetwork)
|
||||||
}
|
}
|
||||||
|
if (source.strokes.length === clone.strokes.length) {
|
||||||
|
cu.strokes = clone.strokes.map((stroke, strokeIndex) => ({
|
||||||
|
...stroke,
|
||||||
|
weight: source.strokes[strokeIndex].weight
|
||||||
|
}))
|
||||||
|
}
|
||||||
if (Object.keys(cu).length > 0) graph.updateNode(cloneId, cu)
|
if (Object.keys(cu).length > 0) graph.updateNode(cloneId, cu)
|
||||||
queue.push(cloneId)
|
queue.push(cloneId)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -27,11 +27,18 @@ function resolveSizeOnlyPosition(
|
||||||
return withinParent ? { x: source.x, y: source.y } : { x: 0, y: 0 }
|
return withinParent ? { x: source.x, y: source.y } : { x: 0, y: 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildDsdTextUpdates(d: DerivedSymbolOverride, blobs: Uint8Array[]): Partial<SceneNode> {
|
function buildDsdTextUpdates(
|
||||||
|
d: DerivedSymbolOverride,
|
||||||
|
blobs: Uint8Array[],
|
||||||
|
target: SceneNode
|
||||||
|
): Partial<SceneNode> {
|
||||||
const updates: Partial<SceneNode> = {}
|
const updates: Partial<SceneNode> = {}
|
||||||
if (d.fontSize !== undefined) updates.fontSize = d.fontSize
|
if (d.fontSize !== undefined) updates.fontSize = d.fontSize
|
||||||
if (d.lineHeight !== undefined) updates.lineHeight = convertLineHeight(d.lineHeight, 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.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)
|
const figmaDerivedTextGlyphs = convertFigmaDerivedTextGlyphs(d.derivedTextData, blobs)
|
||||||
if (figmaDerivedTextGlyphs.length > 0) updates.figmaDerivedTextGlyphs = figmaDerivedTextGlyphs
|
if (figmaDerivedTextGlyphs.length > 0) updates.figmaDerivedTextGlyphs = figmaDerivedTextGlyphs
|
||||||
return updates
|
return updates
|
||||||
|
|
@ -43,7 +50,7 @@ export function buildDsdLayoutUpdates(
|
||||||
d: DerivedSymbolOverride,
|
d: DerivedSymbolOverride,
|
||||||
target: SceneNode
|
target: SceneNode
|
||||||
): { updates: Partial<SceneNode>; hasSize: boolean } {
|
): { updates: Partial<SceneNode>; hasSize: boolean } {
|
||||||
const updates: Partial<SceneNode> = buildDsdTextUpdates(d, ctx.blobs)
|
const updates: Partial<SceneNode> = buildDsdTextUpdates(d, ctx.blobs, target)
|
||||||
const figmaDerivedLayout: NonNullable<SceneNode['figmaDerivedLayout']> = {}
|
const figmaDerivedLayout: NonNullable<SceneNode['figmaDerivedLayout']> = {}
|
||||||
|
|
||||||
if (d.size) {
|
if (d.size) {
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ export interface DerivedSymbolOverride {
|
||||||
fontSize?: number
|
fontSize?: number
|
||||||
lineHeight?: NodeChange['lineHeight']
|
lineHeight?: NodeChange['lineHeight']
|
||||||
letterSpacing?: NodeChange['letterSpacing']
|
letterSpacing?: NodeChange['letterSpacing']
|
||||||
|
strokeWeight?: number
|
||||||
derivedTextData?: NodeChange['derivedTextData']
|
derivedTextData?: NodeChange['derivedTextData']
|
||||||
fillGeometry?: Array<{ windingRule?: string; commandsBlob?: number }>
|
fillGeometry?: Array<{ windingRule?: string; commandsBlob?: number }>
|
||||||
strokeGeometry?: Array<{ windingRule?: string; commandsBlob?: number }>
|
strokeGeometry?: Array<{ windingRule?: string; commandsBlob?: number }>
|
||||||
|
|
|
||||||
56
tests/engine/io/fig/import/scaled-instance-strokes.test.ts
Normal file
56
tests/engine/io/fig/import/scaled-instance-strokes.test.ts
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { describe, expect, test } from 'bun:test'
|
||||||
|
|
||||||
|
import { importNodeChanges } from '@open-pencil/core'
|
||||||
|
import type { NodeChange } from '#core/kiwi/binary/codec'
|
||||||
|
|
||||||
|
import { canvas, doc, node } from './legacy/helpers'
|
||||||
|
|
||||||
|
describe('fig import scaled instance strokes', () => {
|
||||||
|
test('preserves vector stroke weight while scaling icon geometry', () => {
|
||||||
|
const componentGuid = { sessionID: 1, localID: 10 }
|
||||||
|
const vectorGuid = { sessionID: 1, localID: 11 }
|
||||||
|
const instanceGuid = { sessionID: 1, localID: 20 }
|
||||||
|
|
||||||
|
const graph = importNodeChanges(
|
||||||
|
[
|
||||||
|
doc(),
|
||||||
|
canvas(),
|
||||||
|
node('SYMBOL', 10, 1, {
|
||||||
|
guid: componentGuid,
|
||||||
|
size: { x: 24, y: 24 }
|
||||||
|
} as Partial<NodeChange>),
|
||||||
|
node('VECTOR', 11, 1, {
|
||||||
|
guid: vectorGuid,
|
||||||
|
parentIndex: { guid: componentGuid, position: '!' },
|
||||||
|
size: { x: 12, y: 12 },
|
||||||
|
horizontalConstraint: 'SCALE',
|
||||||
|
verticalConstraint: 'SCALE',
|
||||||
|
strokeWeight: 2,
|
||||||
|
strokePaints: [
|
||||||
|
{
|
||||||
|
type: 'SOLID',
|
||||||
|
color: { r: 0.2, g: 0.25, b: 0.33, a: 1 },
|
||||||
|
opacity: 1,
|
||||||
|
visible: true,
|
||||||
|
blendMode: 'NORMAL'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
} as Partial<NodeChange>),
|
||||||
|
node('INSTANCE', 20, 1, {
|
||||||
|
guid: instanceGuid,
|
||||||
|
size: { x: 16, y: 16 },
|
||||||
|
symbolData: { symbolID: componentGuid }
|
||||||
|
} as Partial<NodeChange>)
|
||||||
|
],
|
||||||
|
[],
|
||||||
|
undefined,
|
||||||
|
{ populate: 'all' }
|
||||||
|
)
|
||||||
|
|
||||||
|
const instance = Array.from(graph.getAllNodes()).find((sceneNode) => sceneNode.name === 'INSTANCE_20')
|
||||||
|
const vector = instance?.childIds.map((id) => graph.getNode(id)).find(Boolean)
|
||||||
|
|
||||||
|
expect(vector?.strokes[0]?.weight).toBe(2)
|
||||||
|
expect(vector?.strokes[0]?.color).toEqual({ r: 0.2, g: 0.25, b: 0.33, a: 1 })
|
||||||
|
})
|
||||||
|
})
|
||||||
Loading…
Reference in a new issue