name: io-svg description: > Guides work on SVG import into the Grida Canvas Rust engine (grida crate). Covers crates/grida/src/import/svg/, the grida_dev svg-to-grida CLI, cross-boundary FBS codec tests (Rust encode → TS decode), SVG fixture authoring, and known SVG import limitations (text model, filters, transforms). Use when adding SVG feature support, fixing import bugs, authoring SVG test fixtures, debugging cross-boundary codec failures, or investigating what SVG elements map to which Grida node types.
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.
The TypeScript cross-boundary test (fbs-svg-cross-boundary.test.ts) decodes .grida files that were generated by Rust via svg-to-grida, not a TS converter.
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 |
packages/grida-canvas-io/__tests__/fbs-svg-cross-boundary.test.ts |
TS-side codec test |
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. - Run the cross-boundary cycle (see below) to verify Rust→TS round-trip.
Run the cross-boundary codec cycle
# Step 1: Rust encodes all L0 SVG fixtures → .grida files
cargo run -p grida_dev -- svg-to-grida fixtures/test-svg/L0
# Step 2: TS decodes the .grida files and runs assertions
pnpm vitest run fbs-svg-cross-boundary --reporter=verbose
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.
Cross-Boundary Test Conventions
When fixing a codec bug found via the cross-boundary test:
- Add a targeted
it(...)assertion infbs-svg-cross-boundary.test.ts— one assertion per bug. - Do NOT use snapshot comparisons — assert the specific field that was wrong.
- If the feature is a known limitation (e.g. scale/skew in transforms), mark the test
it.fails(...)with a comment explaining why.
Verification After Changes
# Rust check + tests
cargo check -p grida --all-targets
cargo test -p grida
# Cross-boundary cycle
cargo run -p grida_dev -- svg-to-grida fixtures/test-svg/L0
pnpm vitest run fbs-svg-cross-boundary