Merge branch 'master' into master
This commit is contained in:
commit
86d4167cbf
|
|
@ -9,9 +9,9 @@ Vue 3 + CanvasKit (Skia WASM) + Yoga WASM design editor. Tauri v2 desktop, also
|
|||
Bun workspace packages:
|
||||
|
||||
- `packages/scene-graph` — `@open-pencil/scene-graph`: SceneGraph, node types, copy/snap/undo helpers, variables, instances, hit testing. Framework-agnostic.
|
||||
- `packages/pen` — `@open-pencil/pen`: Pen/vector editing helpers shared by core/editor surfaces.
|
||||
- `packages/pen` — `@open-pencil/pen`: Pencil.dev `.pen` document model, parser, and SceneGraph import adapter.
|
||||
- `packages/kiwi` — `@open-pencil/kiwi`: pure Kiwi schema/runtime/protocol package. Owns low-level Figma Kiwi codec/container/parse helpers and stays SceneGraph-agnostic.
|
||||
- `packages/fig` — `@open-pencil/fig`: publishable `.fig` package shell and low-level smoke/test boundary. Production SceneGraph `.fig` policy still lives mostly in core while this package grows.
|
||||
- `packages/fig` — `@open-pencil/fig`: `.fig` archive/parser package and staged home for Figma-specific SceneGraph conversion policy. Core keeps format-neutral IO registration and runtime rendering integration.
|
||||
- `packages/core` — `@open-pencil/core`: renderer, layout, editor core, Figma API, tools, clipboard, vector conversion, and app/CLI-facing document I/O. Depends on scene-graph/pen/kiwi but keeps browser DOM out of core.
|
||||
- `packages/dom-css` — `@open-pencil/dom-css`: DOM/CSS projection layer for HTML/CSS/JSX/Tailwind compatibility. Owns DesignDOM types and browser/headless CSS runtime adapters; keeps DOM/CSS parser dependencies out of core.
|
||||
- `packages/vue` — `@open-pencil/vue`: headless Vue 3 SDK (Reka UI-style) for building custom OpenPencil-powered editor shells and embedded editing surfaces. Renderless components and composables. The app is one consumer of the SDK.
|
||||
|
|
@ -295,7 +295,7 @@ Self-review checklist:
|
|||
|
||||
## File format
|
||||
|
||||
- `.fig` files use Figma's Kiwi schema and `NodeChange[]` records. Low-level schema/runtime/codec/container/parse helpers live in `packages/kiwi/src/fig/**` and `packages/kiwi/src/schema-runtime/**`.
|
||||
- `.fig` files use Figma's Kiwi schema and `NodeChange[]` records. Low-level schema/runtime/codec/container helpers live in `packages/kiwi/src/fig/**` and `packages/kiwi/src/schema-runtime/**`; complete `.fig` archive parsing lives in `packages/fig`.
|
||||
- Core still owns SceneGraph `.fig` policy: import/export orchestration in `packages/core/src/io/formats/fig/**`, SceneGraph ⇄ NodeChange conversion in `packages/core/src/kiwi/fig/node-change/**`, and component/instance override interpretation in `packages/core/src/kiwi/fig/instance-overrides/**`.
|
||||
- `packages/fig` is the publishable boundary for future `.fig` policy extraction; do not move behavior there without package-local tests and dist smoke.
|
||||
- Vector data uses reverse-engineered `vectorNetworkBlob` binary format — encoder/decoder in `packages/core/src/vector/` and scene-graph vector-network types in `@open-pencil/scene-graph`.
|
||||
|
|
|
|||
7
bun.lock
7
bun.lock
|
|
@ -136,6 +136,7 @@
|
|||
"dependencies": {
|
||||
"@chenglou/pretext": "^0.0.7",
|
||||
"@iconify/utils": "^3.1.0",
|
||||
"@open-pencil/fig": "workspace:*",
|
||||
"@open-pencil/kiwi": "workspace:*",
|
||||
"@open-pencil/pen": "workspace:*",
|
||||
"@open-pencil/scene-graph": "workspace:*",
|
||||
|
|
@ -206,6 +207,9 @@
|
|||
"packages/fig": {
|
||||
"name": "@open-pencil/fig",
|
||||
"version": "0.13.2",
|
||||
"dependencies": {
|
||||
"fflate": "^0.8.2",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.14",
|
||||
"tsdown": "^0.22.3",
|
||||
|
|
@ -213,6 +217,7 @@
|
|||
},
|
||||
"peerDependencies": {
|
||||
"@open-pencil/kiwi": "workspace:*",
|
||||
"@open-pencil/scene-graph": "workspace:*",
|
||||
},
|
||||
},
|
||||
"packages/kiwi": {
|
||||
|
|
@ -3261,6 +3266,8 @@
|
|||
|
||||
"@open-pencil/docs/@tailwindcss/vite": ["@tailwindcss/vite@4.3.2", "", { "dependencies": { "@tailwindcss/node": "4.3.2", "@tailwindcss/oxide": "4.3.2", "tailwindcss": "4.3.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-eHpMeX4JXfVNJDEcsouTeCBubJBTcTLigeaw/NTUW6PB5ATKKXdyonnXgTBX2VuRbjz1hjfz6C5XAhr52ImQXA=="],
|
||||
|
||||
"@open-pencil/fig/fflate": ["fflate@0.8.3", "", {}, "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="],
|
||||
|
||||
"@open-pencil/kiwi/fflate": ["fflate@0.8.3", "", {}, "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA=="],
|
||||
|
||||
"@oxc-resolver/binding-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.11.0", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, "sha512-l9Oo58x0HOP5znGzVhYW9U3e5wVuA4LAZU2AGezTmkhO1CgQRFDhDg4nneHsu/t3WniXg9QrG2nIXL/ZS8ln8Q=="],
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@
|
|||
"dependencies": {
|
||||
"@chenglou/pretext": "^0.0.7",
|
||||
"@iconify/utils": "^3.1.0",
|
||||
"@open-pencil/fig": "workspace:*",
|
||||
"@open-pencil/kiwi": "workspace:*",
|
||||
"@open-pencil/pen": "workspace:*",
|
||||
"@open-pencil/scene-graph": "workspace:*",
|
||||
|
|
|
|||
|
|
@ -1,23 +1 @@
|
|||
import { zipSync, type Zippable } from 'fflate'
|
||||
|
||||
import { buildFigKiwi } from '#core/kiwi/fig/node-change/serialize'
|
||||
|
||||
export function compressFigDataSync(
|
||||
schemaDeflated: Uint8Array,
|
||||
kiwiData: Uint8Array,
|
||||
thumbnailPng: Uint8Array,
|
||||
metaJson: string,
|
||||
imageEntries: Array<{ name: string; data: Uint8Array }>,
|
||||
figKiwiVersion?: number
|
||||
): Uint8Array {
|
||||
const canvasData = buildFigKiwi(schemaDeflated, kiwiData, figKiwiVersion)
|
||||
const zipEntries: Zippable = {
|
||||
'canvas.fig': [canvasData, { level: 0 }],
|
||||
'thumbnail.png': [thumbnailPng, { level: 0 }],
|
||||
'meta.json': new TextEncoder().encode(metaJson)
|
||||
}
|
||||
for (const entry of imageEntries) {
|
||||
zipEntries[entry.name] = [entry.data, { level: 0 }]
|
||||
}
|
||||
return zipSync(zipEntries)
|
||||
}
|
||||
export { compressFigDataSync } from '@open-pencil/fig'
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { parseFigBuffer } from '@open-pencil/kiwi/fig/parse'
|
||||
import { parseFigBuffer } from '@open-pencil/fig'
|
||||
import type { SceneGraph } from '@open-pencil/scene-graph'
|
||||
|
||||
import { IS_BROWSER } from '#core/constants'
|
||||
|
|
|
|||
|
|
@ -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 { convertFigmaDerivedTextGlyphs } from '@open-pencil/fig/node-change'
|
||||
import type { SceneNode } from '@open-pencil/scene-graph'
|
||||
|
||||
import type {
|
||||
|
|
@ -5,7 +6,6 @@ import type {
|
|||
OverrideContext
|
||||
} from '#core/kiwi/fig/instance-overrides/types'
|
||||
import { convertLetterSpacing, convertLineHeight } from '#core/kiwi/fig/node-change/convert'
|
||||
import { convertFigmaDerivedTextGlyphs } from '#core/kiwi/fig/node-change/derived-text-glyphs'
|
||||
|
||||
import { resolveDsdGeometry } from './geometry'
|
||||
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
||||
|
|
|
|||
|
|
@ -5,16 +5,17 @@ import { parseVariantName } from '@open-pencil/scene-graph/variant-name'
|
|||
import { DEFAULT_FONT_FAMILY, DEFAULT_STROKE_MITER_LIMIT } from '#core/constants'
|
||||
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 {
|
||||
convertEffects,
|
||||
convertFigmaDerivedTextGlyphs,
|
||||
convertFills,
|
||||
convertFontFeatures,
|
||||
convertFontVariations,
|
||||
convertLetterSpacing,
|
||||
convertLineHeight,
|
||||
convertStrokes,
|
||||
extractBoundVariables,
|
||||
extractExportSettings,
|
||||
extractPluginData,
|
||||
|
|
@ -22,8 +23,19 @@ 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'
|
||||
|
||||
export {
|
||||
convertEffects,
|
||||
convertFills,
|
||||
convertLetterSpacing,
|
||||
convertLineHeight,
|
||||
convertStrokes,
|
||||
mapTextDecoration,
|
||||
setVariableColorResolver
|
||||
} 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,13 +1,16 @@
|
|||
import {
|
||||
convertFontFeatures,
|
||||
convertFontVariations,
|
||||
convertFills,
|
||||
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
|
||||
if (deco) style.textDecoration = mapTextDecoration(deco)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { parseFigBuffer } from '@open-pencil/kiwi/fig/parse'
|
||||
import { parseFigBuffer } from '@open-pencil/fig'
|
||||
|
||||
import { importNodeChanges } from '#core/kiwi/fig/import'
|
||||
import {
|
||||
|
|
|
|||
|
|
@ -1,28 +1,36 @@
|
|||
# @open-pencil/fig
|
||||
|
||||
`.fig` document policy package for OpenPencil.
|
||||
`.fig` file-format package for OpenPencil.
|
||||
|
||||
This package currently exposes low-level `fig-kiwi` container read/write helpers and remains the staging area for the next package-split stage. Use `@open-pencil/core` for production SceneGraph `.fig` read/write APIs until higher-level behavior moves here.
|
||||
The package owns the outer `.fig` archive boundary and is the staged home for Figma-specific
|
||||
SceneGraph conversion policy. Production SceneGraph read/write remains available through
|
||||
`@open-pencil/core/io` while conversion modules move behind this package's public API.
|
||||
|
||||
Current ownership:
|
||||
|
||||
- `readFigContainer()` / `writeFigContainer()` wrappers over `@open-pencil/kiwi` container helpers
|
||||
- `.fig` document source typing
|
||||
- Complete `.fig` archive parsing through `parseFigBuffer()`
|
||||
- `.fig` archive assembly through `writeFigArchive()`
|
||||
- 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:
|
||||
|
||||
- `.fig` read/write orchestration
|
||||
- SceneGraph ⇄ Figma NodeChange conversion policy
|
||||
- raw Figma metadata preservation and invalidation
|
||||
- component/instance interpretation
|
||||
- oracle-backed `.fig` fixtures and package-local tests
|
||||
- SceneGraph ⇄ Figma `NodeChange` conversion
|
||||
- Raw Figma metadata precedence and invalidation policy
|
||||
- Component and instance interpretation
|
||||
- Oracle-backed `.fig` fixtures and package-local tests
|
||||
|
||||
Non-goals:
|
||||
|
||||
- low-level Kiwi schema/runtime/codec internals — use `@open-pencil/kiwi`
|
||||
- editor actions, renderer behavior, Vue/app UI, CLI formatting, or MCP transport
|
||||
- Generic Kiwi schema/runtime internals — use `@open-pencil/kiwi`
|
||||
- Format-neutral IO registration, export targeting, CanvasKit thumbnails, or browser workers — use
|
||||
`@open-pencil/core/io`
|
||||
- Editor actions, renderer behavior, Vue/app UI, CLI formatting, or MCP transport
|
||||
|
||||
See `packages/docs/development/fig-package-plan.md` for the staged migration plan.
|
||||
This follows the existing `@open-pencil/pen` pattern: a format package owns its source model/parser
|
||||
and SceneGraph policy, while core registers it in the shared IO system.
|
||||
|
||||
## Checks
|
||||
|
||||
|
|
|
|||
|
|
@ -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,11 @@
|
|||
"provenance": true
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@open-pencil/kiwi": "workspace:*"
|
||||
"@open-pencil/kiwi": "workspace:*",
|
||||
"@open-pencil/scene-graph": "workspace:*"
|
||||
},
|
||||
"dependencies": {
|
||||
"fflate": "^0.8.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.3.14",
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
export {}
|
||||
|
||||
const mod = await import('../dist/index.js')
|
||||
const nodeChange = await import('../dist/node-change.js')
|
||||
|
||||
if (mod.FIG_PACKAGE_STATUS !== 'container-api') {
|
||||
throw new Error('Expected @open-pencil/fig container API export')
|
||||
if (
|
||||
mod.FIG_PACKAGE_STATUS !== 'archive-api' ||
|
||||
typeof mod.parseFigBuffer !== 'function' ||
|
||||
typeof mod.writeFigArchive !== 'function' ||
|
||||
typeof nodeChange.convertLineHeight !== 'function'
|
||||
) {
|
||||
throw new Error('Expected @open-pencil/fig archive API exports')
|
||||
}
|
||||
|
||||
const bytes = mod.writeFigContainer({
|
||||
|
|
|
|||
99
packages/fig/src/archive.ts
Normal file
99
packages/fig/src/archive.ts
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import { unzipSync, zipSync, type Zippable } from 'fflate'
|
||||
|
||||
import type { NodeChange } from '@open-pencil/kiwi/fig/codec'
|
||||
import { buildFigKiwi } from '@open-pencil/kiwi/fig/container'
|
||||
import { decodeFigKiwiCanvas } from '@open-pencil/kiwi/fig/parse'
|
||||
|
||||
export interface FigImageEntry {
|
||||
name: string
|
||||
data: Uint8Array
|
||||
}
|
||||
|
||||
export interface WriteFigArchiveInput {
|
||||
schemaDeflated: Uint8Array
|
||||
kiwiData: Uint8Array
|
||||
thumbnailPng: Uint8Array
|
||||
metaJson: string
|
||||
images?: FigImageEntry[]
|
||||
figKiwiVersion?: number
|
||||
}
|
||||
|
||||
export interface FigParseResult {
|
||||
nodeChanges: NodeChange[]
|
||||
blobs: Uint8Array[]
|
||||
images: Array<[string, Uint8Array]>
|
||||
figKiwiVersion: number
|
||||
/** Deflated Kiwi schema bytes from the original file, retained for round-trip fidelity. */
|
||||
figSchemaDeflated: Uint8Array
|
||||
}
|
||||
|
||||
function isLikelyAsset(name: string): boolean {
|
||||
const lower = name.toLowerCase()
|
||||
return lower.endsWith('.png') || lower.endsWith('.jpg') || lower.endsWith('.json')
|
||||
}
|
||||
|
||||
function findCanvasData(entries: Partial<Record<string, Uint8Array>>): Uint8Array | null {
|
||||
const canonical = entries['canvas.fig'] ?? entries.canvas
|
||||
if (canonical) return canonical
|
||||
|
||||
let largest: Uint8Array | null = null
|
||||
for (const [name, data] of Object.entries(entries)) {
|
||||
if (!data || isLikelyAsset(name)) continue
|
||||
if (!largest || data.byteLength > largest.byteLength) largest = data
|
||||
}
|
||||
return largest
|
||||
}
|
||||
|
||||
/** Parse a complete zipped `.fig` file into its Figma protocol payload and binary resources. */
|
||||
export function parseFigBuffer(buffer: ArrayBuffer): FigParseResult {
|
||||
const archive = unzipSync(new Uint8Array(buffer), {
|
||||
filter: (file) =>
|
||||
file.name === 'canvas.fig' ||
|
||||
file.name === 'canvas' ||
|
||||
(file.name.startsWith('images/') && file.name !== 'images/')
|
||||
})
|
||||
const canvasData = findCanvasData(archive)
|
||||
if (!canvasData) {
|
||||
throw new Error(
|
||||
`No canvas data found in .fig file. Entries: ${Object.keys(archive).join(', ')}`
|
||||
)
|
||||
}
|
||||
|
||||
const decoded = decodeFigKiwiCanvas(canvasData)
|
||||
const images = Object.entries(archive)
|
||||
.filter(([name]) => name.startsWith('images/') && name !== 'images/')
|
||||
.map(([name, data]) => [name.slice('images/'.length), data] as [string, Uint8Array])
|
||||
|
||||
return { ...decoded, images }
|
||||
}
|
||||
|
||||
/** Assemble a complete zipped `.fig` archive from an encoded Kiwi message and resources. */
|
||||
export function writeFigArchive(input: WriteFigArchiveInput): Uint8Array {
|
||||
const canvasData = buildFigKiwi(input.schemaDeflated, input.kiwiData, input.figKiwiVersion)
|
||||
const entries: Zippable = {
|
||||
'canvas.fig': [canvasData, { level: 0 }],
|
||||
'thumbnail.png': [input.thumbnailPng, { level: 0 }],
|
||||
'meta.json': new TextEncoder().encode(input.metaJson)
|
||||
}
|
||||
for (const image of input.images ?? []) entries[image.name] = [image.data, { level: 0 }]
|
||||
return zipSync(entries)
|
||||
}
|
||||
|
||||
/** Compatibility signature used by core while archive assembly migrates to this package. */
|
||||
export function compressFigDataSync(
|
||||
schemaDeflated: Uint8Array,
|
||||
kiwiData: Uint8Array,
|
||||
thumbnailPng: Uint8Array,
|
||||
metaJson: string,
|
||||
imageEntries: FigImageEntry[],
|
||||
figKiwiVersion?: number
|
||||
): Uint8Array {
|
||||
return writeFigArchive({
|
||||
schemaDeflated,
|
||||
kiwiData,
|
||||
thumbnailPng,
|
||||
metaJson,
|
||||
images: imageEntries,
|
||||
figKiwiVersion
|
||||
})
|
||||
}
|
||||
|
|
@ -1,3 +1,12 @@
|
|||
export {
|
||||
compressFigDataSync,
|
||||
parseFigBuffer,
|
||||
writeFigArchive,
|
||||
type FigImageEntry,
|
||||
type FigParseResult,
|
||||
type WriteFigArchiveInput
|
||||
} from './archive'
|
||||
|
||||
import {
|
||||
FIG_KIWI_DEFAULT_VERSION,
|
||||
buildFigKiwi,
|
||||
|
|
@ -37,7 +46,7 @@ export interface WriteFigContainerOptions {
|
|||
readonly version?: number
|
||||
}
|
||||
|
||||
export const FIG_PACKAGE_STATUS = 'container-api' as const
|
||||
export const FIG_PACKAGE_STATUS = 'archive-api' as const
|
||||
|
||||
export function readFigContainer(
|
||||
bytes: Uint8Array,
|
||||
|
|
@ -66,6 +75,6 @@ export function writeFigContainer(
|
|||
|
||||
export function assertFigPackageReady(): void {
|
||||
throw new Error(
|
||||
'@open-pencil/fig currently exposes low-level container APIs; use @open-pencil/core for SceneGraph .fig read/write APIs for now.'
|
||||
'@open-pencil/fig currently exposes archive/container APIs; use @open-pencil/core for SceneGraph .fig read/write APIs for now.'
|
||||
)
|
||||
}
|
||||
|
|
|
|||
7
packages/fig/src/node-change/index.ts
Normal file
7
packages/fig/src/node-change/index.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export * from './derived-text-glyphs'
|
||||
export * from './font/features'
|
||||
export * from './font/variations'
|
||||
export * from './paint'
|
||||
export * from './plugin-data'
|
||||
export * from './style-refs'
|
||||
export * from './text-values'
|
||||
|
|
@ -11,11 +11,13 @@ import type {
|
|||
StrokeCap,
|
||||
StrokeJoin
|
||||
} from '@open-pencil/scene-graph'
|
||||
import { BLACK } from '@open-pencil/scene-graph/constants'
|
||||
import type { Color, Matrix } from '@open-pencil/scene-graph/primitives'
|
||||
|
||||
import { normalizeColor } from '#core/color'
|
||||
|
||||
const convertColor = normalizeColor
|
||||
function convertColor(color?: Partial<Color>): Color {
|
||||
if (!color) return { ...BLACK }
|
||||
return { r: color.r ?? 0, g: color.g ?? 0, b: color.b ?? 0, a: color.a ?? 1 }
|
||||
}
|
||||
|
||||
function imageHashToString(hash: Record<string, number>): string {
|
||||
const bytes = Object.keys(hash)
|
||||
|
|
@ -1,15 +1,30 @@
|
|||
import { describe, expect, it } from 'bun:test'
|
||||
import { beforeAll, describe, expect, it } from 'bun:test'
|
||||
|
||||
import { deflateSync } from 'fflate'
|
||||
|
||||
import {
|
||||
createNodeChangesMessage,
|
||||
encodeMessage,
|
||||
getSchemaBytes,
|
||||
initCodec
|
||||
} from '@open-pencil/kiwi/fig/codec'
|
||||
|
||||
import {
|
||||
FIG_PACKAGE_STATUS,
|
||||
assertFigPackageReady,
|
||||
parseFigBuffer,
|
||||
readFigContainer,
|
||||
writeFigArchive,
|
||||
writeFigContainer
|
||||
} from '../src/index'
|
||||
|
||||
describe('@open-pencil/fig package API', () => {
|
||||
it('exports container API status', () => {
|
||||
expect(FIG_PACKAGE_STATUS).toBe('container-api')
|
||||
beforeAll(async () => {
|
||||
await initCodec()
|
||||
})
|
||||
|
||||
it('exports archive API status', () => {
|
||||
expect(FIG_PACKAGE_STATUS).toBe('archive-api')
|
||||
})
|
||||
|
||||
it('round-trips fig-kiwi container bytes', () => {
|
||||
|
|
@ -25,11 +40,35 @@ describe('@open-pencil/fig package API', () => {
|
|||
expect(document.source?.fileName).toBe('fixture.fig')
|
||||
})
|
||||
|
||||
it('parses complete .fig archives and image resources', () => {
|
||||
const bytes = writeFigArchive({
|
||||
schemaDeflated: deflateSync(getSchemaBytes()),
|
||||
kiwiData: encodeMessage(
|
||||
createNodeChangesMessage(0, 0, [
|
||||
{
|
||||
guid: { sessionID: 0, localID: 0 },
|
||||
type: 'DOCUMENT',
|
||||
phase: 'CREATED',
|
||||
name: 'Document'
|
||||
}
|
||||
])
|
||||
),
|
||||
thumbnailPng: new Uint8Array([1]),
|
||||
metaJson: '{}',
|
||||
images: [{ name: 'images/hash', data: new Uint8Array([9, 8, 7]) }]
|
||||
})
|
||||
const parsed = parseFigBuffer(bytes.buffer as ArrayBuffer)
|
||||
|
||||
expect(parsed.nodeChanges).toHaveLength(1)
|
||||
expect(parsed.nodeChanges[0]?.type).toBe('DOCUMENT')
|
||||
expect(parsed.images).toEqual([['hash', new Uint8Array([9, 8, 7])]])
|
||||
})
|
||||
|
||||
it('rejects invalid fig-kiwi containers', () => {
|
||||
expect(() => readFigContainer(new Uint8Array([1, 2, 3]))).toThrow('Invalid fig-kiwi')
|
||||
})
|
||||
|
||||
it('directs consumers to core for SceneGraph read/write', () => {
|
||||
expect(() => assertFigPackageReady()).toThrow('low-level container APIs')
|
||||
expect(() => assertFigPackageReady()).toThrow('archive/container APIs')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
64
packages/fig/tests/node-change.test.ts
Normal file
64
packages/fig/tests/node-change.test.ts
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
import { describe, expect, test } from 'bun:test'
|
||||
|
||||
import {
|
||||
applyStyleRefsToFields,
|
||||
convertEffects,
|
||||
convertFills,
|
||||
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('normalizes imported paints and effects', () => {
|
||||
expect(convertFills([{ type: 'SOLID' }])[0]).toMatchObject({
|
||||
color: { r: 0, g: 0, b: 0, a: 1 },
|
||||
opacity: 1,
|
||||
visible: true
|
||||
})
|
||||
expect(convertEffects([{ type: 'DROP_SHADOW' }])[0]).toMatchObject({
|
||||
type: 'DROP_SHADOW',
|
||||
radius: 0,
|
||||
visible: true
|
||||
})
|
||||
})
|
||||
|
||||
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"]
|
||||
},
|
||||
"include": ["src"]
|
||||
"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/**/*.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'],
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
Scene-graph-agnostic Kiwi runtime utilities for OpenPencil.
|
||||
|
||||
This package owns pure Kiwi schema parsing, Figma Kiwi schema data, low-level Figma message encode/decode, FIG Kiwi container helpers, GUID formatting, and raw `.fig` parse helpers. `.fig` import/export policy stays in `@open-pencil/core`: SceneGraph conversion, raw metadata invalidation, component/instance interpretation, and app/CLI document I/O are not part of this package.
|
||||
This package owns pure Kiwi schema parsing, Figma Kiwi schema data, low-level Figma message encode/decode, raw `fig-kiwi` container helpers, and GUID formatting. Complete `.fig` archive parsing lives in `@open-pencil/fig`; SceneGraph integration remains outside this package.
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -74,19 +74,16 @@ const container = buildFigKiwi(new Uint8Array([1, 2, 3]))
|
|||
const chunks = parseFigKiwiChunks(container)
|
||||
```
|
||||
|
||||
## Raw `.fig` parsing
|
||||
## Raw `fig-kiwi` payload decoding
|
||||
|
||||
```ts
|
||||
import { parseFigBytes } from '@open-pencil/kiwi/fig/parse'
|
||||
import { decodeFigKiwiCanvas } from '@open-pencil/kiwi/fig/parse'
|
||||
|
||||
const parsed = await parseFigBytes(await Bun.file('design.fig').arrayBuffer())
|
||||
|
||||
for (const canvas of parsed.canvases) {
|
||||
console.log(canvas.name, canvas.nodeChanges.length)
|
||||
}
|
||||
const decoded = decodeFigKiwiCanvas(canvasBytes)
|
||||
console.log(decoded.nodeChanges.length, decoded.blobs.length)
|
||||
```
|
||||
|
||||
`parseFigBytes()` returns structural `NodeChange` data, blobs, images, schema bytes, and container metadata. Use `@open-pencil/core` for converting that data into an editable `SceneGraph`.
|
||||
Use `parseFigBuffer()` from `@open-pencil/fig` for complete zipped `.fig` files, including image resources. Use `@open-pencil/core/io` for conversion into an editable `SceneGraph`.
|
||||
|
||||
## GUID helpers
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { unzipSync, inflateSync } from 'fflate'
|
||||
import { inflateSync } from 'fflate'
|
||||
import { decompress as zstdDecompress } from 'fzstd'
|
||||
|
||||
import { decodeBinarySchema, compileSchema, ByteBuffer } from '../schema-runtime'
|
||||
|
|
@ -84,48 +84,17 @@ export function parseFigKiwiContainer(data: Uint8Array): FigKiwiPayload | null {
|
|||
return { schemaDeflated: chunks[0], dataRaw, version }
|
||||
}
|
||||
|
||||
export interface FigParseResult {
|
||||
export interface FigKiwiDecodeResult {
|
||||
nodeChanges: NodeChange[]
|
||||
blobs: Uint8Array[]
|
||||
images: Array<[string, Uint8Array]>
|
||||
figKiwiVersion: number
|
||||
/** Deflated kiwi schema bytes from the original file (for roundtrip fidelity). */
|
||||
figSchemaDeflated: Uint8Array
|
||||
}
|
||||
|
||||
export function parseFigBuffer(buffer: ArrayBuffer): FigParseResult {
|
||||
const zip = unzipSync(new Uint8Array(buffer), {
|
||||
filter: (file) =>
|
||||
file.name === 'canvas.fig' ||
|
||||
file.name === 'canvas' ||
|
||||
(file.name.startsWith('images/') && file.name !== 'images/')
|
||||
})
|
||||
const entries = Object.keys(zip)
|
||||
|
||||
let canvasData: Uint8Array | null = null
|
||||
for (const name of entries) {
|
||||
if (name === 'canvas.fig' || name === 'canvas') {
|
||||
canvasData = zip[name]
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!canvasData) {
|
||||
let maxSize = 0
|
||||
for (const name of entries) {
|
||||
const lower = name.toLowerCase()
|
||||
if (lower.endsWith('.png') || lower.endsWith('.jpg') || lower.endsWith('.json')) continue
|
||||
if (zip[name].byteLength > maxSize) {
|
||||
maxSize = zip[name].byteLength
|
||||
canvasData = zip[name]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!canvasData) {
|
||||
throw new Error(`No canvas data found in .fig file. Entries: ${entries.join(', ')}`)
|
||||
}
|
||||
|
||||
const payload = parseFigKiwiContainer(canvasData)
|
||||
/** Decode one raw `fig-kiwi` canvas payload. Outer `.fig` archive handling lives in `@open-pencil/fig`. */
|
||||
export function decodeFigKiwiCanvas(data: Uint8Array): FigKiwiDecodeResult {
|
||||
const payload = parseFigKiwiContainer(data)
|
||||
if (!payload) throw new Error('Invalid fig-kiwi container')
|
||||
|
||||
const schemaBytes = inflateSync(payload.schemaDeflated)
|
||||
|
|
@ -138,25 +107,15 @@ export function parseFigBuffer(buffer: ArrayBuffer): FigParseResult {
|
|||
throw new Error('No nodes found in .fig file')
|
||||
}
|
||||
|
||||
// Deduplicate before returning — critical for worker path where raw
|
||||
// nodeChanges are serialized via postMessage before extractPluginData runs
|
||||
deduplicateNodeChangePluginData(nodeChanges)
|
||||
|
||||
const blobs: Uint8Array[] = (message.blobs ?? []).map((b) =>
|
||||
b.bytes instanceof Uint8Array ? b.bytes : new Uint8Array(Object.values(b.bytes))
|
||||
const blobs: Uint8Array[] = (message.blobs ?? []).map((blob) =>
|
||||
blob.bytes instanceof Uint8Array ? blob.bytes : new Uint8Array(Object.values(blob.bytes))
|
||||
)
|
||||
|
||||
const images: Array<[string, Uint8Array]> = []
|
||||
for (const name of entries) {
|
||||
if (name.startsWith('images/') && name !== 'images/') {
|
||||
images.push([name.replace('images/', ''), zip[name]])
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
nodeChanges,
|
||||
blobs,
|
||||
images,
|
||||
figKiwiVersion: payload.version,
|
||||
figSchemaDeflated: payload.schemaDeflated
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { beforeAll, describe, expect, test } from 'bun:test'
|
||||
|
||||
import { exportFigFile, initCodec } from '@open-pencil/core'
|
||||
import { parseFigBuffer } from '@open-pencil/kiwi/fig/parse'
|
||||
import { parseFigBuffer } from '@open-pencil/fig'
|
||||
import { SceneGraph } from '@open-pencil/scene-graph'
|
||||
|
||||
import { importNodeChanges } from '#core/kiwi/fig/import'
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { beforeAll, describe, expect, test } from 'bun:test'
|
||||
|
||||
import { exportFigFile, initCodec } from '@open-pencil/core'
|
||||
import { parseFigBuffer } from '@open-pencil/kiwi/fig/parse'
|
||||
import { parseFigBuffer } from '@open-pencil/fig'
|
||||
import { SceneGraph } from '@open-pencil/scene-graph'
|
||||
|
||||
import { sceneNodeToKiwi } from '#core/kiwi/fig/node-change/serialize'
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { expect, setDefaultTimeout, test } from 'bun:test'
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
import { parseFigBuffer } from '@open-pencil/kiwi/fig/parse'
|
||||
import { parseFigBuffer } from '@open-pencil/fig'
|
||||
|
||||
import { importNodeChanges } from '#core/kiwi'
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
SceneGraph,
|
||||
type NodeChange
|
||||
} from '@open-pencil/core'
|
||||
import { parseFigBuffer } from '@open-pencil/kiwi/fig/parse'
|
||||
import { parseFigBuffer } from '@open-pencil/fig'
|
||||
import { MAX_EXPORT_SCALE } from '@open-pencil/scene-graph'
|
||||
|
||||
function decodeExport(bytes: Uint8Array) {
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { resolve } from 'node:path'
|
|||
|
||||
import { exportFigFile, initCodec, parseFigFile, SceneGraph } from '@open-pencil/core'
|
||||
import { fontManager } from '@open-pencil/core/text'
|
||||
import { parseFigBuffer } from '@open-pencil/kiwi/fig/parse'
|
||||
import { parseFigBuffer } from '@open-pencil/fig'
|
||||
|
||||
import { HEAVY_TEST_TIMEOUT_MS } from '#tests/helpers/test-utils'
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { beforeAll, describe, expect, test } from 'bun:test'
|
|||
|
||||
import { exportFigFile, initCodec, parseFigFile, SceneGraph } from '@open-pencil/core'
|
||||
import { guidToString } from '@open-pencil/core/kiwi/fig/node-change/convert'
|
||||
import { parseFigBuffer } from '@open-pencil/kiwi/fig/parse'
|
||||
import { parseFigBuffer } from '@open-pencil/fig'
|
||||
|
||||
function decodeExport(bytes: Uint8Array) {
|
||||
return parseFigBuffer(bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength))
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { readFileSync } from 'node:fs'
|
|||
|
||||
import { exportFigFile, extractExportGraph, parseFigFile } from '@open-pencil/core/io'
|
||||
import { initCodec } from '@open-pencil/core/kiwi'
|
||||
import { parseFigBuffer } from '@open-pencil/fig'
|
||||
import { guidToString } from '@open-pencil/kiwi/fig/guid'
|
||||
import { parseFigBuffer } from '@open-pencil/kiwi/fig/parse'
|
||||
import { SceneGraph } from '@open-pencil/scene-graph'
|
||||
|
||||
describe('export subgraph extraction', () => {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ const scenePath = coreSourcePath('canvas/scene.ts')
|
|||
const rendererPath = coreSourcePath('canvas/renderer.ts')
|
||||
const sgTypesPath = repoPath('packages/scene-graph/src/types.ts')
|
||||
const nodeExportPath = coreSourcePath('kiwi/fig/node-change/export-node.ts')
|
||||
const convertPath = coreSourcePath('kiwi/fig/node-change/paint.ts')
|
||||
const convertPath = repoPath('packages/fig/src/node-change/paint.ts')
|
||||
const schemaPath = repoPath('packages/kiwi/src/fig/schema/fig.kiwi')
|
||||
const codecPath = repoPath('packages/kiwi/src/fig/codec.ts')
|
||||
const lifecyclePath = coreSourcePath('canvas/renderer/lifecycle.ts')
|
||||
|
|
|
|||
|
|
@ -90,7 +90,11 @@ try {
|
|||
tempDir
|
||||
)
|
||||
nodeEval(
|
||||
"const { FIG_PACKAGE_STATUS, readFigContainer, writeFigContainer } = await import('@open-pencil/fig'); if (FIG_PACKAGE_STATUS !== 'container-api') 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')",
|
||||
"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(
|
||||
|
|
|
|||
Loading…
Reference in a new issue