Zum Inhalt springen
CASOON

Typst im Einsatz: Zwei Praxisprojekte für Reporting und Geschäftsdokumente

Wie Typst als Engine für automatisierte PDF-Generierung funktioniert

15 Minuten
Typst im Einsatz: Zwei Praxisprojekte für Reporting und Geschäftsdokumente
#Typst #PDF #Automatisierung #Open Source
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:

  1. docgen – Ein CLI-Tool für Geschäftsdokumente (Rechnungen, Angebote, Konzepte)
  2. 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ösungProblem
SaaS-Tools (Lexoffice, Billomat)Monatliche Kosten, Vendor Lock-in, Daten in der Cloud
Word/ExcelInkonsistente Formatierung, manuelle Fehler, kein Versionskontrolle
LaTeXZu 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

┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ JSON-Daten │────▶│ docgen CLI │────▶│ PDF-Output │ │ (invoice.json) │ │ + Typst │ │ (RE-2025-001) │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ ▼ │ ┌─────────────────┐ │ │ Typst Templates │ │ │ (invoice.typ) │ │ └─────────────────┘ │ │ ▼ ▼ ┌─────────────────┐ ┌─────────────────┐ │ company.json │ │ locale/de.json │ │ (Stammdaten) │ │ (Übersetzungen)│ └─────────────────┘ └─────────────────┘

Der Workflow ist simpel:

  1. Daten in JSON beschreiben – strukturiert, versionierbar, maschinell erzeugbar
  2. docgen aufrufen – das CLI findet automatisch das richtige Template
  3. 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 update aktualisiert 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:

KomponenteBeschreibung
ScoreCardMetrik mit visueller Score-Anzeige
FindingAudit-Finding mit Severity-Level
AuditTableDatentabelle für Ergebnisse
CalloutInfo-Box (Success, Warning, Error)
ChartDiagramme (Bar, Line, Pie, Radar)
CrosstabPivot-Tabelle mit Aggregation
BarcodeQR-Code, EAN, Code128, etc.
ProgressBarVisuelle 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

Aspektdocgenrenderreport
ZielgruppeEndanwender, FreelancerEntwickler, die Reports einbetten
Typst-KenntnisseOptional (für Anpassungen)Nicht nötig
DatenformatJSON-DateienRust-Strukturen
FlexibilitätHoch (eigene Templates)Mittel (Komponenten-basiert)
IntegrationCLI, standaloneLibrary, eingebettet
Use CaseGeschäftsdokumenteAutomatisierte 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: