Metadata-Version: 2.4
Name: trustrender
Version: 0.1.3
Summary: Fast, code-first PDF generation from structured data. No browser, no Chromium.
License-Expression: MIT
Project-URL: Homepage, https://trustrender.dev
Project-URL: Documentation, https://trustrender.dev
Project-URL: Source, https://github.com/verityengine/trustrender
Project-URL: Issues, https://github.com/verityengine/trustrender/issues
Project-URL: Changelog, https://github.com/verityengine/trustrender/releases
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Office/Business :: Financial
Classifier: Topic :: Text Processing :: Markup
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typst>=0.14
Requires-Dist: jinja2>=3.1
Requires-Dist: starlette>=0.40
Requires-Dist: uvicorn>=0.30
Requires-Dist: drafthorse>=2024.0
Requires-Dist: pypdf>=4.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: ruff>=0.4; extra == "dev"
Requires-Dist: httpx>=0.27; extra == "dev"
Requires-Dist: factur-x>=3.0; extra == "dev"
Provides-Extra: zugferd
Requires-Dist: factur-x>=3.0; extra == "zugferd"
Dynamic: license-file

# TrustRender

Structured business PDFs from code. Pre-render validated. No browser. No Chromium.

**Website:** [trustrender.dev](https://trustrender.dev)
**PyPI:** [pypi.org/project/trustrender](https://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](https://typst.app/) 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](https://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 `/render` endpoint accepts template source code via the `template_source` field. Without authentication, any client that can reach the server can submit arbitrary templates for rendering.
- The `/template-source` endpoint 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.1` by default. Passing `--host 0.0.0.0` opens 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:**

```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](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.

```python
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](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](docs/validation.md) |
| E-invoice scope matrix | [docs/einvoice-scope.md](docs/einvoice-scope.md) |
| HTTP server & error model | [docs/server.md](docs/server.md) |
| Templates & escaping | [docs/templates.md](docs/templates.md) |
| Fonts | [docs/fonts.md](docs/fonts.md) |
| Provenance | [docs/provenance.md](docs/provenance.md) |
| Known limits | [docs/known-limits.md](docs/known-limits.md) |

## Caveats

- Typst silently substitutes fonts when a declared font is missing — `preflight` and `doctor` catch 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)
