---
title: SVG import
description: 'How static SVG content is parsed into Inkfinite native shapes and assets.'
section: Development
group: Development
order: 20
---

Inkfinite parses the supported static SVG subset in Rust and maps it to native
shape properties. The importer does not retain an SVG-specific document model.
Its output is a normalized tree. `build_svg_import_transaction` turns that
result into one ordered transaction containing the source asset, extracted
assets, a root container, nested groups, and native shapes.

## Import boundary

`inkfinite_core::svg_import` exposes two entry points:

- `parse_svg(&str)` parses UTF-8 SVG text.
- `import_svg(impl AsRef<[u8]>)` accepts SVG text or UTF-8 file bytes.

Both functions return an `SvgImport` value:

```rs
SvgImport {
    view_box: Option<SvgViewBox>,
    root: SvgGroup,
    source_asset: SvgAsset,
    assets: Vec<SvgAsset>,
    warnings: Vec<SvgImportWarning>,
}
```

`SvgGroup` retains its source ID, parent-relative transform, opacity, calculated
size, and ordered `SvgImportNode` children. A node is a group, a native
`SvgShape`, or an `SvgImage` referencing an extracted asset. Source IDs are
hints for the transaction layer. Callers assign document record IDs when they
create the import transaction.

`source_asset` is the exact UTF-8 input as an `image/svg+xml` asset with a
content-addressed ID. `assets` contains the embedded raster assets referenced
by image nodes. The source asset remains available for provenance and re-import
even when the native tree omits unsupported content.

## Native mappings

| SVG element             | Import result                                             |
| ----------------------- | --------------------------------------------------------- |
| `g`                     | `SvgGroup`, which maps to a native container              |
| `rect`                  | `rect` shape with width, height, radius, fill, and stroke |
| `circle`                | `ellipse` shape with equal width and height               |
| `ellipse`               | `ellipse` shape                                           |
| `line`                  | `line` shape with local endpoints, stroke, and width      |
| `polygon`               | Closed `path` shape                                       |
| `polyline`              | Open `path` shape                                         |
| `path`                  | `path` shape                                              |
| `text`                  | `text` shape                                              |
| embedded raster `image` | `SvgImage` plus an embedded `SvgAsset`                    |

The importer handles SVG coordinates in user units. Numeric lengths can use
`px`, `pt`, `pc`, `mm`, `cm`, `in`, or percentages when the root has a view box.
Negative coordinates are valid, and negative dimensions are rejected.

## Path normalization

SVG path data is normalized to the native path representation described in the
[native path geometry guide](/docs/development/native-path-geometry/):

- relative and absolute move, line, horizontal, and vertical commands become
  move and line segments
- quadratic, cubic, smooth quadratic, and smooth cubic commands become native
  quadratic and cubic segments
- elliptical arcs become one or more cubic Bézier segments
- `Z` sets the current subpath's `closed` flag
- a later command after `Z` starts a new normalized subpath at the closing point
- `fill-rule="nonzero"` and `fill-rule="evenodd"` are preserved

Path parsing rejects malformed command data and produces no partial path.

## Transforms

Each group keeps its own SVG transform. A shape keeps its element transform and
combines it with its local geometry origin. Nested groups therefore compose in
the same order as the SVG hierarchy.

The native transform model represents translation, rotation, and scale. SVG
transform lists and matrices are accepted when they decompose into that model.
Skewed matrices and zero scales return an error rather than changing the
geometry silently.

## Painting and opacity

Presentation attributes and declarations in `style` are resolved together with
inherited values. The importer preserves supported `fill`, `stroke`,
`stroke-width`, `fill-rule`, `fill-opacity`, `stroke-opacity`, and `opacity`
values. SVG defaults are a black fill and no stroke. `none` and `transparent`
become absent native paint values.

Supported linear and radial gradients are converted to native paint properties
and remain editable after import. Basic user-space clip paths, path-based alpha
and luminance masks, and the initial filter subset (blur, colour adjustments,
opacity, and drop shadows) are stored as native effect properties. Unsupported
clip units, mask content, and filter primitives remain visible through the
sanitized static fallback when they affect the visual result. Stylesheet blocks,
event-handler attributes, scripts, and SVG animation elements are removed with
warnings. External image URLs and other resource references are also omitted.
No script, animation, stylesheet, or resource is executed, inserted into a live
DOM, or fetched during import. The retained source asset is input data for
provenance and future re-import, not executable document content.

## Text

Simple SVG text becomes one native `text` shape. The importer uses the first
`x` and `y` value as the native text origin, copies the first font family,
`font-size`, and fill, and concatenates descendant text nodes. Nested `tspan`
elements contribute their text but do not create separate shapes. Text layout,
text paths, anchors, and rich span styling are not represented yet.

## Embedded images

The importer accepts embedded PNG, JPEG, GIF, and WebP data URLs. It decodes
the bytes once, creates a content-addressed `SvgAsset` with a SHA-256 digest,
and reuses that asset when multiple image elements contain the same bytes.
External URLs and unsupported media types are skipped with warnings. The parser
does not fetch resources.

`SvgImage` nodes retain the source element's position, size, transform,
opacity, and asset ID. The SVG transaction maps each node to a native `image`
shape that references the extracted asset.

## Unsupported content and security

The importer is a static parser. Named unsupported visual features are reported through typed
`SvgImportWarning::UnsupportedFeature` values. Other unsupported elements use
`SvgImportWarning::UnsupportedElement`. If unsupported content affects the
visual result, the importer replaces that SVG's partial native tree with one
image backed by sanitized SVG bytes. The sanitizer retains static SVG geometry,
paint servers, clips, masks, and filters. It removes scripts, event handlers,
animations, stylesheets, `foreignObject`, external resources, and unknown
elements. The exact original bytes remain in `source_asset` for provenance.

Desktop imports use the native Tauri dialog plugin to select a path, then Rust
reads, parses, and commits the file through the active session. The browser adapter exposes separate Import menu actions for adding an SVG to the
active document and creating a document from an SVG file. Pasted SVG code is added
to the active document. SVG file bytes and pasted markup cross one reusable worker. The
Rust document session parses the bytes, builds the shared SVG transaction, commits
one history entry, and returns the new canonical snapshot and editor projection.
The browser caches that projection with the canonical IndexedDB bytes and uses it
to hydrate the editor. File selection, drag-and-drop, and pasted markup use this
path. The web build runs `scripts/build-wasm.mjs` before Vite packages the worker.
It requires the matching `wasm-bindgen` CLI. The CLI accepts
`inkfinite import svg FILE --input ARTWORK.svg` and can validate the transaction
with `--dry-run` before saving.

## Browser WASM facade

`@inkfinite/wasm` exposes the Rust document session, importer, and deterministic
SVG renderer. The browser loads the generated module once inside the shared worker.
Import requests transfer their byte buffer to the session. Render requests send one
canonical snapshot and one render-options object across the worker boundary. Rust
response envelopes and renderer options use the generated types in
`@inkfinite/bindings/wasm`.

The web adapter uses the canonical session snapshot before export. Page and
selection filters become Rust renderer options, and renderer warnings become
export conversion notes. Canvas drawing and PNG capture stay in TypeScript, where
they can use the browser's frame loop and canvas APIs.

## Input safety and failures

The parser accepts at most 16 MiB of UTF-8 input. XML, numeric attributes,
transforms, path data, and embedded image data are validated before they enter
the result. Malformed input returns an error rather than a partial import. The
WASM response preserves the parser's structured error code and message across
the worker boundary, and a failed import leaves the editor's busy state and
current document unchanged.
