6. August 2026

Barrierefreie PDFs aus HTML erzeugen mit WeasyPrint

PDF/UA-1 direkt aus HTML und CSS, ohne Acrobat und ohne Nacharbeit. Der Weg funktioniert — aber nur, wenn die HTML-Struktur stimmt, und genau daran ist mein erster Versuch gescheitert.

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.

Falls du nicht weiterkommst

Ich mache das beruflich. Schreib mir kurz, worum es geht — ich sag dir ehrlich, ob ich helfen kann.
Zu purin.at
← Alle Beiträge