interface FigGuid { sessionID: number; localID: number; } interface FigColor { r: number; g: number; b: number; a: number; } interface FigVector { x: number; y: number; } interface FigTransform { m00: number; m01: number; m02: number; m10: number; m11: number; m12: number; } interface FigGradientStop { color: FigColor; position: number; colorVar?: any; } interface FigPaint { type: string; color?: FigColor; opacity?: number; visible?: boolean; blendMode?: string; stops?: FigGradientStop[]; stopsVar?: FigGradientStop[]; transform?: FigTransform; image?: { hash?: Uint8Array | string; [key: string]: any; }; imageThumbnail?: { hash?: Uint8Array | string; [key: string]: any; }; } /** * EffectType enum values from the Kiwi schema. * * | Value | Name | * |-------|-------------------| * | 0 | INNER_SHADOW | * | 1 | DROP_SHADOW | * | 2 | FOREGROUND_BLUR | * | 3 | BACKGROUND_BLUR | * | 4 | REPEAT | * | 5 | SYMMETRY | * | 6 | GRAIN | * | 7 | NOISE | * | 8 | GLASS | */ type FigEffectType = "INNER_SHADOW" | "DROP_SHADOW" | "FOREGROUND_BLUR" | "BACKGROUND_BLUR" | "REPEAT" | "SYMMETRY" | "GRAIN" | "NOISE" | "GLASS"; /** * An effect applied to a node (shadow, blur, etc.). * Maps to the Effect message in the Kiwi schema (42 fields). * Only the most commonly used fields are typed here; the rest * pass through via the catch-all index signature. */ interface FigEffect { type: FigEffectType; color?: FigColor; offset?: FigVector; /** Blur radius. */ radius?: number; /** Spread distance (shadows only). */ spread?: number; visible?: boolean; blendMode?: string; /** When true the shadow renders behind the node (not clipped by it). */ showShadowBehindNode?: boolean; /** Effect-level opacity (separate from color.a). */ opacity?: number; /** Allow additional/future fields from the kiwi schema. */ [key: string]: any; } /** StrokeAlign enum: CENTER (0), INSIDE (1), OUTSIDE (2). */ type FigStrokeAlign = "CENTER" | "INSIDE" | "OUTSIDE"; /** StackMode enum: NONE (0), HORIZONTAL (1), VERTICAL (2), GRID (3). */ type FigStackMode = "NONE" | "HORIZONTAL" | "VERTICAL" | "GRID"; interface FigFontName { family?: string; style?: string; postScriptName?: string; } /** TextAlignHorizontal: LEFT (0), CENTER (1), RIGHT (2), JUSTIFIED (3). */ type FigTextAlign = "LEFT" | "CENTER" | "RIGHT" | "JUSTIFIED"; interface FigNode { guid: FigGuid; type: string; name: string; phase?: string; parentIndex?: { guid: FigGuid; position: string; }; size?: FigVector; transform?: FigTransform; fillPaints?: FigPaint[]; strokePaints?: FigPaint[]; strokeWeight?: number; strokeAlign?: FigStrokeAlign; cornerRadius?: number; opacity?: number; effects?: FigEffect[]; textData?: { characters: string; lines?: any[]; }; fontSize?: number; fontName?: FigFontName; textAlignHorizontal?: FigTextAlign; paragraphSpacing?: number; derivedTextData?: any; shapeWithTextType?: string; /** True for group-like frames (Figma encodes groups as FRAME + resizeToFit). */ resizeToFit?: boolean; /** Frame clips children when frameMaskDisabled is false (default). */ frameMaskDisabled?: boolean; /** Auto-layout mode: HORIZONTAL, VERTICAL, GRID, or absent. */ stackMode?: FigStackMode; stackPrimarySizing?: string; stackCounterSizing?: string; /** Allow additional fields from the kiwi schema. */ [key: string]: any; } interface FigDocument { header: { prelude: string; version: number; }; nodes: FigNode[]; nodeMap: Map; childrenMap: Map; /** Decoded kiwi binary schema (needed for re-encoding). */ schema: any; /** Compiled kiwi schema with encodeMessage/decodeMessage (needed for re-encoding). */ compiledSchema: any; /** Raw length-prefixed chunks from the binary (chunks[2+] are passed through on re-encode). */ rawChunks: Uint8Array[]; /** Full decoded kiwi message (contains nodeChanges, blobs, etc. — needed for re-encoding). */ message: any; meta?: Record; thumbnail?: Uint8Array; images: Map; } /** * Isomorphic .fig binary parser. * * .fig files are ZIP archives containing: * - canvas.fig (binary: prelude + version + kiwi-encoded chunks) * - meta.json (optional) * - thumbnail.png (optional) * - images/ (optional) * * Parsing flow: * 1. Unzip → extract canvas.fig * 2. Read 8-byte prelude + 4-byte version * 3. Chunk 0: deflateRaw → kiwi binary schema * 4. Chunk 1: zstd or deflateRaw → kiwi message (nodeChanges[]) * 5. Build node maps */ /** * Parse raw canvas.fig binary data (the blob inside the ZIP). * Use this if you extract the ZIP yourself. */ declare function parseFigBinary(data: Uint8Array): FigDocument; /** * Parse a complete .fig file (ZIP archive). * Extracts canvas.fig, meta.json, thumbnail.png, and images/*. */ declare function parseFig(data: Uint8Array): FigDocument; /** * .fig file encoder — the write side of the roundtrip. * * Encodes a FigDocument back to .fig binary format. * Zstd compression of chunk 1 (message) is NOT included — the caller * provides pre-compressed bytes. This keeps openfig-core isomorphic * (no WASM dependency). * * Encoding flow: * 1. compiledSchema.encodeMessage(message) → kiwi binary * 2. encodeBinarySchema(schema) + deflateSync → compressed chunk 0 * 3. Caller zstd-compresses the message → compressed chunk 1 * 4. assembleCanvasFig() builds the binary * 5. createFigZip() packages into ZIP */ interface EncodedFigParts { /** deflateRaw-compressed kiwi schema (ready for chunk 0) */ schemaCompressed: Uint8Array; /** Raw kiwi-encoded message — caller MUST zstd-compress this for chunk 1 */ messageRaw: Uint8Array; /** Original prelude string (e.g., "fig-kiwi") */ prelude: string; /** Original version number */ version: number; /** Passthrough chunks (rawChunks[2+]) — included as-is */ passThrough: Uint8Array[]; } interface AssembleCanvasFigInput { prelude: string; version: number; schemaCompressed: Uint8Array; messageCompressed: Uint8Array; passThrough?: Uint8Array[]; } interface CreateFigZipInput { canvasFig: Uint8Array; meta?: Record; thumbnail?: Uint8Array; images?: Map; } /** * Encode a FigDocument into parts ready for assembly. * The message is returned as raw kiwi bytes — caller must zstd-compress it. */ declare function encodeFigParts(doc: FigDocument): EncodedFigParts; /** * Assemble a canvas.fig binary from pre-compressed chunks. * * Format: [prelude 8B][version uint32 LE][len uint32 LE][chunk0][len][chunk1][len][chunk2+]... */ declare function assembleCanvasFig(input: AssembleCanvasFigInput): Uint8Array; /** * Create a .fig/.deck ZIP archive from canvas.fig + optional metadata. * Uses store mode (no compression) via fflate. */ declare function createFigZip(input: CreateFigZipInput): Uint8Array; interface ConvertOptions { title?: string; layout?: "row" | "grid"; gap?: number; wrap?: number; } declare function convertDeckToFig(deckDoc: FigDocument, options?: ConvertOptions): FigDocument; /** * Creates an empty FigDocument by parsing a pre-built template. * The template was created from a valid .fig file with user content * marked as REMOVED — proven to open in Figma. */ declare function createEmptyFigDoc(): FigDocument; /** * Returns the string ID for a node ("sessionID:localID"), or null if no guid. */ declare function nodeId(node: FigNode): string | null; type GradientKind = "linear" | "radial"; interface GradientFillLike { type: GradientKind; transform: FigTransform; } interface RenderableGradientFill extends GradientFillLike { opacity: number; stops: FigGradientStop[]; } interface GradientPoint { x: number; y: number; } interface ResolvedLinearGradientGeometry { type: "linear"; start: GradientPoint; end: GradientPoint; } interface ResolvedRadialGradientGeometry { type: "radial"; center: GradientPoint; radiusX: number; radiusY: number; angle: number; } type ResolvedGradientGeometry = ResolvedLinearGradientGeometry | ResolvedRadialGradientGeometry; declare function extractRenderableGradientFill(paints: FigPaint[] | null | undefined): RenderableGradientFill | null; declare function resolveGradientGeometry(fill: GradientFillLike, width: number, height: number): ResolvedGradientGeometry | null; type GeometryRef = { commandsBlob?: number; windingRule?: string; styleID?: number; }; interface ResolvedGeometryPath { blobIndex: number; commandsBlob: Uint8Array; svgPath: string; windingRule?: string; styleID: number; paints?: FigPaint[]; } interface ResolvedVectorNodePaths { fill: ResolvedGeometryPath[]; stroke: ResolvedGeometryPath[]; } type VectorPathCommand = { type: "M"; x: number; y: number; } | { type: "L"; x: number; y: number; } | { type: "C"; c1x: number; c1y: number; c2x: number; c2y: number; x: number; y: number; } | { type: "Z"; }; interface VectorGeometryInput { svgPath?: string; commands?: readonly VectorPathCommand[]; windingRule?: string; styleID?: number; } interface VectorStyleOverride { styleID: number; fillPaints?: FigPaint[]; [key: string]: any; } interface AppendVectorPayloadInput { width: number; height: number; normalizedWidth?: number; normalizedHeight?: number; fillPaths?: readonly VectorGeometryInput[]; /** * Stroke geometry is expected to already be expanded into outline paths. * This helper does not expand SVG strokes into strokeGeometry. */ strokePaths?: readonly VectorGeometryInput[]; styleOverrideTable?: readonly VectorStyleOverride[]; } interface AuthoredVectorPayload { fillGeometry: GeometryRef[]; strokeGeometry: GeometryRef[]; vectorData: { vectorNetworkBlob: number; normalizedSize: { x: number; y: number; }; styleOverrideTable?: VectorStyleOverride[]; }; } declare function getBlobBytes(doc: FigDocument, blobIndex: number | null | undefined): Uint8Array | null; declare function geometryBlobToSVGPath(blob: Uint8Array): string; declare function parseSVGPathData(svgPath: string): VectorPathCommand[]; declare function encodeCommandsBlob(commands: readonly VectorPathCommand[], scaleX?: number, scaleY?: number): Uint8Array; /** * Build a vector network from SVG-style path commands and encode it. * * Each entry in `pathCommandsList` becomes **one region**, and each `M…Z` * sub-path within it becomes a **loop** of that region. That grouping is what * Figma writes: reference blobs carry regions of 1, 2 and 3 loops — a letter's * counter, or the inner and outer ring of an outline-stroked shape, are loops of * a single region rather than separate regions. Emitting one region per sub-path * makes Figma treat each as its own filled area, so counters fill in instead of * punching through. * * @param pathCommandsList one entry per path; each becomes a region * @param emitRegions set false for open, stroked paths. A region asks Figma * to fill the area bounded by the loop, which on an open * path closes it visually — a "lens" between the * endpoints — even with no fill paint set. */ declare function encodeVectorNetworkBlob(pathCommandsList: readonly (readonly VectorPathCommand[])[], { emitRegions }?: { emitRegions?: boolean; }): Uint8Array; interface VectorNetworkVertex { x: number; y: number; /** * The vertex's leading u32 — an index into the node's * `vectorData.styleOverrideTable`, where 0 means "no override". Observed * values are 0 and 1 across the reference corpus (openfig once wrote 4 here, * which Figma never emits). * * This was previously named `handleMirroring`, because the one fixture with a * non-zero value has a single override entry `{styleID: 1, handleMirroring: * "ANGLE"}` — and `VectorMirror.ANGLE` is also 1, so the two readings were * indistinguishable from that file alone. Other Figma files carry override * entries with six properties (cornerRadius, strokeCap, strokeJoin, * handleMirroring, cornerSmoothing), which cannot be encoded in one u32 — only * an index can reference them — and carry styleIDs 1 and 2 in sequence. * * It does not affect rendered geometry, but it is preserved verbatim so a * decoded blob re-encodes byte-identically. Authoring a non-zero value without * a matching `styleOverrideTable` entry produces a dangling reference. */ styleID: number; } interface VectorNetworkSegment { start: { vertex: number; dx: number; dy: number; }; end: { vertex: number; dx: number; dy: number; }; isStraight: boolean; } interface VectorNetworkRegion { windingRule: "NONZERO" | "ODD"; styleID: number; /** Each loop is an ordered list of segment indices. */ loops: number[][]; } interface VectorNetwork { vertices: VectorNetworkVertex[]; segments: VectorNetworkSegment[]; regions: VectorNetworkRegion[]; /** Must equal the input length on success. */ bytesConsumed: number; } /** * Decode a Figma `vectorNetworkBlob` into structured geometry. * * Verified byte-exact layout (little-endian): * header 12B : [vertexCount u32][segmentCount u32][regionCount u32] * vertex 12B : [styleID u32][x f32][y f32] * segment 28B : [word0 u32][startVertex u32][tsx f32][tsy f32] * [endVertex u32][tex f32][tey f32] * region : [packed u32][numLoops u32] * per loop: [segCount u32][segIndex u32 × segCount] * * `packed` decodes as windingRule = (packed & 1) ? "NONZERO" : "ODD", * styleID = packed >> 1. * * The vertex's leading word is Figma's handle-mirroring mode (observed 0 and 1); * it is preserved verbatim for byte-identical re-encoding. The segment's leading * word is 0 throughout the reference corpus and its meaning is unknown — it is * never used. A segment is straight iff all four tangent components are zero; * there is no segment-type field. */ declare function parseVectorNetworkBlob(bytes: Uint8Array): VectorNetwork; /** The geometry `encodeVectorNetwork` needs — a parsed network minus `bytesConsumed`. */ type VectorNetworkInput = Pick; /** * Encode structured vector-network geometry into a Figma `vectorNetworkBlob`. * * Emits the exact layout `parseVectorNetworkBlob` reads (which see for the field * table): 12-byte header; vertex `[styleID, x, y]`; segment `[word0, * startVertex, tsx, tsy, endVertex, tex, tey]`; region `[styleID<<1|windingRule, * numLoops, (segCount, indices)×numLoops]`. The vertex handle-mirroring word is * written back as parsed; the segment word0 is written as 0, the only value * observed in Figma-authored output. A Figma-authored blob decoded and re-encoded * here comes back byte-for-byte identical — the acceptance criterion verified by * the corpus round-trip test. */ declare function encodeVectorNetwork(network: VectorNetworkInput): Uint8Array; declare function appendVectorPayloadToDocument(doc: FigDocument, input: AppendVectorPayloadInput): AuthoredVectorPayload; declare function resolveVectorNodePaths(doc: FigDocument, node: FigNode): ResolvedVectorNodePaths; /** * CSS / hex color ↔ Figma normalized RGBA color helpers. * * All functions are isomorphic (no DOM required). * For named CSS colors (e.g. "coral"), pass an optional `resolveNamed` * callback that uses the browser's computed-style machinery. */ declare function hexToFigColor(hex: string): FigColor; declare function parseCssRgbColor(value: string): FigColor | null; declare function cssColorToFigColor(value: string, resolveNamed?: (name: string) => FigColor | null): FigColor; declare function makeSolidPaint(fill: string, resolveNamed?: (name: string) => FigColor | null): FigPaint; /** * SVG path serialization, transformation, and stroke/cap enum mapping. */ declare function serializeSvgPathData(commands: readonly VectorPathCommand[]): string; declare function transformSvgPathData(svgPath: string, { scaleX, scaleY, translateX, translateY, }: { scaleX?: number; scaleY?: number; translateX?: number; translateY?: number; }): string; declare function mapStrokeJoin(value: string | undefined): string; declare function mapStrokeCap(value: string | undefined): string; export { type AppendVectorPayloadInput, type AssembleCanvasFigInput, type AuthoredVectorPayload, type ConvertOptions, type CreateFigZipInput, type EncodedFigParts, type FigColor, type FigDocument, type FigGradientStop, type FigGuid, type FigNode, type FigPaint, type FigTransform, type GradientFillLike, type GradientKind, type GradientPoint, type RenderableGradientFill, type ResolvedGeometryPath, type ResolvedGradientGeometry, type ResolvedLinearGradientGeometry, type ResolvedRadialGradientGeometry, type ResolvedVectorNodePaths, type VectorGeometryInput, type VectorNetwork, type VectorNetworkInput, type VectorNetworkRegion, type VectorNetworkSegment, type VectorNetworkVertex, type VectorPathCommand, type VectorStyleOverride, appendVectorPayloadToDocument, assembleCanvasFig, convertDeckToFig, createEmptyFigDoc, createFigZip, cssColorToFigColor, encodeCommandsBlob, encodeFigParts, encodeVectorNetwork, encodeVectorNetworkBlob, extractRenderableGradientFill, geometryBlobToSVGPath, getBlobBytes, hexToFigColor, makeSolidPaint, mapStrokeCap, mapStrokeJoin, nodeId, parseCssRgbColor, parseFig, parseFigBinary, parseSVGPathData, parseVectorNetworkBlob, resolveGradientGeometry, resolveVectorNodePaths, serializeSvgPathData, transformSvgPathData };