* fix(vue): update instance text properties while typing Instance text property edits only reached the canvas on Enter or blur, so the canvas and layer tree lagged behind the field. Emit model updates as the text changes, commit on blur or Enter, and route bursts through the existing interactive-edit lease and undo batch so rapid edits collapse into one transaction. * fix(vue): present the canvas in sRGB to keep P3 blends correct CanvasKit 0.41 wraps sRGB on-screen surfaces as RGBA8 but every other color space as RGBA16F, while browser drawing buffers stay RGBA8 when their color space changes. Requesting DISPLAY_P3 therefore produced invalid destination copies and broken blends: black rectangles and brown Overlay fills over Display-P3 documents. Keep presentation in sRGB and read the buffer back rather than trusting the setter, leaving the document color space and its stored colors untouched. * perf(fig): encode glyph path commands without per-coordinate allocation Glyph outline encoding allocated an ArrayBuffer, DataView, and Uint8Array for every coordinate and spread each byte into a number array, so recovery snapshots and text-heavy exports blocked the main thread for 159-167ms. Size the output once and write through a single DataView; encoded bytes are unchanged. * refactor(vue): separate component property edit resolution from batching The live text path had grown a boolean flag through a single applyValue that resolved the edit, chose the batch, and mutated instances, which made the two entry points differ only by that flag. Resolve an edit once, keep a named batch key, and let setValue and setTextValue state their own batching policy. Watch the page and selection sources directly now that selection is replaced by identity, drop the redundant scene dependencies the useSceneComputed wrapper already tracks, move the variant option projection next to the swap projection, and resolve the swap candidate list once per controls pass instead of once per control. * feat(vue): restore wide-gamut P3 presentation where the renderer supports it CanvasKit wraps sRGB on-screen surfaces as RGBA_8888 and every other color space as RGBA_F16, with the pixel format deliberately not exposed, so a Display-P3 surface only matches the browser buffer when that buffer is floating point. Chromium 122+ provides drawingBufferStorage for that; this negotiates the pairing, keeps the sRGB fallback everywhere else, and warns with the existing dismissible banner when a Display-P3 document cannot be presented in wide gamut. Software rasterizers advertise the float extensions but fail an offscreen framebuffer attach on the first content frame, so they stay on sRGB, as do WebKit and Firefox, which have no drawingBufferStorage. A new document:color-space-changed event recreates the surface when a P3 document arrives after mount, which previously kept whatever surface the first document created. * refactor(web): report the canvas presentation instead of re-deriving it The wide-gamut notice decided availability from a capability probe, which can disagree with the surface: configurePresentation also falls back when the float storage install is rejected or the color space setter is ignored. Pass the surface's actual result through a new onPresentation option, mirror the document color space into app state, and let the notice read both, so it appears exactly when a Display-P3 document is really presented in sRGB. That also removes two editor-event subscriptions and a tab watcher. Rename SafariBanner to FileApiBanner, since the condition is the File System Access API rather than Safari, and move the availability check and picker call into one capability module instead of repeating them at each save site. * refactor(web): point capability notices at one neutral support reference The file API notice linked "Use Chrome" to a Chrome download page while naming Edge as inert text, and the wide-gamut notice offered no browser guidance at all. Both now link to the caniuse support table for the API that decides the capability, so the advice is vendor-neutral and stays correct as versions move. External link behavior moves into one primitive: SettingsLink and both notices share it, gaining rel="noopener noreferrer" and the desktop opener path, which the notices need because the wide-gamut notice also renders in Tauri where a raw anchor cannot open an external page. * fix(fig): stop failing .fig export on unencodable OpenType feature tags The Kiwi schema types toggledOn/OffOTFeatures as its OpenTypeFeature enum, which has no PNUM, TNUM, LNUM, ONUM, FRAC, SMCP, C2SC, SUPS, or SUBS member. Features that map to a typed axis were only written when enabled, so a disabled one fell through to a raw tag, and encoding then rejected it: `Invalid value "PNUM" for enum "OpenTypeFeature"`. Because save and recovery snapshots share that export path, any text using those features could not be written to a `.fig` file at all — the demo's own typography comparison hit it in every run. Disabled mapped tags now clear their axis to the schema's neutral NORMAL value, an enabled tag on the same axis wins over a disabled sibling so "TNUM on, PNUM off" still means tabular figures, and tags with no Kiwi representation are dropped instead of poisoning the whole export. * perf(core): recompute layout only for the pages a component edit affects Editing a component recomputed layout for the entire graph, which cost tens of milliseconds per edit in documents with several populated pages. Layout now runs once per affected page: the pages of the edited subtrees, their components, and every instance of those components, which may live on another page. The layout function is injected so the scoping contract is testable, and the existing behaviour is kept when no page can be resolved. * feat(core): follow the document colour profile when painting Numbers in a document are coordinates in the profile that document declares, so painting into a surface with a different profile has to convert them. Nothing did: stored values were handed to the GPU as-is, which is why a Display-P3 document looked more saturated on a wide-gamut display than on an sRGB one, and why export labels and stored values disagreed. Rendering now resolves each colour from the document's profile into the surface's profile, reporting clipping when a wider profile does not fit, and OKHCL colours resolve into the requested target instead of being baked to sRGB. New documents also default to sRGB, matching Figma, so Display P3 is reserved for documents that declare it rather than being assumed for everything OpenPencil creates. * test(canvas): exercise the P3 spec on the paint page The P3 rendering spec used the demo's reference page and the shared `selectDemoReferencePage` helper. The paint page covers the same ground — gradients, shadows, blurs, multiply and screen blends, an alpha mask — and the helper is going away with the reference page, so this keeps the spec independent of that demo content. The card specs now follow whichever page owns the card instead of switching by page name, which works for either demo layout. |
||
|---|---|---|
| .. | ||
| scripts | ||
| src | ||
| tests | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
| tsconfig.test.json | ||
| tsdown.config.ts | ||
@open-pencil/fig
.fig file-format package for OpenPencil.
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:
- Complete
.figarchive parsing throughparseFigBuffer() .figarchive assembly throughwriteFigArchive()- Canvas payload and image resource handling
readFigContainer()/writeFigContainer()helpers for rawfig-kiwipayloads.figsource and archive result types- NodeChange-to-SceneGraph property conversion, including styles, plugin metadata, text, paint, vector, and font policy, through
@open-pencil/fig/node-change - Component-property, symbol-override, derived-symbol-data, and instance synchronization policy through
@open-pencil/fig/instance-overrides - Effective raw-metadata precedence and invalidation over SceneGraph's format-neutral edited-field tracking
- SceneGraph-to-
NodeChangeexport conversion with an explicit glyph-outline runtime service - Package-local archive, conversion, instance, export, and dist smoke tests
Planned ownership:
- Oracle-backed
.figfixtures
Non-goals:
- 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
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
cd packages/fig
bun run check