#[cfg(feature = "png")]
use crate::{Error, Result};
#[cfg(feature = "png")]
fn shared_fontdb() -> std::sync::Arc<resvg::usvg::fontdb::Database> {
use std::sync::{Arc, OnceLock};
static DB: OnceLock<Arc<resvg::usvg::fontdb::Database>> = OnceLock::new();
DB.get_or_init(|| {
let mut db = resvg::usvg::fontdb::Database::new();
db.load_system_fonts();
Arc::new(db)
})
.clone()
}
#[cfg(feature = "png")]
pub fn svg_to_png(svg: &str, scale: f32) -> Result<Vec<u8>> {
use resvg::tiny_skia;
use resvg::usvg;
let mut opts = usvg::Options::default();
opts.fontdb = shared_fontdb();
let tree =
usvg::Tree::from_str(svg, &opts).map_err(|e| Error::Xml(format!("usvg parse: {e}")))?;
let size = tree.size();
let width = (size.width() * scale).round().max(1.0) as u32;
let height = (size.height() * scale).round().max(1.0) as u32;
let mut pixmap = tiny_skia::Pixmap::new(width, height)
.ok_or_else(|| Error::Xml(format!("pixmap allocation failed for {width}x{height}")))?;
resvg::render(
&tree,
tiny_skia::Transform::from_scale(scale, scale),
&mut pixmap.as_mut(),
);
pixmap
.encode_png()
.map_err(|e| Error::Xml(format!("PNG encode: {e}")))
}
#[cfg(feature = "pdf")]
pub fn svg_to_pdf(svg: &str) -> crate::Result<Vec<u8>> {
let options = svg2pdf::ConversionOptions::default();
let page_options = svg2pdf::PageOptions::default();
let bytes = svg2pdf::to_pdf(
&svg2pdf::usvg::Tree::from_str(svg, &svg2pdf::usvg::Options::default())
.map_err(|e| crate::Error::Xml(format!("usvg parse: {e}")))?,
options,
page_options,
)
.map_err(|e| crate::Error::Xml(format!("PDF encode: {e}")))?;
Ok(bytes)
}
#[cfg(feature = "pdf")]
pub fn svgs_to_pdf(svgs: &[String]) -> crate::Result<Vec<u8>> {
use std::collections::HashMap;
use pdf_writer::{Chunk, Content, Finish, Name, Pdf, Rect, Ref};
use svg2pdf::usvg::{Options, Tree};
if svgs.is_empty() {
return Err(crate::Error::RenderFailed { page: 0 });
}
let mut pdf = Pdf::new();
let mut next_id: i32 = 1;
let mut alloc = || -> Ref {
let r = Ref::new(next_id);
next_id += 1;
r
};
let catalog_ref = alloc();
let page_tree_ref = alloc();
let mut page_refs: Vec<Ref> = Vec::with_capacity(svgs.len());
let mut content_refs: Vec<Ref> = Vec::with_capacity(svgs.len());
for _ in svgs {
page_refs.push(alloc());
content_refs.push(alloc());
}
pdf.catalog(catalog_ref).pages(page_tree_ref);
{
let mut pages = pdf.pages(page_tree_ref);
pages
.count(svgs.len() as i32)
.kids(page_refs.iter().copied());
pages.finish();
}
for (i, svg) in svgs.iter().enumerate() {
let tree = Tree::from_str(svg, &Options::default())
.map_err(|e| crate::Error::Xml(format!("usvg parse page {}: {e}", i + 1)))?;
let (chunk, svg_id) = svg2pdf::to_chunk(&tree, svg2pdf::ConversionOptions::default())
.map_err(|e| crate::Error::Xml(format!("svg2pdf chunk page {}: {e}", i + 1)))?;
let mut remap = HashMap::new();
let renumbered: Chunk = chunk.renumber(|old| {
*remap.entry(old).or_insert_with(|| {
let r = Ref::new(next_id);
next_id += 1;
r
})
});
let svg_ref = remap[&svg_id];
pdf.extend(&renumbered);
let size = tree.size();
let width = size.width();
let height = size.height();
let mut content = Content::new();
content.transform([width, 0.0, 0.0, height, 0.0, 0.0]);
let xobj_name = Name(b"S");
content.x_object(xobj_name);
let content_bytes = content.finish();
pdf.stream(content_refs[i], &content_bytes);
let mut page = pdf.page(page_refs[i]);
page.media_box(Rect::new(0.0, 0.0, width, height));
page.parent(page_tree_ref);
page.contents(content_refs[i]);
let mut resources = page.resources();
resources.x_objects().pair(xobj_name, svg_ref);
resources.finish();
page.finish();
}
Ok(pdf.finish())
}
#[cfg(any(feature = "png", feature = "pdf"))]
impl crate::Toolkit {
#[cfg(feature = "png")]
pub fn render_to_png(&mut self, page: u32, scale: f32) -> crate::Result<Vec<u8>> {
let svg = self.render_to_svg(page)?;
svg_to_png(&svg, scale)
}
#[cfg(feature = "png")]
pub fn render_to_png_all_pages(&mut self, scale: f32) -> crate::Result<Vec<Vec<u8>>> {
let pages = self.page_count();
let mut out = Vec::with_capacity(pages as usize);
for page in 1..=pages {
out.push(self.render_to_png(page, scale)?);
}
Ok(out)
}
#[cfg(feature = "pdf")]
pub fn render_to_pdf(&mut self, page: u32) -> crate::Result<Vec<u8>> {
let svg = self.render_to_svg(page)?;
svg_to_pdf(&svg)
}
#[cfg(feature = "pdf")]
pub fn render_to_pdf_all_pages(&mut self) -> crate::Result<Vec<u8>> {
let pages = self.page_count();
if pages == 0 {
return Err(crate::Error::RenderFailed { page: 0 });
}
let mut svgs: Vec<String> = Vec::with_capacity(pages as usize);
for page in 1..=pages {
svgs.push(self.render_to_svg(page)?);
}
svgs_to_pdf(&svgs)
}
}