Commit: 1e0ebfb
Parent: 91b9e95

tonic HelloWorld tutorial

Mårten Åsberg committed on 2026-03-19 at 17:12
Cargo.lock +633 -3178
Large diff (3811 lines changed) - not displayed
Cargo.toml +7 -4
diff --git a/Cargo.toml b/Cargo.toml
index ee16a38..350db14 100644
@@ -4,7 +4,10 @@ version = "0.1.0"
edition = "2024"
[dependencies]
escpos = { version = "0.17.0", features = ["graphics"] }
typst = "0.14.2"
typst-as-lib = { version = "0.15.4", features = ["typst-kit-fonts"] }
typst-render = "0.14.2"
prost = "0.14.3"
tokio = { version = "1.50.0", features = ["rt-multi-thread", "macros"] }
tonic = "0.14.5"
tonic-prost = "0.14.5"
[build-dependencies]
tonic-prost-build = "0.14.5"
build.rs +4 -0
diff --git a/build.rs b/build.rs
new file mode 100644
index 0000000..3100f02
@@ -0,0 +1,4 @@
fn main() -> Result<(), Box<dyn std::error::Error>> {
tonic_prost_build::compile_protos("proto/helloworld.proto")?;
Ok(())
}
proto/helloworld.proto +18 -0
diff --git a/proto/helloworld.proto b/proto/helloworld.proto
new file mode 100644
index 0000000..6be2196
@@ -0,0 +1,18 @@
syntax = "proto3";
package helloworld;
service Greeter {
// Our SayHello rpc accepts HelloRequests and returns HelloReplies
rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest {
// Request message contains the name to be greeted
string name = 1;
}
message HelloReply {
// Reply contains the greeting message
string message = 1;
}
receipt.typ +0 -14
diff --git a/receipt.typ b/receipt.typ
deleted file mode 100644
index c5a3cb8..0000000
@@ -1,14 +0,0 @@
#set page(
width: 7cm,
height: auto,
margin: (y: 0.1cm, x: 0.05cm),
fill: none,
)
#set text(font: "Times New Roman")
#show title: set align(center)
#title[Mårtens Split App]
#set par(justify: true)
#lorem(40)
src/main.rs +31 -77
diff --git a/src/main.rs b/src/main.rs
index 2d11b92..f0638ff 100644
@@ -1,86 +1,40 @@
use std::{env::args, time::Duration};
use tonic::{transport::Server, Request, Response, Status};
use escpos::{
driver::NetworkDriver,
printer::Printer,
utils::{BitImageOption, BitImageSize, Protocol},
};
use typst::layout::PagedDocument;
use typst_as_lib::{TypstEngine, typst_kit_options::TypstKitFontOptions};
use hello_world::greeter_server::{Greeter, GreeterServer};
use hello_world::{HelloReply, HelloRequest};
fn main() {
let args: Vec<_> = args().skip(1).collect();
if args.len() != 2 {
println!("Please provide the printer connection string and .typ template as arguments");
return;
};
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;
};
let template_path = &args[1];
pub mod hello_world {
tonic::include_proto!("helloworld"); // The string specified here must match the proto package name
}
let engine = TypstEngine::builder()
.with_file_system_resolver(".")
.search_fonts_with(TypstKitFontOptions::new().include_system_fonts(true))
.build();
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let addr = "[::1]:50051".parse()?;
let greeter = MyGreeter::default();
println!("Compiling document...");
let document: PagedDocument = match engine.compile(template_path.as_ref()).output {
Ok(document) => document,
Err(error) => {
println!("Failed to compile with message: {error}");
return;
}
};
Server::builder()
.add_service(GreeterServer::new(greeter))
.serve(addr)
.await?;
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;
Ok(())
}
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;
}
};
#[derive(Debug, Default)]
pub struct MyGreeter {}
println!("Connecting to printer...");
let driver = match NetworkDriver::open(printer_host, printer_port, Some(Duration::from_secs(5)))
{
Ok(driver) => driver,
Err(error) => {
println!("Error connecting to printer: {error}");
return;
}
};
println!("Initializing printer...");
let mut printer = Printer::new(driver, Protocol::default(), None);
if let Err(error) = printer.init() {
println!("Error initializing printer: {error}");
return;
}
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;
}
println!("Printing...");
if let Err(error) = printer.print_cut() {
println!("Error when printing: {error}");
return;
#[tonic::async_trait]
impl Greeter for MyGreeter {
async fn say_hello(
&self,
request: Request<HelloRequest>, // Accept request of type HelloRequest
) -> Result<Response<HelloReply>, Status> { // Return an instance of type HelloReply
println!("Got a request: {:?}", request);
let reply = HelloReply {
message: format!("Hello {}!", request.into_inner().name), // We must use .into_inner() as the fields of gRPC requests and responses are private
};
Ok(Response::new(reply)) // Send back our formatted greeting
}
println!("Done!");
}