fix(clipboard): include text glyph blobs
This commit is contained in:
parent
992be9b713
commit
91f516ecaa
|
|
@ -335,7 +335,7 @@ export async function buildFigmaClipboardHTML(
|
||||||
const source = textNodeQueue.shift()
|
const source = textNodeQueue.shift()
|
||||||
if (!source) return
|
if (!source) return
|
||||||
change.textUserLayoutVersion = 4
|
change.textUserLayoutVersion = 4
|
||||||
change.derivedTextData = await buildDerivedTextDataV4(source, fontDigestMap)
|
change.derivedTextData = await buildDerivedTextDataV4(source, fontDigestMap, null, blobs)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -331,7 +331,7 @@ export interface NodeChange {
|
||||||
lineAscent: number
|
lineAscent: number
|
||||||
}>
|
}>
|
||||||
glyphs?: Array<{
|
glyphs?: Array<{
|
||||||
commands: Array<string | number>
|
commandsBlob?: number
|
||||||
position: Vector
|
position: Vector
|
||||||
fontSize: number
|
fontSize: number
|
||||||
firstCharacter: number
|
firstCharacter: number
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,8 @@ interface SceneNodeToKiwiContext {
|
||||||
node: SceneNode,
|
node: SceneNode,
|
||||||
nc: KiwiNodeChange,
|
nc: KiwiNodeChange,
|
||||||
graph: SceneGraph,
|
graph: SceneGraph,
|
||||||
fontDigestMap?: Map<string, Uint8Array>
|
fontDigestMap: Map<string, Uint8Array> | undefined,
|
||||||
|
blobs: Uint8Array[]
|
||||||
) => void
|
) => void
|
||||||
serializeLayoutProps: (node: SceneNode, nc: KiwiNodeChange) => void
|
serializeLayoutProps: (node: SceneNode, nc: KiwiNodeChange) => void
|
||||||
serializeGeometry: (node: SceneNode, nc: KiwiNodeChange, blobs: Uint8Array[]) => void
|
serializeGeometry: (node: SceneNode, nc: KiwiNodeChange, blobs: Uint8Array[]) => void
|
||||||
|
|
@ -128,7 +129,7 @@ function applyNodeVisualProps(
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.type === 'TEXT') {
|
if (node.type === 'TEXT') {
|
||||||
context.serializeTextProps(node, nc, context.graph, context.fontDigestMap)
|
context.serializeTextProps(node, nc, context.graph, context.fontDigestMap, context.blobs)
|
||||||
}
|
}
|
||||||
|
|
||||||
nc.frameMaskDisabled = !node.clipsContent
|
nc.frameMaskDisabled = !node.clipsContent
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { buildDerivedTextData as buildSharedDerivedTextData } from '#core/text/derived-text-data'
|
import { buildDerivedTextData as buildSharedDerivedTextData } from '#core/text/derived-text-data'
|
||||||
import { normalizeFontFamily, weightToFigmaStyle, weightToStyle } from '#core/text/fonts'
|
import { normalizeFontFamily, weightToFigmaStyle, weightToStyle } from '#core/text/fonts'
|
||||||
import { getGlyphOutlineCommandsSync } from '#core/text/opentype'
|
import { getGlyphOutlineMetricsSync } from '#core/text/opentype'
|
||||||
import { encodeVectorNetworkBlob, buildStyleOverrideTable } from '#core/vector'
|
import { encodeVectorNetworkBlob, buildStyleOverrideTable } from '#core/vector'
|
||||||
export {
|
export {
|
||||||
buildFigKiwi,
|
buildFigKiwi,
|
||||||
|
|
@ -73,7 +73,8 @@ function textLines(text: string): NonNullable<NodeChange['textData']>['lines'] {
|
||||||
|
|
||||||
function buildDerivedTextData(
|
function buildDerivedTextData(
|
||||||
node: SceneNode,
|
node: SceneNode,
|
||||||
digestMap: Map<string, Uint8Array>
|
digestMap: Map<string, Uint8Array>,
|
||||||
|
blobs: Uint8Array[]
|
||||||
): NodeChange['derivedTextData'] {
|
): NodeChange['derivedTextData'] {
|
||||||
const fontMeta: NonNullable<NodeChange['derivedTextData']>['fontMetaData'] = []
|
const fontMeta: NonNullable<NodeChange['derivedTextData']>['fontMetaData'] = []
|
||||||
const seen = new Set<string>()
|
const seen = new Set<string>()
|
||||||
|
|
@ -103,17 +104,16 @@ function buildDerivedTextData(
|
||||||
}
|
}
|
||||||
|
|
||||||
const style = weightToStyle(node.fontWeight, node.italic)
|
const style = weightToStyle(node.fontWeight, node.italic)
|
||||||
const glyphCommandLists =
|
const glyphMetrics = getGlyphOutlineMetricsSync(node.fontFamily, style, node.text, node.fontSize) ?? []
|
||||||
getGlyphOutlineCommandsSync(node.fontFamily, style, node.text, node.fontSize) ?? []
|
|
||||||
const lineHeight = node.lineHeight ?? Math.ceil(node.fontSize * 1.2)
|
const lineHeight = node.lineHeight ?? Math.ceil(node.fontSize * 1.2)
|
||||||
const glyphAdvance = node.text.length > 0 ? node.width / Math.max(node.text.length, 1) : 0
|
const glyphAdvance = node.text.length > 0 ? node.width / Math.max(node.text.length, 1) : 0
|
||||||
|
|
||||||
const glyphs = glyphCommandLists.map((commands, index) => ({
|
const glyphs = glyphMetrics.map((glyph, index) => ({
|
||||||
commands,
|
commandsBlob: blobs.push(glyph.commandsBlob) - 1,
|
||||||
position: { x: index * glyphAdvance, y: lineHeight },
|
position: { x: glyph.x || index * glyphAdvance, y: lineHeight },
|
||||||
fontSize: node.fontSize,
|
fontSize: node.fontSize,
|
||||||
firstCharacter: index,
|
firstCharacter: index,
|
||||||
advance: glyphAdvance,
|
advance: glyph.advance || glyphAdvance,
|
||||||
rotation: 0
|
rotation: 0
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
@ -233,7 +233,6 @@ function serializeCornerRadii(node: SceneNode, nc: KiwiNodeChange): void {
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolveTextAutoResize(node: SceneNode, graph: SceneGraph): SceneNode['textAutoResize'] {
|
function resolveTextAutoResize(node: SceneNode, graph: SceneGraph): SceneNode['textAutoResize'] {
|
||||||
if (node.textAutoResize === 'NONE') return 'NONE'
|
|
||||||
const parent = node.parentId ? graph.getNode(node.parentId) : undefined
|
const parent = node.parentId ? graph.getNode(node.parentId) : undefined
|
||||||
if (
|
if (
|
||||||
parent &&
|
parent &&
|
||||||
|
|
@ -241,7 +240,7 @@ function resolveTextAutoResize(node: SceneNode, graph: SceneGraph): SceneNode['t
|
||||||
parent.layoutMode !== 'GRID' &&
|
parent.layoutMode !== 'GRID' &&
|
||||||
node.layoutPositioning !== 'ABSOLUTE'
|
node.layoutPositioning !== 'ABSOLUTE'
|
||||||
) {
|
) {
|
||||||
return 'NONE'
|
return 'HEIGHT'
|
||||||
}
|
}
|
||||||
return node.textAutoResize
|
return node.textAutoResize
|
||||||
}
|
}
|
||||||
|
|
@ -250,7 +249,8 @@ function serializeTextProps(
|
||||||
node: SceneNode,
|
node: SceneNode,
|
||||||
nc: KiwiNodeChange,
|
nc: KiwiNodeChange,
|
||||||
graph: SceneGraph,
|
graph: SceneGraph,
|
||||||
fontDigestMap?: Map<string, Uint8Array>
|
fontDigestMap: Map<string, Uint8Array> | undefined,
|
||||||
|
blobs: Uint8Array[]
|
||||||
): void {
|
): void {
|
||||||
upsertPluginData(node, TEXT_DIRECTION_PLUGIN_KEY, node.textDirection)
|
upsertPluginData(node, TEXT_DIRECTION_PLUGIN_KEY, node.textDirection)
|
||||||
nc.fontSize = node.fontSize
|
nc.fontSize = node.fontSize
|
||||||
|
|
@ -264,11 +264,8 @@ function serializeTextProps(
|
||||||
if (autoResize !== 'NONE') nc.textAutoResize = autoResize
|
if (autoResize !== 'NONE') nc.textAutoResize = autoResize
|
||||||
nc.textAlignHorizontal = node.textAlignHorizontal
|
nc.textAlignHorizontal = node.textAlignHorizontal
|
||||||
nc.textUserLayoutVersion = 4
|
nc.textUserLayoutVersion = 4
|
||||||
if (fontDigestMap) nc.derivedTextData = buildDerivedTextData(node, fontDigestMap)
|
if (fontDigestMap) nc.derivedTextData = buildDerivedTextData(node, fontDigestMap, blobs)
|
||||||
// Figma needs explicit lineHeight to compute text bounding boxes.
|
if (node.lineHeight != null) nc.lineHeight = { value: node.lineHeight, units: 'PIXELS' }
|
||||||
// Without it (and without baselines/glyphs data), text gets 0 height.
|
|
||||||
const lh = node.lineHeight != null ? node.lineHeight : Math.ceil(node.fontSize * 1.2)
|
|
||||||
nc.lineHeight = { value: lh, units: 'PIXELS' }
|
|
||||||
if (node.letterSpacing !== 0) nc.letterSpacing = { value: node.letterSpacing, units: 'PIXELS' }
|
if (node.letterSpacing !== 0) nc.letterSpacing = { value: node.letterSpacing, units: 'PIXELS' }
|
||||||
if (node.textDecoration !== 'NONE') {
|
if (node.textDecoration !== 'NONE') {
|
||||||
nc.textDecoration = node.textDecoration === 'UNDERLINE' ? 'UNDERLINE' : 'STRIKETHROUGH'
|
nc.textDecoration = node.textDecoration === 'UNDERLINE' ? 'UNDERLINE' : 'STRIKETHROUGH'
|
||||||
|
|
@ -294,7 +291,7 @@ function serializeLayoutProps(node: SceneNode, nc: KiwiNodeChange): void {
|
||||||
}
|
}
|
||||||
if (node.layoutPositioning === 'ABSOLUTE') nc.stackPositioning = 'ABSOLUTE'
|
if (node.layoutPositioning === 'ABSOLUTE') nc.stackPositioning = 'ABSOLUTE'
|
||||||
if (node.layoutGrow > 0) nc.stackChildPrimaryGrow = node.layoutGrow
|
if (node.layoutGrow > 0) nc.stackChildPrimaryGrow = node.layoutGrow
|
||||||
if (node.layoutAlignSelf !== 'AUTO') {
|
if (node.type !== 'TEXT' && node.layoutAlignSelf !== 'AUTO') {
|
||||||
nc.stackChildAlignSelf = node.layoutAlignSelf
|
nc.stackChildAlignSelf = node.layoutAlignSelf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ export interface ShapedClipboardText {
|
||||||
export async function buildDerivedTextDataV4(
|
export async function buildDerivedTextDataV4(
|
||||||
node: SceneNode,
|
node: SceneNode,
|
||||||
digestMap: Map<string, Uint8Array>,
|
digestMap: Map<string, Uint8Array>,
|
||||||
shaped?: ShapedClipboardText | null
|
shaped?: ShapedClipboardText | null,
|
||||||
|
blobs?: Uint8Array[]
|
||||||
): Promise<NodeChange['derivedTextData']> {
|
): Promise<NodeChange['derivedTextData']> {
|
||||||
const style = weightToStyle(node.fontWeight, node.italic)
|
const style = weightToStyle(node.fontWeight, node.italic)
|
||||||
const normalizedFamily = normalizeFontFamily(node.fontFamily)
|
const normalizedFamily = normalizeFontFamily(node.fontFamily)
|
||||||
|
|
@ -35,8 +36,9 @@ export async function buildDerivedTextDataV4(
|
||||||
const shapedGlyph = shaped?.glyphs[index]
|
const shapedGlyph = shaped?.glyphs[index]
|
||||||
const fallbackX = glyph.x || index * fallbackAdvance
|
const fallbackX = glyph.x || index * fallbackAdvance
|
||||||
const fallbackGlyphAdvance = glyph.advance || fallbackAdvance
|
const fallbackGlyphAdvance = glyph.advance || fallbackAdvance
|
||||||
|
const commandsBlob = blobs ? blobs.push(glyph.commandsBlob) - 1 : undefined
|
||||||
return {
|
return {
|
||||||
commands: glyph.commands,
|
commandsBlob,
|
||||||
position: {
|
position: {
|
||||||
x: shapedGlyph?.x ?? fallbackX,
|
x: shapedGlyph?.x ?? fallbackX,
|
||||||
y: shapedGlyph?.y ?? shaped?.baseline ?? lineHeightFallback
|
y: shapedGlyph?.y ?? shaped?.baseline ?? lineHeightFallback
|
||||||
|
|
|
||||||
|
|
@ -80,26 +80,60 @@ export function measureTextWithOpenType(
|
||||||
return { width: Math.ceil(singleLineWidth), height: lineH }
|
return { width: Math.ceil(singleLineWidth), height: lineH }
|
||||||
}
|
}
|
||||||
|
|
||||||
function commandsToFigmaNumbers(commands: OutlineCommand[]): Array<string | number> {
|
|
||||||
const result: Array<string | number> = []
|
|
||||||
for (const command of commands) {
|
|
||||||
result.push(command.type)
|
|
||||||
if (command.x1 !== undefined) result.push(command.x1)
|
|
||||||
if (command.y1 !== undefined) result.push(command.y1)
|
|
||||||
if (command.x2 !== undefined) result.push(command.x2)
|
|
||||||
if (command.y2 !== undefined) result.push(command.y2)
|
|
||||||
if (command.x !== undefined) result.push(command.x)
|
|
||||||
if (command.y !== undefined) result.push(command.y)
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface GlyphOutlineMetrics {
|
export interface GlyphOutlineMetrics {
|
||||||
commands: Array<string | number>
|
commandsBlob: Uint8Array
|
||||||
x: number
|
x: number
|
||||||
advance: number
|
advance: number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CMD_CLOSE = 0
|
||||||
|
const CMD_MOVE_TO = 1
|
||||||
|
const CMD_LINE_TO = 2
|
||||||
|
const CMD_CUBIC_TO = 4
|
||||||
|
|
||||||
|
function commandsToBlob(commands: OutlineCommand[], fontSize: number): Uint8Array {
|
||||||
|
const bytes: number[] = []
|
||||||
|
const pushFloat = (value: number | undefined) => {
|
||||||
|
const buf = new ArrayBuffer(4)
|
||||||
|
new DataView(buf).setFloat32(0, (value ?? 0) / fontSize, true)
|
||||||
|
bytes.push(...new Uint8Array(buf))
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const command of commands) {
|
||||||
|
switch (command.type) {
|
||||||
|
case 'M':
|
||||||
|
bytes.push(CMD_MOVE_TO)
|
||||||
|
pushFloat(command.x)
|
||||||
|
pushFloat(command.y)
|
||||||
|
break
|
||||||
|
case 'L':
|
||||||
|
bytes.push(CMD_LINE_TO)
|
||||||
|
pushFloat(command.x)
|
||||||
|
pushFloat(command.y)
|
||||||
|
break
|
||||||
|
case 'C':
|
||||||
|
bytes.push(CMD_CUBIC_TO)
|
||||||
|
pushFloat(command.x1)
|
||||||
|
pushFloat(command.y1)
|
||||||
|
pushFloat(command.x2)
|
||||||
|
pushFloat(command.y2)
|
||||||
|
pushFloat(command.x)
|
||||||
|
pushFloat(command.y)
|
||||||
|
break
|
||||||
|
case 'Q':
|
||||||
|
bytes.push(CMD_LINE_TO)
|
||||||
|
pushFloat(command.x)
|
||||||
|
pushFloat(command.y)
|
||||||
|
break
|
||||||
|
case 'Z':
|
||||||
|
bytes.push(CMD_CLOSE)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Uint8Array(bytes)
|
||||||
|
}
|
||||||
|
|
||||||
export function getGlyphOutlineMetricsSync(
|
export function getGlyphOutlineMetricsSync(
|
||||||
family: string,
|
family: string,
|
||||||
style: string,
|
style: string,
|
||||||
|
|
@ -113,23 +147,14 @@ export function getGlyphOutlineMetricsSync(
|
||||||
let x = 0
|
let x = 0
|
||||||
const scale = fontSize / font.unitsPerEm
|
const scale = fontSize / font.unitsPerEm
|
||||||
return glyphs.map((glyph) => {
|
return glyphs.map((glyph) => {
|
||||||
const commands = commandsToFigmaNumbers(glyph.getPath(0, 0, fontSize).commands)
|
const commandsBlob = commandsToBlob(glyph.getPath(0, 0, fontSize).commands, fontSize)
|
||||||
const advance = (glyph.advanceWidth ?? 0) * scale
|
const advance = (glyph.advanceWidth ?? 0) * scale
|
||||||
const metrics = { commands, x, advance }
|
const metrics = { commandsBlob, x, advance }
|
||||||
x += advance
|
x += advance
|
||||||
return metrics
|
return metrics
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getGlyphOutlineCommandsSync(
|
|
||||||
family: string,
|
|
||||||
style: string,
|
|
||||||
text: string,
|
|
||||||
fontSize: number
|
|
||||||
): Array<Array<string | number>> | null {
|
|
||||||
return getGlyphOutlineMetricsSync(family, style, text, fontSize)?.map((glyph) => glyph.commands) ?? null
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function probeGlyphOutlineCommands(
|
export async function probeGlyphOutlineCommands(
|
||||||
family: string,
|
family: string,
|
||||||
style: string,
|
style: string,
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ describe('clipboard derived text export', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const fontDigestMap = await buildFontDigestMap(graph)
|
const fontDigestMap = await buildFontDigestMap(graph)
|
||||||
|
const blobs: Uint8Array[] = []
|
||||||
const derived = await buildDerivedTextDataV4(text, fontDigestMap, {
|
const derived = await buildDerivedTextDataV4(text, fontDigestMap, {
|
||||||
lineHeight: 20,
|
lineHeight: 20,
|
||||||
lineAscent: 15,
|
lineAscent: 15,
|
||||||
|
|
@ -46,7 +47,7 @@ describe('clipboard derived text export', () => {
|
||||||
{ firstCharacter: 4, x: 32, y: 16, advance: 10 }
|
{ firstCharacter: 4, x: 32, y: 16, advance: 10 }
|
||||||
],
|
],
|
||||||
logicalIndexToCharacterOffsetMap: [0, 8, 16, 24, 32, 42]
|
logicalIndexToCharacterOffsetMap: [0, 8, 16, 24, 32, 42]
|
||||||
})
|
}, blobs)
|
||||||
|
|
||||||
const derivedTextData = expectDefined(derived, 'derived text data')
|
const derivedTextData = expectDefined(derived, 'derived text data')
|
||||||
const firstGlyph = expectDefined(derivedTextData.glyphs[0], 'first glyph')
|
const firstGlyph = expectDefined(derivedTextData.glyphs[0], 'first glyph')
|
||||||
|
|
@ -63,7 +64,8 @@ describe('clipboard derived text export', () => {
|
||||||
expect(line.directionality).toBe('LTR')
|
expect(line.directionality).toBe('LTR')
|
||||||
expect(derivedTextData.truncationStartIndex).toBe(-1)
|
expect(derivedTextData.truncationStartIndex).toBe(-1)
|
||||||
expect(derivedTextData.truncatedHeight).toBe(-1)
|
expect(derivedTextData.truncatedHeight).toBe(-1)
|
||||||
expect(firstGlyph.commands.length).toBeGreaterThan(0)
|
expect(firstGlyph.commandsBlob).toBe(0)
|
||||||
|
expect(blobs[0].length).toBeGreaterThan(0)
|
||||||
expect(firstGlyph.position.x).toBe(0)
|
expect(firstGlyph.position.x).toBe(0)
|
||||||
expect(lastGlyph.position.x).toBe(32)
|
expect(lastGlyph.position.x).toBe(32)
|
||||||
expect(baseline.lineHeight).toBe(20)
|
expect(baseline.lineHeight).toBe(20)
|
||||||
|
|
@ -90,9 +92,12 @@ describe('clipboard derived text export', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
const fontDigestMap = await buildFontDigestMap(graph)
|
const fontDigestMap = await buildFontDigestMap(graph)
|
||||||
const derived = expectDefined(await buildDerivedTextDataV4(text, fontDigestMap), 'derived text')
|
const blobs: Uint8Array[] = []
|
||||||
|
const derived = expectDefined(await buildDerivedTextDataV4(text, fontDigestMap, null, blobs), 'derived text')
|
||||||
|
|
||||||
expect(derived.fontMetaData[0].key.style).toBe('Semi Bold')
|
expect(derived.fontMetaData[0].key.style).toBe('Semi Bold')
|
||||||
|
expect(derived.glyphs[0].commandsBlob).toBe(0)
|
||||||
|
expect(blobs[0].length).toBeGreaterThan(0)
|
||||||
expect(derived.glyphs[1].position.x).toBeGreaterThan(0)
|
expect(derived.glyphs[1].position.x).toBeGreaterThan(0)
|
||||||
expect(derived.glyphs[1].advance).toBeGreaterThan(0)
|
expect(derived.glyphs[1].advance).toBeGreaterThan(0)
|
||||||
expect(derived.logicalIndexToCharacterOffsetMap[1]).toBeGreaterThan(0)
|
expect(derived.logicalIndexToCharacterOffsetMap[1]).toBeGreaterThan(0)
|
||||||
|
|
|
||||||
|
|
@ -158,6 +158,49 @@ describe('text node export', () => {
|
||||||
expect((fontMetaData[0].key as Record<string, unknown>).style).toBe('Semi Bold')
|
expect((fontMetaData[0].key as Record<string, unknown>).style).toBe('Semi Bold')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('auto-layout text children export height auto-resize for Figma rendering', async () => {
|
||||||
|
await initCodec()
|
||||||
|
|
||||||
|
const { unzipSync, inflateSync } = await import('fflate')
|
||||||
|
const { decodeBinarySchema, compileSchema, ByteBuffer } = await import('#core/kiwi/kiwi-schema')
|
||||||
|
const { parseFigKiwiChunks } = await import('@open-pencil/core')
|
||||||
|
|
||||||
|
const graph = new SceneGraph()
|
||||||
|
const page = graph.getPages()[0]
|
||||||
|
const frame = graph.createNode('FRAME', page.id, {
|
||||||
|
name: 'Card',
|
||||||
|
layoutMode: 'VERTICAL',
|
||||||
|
width: 280,
|
||||||
|
height: 160
|
||||||
|
})
|
||||||
|
graph.createNode('TEXT', frame.id, {
|
||||||
|
name: 'Body',
|
||||||
|
text: 'Track your key metrics and performance indicators in real time.',
|
||||||
|
width: 240,
|
||||||
|
height: 36,
|
||||||
|
fontFamily: 'Inter',
|
||||||
|
fontSize: 13,
|
||||||
|
fontWeight: 400,
|
||||||
|
textAutoResize: 'NONE'
|
||||||
|
})
|
||||||
|
|
||||||
|
const exported = await exportFigFile(graph)
|
||||||
|
const zip = unzipSync(new Uint8Array(exported))
|
||||||
|
const chunks = parseFigKiwiChunks(zip['canvas.fig'] ?? zip['canvas'])
|
||||||
|
const schemaBytes = inflateSync(chunks?.[0] ?? new Uint8Array())
|
||||||
|
const schema = decodeBinarySchema(new ByteBuffer(schemaBytes))
|
||||||
|
const compiled = compileSchema(schema) as {
|
||||||
|
decodeMessage(data: Uint8Array): Record<string, unknown>
|
||||||
|
}
|
||||||
|
const message = compiled.decodeMessage(inflateSync(chunks?.[1] ?? new Uint8Array()))
|
||||||
|
const nodeChanges = message.nodeChanges as Array<Record<string, unknown>>
|
||||||
|
const textNc = expectDefined(nodeChanges.find((nc) => nc.type === 'TEXT'), 'text node change')
|
||||||
|
|
||||||
|
expect(textNc.textAutoResize).toBe('HEIGHT')
|
||||||
|
expect(textNc.lineHeight).toBeUndefined()
|
||||||
|
expect(textNc.stackChildAlignSelf).toBeUndefined()
|
||||||
|
})
|
||||||
|
|
||||||
test('style runs produce multiple fontMetaData entries', async () => {
|
test('style runs produce multiple fontMetaData entries', async () => {
|
||||||
await initCodec()
|
await initCodec()
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue