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 self-hosted server, 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 in the self-hosted server 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/UA-1 and PDF/A-2 — accessibility and archival compliance, veraPDF-verified since 0.15.0 (UA) and 0.16.0 (A); output from versions before those did not validate
- 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