Unofficial, third-party crate — not affiliated with the Typst project.
Generate PDFs with Typst straight from your Rust types.
Derive ToDict on a struct and it becomes a Typst input dictionary — feed it to
a .typ template as sys.inputs and render to PDF, with no hand-written dict
building. The ToValue / ToDict traits handle the Rust-to-Typst value
conversion, with optional chrono, time, rust_decimal, serde_json, and
uuid support.
Rendering uses two parts:
WorldBase— built once; loads fonts and roots file access at a directory.ConcreteWorld— one per document; attaches this generation'ssys.inputsand any virtual in-memory files (images, generated assets, …).
use typst_template::{ToDict, WorldBaseConfig};
#[derive(ToDict)]
struct Invoice {
client: String,
total: i64,
}
// Build the base once (here using only the embedded fonts).
let base = WorldBaseConfig::new("path/to/template").system_fonts(false).build();
let world = base
.concrete("main.typ")
.file("main.typ", "Invoice for #sys.inputs.client: #sys.inputs.total EUR")
.inputs(Invoice { client: "ACME".into(), total: 42 })
.build();
let pdf = world.render_pdf_default().output.expect("render succeeds");
assert_eq!(&pdf[..4], b"%PDF");compile() and render_pdf() return Typst's native Warned<SourceResult<…>> —
warnings plus errors, with full diagnostics (spans, hints).
On the struct (#[typst(...)]):
rename_all = "..."—lowercase,UPPERCASE,PascalCase,camelCase,snake_case,SCREAMING_SNAKE_CASE,kebab-case,SCREAMING-KEBAB-CASE.
On a field (#[typst(...)]):
rename = "name"— fixed key, overridingrename_all.skip— leave the field out.with = path— callpath(field) -> Valueinstead of the field'sToValue.flatten— merge the field's own dict in place of nesting it (field must beToDict).
Without a time feature, datetime.today() is disabled by default (returns
nothing); pin a value with .today(dt). With chrono or time enabled it
defaults to the system clock, and .today_system_in(tz) (with chrono-tz or
time-tz) sets "local" to a DST-aware named zone — handy when the document's
time zone differs from the server's. A Fixed value answers only offset-less
requests; an explicit datetime.today(offset: N) returns nothing.
derive(default) — theToDictderive macro.chrono—ToValueforchronodate/time types, plus a system-clockdatetime.today().time—ToValuefortimedate/time types, plus a system-clockdatetime.today().chronotakes precedence if both are enabled.chrono-tz/time-tz— named-time-zone support for.today_system_in.rust_decimal—ToValueforrust_decimal::Decimal.serde_json—ToValue/ToDictforserde_jsonvalues.uuid—ToValueforuuid::Uuid.
MIT