# Io Svg

> 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.

- Skill: `gridaco/io-svg` (Agent Skill)
- Install (CLI): `npx skillmds@latest add gridaco/io-svg`
- Raw SKILL.md: https://api.skillmd.com/api/skills/gridaco/io-svg/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: gridaco (https://skillmd.com/u/gridaco)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/gridaco/io-svg

---


# 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

1. Read `crates/grida/AGENTS.md` for crate conventions and commands.
2. Read `docs/wg/feat-svg/text-import.md` before touching text conversion — the text model is intentionally limited and the design is documented there.
3. Grep for the relevant element in `packed_scene.rs`.

### Add support for a new SVG element or attribute

1. Find where usvg exposes the element in `third_party/usvg/src/`.
2. Add the mapping in `packed_scene.rs` (the main `convert_*` functions).
3. Add a minimal SVG fixture to `fixtures/test-svg/L0/` that exercises the feature.
4. Verify the round-trip with the Rust tests (the TS cross-boundary cycle is retired — see above).

### Generate `.grida` outputs from fixtures

```sh
cargo run -p grida_dev -- svg-to-grida fixtures/test-svg/L0
```

Outputs land in `fixtures/test-svg/.generated/` (gitignored).

For custom SVG files:

```sh
cargo run -p grida_dev -- svg-to-grida path/to/svgs -r
```

### Run SVG reftests

```sh
# 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

```sh
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 `TextSpanNodeRec` per usvg `TextChunk` (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 in `docs/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 with `svg-to-grida`.

---

## Codec-Bug Test Conventions

When fixing a codec bug in the SVG→`.grida` path:

1. Add a targeted Rust assertion — one assertion per bug, on the specific field that was wrong.
2. Do NOT use snapshot comparisons for codec bugs.
3. If the feature is a known limitation (e.g. scale/skew in transforms), gate the test with an explanatory comment.

---

## Verification After Changes

```sh
cargo check -p grida --all-targets
cargo test -p grida
cargo run -p grida_dev -- svg-to-grida fixtures/test-svg/L0
```

