Cargo.lock
+3927
-549
Large diff (4476 lines changed) - not displayed
Cargo.toml
+4
-0
diff --git a/Cargo.toml b/Cargo.toml
index 350db14..09cbc90 100644
@@ -4,10 +4,14 @@ version = "0.1.0"
edition = "2024"
[dependencies]
escpos = { version = "0.17.0", features = ["graphics"] }
prost = "0.14.3"
tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros"] }
tonic = "0.14.5"
tonic-prost = "0.14.5"
typst = "0.14.2"
typst-as-lib = { version = "0.15.4", features = ["typst-kit-fonts"] }
typst-render = "0.14.2"
[build-dependencies]
tonic-prost-build = "0.14.5"
assets/template.typ
+9
-0
diff --git a/assets/template.typ b/assets/template.typ
new file mode 100644
index 0000000..ddbafad
@@ -0,0 +1,9 @@
#set page(
width: 7cm,
height: auto,
margin: (y: 0.1cm, x: 0.05cm),
fill: none,
)
#set text(font: "Times New Roman")
#eval(mode: "markup", sys.inputs.content)
src/main.rs
+116
-10
diff --git a/src/main.rs b/src/main.rs
index 71ac8ea..c1a1718 100644
@@ -1,37 +1,143 @@
mod proto;
use std::env::args;
use std::time::Duration;
use escpos::driver::NetworkDriver;
use escpos::printer::Printer;
use escpos::utils::{BitImageOption, BitImageSize, Protocol};
use tonic::{Request, Response, Status, transport::Server};
use proto::receipt_printer_server::{ReceiptPrinter, ReceiptPrinterServer};
use proto::{PrintRequest, PrintResponse};
use typst::foundations::{Dict, IntoValue};
use typst::layout::PagedDocument;
use typst_as_lib::TypstEngine;
use typst_as_lib::typst_kit_options::TypstKitFontOptions;
use crate::proto::TypstContent;
use crate::proto::print_request::Content;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse()?;
let receipt_printer_service = ReceiptPrinterService::default();
let args: Vec<_> = args().skip(1).collect();
if args.len() != 2 {
println!("Please provide the printer connection string and .typ template as arguments");
return Ok(());
};
let Some((printer_host, printer_port)) = args[0]
.split_once(':')
.map(|(a, b)| b.parse::<u16>().ok().map(|b| (a, b)))
.flatten()
else {
println!("Printer connection string must be on format `host:port`");
return Ok(());
};
let template_path = &args[1];
let receipt_printer_service = ReceiptPrinterService {
template_path: template_path.to_owned(),
printer_host: printer_host.to_owned(),
printer_port: printer_port,
};
Server::builder()
.add_service(ReceiptPrinterServer::new(receipt_printer_service))
.serve(addr)
.serve("[::1]:50051".parse()?)
.await?;
Ok(())
}
#[derive(Debug, Default)]
pub struct ReceiptPrinterService {}
#[derive(Debug)]
pub struct ReceiptPrinterService {
template_path: String,
printer_host: String,
printer_port: u16,
}
#[tonic::async_trait]
impl ReceiptPrinter for ReceiptPrinterService {
async fn print(
&self,
request: Request<PrintRequest>, // Accept request of type PrintRequest
request: Request<PrintRequest>,
) -> Result<Response<PrintResponse>, Status> {
// Return an instance of type PrintResponse
println!("Got a request: {:?}", request);
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()));
}
};
let width_pixel = 72 * 8;
let width_cm = 7.2;
let width_pt = width_cm / 0.03527;
let pixel_per_pt = width_pixel as f32 / width_pt;
println!("Rendering document...");
let pixmap = typst_render::render(&document.pages[0], pixel_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()));
}
};
let reply = PrintResponse {};
println!("Connecting to printer...");
let driver = match NetworkDriver::open(
self.printer_host.as_ref(),
self.printer_port,
Some(Duration::from_secs(5)),
) {
Ok(driver) => driver,
Err(error) => {
println!("Error connecting to printer: {error}");
return Ok(Response::new(PrintResponse::default()));
}
};
println!("Initializing printer...");
let mut printer = Printer::new(driver, Protocol::default(), None);
if let Err(error) = printer.init() {
println!("Error initializing printer: {error}");
return Ok(Response::new(PrintResponse::default()));
}
println!("Sending image to printer...");
if let Err(error) = printer.bit_image_from_bytes_option(
&png,
BitImageOption::new(Some(width_pixel), None, BitImageSize::Normal).unwrap(),
) {
println!("Error when printing document: {error}");
return Ok(Response::new(PrintResponse::default()));
}
println!("Printing...");
if let Err(error) = printer.print_cut() {
println!("Error when printing: {error}");
return Ok(Response::new(PrintResponse::default()));
}
println!("Done!");
Ok(Response::new(reply))
Ok(Response::new(PrintResponse::default()))
}
}