Ein PDF, das ein Screenreader vorlesen kann, braucht eine Tag-Struktur: Überschriftenebenen, Lesereihenfolge, Alternativtexte, Tabellenköpfe. Wer das nachträglich in Acrobat baut, verbringt pro Dokument eine halbe Stunde — und beim nächsten Export fängt er wieder von vorne an.
Mit WeasyPrint entsteht die Struktur aus dem HTML. Du baust die Barrierefreiheit einmal in die Vorlage und bekommst sie bei jedem Export geschenkt.
Getestet mit
- WeasyPrint 69
- Python 3.12
- Debian 12
- Geprüft mit veraPDF und PAC
Schritt 1: Installieren
python3 -m venv /opt/pdfgen/venv
/opt/pdfgen/venv/bin/pip install weasyprint
Auf Debian braucht WeasyPrint noch die Pango-Bibliotheken:
apt install libpango-1.0-0 libpangoft2-1.0-0
Schritt 2: HTML mit tragfähiger Struktur
Das ist der eigentliche Teil der Arbeit. Alles, was später im PDF als Struktur auftauchen soll, muss im HTML als Struktur vorhanden sein.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>Wartungsbericht Juli 2026</title>
</head>
<body>
<h1>Wartungsbericht Juli 2026</h1>
<p>Zusammenfassung der durchgeführten Arbeiten.</p>
<h2>Updates</h2>
<table>
<caption>Installierte Aktualisierungen</caption>
<thead>
<tr><th scope="col">Komponente</th><th scope="col">Version</th></tr>
</thead>
<tbody>
<tr><td>WordPress</td><td>6.8</td></tr>
</tbody>
</table>
<img src="diagramm.png" alt="Ladezeit sinkt von 3,1 auf 0,9 Sekunden">
</body>
</html>
Drei Dinge sind hier nicht Kosmetik, sondern Pflicht:
lang="de" am html-Element. Ohne Sprachangabe liest der Screenreader deutschen Text mit englischer Aussprache vor. Das ist unbrauchbar, und PDF/UA verlangt es.
scope="col" an den Tabellenköpfen. Ohne das ist eine Tabelle für den Screenreader eine Ansammlung zusammenhangloser Zellen.
Ein alt, das die Aussage transportiert, nicht den Dateinamen. „Diagramm“ hilft niemandem.
Schritt 3: Erzeugen
from weasyprint import HTML
HTML(filename='bericht.html').write_pdf(
'bericht.pdf',
pdf_variant='pdf/ua-1',
)
Oder über die Kommandozeile:
/opt/pdfgen/venv/bin/weasyprint --pdf-variant pdf/ua-1 bericht.html bericht.pdf
Metadaten setzt du über die üblichen HTML-Elemente — <title> wird zum Dokumenttitel, den PDF/UA zwingend verlangt.
Der Export lief durch und war trotzdem ungültig
WeasyPrint meldete keinen Fehler. veraPDF hat die Datei abgelehnt, mit einer Meldung über eine fehlerhafte Struktur-Hierarchie.
Es lag an der Vorlage. Für das Layout hatte ich Umbrüche mit <div>-Wrappern gebaut, die mitten in einem Abschnitt begannen und erst nach der nächsten Überschrift endeten. Im Browser sah das perfekt aus. Im PDF wurde daraus eine verschachtelte Tag-Struktur, in der eine H3 innerhalb eines Absatzes lag — was es laut Spezifikation nicht geben darf.
Die Meldung von WeasyPrint gab darauf keinen Hinweis, weil WeasyPrint nur übersetzt, was es bekommt. Gefunden habe ich es über die Tag-Struktur des erzeugten PDFs:
/opt/pdfgen/venv/bin/python -c "
import pikepdf
pdf = pikepdf.open('bericht.pdf')
print(pdf.Root.get('/StructTreeRoot'))
"
WeasyPrint macht aus schlechtem HTML kein gutes PDF. Wenn die Ausgabe nicht validiert, liegt der Fehler fast immer in der Vorlage — typischerweise dort, wo Layout-Container die inhaltliche Gliederung durchschneiden.
Der Umbau bestand darin, das Layout über CSS zu lösen statt über zusätzliche Container. Seitenumbrüche macht ohnehin CSS besser:
h2 { break-before: page; }
table { break-inside: avoid; }
thead { display: table-header-group; }
display: table-header-group sorgt dafür, dass die Kopfzeile einer mehrseitigen Tabelle auf jeder Seite wiederholt wird — für sehende Leser eine Erleichterung, für Screenreader eine Notwendigkeit.
Kontrolle
Verlass dich nicht auf „ist durchgelaufen“. Prüf mit veraPDF:
verapdf --flavour ua1 bericht.pdf
Erwartet wird PASS. Zusätzlich lohnt ein Blick mit PAC, weil dort die Lesereihenfolge sichtbar wird — ein PDF kann formal gültig sein und trotzdem in unsinniger Reihenfolge vorgelesen werden.
Häufige Fehler
Reicht PDF/UA für die gesetzlichen Anforderungen?
PDF/UA-1 ist der technische Standard für barrierefreie PDFs und die übliche Referenz. Ob ein Dokument in deinem konkreten Fall genügt, hängt vom Kontext ab — gerade bei Dokumenten, die Teil einer Dienstleistung sind, lohnt eine Einzelprüfung.
Warum kommt der Text als Bild raus?
Dann liegt eine Schrift nicht vor und WeasyPrint rendert einen Ersatz. Bind die Schrift lokal per @font-face ein.
Geht das auch aus WordPress heraus?
Ja. Du renderst eine Vorlage als HTML und schickst sie an ein kleines Python-Skript. Der Vorteil bleibt derselbe: Die Barrierefreiheit steckt in der Vorlage, nicht in der Nacharbeit.