use resvg::{tiny_skia, usvg};
#[cfg(feature = "python")]
mod python;
#[cfg(feature = "wasm")]
mod wasm;
#[cfg(feature = "bpmn")]
pub mod bpmn;
#[derive(Debug)]
pub enum RenderError {
Parse(usvg::Error),
Size { width: u32, height: u32 },
Encode(String),
Pdf(String),
}
impl std::fmt::Display for RenderError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
RenderError::Parse(e) => write!(f, "invalid SVG: {e}"),
RenderError::Size { width, height } => {
write!(f, "invalid raster size {width}x{height}")
}
RenderError::Encode(e) => write!(f, "PNG encoding failed: {e}"),
RenderError::Pdf(e) => write!(f, "SVG→PDF conversion failed: {e}"),
}
}
}
impl std::error::Error for RenderError {}
impl From<usvg::Error> for RenderError {
fn from(e: usvg::Error) -> Self {
RenderError::Parse(e)
}
}
pub fn svg_to_png(svg: &[u8], scale: f32) -> Result<Vec<u8>, RenderError> {
#[allow(unused_mut)]
let mut opt = usvg::Options::default();
#[cfg(feature = "system-fonts")]
opt.fontdb_mut().load_system_fonts();
let tree = usvg::Tree::from_data(svg, &opt)?;
let size = tree.size();
let width = ((size.width() * scale).round() as i64).clamp(1, u32::MAX as i64) as u32;
let height = ((size.height() * scale).round() as i64).clamp(1, u32::MAX as i64) as u32;
let mut pixmap =
tiny_skia::Pixmap::new(width, height).ok_or(RenderError::Size { width, height })?;
let transform = tiny_skia::Transform::from_scale(scale, scale);
resvg::render(&tree, transform, &mut pixmap.as_mut());
pixmap
.encode_png()
.map_err(|e| RenderError::Encode(e.to_string()))
}
#[cfg(feature = "pdf")]
pub fn svg_to_pdf(svg: &[u8]) -> Result<Vec<u8>, RenderError> {
use svg2pdf::usvg as pdf_usvg;
#[allow(unused_mut)]
let mut opt = pdf_usvg::Options::default();
#[cfg(feature = "system-fonts")]
opt.fontdb_mut().load_system_fonts();
let tree = pdf_usvg::Tree::from_data(svg, &opt).map_err(|e| RenderError::Pdf(e.to_string()))?;
svg2pdf::to_pdf(
&tree,
svg2pdf::ConversionOptions::default(),
svg2pdf::PageOptions::default(),
)
.map_err(|e| RenderError::Pdf(e.to_string()))
}
#[cfg(test)]
mod tests {
const SVG: &[u8] =
br##"<svg xmlns="http://www.w3.org/2000/svg" width="40" height="20"><rect width="40" height="20" fill="#09f"/></svg>"##;
#[test]
fn png_has_magic() {
let png = super::svg_to_png(SVG, 1.0).expect("render png");
assert_eq!(&png[..8], b"\x89PNG\r\n\x1a\n");
}
#[cfg(feature = "pdf")]
#[test]
fn pdf_has_magic() {
let pdf = super::svg_to_pdf(SVG).expect("render pdf");
assert_eq!(&pdf[..5], b"%PDF-");
}
}