resvg
| Language | Rust (C API and CLI available) |
|---|---|
| License | Apache-2.0 OR MIT |
| Repository | github.com/linebender/resvg |
| Documentation | docs.rs/resvg, docs.rs/usvg |
| Depends on | pure Rust: usvg (parser), tiny-skia (rasterizer), fontdb and rustybuzz (text) |
| Read / write | read (renders to RGBA pixmaps, PNG) / write normalized SVG via usvg |
resvg renders static SVG: shapes, paths, text with full shaping, gradients, patterns, clipping, masks, markers, filters and CSS. It passes more of its 1600-test suite than any other library (support table). It doesn’t support animation or scripts, by design.
Rendering happens in 2 steps. usvg parses SVG and resolves everything (CSS, <use>, units, text layout) into a small tree with only groups, paths, images and text. resvg draws that tree with tiny-skia. There’s no C code in the build, it compiles to WebAssembly, and output is pixel-identical across platforms. Typst, many static site and diagram tools, and image servers use it.
Installation#
cargo add resvg
resvg re-exports usvg and tiny_skia, so one dependency is enough. The command line tool installs with cargo install resvg.
Basic usage: convert SVG to PNG#
use resvg::{tiny_skia, usvg};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let data = std::fs::read("in.svg")?;
let mut opt = usvg::Options::default();
opt.fontdb_mut().load_system_fonts(); // needed if the SVG has text
let tree = usvg::Tree::from_data(&data, &opt)?;
let size = tree.size().to_int_size();
let mut pixmap = tiny_skia::Pixmap::new(size.width(), size.height()).ok_or("invalid size")?;
resvg::render(&tree, tiny_skia::Transform::default(), &mut pixmap.as_mut());
pixmap.save_png("out.png")?;
Ok(())
}
Tree::from_data also reads .svgz (gzip) data.
How to render SVG at a given width#
Scale with a transform and size the pixmap to match:
let scale = 512.0 / tree.size().width();
let w = (tree.size().width() * scale).ceil() as u32;
let h = (tree.size().height() * scale).ceil() as u32;
let mut pixmap = tiny_skia::Pixmap::new(w, h).ok_or("invalid size")?;
pixmap.fill(tiny_skia::Color::WHITE); // optional background
resvg::render(&tree, tiny_skia::Transform::from_scale(scale, scale), &mut pixmap.as_mut());
pixmap.save_png("out-512.png")?;
How to get the raw pixels#
let rgba: &[u8] = pixmap.data(); // premultiplied RGBA, width * height * 4 bytes
Use pixmap.encode_png() to get PNG bytes in memory instead of writing a file.
How to read shapes and bounding boxes#
usvg gives you the resolved geometry, with transforms and styles applied:
use resvg::usvg;
for node in tree.root().children() {
match node {
usvg::Node::Group(g) => println!("group {}", g.id()),
usvg::Node::Path(p) => println!("path {:?} bbox {:?}", p.id(), p.bounding_box()),
usvg::Node::Image(_) => println!("image"),
usvg::Node::Text(_) => println!("text"),
}
}
println!("drawing bounds: {:?}", tree.root().abs_bounding_box());
How to write a simplified SVG#
usvg can serialize its tree back to SVG: the result has no CSS, no <use> and text converted to paths, which other tools read more reliably:
let simplified: String = tree.to_string(&usvg::WriteOptions::default());
std::fs::write("simplified.svg", simplified)?;
How to convert from the command line#
resvg in.svg out.png
resvg -w 1024 in.svg out.png # width 1024, height keeps the ratio
resvg --background white in.svg out.png
Tips#
- Options include
dpi,default_size(for SVG files without a size),resources_dir(base for relative image paths) andfont_family. - The default
image_href_resolverdecodesdata:URLs and loads local image files referenced by the SVG (relative toresources_dir). For untrusted input, set itsresolve_stringfield to a function that returnsNone. - resvg is also usable from C through the
resvgC API in the repository’scrates/c-apidirectory.