Files
craft-libs/crates/fig/Cargo.toml
6f51ec7f8d craft-fig: map the .fig message onto the REST file JSON (#980)
* Add craft-figma-rest: a typed model of the Figma REST file JSON

The neutral model for design files: the .fig and .sketch readers map onto
it and an application's importer takes it. Typed records for the document
tree, paints, effects, text styles, layout grids, constraints, export
settings and the component and style metadata; every record keeps the
fields it has no name for, so a file read and written loses nothing.
The vectorNetwork extension is typed and documented with the others.

The layer tree is converted level by level in both directions, and the
depth and node limits are checked on the JSON before any typed value is
built.

* craft-figma-rest: Warnings and style_name for the readers that build this shape

Moved from FigCraft's REST importer and .fig reader (crates/format/src/rest.rs
Warnings, fig.rs style_name), which the .fig and Sketch mappings share.

* craft-fig: map the message onto the REST file JSON

FigCraft's .fig mapping (crates/format/src/fig/map and fig/text_path.rs
at e8b53e33) moved onto craft-figma-rest: rest::to_rest turns a decoded
message into the REST API file JSON, with the vector networks layers name
by vectorNetworkRef handed over beside it.

Connector routing and the text path measure check are the caller's
geometry (rest::Geometry; rest::Straight when it has none). The plugin
data key under which a writer keeps an outlined stroke is an option.
The geometry blob is read with craft_fig::path instead of a second
decoder.

* craft-fig: an empty overriddenSymbolID is no swap

The empty GUID (4294967295:4294967295) in overriddenSymbolID means "no
swap". The mapping took it as the main, found no node and made the
instance an empty frame; it now keeps the main its symbolData names, in
the instance mapping and in the list of mains a file's instances use.

Ported from storytold/figcraft#2208.

* craft-fig: a swapped nested instance takes its new main's look

A nested instance swapped to another main stores the previous main's
look on its own node, so the mapping drew that. Each group of the look
(fills, strokes, stroke settings, effects, corners) whose stored values
are a copy of the previous main's now takes the new main's; a group the
node or a row above it changes is kept, and a style link is decided by
the topmost layer that names it.

Ported from storytold/figcraft#2204 (the fig/map part; the warning's
locale entry stays in the app).

* craft-fig: a vector point without a corner radius of its own takes the layer's

A point whose style sets no cornerRadius, or that has no style, takes the
layer's cornerRadius, as the file draws it; a point's own value (an
explicit 0 included) still wins. The layer's radius is part of the
network cache key, so an instance overriding the radius doesn't share its
main's network.

Ported from storytold/figcraft#2115 (the fig/map part; its export change
stays in the app).

* craft-fig: map deep files within a 1 MiB stack

The mapping recursed once per level of layer nesting in a frame of about
2 KB (an unoptimised build): 300 nested frames, or a chain of instances
of mains holding each other, overflowed a 1 MiB stack before the 256
level limit could stop them, a crash on hostile input.

The work on one layer now happens in layer_head and layer_tail, which
return before and after its children are mapped, and what they hand over
is boxed, so the recursive frame is about 800 bytes. An unoptimised build
now maps past the limit on 512 KiB; the limit stays 256. The test at the
limit runs on a 1 MiB thread, for frames and for instances, and
overflowed before this change.

---------

Co-authored-by: Trevor Brown <trevorbrown.dev@gmail.com>
Co-authored-by: Giovanni Almeida <gioalmeida07@gmail.com>
2026-11-11 20:15:28 -05:00

28 lines
1.1 KiB
TOML

[package]
name = "craft-fig"
description = "Clean-room reader and writer for the .fig container (Figma Design and FigJam local copies): the ZIP archive, the chunked canvas with its kiwi schema and message, images, vector network and geometry blobs, with bounded reads."
version.workspace = true
edition.workspace = true
license.workspace = true
rust-version.workspace = true
repository.workspace = true
[lints]
workspace = true
[dependencies]
craft-figma-rest = { workspace = true }
craft-kiwi = { workspace = true }
kurbo = { workspace = true }
serde_json = { workspace = true }
thiserror = { workspace = true }
craft-zip = { workspace = true }
# The canvas chunks are deflate or zlib streams.
flate2 = { version = "1", default-features = false, features = ["rust_backend"] }
ruzstd = { version = "0.9", default-features = false, features = ["std"] }
[dev-dependencies]
# The tests build archives, hostile ones included, by hand.
zip = { version = "9", default-features = false, features = ["deflate-flate2"] }
craft-zip = { workspace = true, features = ["test-support"] }