Commit: 57c7ead
Parent: d0b8f61

Handle printing on a separate thread

Mårten Åsberg committed on 2026-03-20 at 18:40
src/escpos_printer.rs +0 -34
diff --git a/src/escpos_printer.rs b/src/escpos_printer.rs
deleted file mode 100644
index 82eccab..0000000
@@ -1,34 +0,0 @@
use escpos::{
driver::NetworkDriver,
errors::PrinterError,
printer::Printer,
utils::{BitImageOption, BitImageSize, Protocol},
};
pub struct EscposPrinter {
printer: Printer<NetworkDriver>,
}
impl EscposPrinter {
pub const IMAGE_WIDTH_PIXEL: u32 = 72 * 8;
pub const IMAGE_WIDTH_CM: f32 = 7.2;
pub const IMAGE_WIDTH_PT: f32 = Self::IMAGE_WIDTH_CM / 0.03527;
pub const IMAGE_PIXELS_PER_PT: f32 = Self::IMAGE_WIDTH_PIXEL as f32 / Self::IMAGE_WIDTH_PT;
pub fn open(host: &str, port: u16) -> Result<Self, PrinterError> {
let driver = NetworkDriver::open(host, port, None)?;
Ok(Self {
printer: Printer::new(driver, Protocol::default(), None),
})
}
pub fn print_image(&mut self, image: &[u8]) -> Result<(), PrinterError> {
self.printer.init()?;
self.printer.bit_image_from_bytes_option(
image,
BitImageOption::new(Some(Self::IMAGE_WIDTH_PIXEL), None, BitImageSize::Normal)?,
)?;
self.printer.print_cut()?;
Ok(())
}
}
src/main.rs +6 -6
diff --git a/src/main.rs b/src/main.rs
index 89738a4..a3167de 100644
@@ -1,5 +1,5 @@
mod escpos_printer;
mod proto;
mod receipt_printer;
mod receipt_printer_service;
mod typst_renderer;
@@ -7,6 +7,7 @@ use std::env::args;
use tonic::transport::Server;
use crate::proto::receipt_printer_server::ReceiptPrinterServer;
use crate::receipt_printer::ReceiptPrinter;
use crate::receipt_printer_service::ReceiptPrinterService;
#[tokio::main]
@@ -25,11 +26,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
};
let template_path = &args[1];
let receipt_printer_service = ReceiptPrinterService::new(
template_path.to_owned(),
printer_host.to_owned(),
printer_port,
);
let receipt_printer = ReceiptPrinter::init(printer_host.to_owned(), printer_port);
let receipt_printer_service =
ReceiptPrinterService::new(template_path.to_owned(), receipt_printer);
Server::builder()
.add_service(ReceiptPrinterServer::new(receipt_printer_service))
src/receipt_printer.rs +152 -0
diff --git a/src/receipt_printer.rs b/src/receipt_printer.rs
new file mode 100644
index 0000000..e9b71de
@@ -0,0 +1,152 @@
use std::{fmt::Display, sync::Arc};
use escpos::{
driver::NetworkDriver,
errors::PrinterError,
printer::Printer,
utils::{BitImageOption, BitImageSize, Protocol},
};
use tokio::sync::{mpsc, oneshot};
pub struct ReceiptPrinter {
sender: mpsc::UnboundedSender<EscposPrinterRequest>,
}
impl ReceiptPrinter {
pub const IMAGE_WIDTH_PIXEL: u32 = 72 * 8;
pub const IMAGE_WIDTH_CM: f32 = 7.2;
pub const IMAGE_WIDTH_PT: f32 = Self::IMAGE_WIDTH_CM / 0.03527;
pub const IMAGE_PIXELS_PER_PT: f32 = Self::IMAGE_WIDTH_PIXEL as f32 / Self::IMAGE_WIDTH_PT;
pub fn init(host: String, port: u16) -> Self {
let (sender, receiver) = mpsc::unbounded_channel();
EscposPrinterActor {
host,
port,
printer: None,
receiver,
}
.run();
Self { sender }
}
pub async fn print_image(&self, image: Arc<[u8]>) -> Result<(), ReceiptPrinterError> {
let (responder, receiver) = oneshot::channel();
self.sender
.send(EscposPrinterRequest::PrintImage(image, responder))
.map_err(ReceiptPrinterError::send)?;
receiver
.await
.map_err(ReceiptPrinterError::receive)?
.map_err(ReceiptPrinterError::printer)
}
}
struct EscposPrinterActor {
host: String,
port: u16,
printer: Option<EscposPrinter>,
receiver: mpsc::UnboundedReceiver<EscposPrinterRequest>,
}
enum EscposPrinterRequest {
PrintImage(Arc<[u8]>, oneshot::Sender<Result<(), PrinterError>>),
}
impl EscposPrinterActor {
fn run(mut self) {
tokio::spawn(async move {
while let Some(request) = self.receiver.recv().await {
let response_result = match request {
EscposPrinterRequest::PrintImage(image, responder) => {
responder.send(self.print_image(&image))
}
};
if let Err(error) = response_result {
println!("Could not respond to request: {error:?}");
}
}
});
}
fn print_image(&mut self, image: &[u8]) -> Result<(), PrinterError> {
let printer = self.ensure_printer_connection()?;
printer.print_image(image)
}
fn ensure_printer_connection<'s>(&'s mut self) -> Result<&'s mut EscposPrinter, PrinterError> {
let printer = match self.printer.take() {
Some(printer) => printer,
None => EscposPrinter::open(&self.host, self.port)?,
};
Ok(self.printer.insert(printer))
}
}
struct EscposPrinter {
printer: Printer<NetworkDriver>,
}
impl EscposPrinter {
fn open(host: &str, port: u16) -> Result<Self, PrinterError> {
let driver = NetworkDriver::open(host, port, None)?;
Ok(Self {
printer: Printer::new(driver, Protocol::default(), None),
})
}
fn print_image(&mut self, image: &[u8]) -> Result<(), PrinterError> {
self.printer.init()?;
self.printer.bit_image_from_bytes_option(
image,
BitImageOption::new(
Some(ReceiptPrinter::IMAGE_WIDTH_PIXEL),
None,
BitImageSize::Normal,
)?,
)?;
self.printer.print_cut()?;
Ok(())
}
}
pub struct ReceiptPrinterError {
variant: ReceiptPrinterErrorVariants,
}
impl ReceiptPrinterError {
fn send(send_error: mpsc::error::SendError<EscposPrinterRequest>) -> Self {
Self {
variant: ReceiptPrinterErrorVariants::Send(send_error),
}
}
fn receive(recv_error: oneshot::error::RecvError) -> Self {
Self {
variant: ReceiptPrinterErrorVariants::Receive(recv_error),
}
}
fn printer(printer_error: PrinterError) -> Self {
Self {
variant: ReceiptPrinterErrorVariants::Printer(printer_error),
}
}
}
enum ReceiptPrinterErrorVariants {
Send(mpsc::error::SendError<EscposPrinterRequest>),
Receive(oneshot::error::RecvError),
Printer(PrinterError),
}
impl Display for ReceiptPrinterError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.variant {
ReceiptPrinterErrorVariants::Send(send_error) => send_error.fmt(f),
ReceiptPrinterErrorVariants::Receive(recv_error) => recv_error.fmt(f),
ReceiptPrinterErrorVariants::Printer(printer_error) => printer_error.fmt(f),
}
}
}
src/receipt_printer_service.rs +7 -18
diff --git a/src/receipt_printer_service.rs b/src/receipt_printer_service.rs
index d7f9341..400b30f 100644
@@ -1,30 +1,27 @@
use tonic::{Request, Response, Status};
use crate::escpos_printer::EscposPrinter;
use crate::proto::print_request::Content;
use crate::proto::receipt_printer_server::ReceiptPrinter;
use crate::proto::receipt_printer_server;
use crate::proto::{PrintRequest, PrintResponse, TypstContent};
use crate::receipt_printer::ReceiptPrinter;
use crate::typst_renderer::render_typst_receipt;
#[derive(Debug)]
pub struct ReceiptPrinterService {
template_path: String,
printer_host: String,
printer_port: u16,
receipt_printer: ReceiptPrinter,
}
impl ReceiptPrinterService {
pub fn new(template_path: String, printer_host: String, printer_port: u16) -> Self {
pub fn new(template_path: String, receipt_printer: ReceiptPrinter) -> Self {
Self {
template_path,
printer_host,
printer_port,
receipt_printer,
}
}
}
#[tonic::async_trait]
impl ReceiptPrinter for ReceiptPrinterService {
impl receipt_printer_server::ReceiptPrinter for ReceiptPrinterService {
async fn print(
&self,
request: Request<PrintRequest>,
@@ -46,15 +43,7 @@ impl ReceiptPrinter for ReceiptPrinterService {
};
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(&image) {
if let Err(error) = self.receipt_printer.print_image(image.into()).await {
println!("Error when printing: {error}");
return Ok(Response::new(PrintResponse::default()));
}
src/typst_renderer.rs +2 -2
diff --git a/src/typst_renderer.rs b/src/typst_renderer.rs
index 50afc39..72df7aa 100644
@@ -7,7 +7,7 @@ use typst::{
};
use typst_as_lib::{TypstAsLibError, TypstEngine, typst_kit_options::TypstKitFontOptions};
use crate::escpos_printer::EscposPrinter;
use crate::receipt_printer::ReceiptPrinter;
pub fn render_typst_receipt(
template_path: &str,
@@ -31,7 +31,7 @@ pub fn render_typst_receipt(
_ => return Err(TypstRenderError::MultiPageOutput),
};
let pixmap = typst_render::render(page, EscposPrinter::IMAGE_PIXELS_PER_PT);
let pixmap = typst_render::render(page, ReceiptPrinter::IMAGE_PIXELS_PER_PT);
let png = pixmap.encode_png().map_err(TypstRenderError::Rendering)?;
Ok(png)