Commit: d0b8f61
Parent: bc868d2

Factor out Typst rendering

Mårten Åsberg committed on 2026-03-19 at 19:47
Cargo.lock +1 -0
diff --git a/Cargo.lock b/Cargo.lock
index feb75ad..4080232 100644
@@ -2496,6 +2496,7 @@ name = "receipt-printer"
version = "0.1.0"
dependencies = [
"escpos",
"png 0.17.16",
"prost",
"tokio",
"tonic",
Cargo.toml +1 -0
diff --git a/Cargo.toml b/Cargo.toml
index 09cbc90..d78ed73 100644
@@ -5,6 +5,7 @@ edition = "2024"
[dependencies]
escpos = { version = "0.17.0", features = ["graphics"] }
png = "0.17.16"
prost = "0.14.3"
tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros"] }
tonic = "0.14.5"
src/main.rs +1 -0
diff --git a/src/main.rs b/src/main.rs
index 510fb3c..89738a4 100644
@@ -1,6 +1,7 @@
mod escpos_printer;
mod proto;
mod receipt_printer_service;
mod typst_renderer;
use std::env::args;
use tonic::transport::Server;
src/receipt_printer_service.rs +5 -30
diff --git a/src/receipt_printer_service.rs b/src/receipt_printer_service.rs
index 8061b50..d7f9341 100644
@@ -1,13 +1,10 @@
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};
use crate::typst_renderer::render_typst_receipt;
#[derive(Debug)]
pub struct ReceiptPrinterService {
@@ -40,32 +37,10 @@ impl ReceiptPrinter for ReceiptPrinterService {
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,
let image = match render_typst_receipt(self.template_path.as_ref(), content) {
Ok(image) => image,
Err(error) => {
println!("Could not encode image to PNG: {error}");
println!("Error rendering receipt: {error}");
return Ok(Response::new(PrintResponse::default()));
}
};
@@ -79,7 +54,7 @@ impl ReceiptPrinter for ReceiptPrinterService {
}
};
println!("Printing...");
if let Err(error) = printer.print_image(&png) {
if let Err(error) = printer.print_image(&image) {
println!("Error when printing: {error}");
return Ok(Response::new(PrintResponse::default()));
}
src/typst_renderer.rs +57 -0
diff --git a/src/typst_renderer.rs b/src/typst_renderer.rs
new file mode 100644
index 0000000..50afc39
@@ -0,0 +1,57 @@
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),
}
}
}