Forme is now on crates.io
The Rust PDF engine that powers Forme is now available as forme-pdf on crates.io. Use the layout engine and PDF writer directly from Rust — cargo add forme-pdf.
The Forme rendering engine has always been written in Rust. It powers the WASM builds, the hosted API, and the VS Code extension. But until now, the only way to use it from Rust was to clone the repo and depend on a path.
forme-pdf is now on crates.io.
What you get
cargo add forme-pdf
use forme::{render_json, FormeError};
fn main() -> Result<(), FormeError> {
let json = r#"{
"pages": [{ "size": "A4" }],
"children": [{
"kind": "text",
"content": "Hello from Forme!",
"style": { "fontSize": 24, "margin": { "top": 72, "left": 72 } }
}]
}"#;
let pdf_bytes = render_json(json)?;
std::fs::write("hello.pdf", pdf_bytes).unwrap();
Ok(())
}
The crate is published as forme-pdf (because forme was taken on crates.io), but the library name is forme — so your use statements are use forme::render_json, use forme::Document, etc.
The full engine, not a wrapper
This isn't a thin client for a hosted API. It's the actual layout engine: flexbox distribution, page-aware line breaking, font subsetting, PDF serialization — all running natively. The same code that runs in the browser via WASM and on the hosted API via Axum.
What's included:
- Page-native layout — every layout decision respects page boundaries. No infinite-canvas-then-slice.
- Flexbox — row, column, wrap, grow/shrink, all alignment modes, cross-axis stretch
- CSS Grid — track sizing, auto/explicit placement,
repeat()syntax - Text — OpenType shaping (rustybuzz), Knuth-Plass line breaking, hyphenation in 35+ languages, BiDi (UAX#9), per-character font fallback with builtin Noto Sans
- Tables — automatic header repetition on continuation pages
- Images — JPEG, PNG, WebP
- Charts — BarChart, LineChart, PieChart, AreaChart, DotPlot rendered as PDF vector graphics
- QR codes & barcodes — Code128, Code39, EAN13, EAN8, Codabar
- SVG — rect, circle, line, path (including arcs)
- Tagged PDF / PDF/A-2a — accessibility and archival compliance
- Templates — expression language (
$ref,$each,$if, comparisons, arithmetic) for dynamic documents - Embedded data — attach JSON inside the PDF, extract it later
Two entry points
JSON — parse a JSON document and render:
let pdf = forme::render_json(json_str)?;
Structs — build a Document directly:
use forme::{Document, Node, NodeKind, Style, PageConfig, PageSize, render};
let doc = Document {
children: vec![Node {
kind: NodeKind::Text { content: "Hello".into() },
style: Some(Style { font_size: Some(24.0), ..Default::default() }),
..Default::default()
}],
pages: vec![PageConfig { size: Some(PageSize::A4), ..Default::default() }],
..Default::default()
};
let pdf = render(&doc)?;
Both return Result<Vec<u8>, FormeError>. The _with_layout variants also return a LayoutInfo struct with the position and dimensions of every element on every page.
Templates
For server-side rendering without a JavaScript runtime, use the template system:
let template = r#"{ "children": [{ "kind": "text", "content": { "$ref": "name" } }] }"#;
let data = r#"{ "name": "Jane Smith" }"#;
let pdf = forme::render_template(template, data)?;
Templates support $ref (data access), $each (iteration), $if (conditionals), comparisons, arithmetic, string operations, and $format (number formatting). The expression evaluator runs in Rust — no JavaScript needed.
WASM compilation
The crate compiles to WebAssembly. This is how @formepdf/core works — the same engine, compiled to WASM via wasm-pack:
[dependencies]
forme-pdf = { version = "0.7", features = ["wasm"] }
The wasm-raw feature provides C-ABI exports for non-JS WASM hosts (Python via wasmtime, Go via wazero).
Install
cargo add forme-pdf