Die Full-Stack-PDF-Bibliothek
brevier ist in Python geschrieben. Es erzeugt PDFs aus den einfachsten Web-Werkzeugen: HTML, CSS, JSON und Handlebars. Oder Sie schreiben die Vorlage in Markdown, das KI-Agenten mühelos lesen und schreiben.
pip install brevierrechnung/rechnung.html
<!DOCTYPE html>
<html lang="de">
<head><meta charset="utf-8"><title>Rechnung {{ rechnung.nummer }}</title></head>
<body>
<p class="absender">{{ firma.name }}, {{ firma.anschrift }}</p>
<address>{{ kunde.name }}<br>{{ kunde.strasse }}<br>{{ kunde.ort }}</address>
<h1>Beitragsrechnung {{ rechnung.nummer }}</h1>
<p>Datum: {{ rechnung.datum | date }}</p>
<table>
<thead>
<tr><th>Pos.</th><th>Leistung</th><th>Betrag</th></tr>
</thead>
<tbody>
{{#each rechnung.positionen}}
<tr><td>{{ pos }}</td><td>{{ text }}</td><td>{{ betrag | money }}</td></tr>
{{/each}}
</tbody>
</table>
<p class="summe">Summe {{ rechnung.summe | money }}</p>
</body>
</html>rechnung/rechnung.css
@page {
size: A4;
margin: 25mm 20mm 25mm 25mm;
@bottom-right {
content: "Seite " counter(page) " von " counter(pages);
font: 8pt sans-serif;
}
}
body { font: 10pt/1.4 sans-serif; color: #16181c; }
.absender { font-size: 7pt; border-bottom: 0.5pt solid #16181c; }
address { font-style: normal; margin: 2mm 0 20mm; }
h1 { font-size: 14pt; }
table { width: 100%; border-collapse: collapse; }
th { text-align: left; border-bottom: 1pt solid #16181c; }
td { padding: 1.5mm 0; border-bottom: 0.5pt solid #d5d9df; }
td:first-child { width: 14mm; }
td:nth-child(2) { width: 34mm; }
td:last-child, th:last-child { text-align: right; }
.summe { text-align: right; font-weight: bold; }rechnung/rechnung.json
{
"firma": { "name": "Muster Versicherung AG", "anschrift": "Hauptstraße 1, 10115 Berlin" },
"kunde": { "name": "Erika Mustermann", "strasse": "Beispielweg 7", "ort": "50667 Köln" },
"rechnung": {
"nummer": "R-2026-0142",
"datum": "2026-09-25",
"positionen": [
{ "pos": 1, "text": "Berufsunfähigkeitsversicherung", "betrag": 84.2 },
{ "pos": 2, "text": "Haftpflichtversicherung", "betrag": 7.9 },
{ "pos": 3, "text": "Hausratversicherung", "betrag": 12.4 }
],
"summe": 104.5
}
}rechnung/rechnung.schema.json
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"type": "object",
"properties": {
"firma": { "$ref": "#/$defs/Firma" },
"kunde": { "$ref": "#/$defs/Kunde" },
"rechnung": { "$ref": "#/$defs/Rechnung" }
},
"required": ["firma", "kunde", "rechnung"],
"$defs": {
"Firma": {
"type": "object",
"properties": {
"name": { "type": "string" },
"anschrift": { "type": "string" }
},
"required": ["name", "anschrift"]
},
"Kunde": {
"type": "object",
"properties": {
"name": { "type": "string" },
"strasse": { "type": "string" },
"ort": { "type": "string" }
},
"required": ["name", "strasse", "ort"]
},
"Rechnung": {
"type": "object",
"properties": {
"nummer": { "type": "string", "title": "Rechnungsnummer" },
"datum": { "type": "string", "format": "date", "title": "Rechnungsdatum" },
"positionen": { "type": "array", "items": { "$ref": "#/$defs/Position" } },
"summe": { "type": "number", "format": "decimal", "title": "Gesamtbetrag" }
},
"required": ["nummer", "datum", "positionen", "summe"]
},
"Position": {
"type": "object",
"properties": {
"pos": { "type": "integer" },
"text": { "type": "string" },
"betrag": { "type": "number", "format": "decimal" }
},
"required": ["pos", "text", "betrag"]
}
}
}rechnung/rechnung.pdf

rechnung/rechnung.md
---
style: rechnung.css
---
{{ firma.name }}, {{ firma.anschrift }}
An: {{ kunde.name }}, {{ kunde.strasse }}, {{ kunde.ort }}
# Beitragsrechnung {{ rechnung.nummer }}
Datum: {{ rechnung.datum | date }}
| Pos. | Leistung | Betrag |
| --- | --- | ---: |
{{#each rechnung.positionen}}
| {{ pos }} | {{ text }} | {{ betrag | money }} |
{{/each}}
**Summe {{ rechnung.summe | money }}**rechnung/rechnung.css
@page {
size: A4;
margin: 25mm 20mm 25mm 25mm;
@bottom-right {
content: "Seite " counter(page) " von " counter(pages);
font: 8pt sans-serif;
}
}
body { font: 10pt/1.4 sans-serif; color: #16181c; }
.absender { font-size: 7pt; border-bottom: 0.5pt solid #16181c; }
address { font-style: normal; margin: 2mm 0 20mm; }
h1 { font-size: 14pt; }
table { width: 100%; border-collapse: collapse; }
th { text-align: left; border-bottom: 1pt solid #16181c; }
td { padding: 1.5mm 0; border-bottom: 0.5pt solid #d5d9df; }
td:first-child { width: 14mm; }
td:nth-child(2) { width: 34mm; }
td:last-child, th:last-child { text-align: right; }
.summe { text-align: right; font-weight: bold; }rechnung/rechnung.json
{
"firma": { "name": "Muster Versicherung AG", "anschrift": "Hauptstraße 1, 10115 Berlin" },
"kunde": { "name": "Erika Mustermann", "strasse": "Beispielweg 7", "ort": "50667 Köln" },
"rechnung": {
"nummer": "R-2026-0142",
"datum": "2026-09-25",
"positionen": [
{ "pos": 1, "text": "Berufsunfähigkeitsversicherung", "betrag": 84.2 },
{ "pos": 2, "text": "Haftpflichtversicherung", "betrag": 7.9 },
{ "pos": 3, "text": "Hausratversicherung", "betrag": 12.4 }
],
"summe": 104.5
}
}src/main/java/RechnungService.java
import com.brevier.Brevier;
public class RechnungService {
public byte[] pdf(Firma firma, Rechnung rechnung, Kunde kunde) {
return Brevier.template("rechnung")
.render(firma, rechnung, kunde);
}
}app/rechnung.py
import brevier
def pdf(firma, rechnung, kunde) -> bytes:
return brevier.template("rechnung").render(firma, rechnung, kunde)Alles, was ein Geschäftsdokument braucht
brevier kümmert sich um die Druckregeln. Sie kümmern sich um Ihre Daten.
Vorlagen in HTML und CSS
Die Seite ist HTML mit CSS. Kein proprietäres Format, kein Zeichencode.
Markdown für einfache Dokumente
Schreiben Sie einen Brief in Markdown mit Front Matter. brevier eject macht daraus das HTML, in das er übersetzt wird.
Datenbindung
Handlebars-Syntax bindet JSON in die Vorlage: Werte, Schleifen und Bedingungen.
Vorlagen gegen ein Schema geprüft
Ein falsch geschriebenes Feld fällt bei der Prüfung auf, bevor etwas gedruckt wird.
from-code: Start bei Ihren Klassen
Liest Java-Klassen oder Python-Modelle und schreibt ein Schema, eine Vorlage und Beispieldaten.
Die sichtbare Mitte
--emit-html schreibt genau das HTML, das die Engine erhalten hat, und --format html rendert eine Webansicht desselben Dokuments.
Deutsche und englische Silbentrennung
Komposita werden an der Fuge getrennt: Berufsunfähigkeits-versicherung, nicht Beruf-sunfähigkeit. Firmen ergänzen eigene Wörter.
Tabellen über mehrere Seiten
Festgelegte Spaltenbreiten und ein Tabellenkopf, der auf jeder Seite wiederkehrt.
Übertrag
Die laufende Summe steht unten auf jeder Seite und oben auf der nächsten.
Kopf- und Fußzeilen mit Seitenzähler
Seite 3 von 12, in einem bearbeitbaren Block, auf jeder Seite.
Verträge und Kleingedrucktes
Nummerierte Klauseln, ein Inhaltsverzeichnis mit Seitenzahlen und Kleingedrucktes, das lesbar bleibt. Keine Überschrift bleibt allein am Seitenende.
Alle Funktionen, jeweils mit Status
Zur Funktionsliste
Ein Brevier ist ein Gebetbuch: das kleine Buch, das man immer bei sich hat und jeden Tag benutzt. Das will brevier für Entwickler sein, die Code schreiben müssen, der PDFs erzeugt. Ich musste diese Arbeit selbst machen, mit Werkzeugen, die es einem schwer machen. Deshalb habe ich brevier gebaut: damit PDF-Erzeugung für Entwickler einfach wird.
Gebaut auf dem, was Sie schon kennen
HTML, CSS, JSON und Ihr eigener Code. Nichts Neues zu lernen.
HTML für die Seite
Die Vorlage ist eine HTML-Seite. Handlebars füllt sie mit Ihren Daten: Werte, Schleifen und Bedingungen.
developer.mozilla.orgCSS für das Aussehen
CSS bestimmt Schriften und Farben und auch die gedruckte Seite: Format, Ränder, Kopf- und Fußzeilen, Seitenzahlen.
CSS @pageJSON für die Daten
Ihre Daten kommen als JSON. Ein JSON Schema prüft jedes Feld, bevor etwas gedruckt wird.
json-schema.orgIhr eigener Code
Übergeben Sie die Objekte, die Sie schon haben, aus Java, Python und anderen Sprachen. brevier macht daraus die Daten.
Zu den Clients
Überall installieren
Installieren Sie brevier mit pip, einem Einzeiler-Skript oder Docker. Es läuft unter Linux, macOS und Windows.
Aus Ihrer Sprache aufrufenFür Python-3-Nutzer. pip braucht die Bibliothek Pango auf Ihrem System.
- brevier installieren
pip install brevier - Prüfen, ob es läuft
brevier --version - Das Beispiel oben auf dieser Seite erzeugen
brevier render rechnung.html --data rechnung.json -o rechnung.pdf
Das Skript installiert brevier in einem eigenen Ordner und legt den Befehl brevier in ~/.local/bin ab. Unter macOS installiert es auch Pango.
- brevier installieren
curl -fsSL https://get.brevier.dev | sh - Prüfen, ob es läuft
brevier --version - Das Beispiel oben auf dieser Seite erzeugen
brevier render rechnung.html --data rechnung.json -o rechnung.pdf
Das PowerShell-Skript installiert Pango über MSYS2, dann brevier für Ihren Benutzer, und trägt es in Ihren PATH ein.
- brevier installieren
iwr -useb https://get.brevier.dev/ps1 | iex - Prüfen, ob es läuft
brevier --version - Das Beispiel oben auf dieser Seite erzeugen
brevier render rechnung.html --data rechnung.json -o rechnung.pdf
Außer Docker ist nichts zu installieren. Das Image führt brevier in dem Ordner aus, den Sie unter /work einbinden.
- brevier installieren
docker pull brevier/brevier:latest - Prüfen, ob es läuft
docker run --rm brevier/brevier --version - Das Beispiel oben auf dieser Seite erzeugen
docker run --rm -v "$PWD:/work" brevier/brevier render rechnung.html --data rechnung.json -o rechnung.pdf
