3.8 KiB
| title | description |
|---|---|
| File Format | Technical reference for OpenPencil .fig and .pen document formats, Kiwi binary structure, import pipeline, and export behavior. |
File Format
::: warning Figma round-trip limitations Complex Figma documents can lose visual fidelity when saved by OpenPencil and reopened in Figma, even without edits. Component content, colors, and text may differ or appear missing. This is an existing compatibility limitation; preserving the layer count does not guarantee the same appearance.
Keep the original .fig, use Save As for an OpenPencil copy, and inspect the exported copy in Figma before replacing your original.
:::
.fig File Structure
A .fig file is a ZIP archive containing a Kiwi-encoded binary message:
| Offset | Content |
|---|---|
| 0 | Magic header fig-kiwi (8 bytes) |
| 8 | Version (4 bytes, uint32 LE) |
| 12 | Schema length (4 bytes, uint32 LE) |
| 16 | Compressed Kiwi schema |
| … | Message length (4 bytes, uint32 LE) |
| … | Compressed Kiwi message — NodeChange[] (entire document) |
| … | Blob data — images, vector networks, fonts |
Import Pipeline
.fig file → parse header → decompress Zstd → decode Kiwi schema
→ decode message → NodeChange[] → build SceneGraph
→ resolve blob refs → render on canvas
Export Pipeline
SceneGraph → NodeChange[] → Kiwi encode → compress (Zstd/deflate)
→ build ZIP (header + schema + message + thumbnail.png)
→ write .fig file
Export uses ⌘S (Save) and ⇧⌘S (Save As) with native OS dialogs on the desktop app. The exported file includes a thumbnail.png required by Figma for file preview. OpenPencil uses the first page named exactly Cover (case-insensitive), then the first page whose name contains Cover. The desktop app also caches this Cover after opening a file so Recent Files can prefer the generated preview without modifying the source .fig.
Compression uses Zstd via Tauri Rust command on desktop, with deflate fallback in the browser.
Kiwi Binary Codec
The codec handles Figma's 194-definition Kiwi schema with NodeChange as the central type (~390 fields). Key components:
| Module | Purpose |
|---|---|
kiwi-schema |
Kiwi parser (from evanw/kiwi), patched for ESM and sparse field IDs |
codec.ts |
Encode/decode messages using the Kiwi schema |
protocol.ts |
Wire format parsing and message type detection |
schema.ts |
194 message/enum/struct definitions |
Sparse Field IDs
Figma's schema uses non-contiguous field IDs (e.g. 1, 2, 5, 10 with gaps). The kiwi-schema parser handles this correctly.
Compression
.fig files use Zstd compression for both the schema and message payloads. Decompression uses the fzstd library. For export, Zstd compression is offloaded to a Tauri Rust command on the desktop app (better performance, correct frame headers). In the browser, deflate via fflate is used as a fallback.
Supported Formats
| Format | Open / Read | Save / Write | Export |
|---|---|---|---|
.fig (Figma) |
✅ | ✅ | ✅ |
.pen (Pencil) |
✅ | — | — |
.png |
— | — | ✅ |
.jpg |
— | — | ✅ |
.webp |
— | — | ✅ |
.svg |
— | — | ✅ |
.jsx |
— | — | ✅ |
Clipboard Format
Copy/paste uses the same Kiwi binary encoding:
- Copy — encode selected
NodeChange[]to Kiwi binary, compress, write to clipboard asapplication/x-figma-designMIME type - Paste — read clipboard, decompress, decode Kiwi binary, create nodes in scene graph
Encoding happens synchronously in the copy event handler (not async Clipboard API) for browser compatibility. This enables bidirectional clipboard between OpenPencil and Figma.