use std::fmt;
use std::sync::{Arc, OnceLock};
use hardless_escpos::Node;
use resvg::{tiny_skia, usvg};
use crate::{render_svg, RenderOptions};
pub const MAX_PNG_HEIGHT: u32 = 20_000;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PngError(String);
impl fmt::Display for PngError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl std::error::Error for PngError {}
fn fonts() -> Arc<usvg::fontdb::Database> {
static FONTS: OnceLock<Arc<usvg::fontdb::Database>> = OnceLock::new();
FONTS
.get_or_init(|| {
let mut db = usvg::fontdb::Database::new();
db.load_font_data(include_bytes!("../fonts/Go-Mono.ttf").to_vec());
db.load_font_data(include_bytes!("../fonts/Go-Mono-Bold.ttf").to_vec());
db.set_monospace_family("Go Mono");
Arc::new(db)
})
.clone()
}
pub fn render_png(nodes: &[Node], options: &RenderOptions) -> Result<Vec<u8>, PngError> {
let svg = render_svg(nodes, options);
let opt = usvg::Options {
font_family: "Go Mono".into(),
fontdb: fonts(),
..usvg::Options::default()
};
let tree = usvg::Tree::from_str(&svg, &opt).map_err(|e| PngError(e.to_string()))?;
let size = tree.size().to_int_size();
if size.height() > MAX_PNG_HEIGHT {
return Err(PngError(format!(
"the receipt is {} dots long; PNG output stops at {MAX_PNG_HEIGHT}",
size.height()
)));
}
let mut pixmap = tiny_skia::Pixmap::new(size.width(), size.height())
.ok_or_else(|| PngError("the receipt has no size".into()))?;
resvg::render(
&tree,
tiny_skia::Transform::identity(),
&mut pixmap.as_mut(),
);
pixmap.encode_png().map_err(|e| PngError(e.to_string()))
}