SVG I/O — Rust SVG Import Pipeline
Crate: crates/grida/src/import/svg/
When to Use This Skill
- Adding support for new SVG elements or attributes
- Fixing SVG-to-Grida conversion bugs (wrong transform, wrong paint, etc.)
- Authoring new SVG test fixtures in
fixtures/test-svg/L0/ - Debugging cross-boundary codec failures (Rust encodes ≠ TS decodes)
- Understanding what SVG features are supported vs. dropped
- Investigating text import fidelity
Architecture
.svg bytes
→ usvg::Tree::from_data() — parse + resolve (third_party/usvg/)
→ packed_scene::* — usvg::Tree → Grida scene graph
→ pack::* — pack nodes into IPackedSceneDocument
→ io::archive::pack() — produce .grida ZIP
Key design: SVG import is Rust-only. There is no TypeScript path for SVG→Grida.
Cross-boundary status: the TS-side codec test (fbs-svg-cross-boundary.test.ts,
grida repo) was retired at the engine split — it reached across the repo seam
and consumed Rust-generated artifacts from this repo's gitignored
fixtures/test-svg/.generated/. Verification is now Rust-side (cargo test -p grida
round-trips). If TS-decode coverage is ever wanted again, it must be re-plumbed
deliberately (publishable artifact or in-repo wasm-decode test), not revived as-was.
Key Files
| Path | Role |
|---|---|
crates/grida/src/import/svg/packed_scene.rs |
Core conversion: usvg nodes → Grida nodes |
crates/grida/src/import/svg/pack.rs |
Packs converted nodes into scene document |
crates/grida/src/import/svg/from_usvg.rs |
High-level entry: bytes → scene |
crates/grida/src/formats/svg/sanitize.rs |
Pre-processing / sanitization |
crates/grida_dev/src/main.rs |
svg-to-grida subcommand |
fixtures/test-svg/L0/ |
Committed SVG fixtures |
fixtures/test-svg/.generated/ |
Gitignored, generated .grida outputs |
Common Tasks
Orient before touching code
- Read
crates/grida/AGENTS.mdfor crate conventions and commands. - Read
docs/wg/feat-svg/text-import.mdbefore touching text conversion — the text model is intentionally limited and the design is documented there. - Grep for the relevant element in
packed_scene.rs.
Add support for a new SVG element or attribute
- Find where usvg exposes the element in
third_party/usvg/src/. - Add the mapping in
packed_scene.rs(the mainconvert_*functions). - Add a minimal SVG fixture to
fixtures/test-svg/L0/that exercises the feature. - Verify the round-trip with the Rust tests (the TS cross-boundary cycle is retired — see above).
Generate .grida outputs from fixtures
cargo run -p grida_dev -- svg-to-grida fixtures/test-svg/L0
Outputs land in fixtures/test-svg/.generated/ (gitignored).
For custom SVG files:
cargo run -p grida_dev -- svg-to-grida path/to/svgs -r
Run SVG reftests
# W3C SVG test suite (requires separate download — see docs/wg/feat-svg/testing.md)
cargo run -p grida_dev --release -- reftest path/to/w3c-suite/
# resvg test suite
cargo run -p grida_dev --release -- reftest path/to/resvg-test-suite/
See crates/grida_dev/TESTING.md for full reftest flags.
Rust tests for SVG
cargo test -p grida
cargo test -p grida svg # filter to SVG tests only
SVG Feature Coverage
Fully supported
- Basic shapes:
<rect>,<circle>,<ellipse>,<line>,<polyline>,<polygon>,<path> - Groups
<g>with transforms - Fills and strokes (solid color, linear gradient, radial gradient)
- Clip paths and masks
- Filters: drop-shadow, blur, color-matrix, lighting, compositing primitives
- Opacity and blend modes
<use>/<defs>(resolved by usvg)- Text: one Grida
TextSpanNodeRecper usvgTextChunk(see text model below) - Stroke dash arrays
- Nested transforms
Text model (important)
SVG text is chunk-based: <text> → GroupNodeRec, each TextChunk → TextSpanNodeRec.
What is lost: inline style variation within a line, per-character x/y lists, baseline-shift, text-decoration per span, text-on-path.
This matches Figma's SVG import fidelity. See docs/wg/feat-svg/text-import.md for full details before changing text conversion.
Known gaps / unsupported
<pattern>fill (partially tracked indocs/wg/feat-svg/pattern.md)<textPath>(text on path)- Animations (
<animate>, SMIL) - CSS stylesheets inside SVG
Fixture Conventions
- Committed fixtures live in
fixtures/test-svg/L0/— one file per feature. - Naming:
<feature>.svg(e.g.stroke-dasharray.svg,transforms-nested.svg). - Keep fixtures minimal — isolate one feature per file.
- Generated outputs in
.generated/are gitignored; regenerate on demand withsvg-to-grida.
Codec-Bug Test Conventions
When fixing a codec bug in the SVG→.grida path:
- Add a targeted Rust assertion — one assertion per bug, on the specific field that was wrong.
- Do NOT use snapshot comparisons for codec bugs.
- If the feature is a known limitation (e.g. scale/skew in transforms), gate the test with an explanatory comment.
Verification After Changes
cargo check -p grida --all-targets
cargo test -p grida
cargo run -p grida_dev -- svg-to-grida fixtures/test-svg/L0