📄 src/typst_renderer.rs
use std::fmt::Display;

use png::EncodingError;
use typst::{
    foundations::{Dict, IntoValue},
    layout::PagedDocument,
};
use typst_as_lib::{TypstAsLibError, TypstEngine, typst_kit_options::TypstKitFontOptions};

use crate::escpos_printer::EscposPrinter;

pub fn render_typst_receipt(
    template_path: &str,
    content: &str,
) -> Result<Vec<u8>, TypstRenderError> {
    let engine = TypstEngine::builder()
        .with_file_system_resolver(".")
        .search_fonts_with(TypstKitFontOptions::new().include_system_fonts(true))
        .build();

    let mut inputs = Dict::new();
    inputs.insert("content".into(), content.into_value());
    let document: PagedDocument = engine
        .compile_with_input(template_path, inputs)
        .output
        .map_err(TypstRenderError::Compilation)?;

    let page = match &document.pages[..] {
        [] => return Err(TypstRenderError::NoOutput),
        [page] => page,
        _ => return Err(TypstRenderError::MultiPageOutput),
    };

    let pixmap = typst_render::render(page, EscposPrinter::IMAGE_PIXELS_PER_PT);
    let png = pixmap.encode_png().map_err(TypstRenderError::Rendering)?;

    Ok(png)
}

#[derive(Debug)]
pub enum TypstRenderError {
    Compilation(TypstAsLibError),
    NoOutput,
    MultiPageOutput,
    Rendering(EncodingError),
}

impl Display for TypstRenderError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            TypstRenderError::Compilation(typst_as_lib_error) => typst_as_lib_error.fmt(f),
            TypstRenderError::NoOutput => write!(f, "No output"),
            TypstRenderError::MultiPageOutput => write!(f, "Multi-page output"),
            TypstRenderError::Rendering(encoding_error) => encoding_error.fmt(f),
        }
    }
}