1const EMBEDDED_FONT: &[u8] = include_bytes!("../fonts/Go-Regular.ttf");
17const EMBEDDED_FONT_FAMILY: &str = "Go";
19
20pub fn to_png(svg: &str, scale: f32) -> Result<Vec<u8>, String> {
23 use resvg::{tiny_skia, usvg};
24
25 if !scale.is_finite() || scale <= 0.0 {
26 return Err("scale must be a positive finite number".into());
27 }
28
29 let mut opt = usvg::Options::default();
30 {
31 let db = opt.fontdb_mut();
32 db.load_font_data(EMBEDDED_FONT.to_vec());
33 db.set_serif_family(EMBEDDED_FONT_FAMILY);
34 db.set_sans_serif_family(EMBEDDED_FONT_FAMILY);
35 db.set_monospace_family(EMBEDDED_FONT_FAMILY);
36 db.set_cursive_family(EMBEDDED_FONT_FAMILY);
37 db.set_fantasy_family(EMBEDDED_FONT_FAMILY);
38 }
39 let tree = usvg::Tree::from_str(svg, &opt).map_err(|e| e.to_string())?;
40
41 let size = tree.size();
42 let w = ((size.width() * scale).ceil() as u32).max(1);
43 let h = ((size.height() * scale).ceil() as u32).max(1);
44 let mut pixmap = tiny_skia::Pixmap::new(w, h).ok_or("failed to allocate pixmap")?;
45 resvg::render(
46 &tree,
47 tiny_skia::Transform::from_scale(scale, scale),
48 &mut pixmap.as_mut(),
49 );
50 pixmap.encode_png().map_err(|e| e.to_string())
51}
52
53pub fn to_pdf(svg: &str) -> Result<Vec<u8>, String> {
55 use svg2pdf::usvg;
56
57 let mut opt = usvg::Options::default();
58 {
59 let db = opt.fontdb_mut();
60 db.load_font_data(EMBEDDED_FONT.to_vec());
61 db.set_serif_family(EMBEDDED_FONT_FAMILY);
62 db.set_sans_serif_family(EMBEDDED_FONT_FAMILY);
63 db.set_monospace_family(EMBEDDED_FONT_FAMILY);
64 db.set_cursive_family(EMBEDDED_FONT_FAMILY);
65 db.set_fantasy_family(EMBEDDED_FONT_FAMILY);
66 }
67 let tree = usvg::Tree::from_str(svg, &opt).map_err(|e| e.to_string())?;
68
69 svg2pdf::to_pdf(
70 &tree,
71 svg2pdf::ConversionOptions::default(),
72 svg2pdf::PageOptions::default(),
73 )
74 .map_err(|e| e.to_string())
75}
76
77#[cfg(test)]
78mod tests {
79 use super::*;
80
81 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>";
82 const SVG_TEXT: &str = "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"120\" height=\"40\" viewBox=\"0 0 120 40\" font-family=\"sans-serif\" font-size=\"16\"><text x=\"4\" y=\"24\">Hello world</text></svg>";
83
84 #[test]
85 fn png_has_magic_and_scales() {
86 let one = to_png(SVG, 1.0).unwrap();
87 assert_eq!(&one[..4], &[0x89, 0x50, 0x4E, 0x47]); let two = to_png(SVG, 2.0).unwrap();
89 assert!(two.len() > one.len()); }
91
92 #[test]
93 fn png_rejects_invalid_scale() {
94 assert!(to_png(SVG, 0.0).is_err());
95 assert!(to_png(SVG, f32::NAN).is_err());
96 }
97
98 #[test]
99 fn pdf_has_magic() {
100 let pdf = to_pdf(SVG).unwrap();
101 assert_eq!(&pdf[..4], b"%PDF");
102 }
103
104 #[test]
105 fn bundled_font_rasterizes_text() {
106 let with_text = to_png(SVG_TEXT, 1.0).unwrap();
110 let blank = to_png(
111 "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"120\" height=\"40\" viewBox=\"0 0 120 40\"></svg>",
112 1.0,
113 )
114 .unwrap();
115 assert!(
116 with_text.len() > blank.len() + 200,
117 "text PNG {} vs blank {}",
118 with_text.len(),
119 blank.len()
120 );
121 }
122}