diff --git a/packages/core/src/kiwi/binary/codec.ts b/packages/core/src/kiwi/binary/codec.ts index db282c185..14a28325d 100644 --- a/packages/core/src/kiwi/binary/codec.ts +++ b/packages/core/src/kiwi/binary/codec.ts @@ -227,6 +227,7 @@ export interface VariableAnyValue { floatValue?: number colorValue?: Color alias?: { guid?: GUID; assetRef?: { key: string; version?: string } } + symbolIdValue?: { guid?: GUID } } export interface VariableDataEntry { diff --git a/packages/core/src/kiwi/binary/schema.ts b/packages/core/src/kiwi/binary/schema.ts index 66f0e6476..d811dd51c 100644 --- a/packages/core/src/kiwi/binary/schema.ts +++ b/packages/core/src/kiwi/binary/schema.ts @@ -1580,6 +1580,7 @@ enum ComponentPropNodeField { message ComponentPropAssignment { GUID defID = 1; ComponentPropValue value = 2; + VariableData varValue = 3; } message ComponentPropDef { @@ -2044,6 +2045,8 @@ enum VariableDataType { STRING = 2; ALIAS = 3; COLOR = 4; + SYMBOL_ID = 7; + TEXT_DATA = 9; } enum VariableResolvedDataType { @@ -2051,12 +2054,18 @@ enum VariableResolvedDataType { FLOAT = 1; STRING = 2; COLOR = 4; + SYMBOL_ID = 6; + TEXT_DATA = 8; } message VariableID { GUID guid = 1; } +message SymbolId { + GUID guid = 1; +} + message VariableSetID { GUID guid = 1; } @@ -2067,6 +2076,7 @@ message VariableAnyValue { float floatValue = 3; VariableID alias = 4; Color colorValue = 5; + SymbolId symbolIdValue = 8; } message VariableData { diff --git a/packages/core/src/kiwi/node-change/convert.ts b/packages/core/src/kiwi/node-change/convert.ts index dd7b01e7b..609143a6a 100644 --- a/packages/core/src/kiwi/node-change/convert.ts +++ b/packages/core/src/kiwi/node-change/convert.ts @@ -483,6 +483,7 @@ export function nodeChangeToProps( nodeType, name: nc.name ?? nodeType, figmaGuid: nc.guid ? guidToString(nc.guid) : null, + ...extractFigmaRawGeometry(nc), ...extractFigmaSymbolMetadata(nc, blobs), ...convertTransformProps(nc), opacity: nc.opacity ?? 1, @@ -698,6 +699,15 @@ function preserveFigmaPayloadBlobs(value: unknown, blobs: Uint8Array[]): unknown return result } +function extractFigmaRawGeometry( + nc: NodeChange +): Pick { + return { + figmaRawSize: nc.size ? { ...nc.size } : null, + figmaRawTransform: nc.transform ? { ...nc.transform } : null + } +} + function extractFigmaSymbolMetadata( nc: NodeChange, blobs: Uint8Array[] diff --git a/packages/core/src/kiwi/node-change/export-node.ts b/packages/core/src/kiwi/node-change/export-node.ts index 9bb7737a5..edf2802b1 100644 --- a/packages/core/src/kiwi/node-change/export-node.ts +++ b/packages/core/src/kiwi/node-change/export-node.ts @@ -1,6 +1,6 @@ import type { NodeChange, Paint } from '#core/kiwi/binary/codec' import type { SceneGraph, SceneNode } from '#core/scene-graph' -import type { Color, GUID, Matrix } from '#core/types' +import type { Color, GUID, Matrix, Vector } from '#core/types' import { stringToGuid } from './guid' import { @@ -148,6 +148,36 @@ function getOrCreateNodeGuid( return guid } +function applyInstancePayload( + context: SceneNodeToKiwiContext, + node: SceneNode, + nc: KiwiNodeChange, + localIdCounter: { value: number } +): void { + if (node.type !== 'INSTANCE' || !node.componentId) return + const symbolID = getOrCreateNodeGuid( + context, + resolveInstanceComponentId(context, node.componentId), + localIdCounter + ) + if (symbolID) { + const symbolData: Record = { symbolID } + if (node.figmaSymbolOverrides.length > 0) { + symbolData.symbolOverrides = materializeFigmaPayload(node.figmaSymbolOverrides, context.blobs) + } + if (node.figmaUniformScaleFactor != null) { + symbolData.uniformScaleFactor = node.figmaUniformScaleFactor + } + nc.symbolData = symbolData as KiwiNodeChange['symbolData'] + } + if (node.figmaDerivedSymbolData.length > 0) { + nc.derivedSymbolData = materializeFigmaPayload(node.figmaDerivedSymbolData, context.blobs) + } + if (node.figmaDerivedSymbolDataLayoutVersion != null) { + nc.derivedSymbolDataLayoutVersion = node.figmaDerivedSymbolDataLayoutVersion + } +} + function applyComponentMetadata(node: SceneNode, nc: KiwiNodeChange): void { if (node.componentKey) nc.componentKey = node.componentKey if (node.sourceLibraryKey) nc.sourceLibraryKey = node.sourceLibraryKey @@ -185,6 +215,14 @@ function applyComponentMetadata(node: SceneNode, nc: KiwiNodeChange): void { if (variantPropSpecs.length > 0) nc.variantPropSpecs = variantPropSpecs } +function exportNodeSize(node: SceneNode): Vector { + return node.figmaRawSize ? { ...node.figmaRawSize } : { x: node.width, y: node.height } +} + +function exportNodeTransform(context: SceneNodeToKiwiContext, node: SceneNode): Matrix { + return node.figmaRawTransform ? { ...node.figmaRawTransform } : context.computeExportTransform(node) +} + function applyNodeVisualProps( context: SceneNodeToKiwiContext, node: SceneNode, @@ -273,37 +311,15 @@ export function sceneNodeToKiwiWithContext( visible: node.visible, opacity: node.opacity, phase: 'CREATED', - size: { x: node.width, y: node.height }, - transform: context.computeExportTransform(node), + size: exportNodeSize(node), + transform: exportNodeTransform(context, node), strokeWeight: node.strokes[0]?.weight ?? DEFAULT_STROKE_WEIGHT, strokeAlign: node.strokes[0]?.align ?? 'INSIDE' } applyNodeVisualProps(context, node, nc) applyComponentMetadata(node, nc) - if (node.type === 'INSTANCE' && node.componentId) { - const symbolID = getOrCreateNodeGuid( - context, - resolveInstanceComponentId(context, node.componentId), - localIdCounter - ) - if (symbolID) { - const symbolData: Record = { symbolID } - if (node.figmaSymbolOverrides.length > 0) { - symbolData.symbolOverrides = materializeFigmaPayload(node.figmaSymbolOverrides, context.blobs) - } - if (node.figmaUniformScaleFactor != null) { - symbolData.uniformScaleFactor = node.figmaUniformScaleFactor - } - nc.symbolData = symbolData as KiwiNodeChange['symbolData'] - } - if (node.figmaDerivedSymbolData.length > 0) { - nc.derivedSymbolData = materializeFigmaPayload(node.figmaDerivedSymbolData, context.blobs) - } - if (node.figmaDerivedSymbolDataLayoutVersion != null) { - nc.derivedSymbolDataLayoutVersion = node.figmaDerivedSymbolDataLayoutVersion - } - } + applyInstancePayload(context, node, nc, localIdCounter) if (node.type === 'COMPONENT_SET') upsertPluginData(node, NODE_TYPE_PLUGIN_KEY, node.type) if (strokePaints.length > 0) nc.strokePaints = strokePaints diff --git a/packages/core/src/scene-graph/node-defaults.ts b/packages/core/src/scene-graph/node-defaults.ts index 69161db54..c033ce9d6 100644 --- a/packages/core/src/scene-graph/node-defaults.ts +++ b/packages/core/src/scene-graph/node-defaults.ts @@ -18,6 +18,8 @@ export function createDefaultNode( width: 100, height: 100, rotation: 0, + figmaRawSize: null, + figmaRawTransform: null, figmaDerivedLayout: null, fills: type === 'TEXT' diff --git a/packages/core/src/scene-graph/types.ts b/packages/core/src/scene-graph/types.ts index a164d7c5e..afff3f2ce 100644 --- a/packages/core/src/scene-graph/types.ts +++ b/packages/core/src/scene-graph/types.ts @@ -269,6 +269,8 @@ export interface SceneNode { width: number height: number rotation: number + figmaRawSize: Vector | null + figmaRawTransform: Matrix | null figmaDerivedLayout: Partial | null fills: Fill[]