refactor(kiwi): move variable binding codec
This commit is contained in:
parent
6877516f06
commit
086ab2d2ae
|
|
@ -8,13 +8,19 @@
|
|||
|
||||
import { decompress as zstdDecompress } from 'fzstd'
|
||||
|
||||
import { figmaSchema, isZstdCompressed, getKiwiMessageType } from '@open-pencil/kiwi/fig'
|
||||
import {
|
||||
figmaSchema,
|
||||
isZstdCompressed,
|
||||
getKiwiMessageType,
|
||||
encodeVarint,
|
||||
encodePaintWithVariableBinding as encodePaintVariableBinding,
|
||||
encodeNodeChangeWithVariables as encodeNodeChangeVariableBindings,
|
||||
parseVariableId
|
||||
} from '@open-pencil/kiwi/fig'
|
||||
import { compileSchema, encodeBinarySchema } from '@open-pencil/kiwi/schema-runtime'
|
||||
|
||||
import { parseColor } from '#core/color'
|
||||
|
||||
import * as VariableBindings from './variable-bindings'
|
||||
|
||||
interface CompiledSchema {
|
||||
encodeMessage(message: unknown): Uint8Array
|
||||
decodeMessage(data: Uint8Array): unknown
|
||||
|
|
@ -123,7 +129,7 @@ export function encodeMessage(message: FigmaMessage): Uint8Array {
|
|||
|
||||
// Build nodeChanges array with our encoded nodes
|
||||
const ncBytes: number[] = [0x04] // field 4
|
||||
ncBytes.push(...VariableBindings.encodeVarint(nodeChangeBytes.length)) // array length
|
||||
ncBytes.push(...encodeVarint(nodeChangeBytes.length)) // array length
|
||||
for (const ncArr of nodeChangeBytes) {
|
||||
ncBytes.push(...Array.from(ncArr))
|
||||
}
|
||||
|
|
@ -557,19 +563,14 @@ export function encodePaintWithVariableBinding(
|
|||
if (!compiledSchema) {
|
||||
throw new Error('Codec not initialized. Call initCodec() first.')
|
||||
}
|
||||
return VariableBindings.encodePaintWithVariableBinding(
|
||||
compiledSchema,
|
||||
paint,
|
||||
variableSessionID,
|
||||
variableLocalID
|
||||
)
|
||||
return encodePaintVariableBinding(compiledSchema, paint, variableSessionID, variableLocalID)
|
||||
}
|
||||
|
||||
export { parseVariableId } from './variable-bindings'
|
||||
export { parseVariableId }
|
||||
|
||||
export function encodeNodeChangeWithVariables(nodeChange: NodeChange): Uint8Array {
|
||||
if (!compiledSchema) {
|
||||
throw new Error('Codec not initialized. Call initCodec() first.')
|
||||
}
|
||||
return VariableBindings.encodeNodeChangeWithVariables(compiledSchema, nodeChange)
|
||||
return encodeNodeChangeVariableBindings(compiledSchema, nodeChange)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
export { default as figmaSchema } from './schema'
|
||||
export * from './protocol'
|
||||
export * from './variable-bindings'
|
||||
|
|
|
|||
|
|
@ -1,9 +1,22 @@
|
|||
import { hexToBytes } from '#core/bytes/hex'
|
||||
import type { GUID } from '#core/types'
|
||||
export interface GUID {
|
||||
sessionID: number
|
||||
localID: number
|
||||
}
|
||||
|
||||
import type { NodeChange, Paint } from './index'
|
||||
export interface VariableBinding {
|
||||
variableID: GUID
|
||||
}
|
||||
|
||||
export interface VariableBindingCodec {
|
||||
export interface PaintWithVariableBinding {
|
||||
colorVariableBinding?: VariableBinding
|
||||
}
|
||||
|
||||
export interface NodeChangeWithVariableBindings {
|
||||
fillPaints?: PaintWithVariableBinding[]
|
||||
strokePaints?: PaintWithVariableBinding[]
|
||||
}
|
||||
|
||||
export interface VariableBindingCodec<Paint, NodeChange> {
|
||||
encodePaint(paint: Paint): Uint8Array
|
||||
encodeNodeChange(nodeChange: NodeChange): Uint8Array
|
||||
}
|
||||
|
|
@ -18,8 +31,8 @@ export function encodeVarint(value: number): number[] {
|
|||
return bytes
|
||||
}
|
||||
|
||||
export function encodePaintWithVariableBinding(
|
||||
codec: VariableBindingCodec,
|
||||
export function encodePaintWithVariableBinding<Paint extends PaintWithVariableBinding>(
|
||||
codec: VariableBindingCodec<Omit<Paint, 'colorVariableBinding'>, unknown>,
|
||||
paint: Paint,
|
||||
variableSessionID: number,
|
||||
variableLocalID: number
|
||||
|
|
@ -52,12 +65,18 @@ export function parseVariableId(variableId: string): GUID | null {
|
|||
}
|
||||
}
|
||||
|
||||
export function encodeNodeChangeWithVariables(
|
||||
codec: VariableBindingCodec,
|
||||
export function encodeNodeChangeWithVariables<NodeChange extends NodeChangeWithVariableBindings>(
|
||||
codec: VariableBindingCodec<
|
||||
unknown,
|
||||
Omit<NodeChange, 'fillPaints' | 'strokePaints'> & {
|
||||
fillPaints?: Omit<PaintWithVariableBinding, 'colorVariableBinding'>[]
|
||||
strokePaints?: Omit<PaintWithVariableBinding, 'colorVariableBinding'>[]
|
||||
}
|
||||
>,
|
||||
nodeChange: NodeChange
|
||||
): Uint8Array {
|
||||
const hasFillBinding = nodeChange.fillPaints?.some((p) => p.colorVariableBinding)
|
||||
const hasStrokeBinding = nodeChange.strokePaints?.some((p) => p.colorVariableBinding)
|
||||
const hasFillBinding = nodeChange.fillPaints?.some((paint) => paint.colorVariableBinding)
|
||||
const hasStrokeBinding = nodeChange.strokePaints?.some((paint) => paint.colorVariableBinding)
|
||||
|
||||
if (!hasFillBinding && !hasStrokeBinding) {
|
||||
return codec.encodeNodeChange(nodeChange)
|
||||
|
|
@ -91,7 +110,7 @@ export function encodeNodeChangeWithVariables(
|
|||
return hexToBytes(hex)
|
||||
}
|
||||
|
||||
function injectVariableBinding(hex: string, marker: string, binding: { variableID: GUID }): string {
|
||||
function injectVariableBinding(hex: string, marker: string, binding: VariableBinding): string {
|
||||
const markerIdx = hex.indexOf(marker)
|
||||
if (markerIdx === -1) return hex
|
||||
|
||||
|
|
@ -132,3 +151,13 @@ function injectVariableBinding(hex: string, marker: string, binding: { variableI
|
|||
|
||||
return beforeVar + varHex + afterVar
|
||||
}
|
||||
|
||||
function hexToBytes(hex: string): Uint8Array {
|
||||
if (hex.length % 2 !== 0) throw new Error('Hex string must have an even length')
|
||||
|
||||
const bytes = new Uint8Array(hex.length / 2)
|
||||
for (let index = 0; index < bytes.length; index++) {
|
||||
bytes[index] = Number.parseInt(hex.slice(index * 2, index * 2 + 2), 16)
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
54
packages/kiwi/tests/variable-bindings.test.ts
Normal file
54
packages/kiwi/tests/variable-bindings.test.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import { describe, expect, test } from 'bun:test'
|
||||
|
||||
import {
|
||||
encodeNodeChangeWithVariables,
|
||||
encodePaintWithVariableBinding,
|
||||
encodeVarint,
|
||||
parseVariableId
|
||||
} from '../src/fig/variable-bindings'
|
||||
|
||||
describe('variable binding codec helpers', () => {
|
||||
test('encodes varints', () => {
|
||||
expect(encodeVarint(0)).toEqual([0])
|
||||
expect(encodeVarint(127)).toEqual([127])
|
||||
expect(encodeVarint(128)).toEqual([128, 1])
|
||||
expect(encodeVarint(16_384)).toEqual([128, 128, 1])
|
||||
})
|
||||
|
||||
test('parses Figma variable IDs', () => {
|
||||
expect(parseVariableId('VariableID:123:456')).toEqual({ sessionID: 123, localID: 456 })
|
||||
expect(parseVariableId('not-a-variable')).toBeNull()
|
||||
})
|
||||
|
||||
test('appends paint variable binding bytes', () => {
|
||||
const codec = {
|
||||
encodePaint: () => new Uint8Array([0x01, 0x02, 0x00]),
|
||||
encodeNodeChange: () => new Uint8Array()
|
||||
}
|
||||
|
||||
const encoded = encodePaintWithVariableBinding(
|
||||
codec,
|
||||
{ colorVariableBinding: { variableID: { sessionID: 5, localID: 130 } } },
|
||||
5,
|
||||
130
|
||||
)
|
||||
|
||||
expect([...encoded]).toEqual([
|
||||
0x01, 0x02, 0x15, 0x01, 0x04, 0x01, 0x05, 0x82, 0x01, 0x00, 0x00, 0x02, 0x03, 0x03, 0x04,
|
||||
0x00, 0x00
|
||||
])
|
||||
})
|
||||
|
||||
test('injects node change variable bindings after paint markers', () => {
|
||||
const codec = {
|
||||
encodePaint: () => new Uint8Array(),
|
||||
encodeNodeChange: () => new Uint8Array([0x26, 0x01, 0x04, 0x01, 0x00, 0xaa])
|
||||
}
|
||||
|
||||
const encoded = encodeNodeChangeWithVariables(codec, {
|
||||
fillPaints: [{ colorVariableBinding: { variableID: { sessionID: 2, localID: 3 } } }]
|
||||
})
|
||||
|
||||
expect(Buffer.from(encoded).toString('hex')).toBe('260104011501040102030000020303040000aa')
|
||||
})
|
||||
})
|
||||
Loading…
Reference in a new issue