📄 src/receipt_printer_service.rs
use tonic::{Request, Response, Status};
use typst::foundations::{Dict, IntoValue};
use typst::layout::PagedDocument;
use typst_as_lib::TypstEngine;
use typst_as_lib::typst_kit_options::TypstKitFontOptions;

use crate::escpos_printer::EscposPrinter;
use crate::proto::print_request::Content;
use crate::proto::receipt_printer_server::ReceiptPrinter;
use crate::proto::{PrintRequest, PrintResponse, TypstContent};

#[derive(Debug)]
pub struct ReceiptPrinterService {
    template_path: String,
    printer_host: String,
    printer_port: u16,
}

impl ReceiptPrinterService {
    pub fn new(template_path: String, printer_host: String, printer_port: u16) -> Self {
        Self {
            template_path,
            printer_host,
            printer_port,
        }
    }
}

#[tonic::async_trait]
impl ReceiptPrinter for ReceiptPrinterService {
    async fn print(
        &self,
        request: Request<PrintRequest>,
    ) -> Result<Response<PrintResponse>, Status> {
        let Some(Content::Typst(TypstContent {
            content: Some(content),
        })) = &request.get_ref().content
        else {
            println!("No Typst content received");
            return Ok(Response::new(PrintResponse::default()));
        };

        let engine = TypstEngine::builder()
            .with_file_system_resolver(".")
            .search_fonts_with(TypstKitFontOptions::new().include_system_fonts(true))
            .build();

        println!("Compiling document...");
        let mut inputs = Dict::new();
        inputs.insert("content".into(), content.clone().into_value());
        let document: PagedDocument = match engine
            .compile_with_input(self.template_path.as_ref(), inputs)
            .output
        {
            Ok(document) => document,
            Err(error) => {
                println!("Failed to compile with message: {error}");
                return Ok(Response::new(PrintResponse::default()));
            }
        };

        println!("Rendering document...");
        let pixmap = typst_render::render(&document.pages[0], EscposPrinter::IMAGE_PIXELS_PER_PT);
        println!("Pixel size: {}x{}", pixmap.width(), pixmap.height());
        let png = match pixmap.encode_png() {
            Ok(png) => png,
            Err(error) => {
                println!("Could not encode image to PNG: {error}");
                return Ok(Response::new(PrintResponse::default()));
            }
        };

        println!("Connecting to printer...");
        let mut printer = match EscposPrinter::open(self.printer_host.as_ref(), self.printer_port) {
            Ok(printer) => printer,
            Err(error) => {
                println!("Error connecting to printer: {error}");
                return Ok(Response::new(PrintResponse::default()));
            }
        };
        println!("Printing...");
        if let Err(error) = printer.print_image(&png) {
            println!("Error when printing: {error}");
            return Ok(Response::new(PrintResponse::default()));
        }
        println!("Done!");

        Ok(Response::new(PrintResponse::default()))
    }
}