Fast, code-first PDF generation from structured data. No browser, no Chromium.
Project description
TrustRender
Structured business PDFs from code. Pre-render validated. No browser. No Chromium.
Website: trustrender.dev PyPI: pypi.org/project/trustrender
Why TrustRender
- Pre-render validation — catches bad payloads before they reach the renderer
- Compliance support — EN 16931 / ZUGFeRD for the supported e-invoice path
- Provenance and hashing — records template, data, and output fingerprints for traceability
- Operationally lean — no browser or Chromium dependency
TrustRender renders invoices, statements, receipts, and similar structured documents using Typst as the layout engine and Jinja2 for data binding. It ships as a Python library, CLI, and HTTP server.
Non-goals
TrustRender is not:
- arbitrary HTML-to-PDF conversion
- a browser or headless renderer
- a visual or WYSIWYG editor
- a multi-format converter
It does one thing: structured business PDFs from code.
Install
pip install trustrender
Requires Python 3.11+ and the Typst CLI binary (brew install typst on macOS, or typst.app).
For e-invoice support: pip install "trustrender[zugferd]"
Development
git clone https://github.com/verityengine/trustrender.git
cd trustrender
pip install -e ".[dev]"
Verify
trustrender doctor --smoke
Checks Python version, backends, fonts, and runs a real render + server health check.
Security
trustrender serve has no built-in authentication, authorization, TLS, or rate limiting. It is designed to run as a backend service behind a reverse proxy. Do not expose the server port to the public internet.
If you deploy TrustRender as an HTTP server:
- Place it behind a reverse proxy (Nginx, Caddy, Traefik, cloud load balancer) that handles TLS termination and authentication.
- The
/renderendpoint accepts template source code via thetemplate_sourcefield. Without authentication, any client that can reach the server can submit arbitrary templates for rendering. - The
/template-sourceendpoint returns raw template file contents. Restrict access if templates contain business logic you consider sensitive. - Backpressure (503 when at concurrency limit) is the only built-in traffic control. It is not a substitute for rate limiting.
- The server binds to
127.0.0.1by default. Passing--host 0.0.0.0opens it to all interfaces — do this only behind a proxy.
TrustRender is a rendering engine, not a security boundary. Treat it like a database: powerful, essential, and never internet-facing without a gateway.
Quick start
pip install trustrender
trustrender quickstart
This creates a sample invoice template and starts the server. Open http://localhost:8190 to render your first PDF.
Python:
from trustrender import render
pdf = render("invoice.j2.typ", "invoice_data.json", output="invoice.pdf")
CLI:
trustrender render invoice.j2.typ invoice_data.json -o invoice.pdf
Server:
trustrender serve --templates . --dashboard --port 8190
Why TrustRender
Validated before render
Every render() call on a .j2.typ template validates data against the template's inferred contract by default. Missing fields, null values, and wrong structural types are rejected with specific field-level errors before Typst compilation starts.
TrustRenderError: Data validation failed: 11 field errors in invoice.j2.typ
sender: missing required field (expected: object)
items: missing required field (expected: list[object])
invoice_date: missing required field
preflight() goes further: structural validation, semantic checks, font verification, compliance eligibility, and text safety scanning — all without rendering.
No browser dependency
No Chromium, no Puppeteer, no headless browser. Typst compiles directly to PDF. The server runs renders as killable subprocesses with real timeout enforcement.
Measured on Apple Silicon (macOS, Python 3.12, Typst 0.14): 1,000-row invoice renders in 211ms (33 pages). Server throughput: 53.8 RPS. Peak RSS: 69.5 MB.
EN 16931 e-invoicing (narrow scope)
Supports a narrow subset of EN 16931 e-invoicing: domestic German B2B invoices with standard VAT, in EUR, via SEPA payment only. Reverse charge, cross-border, allowances/discounts, and non-EUR currencies are not supported. This is not full German e-invoicing mandate coverage. PDF/A-3b output with embedded CII XML. When the optional facturx library is installed (pip install "trustrender[zugferd]"), XSD and Schematron validation run before embedding; without it, field-level and arithmetic consistency validation still run but schema validation is skipped.
trustrender render einvoice.j2.typ data.json -o invoice.pdf --zugferd en16931
Supported: DE, EUR, standard VAT (single or mixed rates), invoices and credit notes. Not supported (fails loudly): reverse charge, cross-border, allowances/charges, non-EUR, zero/negative tax rates.
See docs/einvoice-scope.md for the full scope matrix.
Output provenance
Embeds a cryptographic generation proof in the PDF: template hash, data hash, engine version, timestamp, and a combined proof hash. Verifiable without re-rendering.
from trustrender.provenance import verify_provenance
result = verify_provenance(pdf_bytes, "invoice.j2.typ", original_data)
# result.verified → True if hashes match
Not a digital signature. A generation proof: "was this document produced from this data using this template?"
CLI
trustrender render <template> <data.json> -o <output.pdf> [--zugferd en16931] [--provenance] [--no-validate]
trustrender preflight <template> <data.json> [--semantic] [--strict]
trustrender check <template> [--data <data.json>]
trustrender serve --templates <dir> [--port 8190] [--dashboard] [--history <path>]
trustrender audit <template> <data.json> -o <output.pdf> [--baseline-dir <dir>]
trustrender doctor [--smoke]
Full flag reference: trustrender <command> --help.
HTTP server
| Method | Path | Purpose |
|---|---|---|
POST |
/render |
Render template to PDF |
POST |
/preflight |
Pre-render readiness check |
GET |
/health |
Health check |
GET |
/template-source?name= |
Raw template source |
GET |
/history |
Render trace list (requires --history) |
GET |
/dashboard |
Ops dashboard (requires --dashboard) |
Backpressure: max 8 concurrent renders (configurable), 503 when at capacity. Max body: 10 MB (configurable). Timeout: 30s (subprocess killed on expiry).
See docs/server.md for full API detail, error model, and configuration.
Bundled templates
| Template | File | Description |
|---|---|---|
| Invoice | examples/invoice.j2.typ |
Standard invoice with line items |
| E-Invoice | examples/einvoice.j2.typ |
ZUGFeRD EN 16931 compliant |
| Statement | examples/statement.j2.typ |
Account/transaction statement |
| Receipt | examples/receipt.j2.typ |
Point-of-sale receipt |
| Letter | examples/letter.j2.typ |
Business letter |
| Report | examples/report.j2.typ |
Executive report with metrics |
Each has a matching _data.json file in examples/.
Docker
docker build -t trustrender .
docker run -p 8190:8190 trustrender
Mount custom templates or fonts:
docker run -p 8190:8190 \
-v /path/to/templates:/templates -e TRUSTRENDER_TEMPLATES_DIR=/templates \
-v /path/to/fonts:/fonts -e TRUSTRENDER_FONT_PATH=/fonts \
trustrender
Configuration
| Variable | Purpose | Default |
|---|---|---|
TRUSTRENDER_BACKEND |
typst-py or typst-cli |
Auto-detect |
TRUSTRENDER_FONT_PATH |
Font directory | Bundled Inter fonts |
TRUSTRENDER_TEMPLATES_DIR |
Template directory for serve |
— |
TRUSTRENDER_MAX_BODY_SIZE |
Max request body (bytes) | 10 MB |
Development
make dev # editable install + dev deps
trustrender doctor --smoke # verify environment
make test # pytest
make lint # ruff
make docker # build image
make help # all targets
854 tests (unit, integration, contract, semantic, ZUGFeRD, provenance, ugly-data, font, pagination, text safety, Schematron).
Documentation
| Topic | Link |
|---|---|
| Validation & readiness | docs/validation.md |
| E-invoice scope matrix | docs/einvoice-scope.md |
| HTTP server & error model | docs/server.md |
| Templates & escaping | docs/templates.md |
| Fonts | docs/fonts.md |
| Provenance | docs/provenance.md |
| Known limits | docs/known-limits.md |
Caveats
- Typst silently substitutes fonts when a declared font is missing —
preflightanddoctorcatch this for configured font paths, but the render path itself does not error - Source mapping from generated Typst back to Jinja2 source is limited
typst_markup()intentionally bypasses escaping — template author's responsibility- Code/math mode contexts are not auto-escaped (text-interpolation only)
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file trustrender-0.1.2.tar.gz.
File metadata
- Download URL: trustrender-0.1.2.tar.gz
- Upload date:
- Size: 1.8 MB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
36a210fda3153ea26cf48ac05a51bce326f3f05ac2133017fff4d03f59145013
|
|
| MD5 |
35636220127cae070fa6dc165c40f801
|
|
| BLAKE2b-256 |
bd183eed9e3d8cd25259e8e260abf1bd3bf35fff8adac1298bf24b17ce80a8d3
|
Provenance
The following attestation bundles were made for trustrender-0.1.2.tar.gz:
Publisher:
publish.yml on verityengine/trustrender
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
trustrender-0.1.2.tar.gz -
Subject digest:
36a210fda3153ea26cf48ac05a51bce326f3f05ac2133017fff4d03f59145013 - Sigstore transparency entry: 1280355875
- Sigstore integration time:
-
Permalink:
verityengine/trustrender@a915fd0507d2ddff36482a1295569af3c5331421 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/verityengine
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a915fd0507d2ddff36482a1295569af3c5331421 -
Trigger Event:
release
-
Statement type:
File details
Details for the file trustrender-0.1.2-py3-none-any.whl.
File metadata
- Download URL: trustrender-0.1.2-py3-none-any.whl
- Upload date:
- Size: 1.7 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
257a3c5e010cd739ba3e0dac7798786c88a8f9ff32037990fb99f1e799143b85
|
|
| MD5 |
dc17c533716b5fc53a9c795b58f88f75
|
|
| BLAKE2b-256 |
e1ed42060ed9bb0f830fc06fcbf0bd474917658e7c386c042efb5069c2c38a01
|
Provenance
The following attestation bundles were made for trustrender-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on verityengine/trustrender
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
trustrender-0.1.2-py3-none-any.whl -
Subject digest:
257a3c5e010cd739ba3e0dac7798786c88a8f9ff32037990fb99f1e799143b85 - Sigstore transparency entry: 1280355885
- Sigstore integration time:
-
Permalink:
verityengine/trustrender@a915fd0507d2ddff36482a1295569af3c5331421 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/verityengine
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@a915fd0507d2ddff36482a1295569af3c5331421 -
Trigger Event:
release
-
Statement type: