Pure-Go SVG rendering via resvg compiled to WASM and run with
wazero. No CGo, no external binaries at go get / go build time.
go get github.com/boolean-maybe/go-resvg
r, err := resvg.New()
if err != nil {
log.Fatal(err)
}
defer r.Close()
png, err := r.RenderPNG(svgBytes, resvg.RenderOptions{Width: 800, Height: 600})Render returns an image.Image (*image.RGBA); RenderPNG returns encoded PNG bytes. Setting only Width or
only Height derives the other from the SVG's intrinsic aspect ratio; setting both renders at exactly those
dimensions; setting neither uses the intrinsic size. Create one Renderer and reuse it; it pools wasm instances
internally and is safe for concurrent use.
Invalid input returns ErrInvalidSVG; a rasterization failure returns ErrRenderFailed — both matchable with
errors.Is.
DejaVu Sans (Regular / Bold / Oblique / BoldOblique, subsetted to common Latin/Cyrillic/Greek/punctuation
ranges) is embedded and registered as the sans-serif family. Text renders when the SVG:
- names a generic family (
sans-serif, etc.), or - omits
font-family(the default family is DejaVu Sans), or - names
DejaVu Sansexplicitly.
An explicitly named family that is not embedded (e.g. font-family="Helvetica Neue") is not silently
substituted — resvg drops that text run. Author SVGs against the embedded family or a generic name.
The committed resvg.wasm is go:embed-ed, so consumers never need a Rust toolchain. To rebuild it you need
Rust + the wasm32-wasip1 target, binaryen (wasm-opt), and fonttools (pyftsubset):
# subset DejaVu into rust/fonts/ (override FONT_SRC / PYFTSUBSET for your system)
make fonts FONT_SRC=/path/to/dejavu PYFTSUBSET=/path/to/pyftsubset
# cargo build + wasm-opt -> resvg.wasm
make wasm
rust/fonts/*.ttf are committed, so a plain make wasm rebuilds the artifact without re-subsetting.
Or reproducibly via the pinned toolchain (requires Docker):
make wasm-docker
The committed resvg.wasm is the output of make wasm-docker (pinned Rust + binaryen). A local make wasm
with a different rustc / wasm-opt version produces a functionally identical but not byte-identical artifact —
the WASM ABI is stable, but codegen and optimizer output vary across toolchain versions and host architectures.
Rebuild with make wasm-docker to reproduce the committed bytes.
The Rust crate exports a minimal C-ABI over resvg:
wasm_alloc(len) -> ptr/wasm_free(ptr, len)— linear-memory allocation (namedwasm_*to avoid a duplicate-symbol clash with the wasi libcfree).render(svg_ptr, svg_len, width, height) -> header_ptr— returns a pointer to a 20-byte little-endian result header:[status i32, width u32, height u32, data_ptr u32, data_len u32].status == 0is success withdata_ptr/data_lenpointing atwidth*height*4RGBA bytes;1= invalid SVG;2= render failure.