SVG import
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:
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:
- 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
Zsets the current subpath’sclosedflag- a later command after
Zstarts a new normalized subpath at the closing point fill-rule="nonzero"andfill-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.