Wie Typst als Engine für automatisierte PDF-Generierung funktioniert
SerieTypst
Teil 4 von 6
Theorie ist gut, Praxis ist besser. In den vorherigen Artikeln ging es um Typst-Grundlagen, Syntax und Templates. Jetzt zeige ich, wie Typst in realen Projekten als PDF-Engine eingesetzt wird – mit konkreten Architekturentscheidungen, Workflows und den Lektionen, die ich dabei gelernt habe.
Zwei Open-Source-Projekte stehen im Fokus:
- docgen – Ein CLI-Tool für Geschäftsdokumente (Rechnungen, Angebote, Konzepte)
- renderreport – Eine Rust-Library für datengetriebene Reports
Beide nutzen Typst, aber auf völlig unterschiedliche Weise.
Projekt 1: docgen – Geschäftsdokumente ohne teure Software
Das Problem
Kleine Unternehmen und Freelancer brauchen professionelle Dokumente: Rechnungen, Angebote, Zugangsdaten für Kunden, Projektkonzepte. Die üblichen Lösungen haben Nachteile:
| Lösung | Problem |
|---|---|
| SaaS-Tools (Lexoffice, Billomat) | Monatliche Kosten, Vendor Lock-in, Daten in der Cloud |
| Word/Excel | Inkonsistente Formatierung, manuelle Fehler, kein Versionskontrolle |
| LaTeX | Zu komplex für einfache Geschäftsdokumente |
Die Idee: JSON-Daten + Typst-Templates = professionelle PDFs. Keine Abo-Kosten, volle Kontrolle über die Daten, Git-versionierbar.
Die Architektur
Der Workflow ist simpel:
- Daten in JSON beschreiben – strukturiert, versionierbar, maschinell erzeugbar
- docgen aufrufen – das CLI findet automatisch das richtige Template
- PDF landet im Output-Ordner – mit korrekter Nummerierung
JSON statt Formulare
Eine Rechnung sieht als JSON so aus:
{
"metadata": {
"invoice_number": "RE-2025-017",
"invoice_date": { "date": "2025-01-20" },
"due_date": { "date": "2025-02-03" },
"customer_number": "K-012",
"project_reference": "P-012-03 Website Relaunch"
},
"recipient": {
"name": "Klaus Zimmermann",
"company": "Zimmermann Schreinerei GmbH",
"address": {
"street": "Handwerkerweg",
"house_number": "8",
"postal_code": "51465",
"city": "Bergisch Gladbach"
}
},
"items": [
{
"description": "Konzeption & Wireframes",
"sub_items": [
"- Analyse der bestehenden Website",
"- Erstellung von Wireframes für 8 Seitentypen"
],
"quantity": "16",
"unit": "Stunden",
"unit_price": { "amount": "110", "currency": "EUR" },
"vat_rate": { "percentage": "19" }
}
]
}
Das mag auf den ersten Blick mehr Aufwand erscheinen als ein Formular. Aber:
- Versionskontrolle: Jede Änderung ist in Git nachvollziehbar
- KI-Generierung: Claude oder andere LLMs erzeugen JSON zuverlässig
- Automatisierung: Externe Systeme können Rechnungen programmatisch erstellen
- Keine Vendor-Abhängigkeit: Die Daten gehören dir, in einem offenen Format
Der KI-Workflow
Der eigentliche Produktivitätsgewinn liegt im Zusammenspiel mit KI-Assistenten:
Prompt an Claude:
"Erstelle eine Rechnung für Kunde Zimmermann Schreinerei (K-012).
40 Stunden Webentwicklung à 110 EUR, Projekt Website Relaunch.
Zahlungsziel 14 Tage."
→ Claude generiert das vollständige JSON
→ Kurze Prüfung
→ docgen compile invoice.json
→ PDF fertig
Statt durch Formulare zu klicken, beschreibt man in natürlicher Sprache, was man braucht. Das JSON-Schema gibt der KI die Struktur vor.
Template-System mit Packages
Die Typst-Templates sind als lokale Packages organisiert:
#import "@local/docgen-invoice:0.4.2": invoice
#let data = json("/documents/invoices/2025/RE-2025-017.json")
#let company = json("/data/company.json")
#let locale = json("/locale/de.json")
#show: invoice.with(
data: data,
company: company,
locale: locale,
)
Vorteile:
- Saubere Imports: Keine relativen Pfade wie
../../../templates/ - Versionierung: Dokumente bleiben reproduzierbar
- Einfache Updates:
docgen template updateaktualisiert alle Templates
Firmendaten zentral verwalten
Die company.json enthält alle Stammdaten:
{
"name": "Pixelwerk Digitalagentur",
"language": "de",
"branding": {
"accent_color": "#E94B3C",
"primary_color": "#2c3e50",
"font_preset": "inter"
},
"address": { ... },
"contact": { ... },
"bank_account": { ... },
"default_terms": {
"hourly_rate": "95.00",
"payment_days": 14,
"vat_rate": 19
}
}
Eine Änderung hier (neues Logo, neue Bankverbindung) wirkt sich auf alle zukünftigen Dokumente aus. Keine Suche durch Word-Vorlagen.
Lessons Learned
Was gut funktioniert:
- JSON + Typst ist eine mächtige Kombination
- KI-Assistenten erzeugen strukturierte Daten zuverlässig
- Einmal eingerichtet, ist der Workflow schneller als jede GUI
- Git-Historie für Geschäftsdokumente ist Gold wert
Stolpersteine:
- Initiale Einrichtung erfordert technisches Verständnis
- Nicht-technische Teammitglieder brauchen Einarbeitung
- Typst-Fehlermeldungen bei komplexen Templates manchmal kryptisch
Tipp für die Einführung:
Mit einfachen Dokumenten starten (Konzepte, Dokumentationen), dann zu Rechnungen übergehen. Die direkte .typ-Unterstützung macht den Einstieg leichter als JSON.
Projekt 2: renderreport – Typst als eingebettete Engine
Ein anderer Ansatz
Während docgen ein CLI-Tool ist, das Typst extern aufruft, geht renderreport einen Schritt weiter: Typst läuft als eingebettete Library in einer Rust-Anwendung.
Das Ziel: Datengetriebene Reports ohne Typst-Kenntnisse erstellen. Entwickler arbeiten mit einer Rust-API, die intern Typst-Code generiert und rendert.
Die Architektur
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Rust Code │────▶│ renderreport │────▶│ PDF-Output │
│ (Components) │ │ Engine │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│
▼
┌─────────────────┐
│ Embedded Typst │
│ (Library) │
└─────────────────┘
Der entscheidende Unterschied: Kein CLI-Aufruf, kein externer Prozess. Typst ist eine Rust-Library, die direkt eingebunden wird.
Komponenten statt Markup
Statt Typst-Syntax zu schreiben, verwendet man vordefinierte Komponenten:
use renderreport::prelude::*;
fn main() -> renderreport::Result<()> {
let engine = Engine::new()?;
let report = engine
.report("default")
.title("SEO-Audit Report")
.subtitle("example.com")
.add_component(ScoreCard::new("Performance", 87))
.add_component(ScoreCard::new("Accessibility", 92))
.add_component(ScoreCard::new("SEO Score", 78))
.add_component(Finding::new(
"Missing Alt-Texte",
Severity::Medium,
"23 Bilder ohne Alt-Attribute gefunden"
))
.add_component(
AuditTable::new(vec![
TableColumn::new("Seite"),
TableColumn::new("Ladezeit"),
TableColumn::new("Status")
])
.add_row(vec!["Startseite", "1.2s", "OK"])
.add_row(vec!["/produkte", "3.8s", "Langsam"])
.add_row(vec!["/kontakt", "0.9s", "OK"])
)
.build();
let pdf = engine.render_pdf(&report)?;
std::fs::write("seo-report.pdf", pdf)?;
Ok(())
}
Der Entwickler sieht nie Typst-Code. Die Engine übersetzt die Komponenten intern in Typst-Markup und rendert das PDF.
Verfügbare Komponenten
Die Komponentenbibliothek deckt typische Report-Anforderungen ab:
| Komponente | Beschreibung |
|---|---|
ScoreCard | Metrik mit visueller Score-Anzeige |
Finding | Audit-Finding mit Severity-Level |
AuditTable | Datentabelle für Ergebnisse |
Callout | Info-Box (Success, Warning, Error) |
Chart | Diagramme (Bar, Line, Pie, Radar) |
Crosstab | Pivot-Tabelle mit Aggregation |
Barcode | QR-Code, EAN, Code128, etc. |
ProgressBar | Visuelle Fortschrittsanzeige |
Die Inspiration kommt von etablierten Reporting-Engines wie JasperReports und Eclipse BIRT – aber mit moderner Rust-API und Typst als Rendering-Backend.
Theming mit Tokens
Das Styling funktioniert über ein Token-System, ähnlich CSS Custom Properties:
use renderreport::theme::{Theme, TokenValue};
let mut theme = Theme::new("brand", "Corporate Theme");
theme.tokens.set("color.primary", TokenValue::Color("#1a56db".into()));
theme.tokens.set("color.ok", TokenValue::Color("#059669".into()));
theme.tokens.set("color.warning", TokenValue::Color("#d97706".into()));
theme.tokens.set("font.heading", TokenValue::Font("Montserrat".into()));
let report = engine.report("default")
.theme(theme)
.title("Branded Report")
// ...
Ein Theme ändert das Erscheinungsbild aller Komponenten konsistent – ohne jede einzeln anzupassen.
Template Packs
Für spezialisierte Reports gibt es erweiterbare Packs:
// SEO-Audit Pack laden
engine.load_pack("seo-audit")?;
let report = engine
.report("seo-audit") // Pack-spezifisches Template
.pack("seo-audit")
// ...
Packs bringen eigene Komponenten, Templates und Themes mit. So lassen sich domänenspezifische Reports (SEO-Audits, Security-Assessments, Performance-Analysen) als wiederverwendbare Module verpacken.
Warum eingebettetes Typst?
Die Alternative wäre, Typst als CLI aufzurufen:
// CLI-Ansatz (nicht verwendet)
std::process::Command::new("typst")
.args(["compile", "report.typ", "output.pdf"])
.output()?;
Nachteile:
- Typst muss installiert sein
- Prozess-Overhead bei vielen Reports
- Fehlerbehandlung komplizierter
- Keine Kontrolle über das virtuelle Dateisystem
Mit eingebettetem Typst:
- Keine externe Abhängigkeit – alles in einer Binary
- Schnellere Kompilierung – kein Prozess-Spawn
- Virtuelles Dateisystem – Templates und Fonts in der Binary eingebettet
- Bessere Fehlerbehandlung – Rust-Typen statt Exit-Codes
Lessons Learned
Was gut funktioniert:
- Entwickler ohne Typst-Kenntnisse erstellen Reports
- Konsistentes Styling über Themes
- Pack-System für spezialisierte Reports
- Rust-Typsystem fängt viele Fehler zur Compile-Zeit
Herausforderungen:
- Typst als Library einbinden erfordert tiefes Verständnis
- Virtuelles Dateisystem für Fonts und Assets aufwendig
- Balance zwischen Flexibilität und Einfachheit
Architektur-Erkenntnis: Die Trennung in Komponenten (was wird dargestellt) und Themes (wie es aussieht) skaliert gut. Neue Report-Typen entstehen durch Kombination existierender Komponenten.
Vergleich der Ansätze
| Aspekt | docgen | renderreport |
|---|---|---|
| Zielgruppe | Endanwender, Freelancer | Entwickler, die Reports einbetten |
| Typst-Kenntnisse | Optional (für Anpassungen) | Nicht nötig |
| Datenformat | JSON-Dateien | Rust-Strukturen |
| Flexibilität | Hoch (eigene Templates) | Mittel (Komponenten-basiert) |
| Integration | CLI, standalone | Library, eingebettet |
| Use Case | Geschäftsdokumente | Automatisierte Reports |
Beide Projekte zeigen: Typst eignet sich hervorragend als PDF-Engine – ob als externes Tool oder eingebettete Library.
Tipps für die Team-Adoption
1. Klein starten
Nicht mit dem komplexesten Dokument beginnen. Ein einfaches Konzept-PDF oder eine Dokumentation eignet sich besser als Einstieg.
2. Templates vorbereiten
Bevor das Team startet, sollten die wichtigsten Templates fertig sein. Niemand möchte am ersten Tag Typst-Layouts debuggen.
3. JSON-Schemas dokumentieren
Für JSON-basierte Workflows: Schemas mit Beispielen bereitstellen. Tools wie JSON Schema Validator helfen bei der Validierung.
4. KI-Assistenten einsetzen
Claude, ChatGPT oder Copilot beschleunigen die Erstellung von JSON-Daten erheblich. Das senkt die Einstiegshürde.
5. Watch-Mode nutzen
docgen watch documents/
Automatische Neukompilierung bei Änderungen verkürzt die Feedback-Schleife drastisch.
6. Fehler als Lernchance
Typst-Fehlermeldungen sind verständlicher als LaTeX, aber Einarbeitung braucht Zeit. Eine kurze interne Doku mit häufigen Fehlern hilft.
Typst ist mehr als ein LaTeX-Ersatz – es ist eine moderne Plattform für dokumentenzentrierte Automatisierung. Die beiden Projekte zeigen unterschiedliche Integrationsansätze:
- docgen: Typst als externes Tool, JSON als Datenformat, ideal für Geschäftsdokumente
- renderreport: Typst als eingebettete Library, Komponenten-API, ideal für automatisierte Reports
Beide Ansätze profitieren von Typsts Stärken: schnelle Kompilierung, lesbare Syntax, moderne Tooling-Integration.
Für Teams, die von Word-Vorlagen oder teuren SaaS-Tools weg wollen, ist der Einstieg über ein Projekt wie docgen niedrigschwellig. Für Entwickler, die PDF-Generierung in ihre Anwendungen einbauen wollen, bietet renderreport einen Weg ohne Typst-Lernkurve.
Projekt-Links:
- typst-business-templates (docgen)
- renderreport
- Typst Documentation
- docgen Landingpage — CLI-Tool für professionelle Geschäftsdokumente aus der Kommandozeile