No headless browser
Rust all the way down. The compiler runs in your own process: no subprocess, no browser, no document service to operate.
Rust has solid low-level PDF crates, but hand-coding every table and page break gets old fast. And bundling a headless browser defeats the point of a lean Rust service.
Oicana is written in Rust, and the oicana crate is the most direct way to use it. Add it with cargo add oicana, load a packed template once, and compile documents from JSON:
use std::fs::File;
use oicana::Template;use oicana::export::pdf::export_pdf;use oicana::input::input::json::JsonInput;use oicana::input::{CompilationConfig, TemplateInputs};
let mut template = Template::init(File::open("invoice-0.1.0.zip")?)?;
let mut inputs = TemplateInputs::new();inputs.with_config(CompilationConfig::production());inputs.with_input(JsonInput::new( "invoice", serde_json::json!({ "number": "2026-001", "customer": "Acme GmbH", "total": "€1,190.00" }) .to_string(),));
let document = template.compile(inputs)?;
let pdf = export_pdf( &document.document, &template, template.manifest().pdf_standards(), template.manifest().pdf_tagged(), None,)?;A warmed up template produces a print-ready PDF in single digit milliseconds, directly in your service.
The snippet loads invoice-0.1.0.zip, a packed Oicana template. Templates are plain Typst projects: a typst.toml manifest declares a JSON input named invoice, and main.typ uses it:
#import "@preview/oicana:0.2.0": setup
#let read-project-file(path) = read(path, encoding: none)#let (input, oicana-image, oicana-config) = setup(read-project-file)
#set document(date: datetime.today())
= Invoice #input.invoice.number
Billed to: #input.invoice.customer
*Total: #input.invoice.total*Running oicana pack in the template directory produces the zip. Create a Template walks through the manifest and input definitions, and the open source example templates include a complete invoice.
Load the template once at startup and share it across handlers, for example via Arc in your Axum state; after that there is no file I/O on the hot path. For managing multiple templates, the example project demonstrates thread-safe template caching with a DashMap.
The Axum example project shows a complete service, including blob inputs, error handling, and OpenAPI documentation.
No headless browser
Rust all the way down. The compiler runs in your own process: no subprocess, no browser, no document service to operate.
Your data stays with you
Documents are generated inside your own service. No customer data is sent to a third party, which keeps GDPR and HIPAA compliance in your hands.
Flat price, unlimited documents
One flat license per application, no metering. A hundred invoices a month cost the same as a hundred thousand. Non-commercial use is free.