refactor(fig): extract NodeChange policy helpers
- Move style references, plugin metadata, text values, and font feature conversion into @open-pencil/fig\n- Add a public node-change subpath with package-local and packed smoke coverage\n- Keep core conversion callers on compatibility exports during the staged migration
This commit is contained in:
parent
f0d10edf69
commit
a10911c772
1
bun.lock
1
bun.lock
|
|
@ -216,6 +216,7 @@
|
|||
},
|
||||
"peerDependencies": {
|
||||
"@open-pencil/kiwi": "workspace:*",
|
||||
"@open-pencil/scene-graph": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/kiwi": {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { isNotNil } from 'es-toolkit/predicate'
|
||||
|
||||
import { applyStyleRefsToFields } from '@open-pencil/fig/node-change'
|
||||
import type { NodeChange, VariableDataValuesEntry, Color, GUID } from '@open-pencil/kiwi/fig/codec'
|
||||
import { SceneGraph } from '@open-pencil/scene-graph'
|
||||
import type { VariableType, VariableValue } from '@open-pencil/scene-graph'
|
||||
|
|
@ -16,7 +17,6 @@ import {
|
|||
setVariableColorResolver,
|
||||
VARIABLE_BINDING_FIELDS_INVERSE
|
||||
} from '#core/kiwi/fig/node-change/convert'
|
||||
import { applyStyleRefsToFields } from '#core/kiwi/fig/node-change/style-refs'
|
||||
|
||||
type AssetRef = { key: string; version?: string }
|
||||
type AliasRef = { guid?: GUID; assetRef?: AssetRef }
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { applyStyleRefsToFields } from '@open-pencil/fig/node-change'
|
||||
import type { GUID } from '@open-pencil/kiwi/fig/codec'
|
||||
|
||||
import type { OverridePatch } from '#core/kiwi/fig/instance-overrides/patches'
|
||||
|
|
@ -7,7 +8,6 @@ import type {
|
|||
SymbolOverrideFields
|
||||
} from '#core/kiwi/fig/instance-overrides/types'
|
||||
import { guidToString, VARIABLE_BINDING_FIELDS_INVERSE } from '#core/kiwi/fig/node-change/convert'
|
||||
import { applyStyleRefsToFields } from '#core/kiwi/fig/node-change/style-refs'
|
||||
|
||||
import { convertOverrideToProps } from './props'
|
||||
|
||||
|
|
|
|||
|
|
@ -8,13 +8,11 @@ import { styleToWeight } from '#core/text/fonts'
|
|||
import { convertEffects, convertFills, convertStrokes } from './paint'
|
||||
import { importStyleRuns } from './style-runs'
|
||||
export { importStyleRuns } from './style-runs'
|
||||
import { convertFigmaDerivedTextGlyphs } from './derived-text-glyphs'
|
||||
import { convertFontFeatures } from './font/features'
|
||||
import { convertFontVariations } from './font/variations'
|
||||
import { convertLetterSpacing, convertLineHeight, mapTextDecoration } from './text-values'
|
||||
export { convertEffects, convertFills, convertStrokes, setVariableColorResolver } from './paint'
|
||||
export { convertLetterSpacing, convertLineHeight, mapTextDecoration } from './text-values'
|
||||
import {
|
||||
convertFontFeatures,
|
||||
convertFontVariations,
|
||||
convertLetterSpacing,
|
||||
convertLineHeight,
|
||||
extractBoundVariables,
|
||||
extractExportSettings,
|
||||
extractPluginData,
|
||||
|
|
@ -22,8 +20,17 @@ import {
|
|||
getOpenPencilPluginValue,
|
||||
LAYOUT_DIRECTION_PLUGIN_KEY,
|
||||
NODE_TYPE_PLUGIN_KEY,
|
||||
TEXT_DIRECTION_PLUGIN_KEY
|
||||
} from './plugin-data'
|
||||
TEXT_DIRECTION_PLUGIN_KEY,
|
||||
mapTextDecoration
|
||||
} from '@open-pencil/fig/node-change'
|
||||
|
||||
import { convertFigmaDerivedTextGlyphs } from './derived-text-glyphs'
|
||||
export { convertEffects, convertFills, convertStrokes, setVariableColorResolver } from './paint'
|
||||
export {
|
||||
convertLetterSpacing,
|
||||
convertLineHeight,
|
||||
mapTextDecoration
|
||||
} from '@open-pencil/fig/node-change'
|
||||
import { resolveGeometryPaths, resolveVectorNetwork } from './vector-geometry'
|
||||
export { resolveGeometryPaths } from './vector-geometry'
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,10 @@
|
|||
import {
|
||||
applyExportSettingsPluginData,
|
||||
mergePluginData,
|
||||
NODE_TYPE_PLUGIN_KEY,
|
||||
serializePluginRelaunchData,
|
||||
upsertPluginData
|
||||
} from '@open-pencil/fig/node-change'
|
||||
import type { NodeChange, Paint } from '@open-pencil/kiwi/fig/codec'
|
||||
import { stringToGuid } from '@open-pencil/kiwi/fig/guid'
|
||||
import type {
|
||||
|
|
@ -12,14 +19,6 @@ import type { Color, GUID, Matrix, Vector } from '@open-pencil/scene-graph/primi
|
|||
import { bytesToHex } from '#core/bytes/hex'
|
||||
import { DEFAULT_STROKE_MITER_LIMIT } from '#core/constants'
|
||||
|
||||
import {
|
||||
applyExportSettingsPluginData,
|
||||
mergePluginData,
|
||||
NODE_TYPE_PLUGIN_KEY,
|
||||
serializePluginRelaunchData,
|
||||
upsertPluginData
|
||||
} from './plugin-data'
|
||||
|
||||
export type KiwiNodeChange = NodeChange & Record<string, unknown>
|
||||
|
||||
type KiwiBooleanOperation = NonNullable<NodeChange['booleanOperation']>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
export { convertFontFeatures, applyFontFeaturesToKiwi } from './features'
|
||||
export { figmaAxisTagToString, stringToFigmaAxisTag, convertFontVariations } from './variations'
|
||||
export {
|
||||
applyFontFeaturesToKiwi,
|
||||
convertFontFeatures,
|
||||
convertFontVariations,
|
||||
figmaAxisTagToString,
|
||||
stringToFigmaAxisTag
|
||||
} from '@open-pencil/fig/node-change'
|
||||
export { buildFontDigestMap } from './digests'
|
||||
|
|
|
|||
|
|
@ -13,6 +13,13 @@ export {
|
|||
} from '@open-pencil/kiwi/fig/container'
|
||||
export { buildFontDigestMap } from './font/digests'
|
||||
|
||||
import {
|
||||
applyFontFeaturesToKiwi,
|
||||
BOUND_VARIABLES_PLUGIN_KEY,
|
||||
LAYOUT_DIRECTION_PLUGIN_KEY,
|
||||
TEXT_DIRECTION_PLUGIN_KEY,
|
||||
upsertPluginData
|
||||
} from '@open-pencil/fig/node-change'
|
||||
import type { NodeChange, Paint, VariableConsumptionEntry } from '@open-pencil/kiwi/fig/codec'
|
||||
import type { SceneGraph, SceneNode } from '@open-pencil/scene-graph'
|
||||
import type { Color, GUID, JsonObject, Matrix } from '@open-pencil/scene-graph/primitives'
|
||||
|
|
@ -23,13 +30,6 @@ import {
|
|||
sceneNodeToKiwiWithContext,
|
||||
type KiwiNodeChange
|
||||
} from './export-node'
|
||||
import { applyFontFeaturesToKiwi } from './font/features'
|
||||
import {
|
||||
BOUND_VARIABLES_PLUGIN_KEY,
|
||||
LAYOUT_DIRECTION_PLUGIN_KEY,
|
||||
TEXT_DIRECTION_PLUGIN_KEY,
|
||||
upsertPluginData
|
||||
} from './plugin-data'
|
||||
import { exportTextData, fontVariationToKiwi } from './text-data-export'
|
||||
|
||||
export function mapToFigmaType(type: SceneNode['type']): string {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,16 @@
|
|||
import {
|
||||
convertFontFeatures,
|
||||
convertFontVariations,
|
||||
convertLetterSpacing,
|
||||
convertLineHeight,
|
||||
mapTextDecoration
|
||||
} from '@open-pencil/fig/node-change'
|
||||
import type { NodeChange } from '@open-pencil/kiwi/fig/codec'
|
||||
import type { CharacterStyleOverride, StyleRun } from '@open-pencil/scene-graph'
|
||||
|
||||
import { styleToWeight } from '#core/text/fonts'
|
||||
|
||||
import { convertFontFeatures } from './font/features'
|
||||
import { convertFontVariations } from './font/variations'
|
||||
import { convertFills } from './paint'
|
||||
import { convertLetterSpacing, convertLineHeight, mapTextDecoration } from './text-values'
|
||||
|
||||
function applyTextDecorationOverride(style: CharacterStyleOverride, override: NodeChange): void {
|
||||
const deco = override.textDecoration
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
import { applyFontFeaturesToKiwi, stringToFigmaAxisTag } from '@open-pencil/fig/node-change'
|
||||
import type { NodeChange, Paint } from '@open-pencil/kiwi/fig/codec'
|
||||
import type { CharacterStyleOverride, SceneNode } from '@open-pencil/scene-graph'
|
||||
|
||||
import { normalizeFontFamily, weightToFigmaStyle } from '#core/text/fonts'
|
||||
|
||||
import { applyFontFeaturesToKiwi } from './font/features'
|
||||
import { stringToFigmaAxisTag } from './font/variations'
|
||||
|
||||
export function fontVariationToKiwi(variation: SceneNode['fontVariations'][number]) {
|
||||
const axisTag = stringToFigmaAxisTag(variation.axis)
|
||||
return axisTag === undefined
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ Current ownership:
|
|||
- Canvas payload and image resource handling
|
||||
- `readFigContainer()` / `writeFigContainer()` helpers for raw `fig-kiwi` payloads
|
||||
- `.fig` source and archive result types
|
||||
- Dependency-free NodeChange policy helpers for styles, plugin metadata, text values, and font axes/features through `@open-pencil/fig/node-change`
|
||||
|
||||
Planned ownership:
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,12 @@
|
|||
"bun": "./src/index.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"./node-change": {
|
||||
"types": "./dist/node-change.d.ts",
|
||||
"bun": "./src/node-change/index.ts",
|
||||
"import": "./dist/node-change.js",
|
||||
"default": "./dist/node-change.js"
|
||||
}
|
||||
},
|
||||
"main": "./dist/index.js",
|
||||
|
|
@ -36,7 +42,8 @@
|
|||
"provenance": true
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@open-pencil/kiwi": "workspace:*"
|
||||
"@open-pencil/kiwi": "workspace:*",
|
||||
"@open-pencil/scene-graph": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"fflate": "^0.8.2"
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
export {}
|
||||
|
||||
const mod = await import('../dist/index.js')
|
||||
const nodeChange = await import('../dist/node-change.js')
|
||||
|
||||
if (
|
||||
mod.FIG_PACKAGE_STATUS !== 'archive-api' ||
|
||||
typeof mod.parseFigBuffer !== 'function' ||
|
||||
typeof mod.writeFigArchive !== 'function'
|
||||
typeof mod.writeFigArchive !== 'function' ||
|
||||
typeof nodeChange.convertLineHeight !== 'function'
|
||||
) {
|
||||
throw new Error('Expected @open-pencil/fig archive API exports')
|
||||
}
|
||||
|
|
|
|||
5
packages/fig/src/node-change/index.ts
Normal file
5
packages/fig/src/node-change/index.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export * from './font/features'
|
||||
export * from './font/variations'
|
||||
export * from './plugin-data'
|
||||
export * from './style-refs'
|
||||
export * from './text-values'
|
||||
49
packages/fig/tests/node-change.test.ts
Normal file
49
packages/fig/tests/node-change.test.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
import { describe, expect, test } from 'bun:test'
|
||||
|
||||
import {
|
||||
applyStyleRefsToFields,
|
||||
convertFontFeatures,
|
||||
convertLetterSpacing,
|
||||
convertLineHeight,
|
||||
mapTextDecoration
|
||||
} from '../src/node-change'
|
||||
|
||||
describe('@open-pencil/fig NodeChange policy', () => {
|
||||
test('converts normalized text values', () => {
|
||||
expect(convertLineHeight({ value: 120, units: 'PERCENT' }, 20)).toBe(24)
|
||||
expect(convertLetterSpacing({ value: 10, units: 'PERCENT' }, 20)).toBe(2)
|
||||
expect(mapTextDecoration('UNDERLINE')).toBe('UNDERLINE')
|
||||
})
|
||||
|
||||
test('converts Figma OpenType feature toggles', () => {
|
||||
expect(
|
||||
convertFontFeatures({
|
||||
toggledOnOTFeatures: ['DLIG'],
|
||||
toggledOffOTFeatures: ['LIGA']
|
||||
})
|
||||
).toEqual([
|
||||
{ tag: 'DLIG', enabled: true },
|
||||
{ tag: 'LIGA', enabled: false }
|
||||
])
|
||||
})
|
||||
|
||||
test('resolves imported style references before SceneGraph conversion', () => {
|
||||
const fields: Record<string, unknown> = {
|
||||
styleIdForFill: { guid: { sessionID: 2, localID: 3 } }
|
||||
}
|
||||
applyStyleRefsToFields(
|
||||
new Map([
|
||||
[
|
||||
'2:3',
|
||||
{
|
||||
type: 'RECTANGLE',
|
||||
styleType: 'FILL',
|
||||
fillPaints: [{ type: 'SOLID', visible: true }]
|
||||
}
|
||||
]
|
||||
]),
|
||||
fields
|
||||
)
|
||||
expect(fields.fillPaints).toEqual([{ type: 'SOLID', visible: true }])
|
||||
})
|
||||
})
|
||||
|
|
@ -1,14 +1,23 @@
|
|||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": ".",
|
||||
"outDir": "dist",
|
||||
"target": "ES2022",
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"emitDeclarationOnly": true,
|
||||
"noEmit": false,
|
||||
"tsBuildInfoFile": "dist/tsconfig.tsbuildinfo",
|
||||
"types": ["bun"]
|
||||
"outDir": "./dist",
|
||||
"rootDir": ".",
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true,
|
||||
"types": ["bun"],
|
||||
"paths": {
|
||||
"@open-pencil/fig": ["./src/index.ts"],
|
||||
"@open-pencil/fig/node-change": ["./src/node-change/index.ts"]
|
||||
}
|
||||
},
|
||||
"include": ["src"]
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ import { defineConfig } from 'tsdown'
|
|||
|
||||
export default defineConfig({
|
||||
entry: {
|
||||
index: './src/index.ts'
|
||||
index: './src/index.ts',
|
||||
'node-change': './src/node-change/index.ts'
|
||||
},
|
||||
platform: 'neutral',
|
||||
format: ['esm'],
|
||||
|
|
|
|||
|
|
@ -1,9 +1,8 @@
|
|||
import { describe, expect, test } from 'bun:test'
|
||||
|
||||
import { applyStyleRefsToFields } from '@open-pencil/fig/node-change'
|
||||
import type { NodeChange } from '@open-pencil/kiwi/fig/codec'
|
||||
|
||||
import { applyStyleRefsToFields } from '#core/kiwi/fig/node-change/style-refs'
|
||||
|
||||
describe('fig import style refs', () => {
|
||||
test('effect and grid styles replace stale direct payloads', () => {
|
||||
const effectGuid = { sessionID: 4, localID: 5000 }
|
||||
|
|
|
|||
|
|
@ -93,6 +93,10 @@ try {
|
|||
"const { FIG_PACKAGE_STATUS, parseFigBuffer, writeFigArchive, readFigContainer, writeFigContainer } = await import('@open-pencil/fig'); if (FIG_PACKAGE_STATUS !== 'archive-api' || typeof parseFigBuffer !== 'function' || typeof writeFigArchive !== 'function') throw new Error('Fig package status smoke failed'); const document = readFigContainer(writeFigContainer({ schemaDeflated: new Uint8Array([1]), dataRaw: new Uint8Array([2]) })); if (document.dataRaw[0] !== 2) throw new Error('Fig container smoke failed')",
|
||||
tempDir
|
||||
)
|
||||
nodeEval(
|
||||
"const { convertLineHeight } = await import('@open-pencil/fig/node-change'); if (convertLineHeight({ value: 120, units: 'PERCENT' }, 20) !== 24) throw new Error('Fig NodeChange subpath failed')",
|
||||
tempDir
|
||||
)
|
||||
nodeEval(
|
||||
"const { SceneGraph } = await import('@open-pencil/scene-graph'); const graph = new SceneGraph(); if (graph.getPages().length !== 1) throw new Error('SceneGraph package smoke failed')",
|
||||
tempDir
|
||||
|
|
|
|||
Loading…
Reference in a new issue