Skip to main content

rpic_render/
lib.rs

1//! Raster (PNG) and PDF backends for rpic.
2//!
3//! These consume the SVG produced by `rpic-core` and convert it with pure-Rust
4//! libraries (no system dependencies), keeping `rpic-core` itself
5//! dependency-free and WASM-friendly.
6//!
7//! - PNG: parse the SVG with `usvg`, rasterize with `resvg`/`tiny-skia`.
8//! - PDF: parse with svg2pdf's `usvg`, convert with `svg2pdf`.
9//!
10//! Both load system fonts so attached text renders.
11
12/// Rasterize an SVG string to PNG bytes at the given scale (1.0 = 96 dpi, the
13/// SVG's native resolution).
14pub fn to_png(svg: &str, scale: f32) -> Result<Vec<u8>, String> {
15    use resvg::{tiny_skia, usvg};
16
17    let mut opt = usvg::Options::default();
18    opt.fontdb_mut().load_system_fonts();
19    let tree = usvg::Tree::from_str(svg, &opt).map_err(|e| e.to_string())?;
20
21    let size = tree.size();
22    let w = ((size.width() * scale).ceil() as u32).max(1);
23    let h = ((size.height() * scale).ceil() as u32).max(1);
24    let mut pixmap = tiny_skia::Pixmap::new(w, h).ok_or("failed to allocate pixmap")?;
25    resvg::render(
26        &tree,
27        tiny_skia::Transform::from_scale(scale, scale),
28        &mut pixmap.as_mut(),
29    );
30    pixmap.encode_png().map_err(|e| e.to_string())
31}
32
33/// Convert an SVG string to PDF bytes.
34pub fn to_pdf(svg: &str) -> Result<Vec<u8>, String> {
35    use svg2pdf::usvg;
36
37    let mut opt = usvg::Options::default();
38    opt.fontdb_mut().load_system_fonts();
39    let tree = usvg::Tree::from_str(svg, &opt).map_err(|e| e.to_string())?;
40
41    svg2pdf::to_pdf(
42        &tree,
43        svg2pdf::ConversionOptions::default(),
44        svg2pdf::PageOptions::default(),
45    )
46    .map_err(|e| e.to_string())
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    const SVG: &str = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"40\" height=\"20\" viewBox=\"0 0 40 20\"><rect x=\"2\" y=\"2\" width=\"36\" height=\"16\" fill=\"none\" stroke=\"black\"/></svg>";
54
55    #[test]
56    fn png_has_magic_and_scales() {
57        let one = to_png(SVG, 1.0).unwrap();
58        assert_eq!(&one[..4], &[0x89, 0x50, 0x4E, 0x47]); // PNG signature
59        let two = to_png(SVG, 2.0).unwrap();
60        assert!(two.len() > one.len()); // larger raster at 2x
61    }
62
63    #[test]
64    fn pdf_has_magic() {
65        let pdf = to_pdf(SVG).unwrap();
66        assert_eq!(&pdf[..4], b"%PDF");
67    }
68}