Was bedeutet Barrierefrei in einem PDF überhaupt wirklich?
Das PDF genügt dem PDF/UA Standard (ISO 14289-1:2012) als Teil von ISO 32000.
Dazu muss das PDF dem Standard PDF/A-2 oder PDF/A-3 genügen, das sind (sehr große) Subsets des PDF-Standards von Adobe, die International als ISO genormt sind, damit ein langfristiger Datenaustausch (A steht für archivable, IIRC) gewährleistet ist. Wichtig ist nur, dass PDF/A-1 nicht ausreicht.
Für PDF/UA-1 muss das PDF mindestens Version 1.4 haben und für PDF/UA-2 mindestens Version 1.7. Das ist meines Wissens auch gleichzeitig die letzte vor PDF 2.0.
In der Praxis bedeutet dies, das PDF sollte Version 1.7 haben.
PDF ist ein Container-Format das Objekte und Stream enthält, wobei die meist komprimierten Streams neben Bildern die eigentliche PDF-Programmiersprache enthalten, die ein (nicht-turing-vollständiges) Subset von Postscript ist und die Grafikbefehle enthält, die die Seite "malen".
Da ist daher auf den ersten Blick nichts, was ein Screenreader vorlesen könnte, ohne selbst das PDF zu malen. Nun werden aber normalerweise nicht die Umrandungen der Grapheme cluster, die die Buchstaben bilden, direkt gemalt, sondern ein in das PDF (als Stream) eingebetteter Font im Adobe oder Truetype Format oder im Opentype Format enthält Beschreibungen alle Zeichenbestandteile in Form von Umrissen. Dazu gibt es dann in der PDF-Programmiersprache einen Befehl, der im Prinzip sagt, als der Seitenposition X/Y (abhängig von frei wählbarem Ursprung, Rotation und Skalierung) bitte die Umrisse für die Buchstaben ABC darstellen. All diese Befehle kann man extrahieren, dann unter Berücksichtigung der aus Ursprung, Rotation und Skalierung (all die Befehle, die das beeinflussen muss man daher auch extrahieren und interpretieren) die absolution Position des Texts auf der Seite bestimmen, dann die Tripel aus X, Y und Text topologisch sortieren und dann alle Befehle, die ungefähr die selbe Y Koordinate haben, zu einer Zeile zusammenfassen - und dabei berücksichtigen, dass es mehr als eine Spalte geben könnte. Dazu muss man die Lauflängen der Texte kennen, was man aus den Fonts extrahieren kann, wofür man aber wieder wissen muss, welche Zeichenkombinationen als Ligaturen dargestellt werden (was auch in den Fonts verzeichnet ist) und welche Laufrichtung der Text überhaupt hat (was bei Unicode mitten im Text umgeschaltet werden kann, wo ich jetzt gerade nicht auswendig weiß, ob PDF das auch unterstützt oder ob man dafür verschiedene Befehle benutzen muss - was ich glaube). Aus den von anderen Befehlen einstellbaren Schriftgrößen könnte man auch noch eine Hierarchie ableiten. Aber das alles ist eine Heuristik, die mehr oder weniger gut funktioniert.
Alternativ malt man als Screenreader einfach die Seite, hat dann ein Bild und geht dann mit OCR (Optical Character Recognition) dabei, hier wieder den Text zu extrahieren. Das ist im Vergleich auch nicht viel schwerer.
Wirft man einem guten LLM ein PDF vor, wird es genau so vorgehen. Es analysiert die Bilder, die von einem PDF-Viewer erzeugt wurden und weiß (dank KI) was Kopf- und Fußzeilen sind, was Fließtext ist und was die Zwischenüberschriften sind. Gerade wenn das Layout komplexer ist.
Somit käme ein moderner Screenreader wahrscheinlich bereits recht gut mit handelsüblichen PDF klar. Als man sich das mit dem UA-Substandard überlegt hat, war all das aber noch nicht erfunden und man wollte das Problem lieber so lösen, dass das PDF bitte genug Informationen enthält, dass man nicht selbst ein PDF-Parser und Interpreter sein muss, um den Text zu extrahieren.
Daher muss diese Struktur explizit gemacht werden.
Wer mal ein Beispiel sehen will, wie ein PDF wirklich aussieht, das ich gerade angeblich mit Accessibility Infos mit Pages erzeugt habe… (die Streams habe ich weggelassen)
%PDF-1.3
3 0 obj
<< /Filter /FlateDecode /Length 239 >>
stream...endstream
endobj
1 0 obj
<< /Type /Page /Parent 2 0 R /Resources 4 0 R /Contents 3 0 R /MediaBox [0 0 595.28 841.89]
>>
endobj
4 0 obj
<< /ProcSet [ /PDF /Text ] /ColorSpace << /Cs1 5 0 R >> /Font << /TT1 6 0 R
/TT2 7 0 R >> >>
endobj
9 0 obj
<< /N 3 /Alternate /DeviceRGB /Length 2612 /Filter /FlateDecode >>
stream...endstream
endobj
5 0 obj
[ /ICCBased 9 0 R ]
endobj
12 0 obj
<< /Type /StructTreeRoot /K 11 0 R /ParentTree 13 0 R /IDTree 14 0 R>>
endobj
11 0 obj
<< /Type /StructElem /S /Document /P 12 0 R /K [ 15 0 R 16 0 R ] >>
endobj
15 0 obj
<< /Type /StructElem /S /H1 /P 11 0 R /Pg 1 0 R /K 1 >>
endobj
16 0 obj
<< /Type /StructElem /S /P /P 11 0 R /Pg 1 0 R /K 2 >>
endobj
2 0 obj
<< /Type /Pages /MediaBox [0 0 595.28 841.89] /Count 1 /Kids [ 1 0 R ] >>
endobj
17 0 obj
<< /Type /Catalog /Outlines 10 0 R /Pages 2 0 R /MarkInfo << /Marked true
>> /StructTreeRoot 12 0 R >>
endobj
8 0 obj
[ 1 0 R /XYZ 0 841.89 0 ]
endobj
10 0 obj
<< /First 18 0 R /Last 18 0 R /Count -1 >>
endobj
18 0 obj
<< /Dest [ 1 0 R /XYZ 56.69292 786.1971 null ] /Title (<FE><FF>\000<DC>\000b\000e\000r\000s\000c\000h\000r\000i\000f\000t)
>>
endobj
6 0 obj
<< /Type /Font /Subtype /TrueType /BaseFont /AAAAAB+HelveticaNeue-Bold /FontDescriptor
19 0 R /Encoding /MacRomanEncoding /FirstChar 32 /LastChar 134 /Widths [ 278
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 611 574 0 574 333
0 593 258 0 0 0 0 0 0 0 0 389 537 352 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 741
] >>
endobj
19 0 obj
<< /Type /FontDescriptor /FontName /AAAAAB+HelveticaNeue-Bold /Flags 32 /FontBBox
[-1018 -481 1437 1141] /ItalicAngle 0 /Ascent 975 /Descent -217 /CapHeight
714 /StemV 157 /Leading 29 /XHeight 517 /StemH 132 /AvgWidth 478 /MaxWidth
1500 /FontFile2 20 0 R >>
endobj
20 0 obj
<< /Length1 4036 /Length 2265 /Filter /FlateDecode >>
stream...endstream
endobj
7 0 obj
<< /Type /Font /Subtype /TrueType /BaseFont /AAAAAC+HelveticaNeue /FontDescriptor
21 0 R /Encoding /MacRomanEncoding /FirstChar 46 /LastChar 120 /Widths [ 278
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
574 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 537 0 0 0 0 0 0 0 0 0 0 0 0 0 0 315 0
0 0 518 ] >>
endobj
21 0 obj
<< /Type /FontDescriptor /FontName /AAAAAC+HelveticaNeue /Flags 32 /FontBBox
[-951 -481 1987 1077] /ItalicAngle 0 /Ascent 952 /Descent -213 /CapHeight
714 /StemV 95 /Leading 28 /XHeight 517 /StemH 80 /AvgWidth 447 /MaxWidth 2225
/FontFile2 22 0 R >>
endobj
22 0 obj
<< /Length1 2548 /Length 1313 /Filter /FlateDecode >>
stream...endstream
endobj
23 0 obj
<< /Title (A) /Producer (macOS Version 15.5 \(Build 24F74\) Quartz PDFContext)
/Creator (Pages) /CreationDate (D:20250531075728Z00'00') /ModDate (D:20250531075728Z00'00')
>>
endobj
xref
0 24
0000000000 65535 f
0000000333 00000 n
0000003615 00000 n
0000000022 00000 n
0000000443 00000 n
0000003263 00000 n
0000004057 00000 n
0000007092 00000 n
0000003823 00000 n
0000000551 00000 n
0000003865 00000 n
0000003385 00000 n
0000003298 00000 n
0000000000 00000 n
0000000000 00000 n
0000003470 00000 n
0000003543 00000 n
0000003704 00000 n
0000003924 00000 n
0000004465 00000 n
0000004739 00000 n
0000007427 00000 n
0000007693 00000 n
0000009094 00000 n
trailer
<< /Size 24 /Root 17 0 R /Info 23 0 R /ID [ <84091704e29250af8b9e6dc635a1b651>
<84091704e29250af8b9e6dc635a1b651> ] >>
startxref
9284
%%EOF
Das ganze ist eigentlich leicht zu lesen. In der ersten Zeile steht die PDF-Version. Dann kommen Objektdefinitionen, die jeweils Nummer und Revision tragen. In << >> sieht man Dictionaries, deren Schlüssel meist symbolische Namen (an dem / zu erkennen) sind und dessen Werte häufig Objekte sind, an den zwei Zahlen denen ein R folgt zu erkennen. Hinter den Objekte steht eine Tabelle mit den Offsets der Objekte in der Datei und der trailer, der das Objekt in Form eines Dictionaries ist, wo alles los geht. Mein PDF enthält 24 Objekte, als nächstes soll man #17 anschauen und in #23 findet man das _Document Information Dictionary_.
Dieses muss gemäß PDF/UA mindestens /Title, /Language und sollte /Author, /Subject, /Creator enthalten. Und schon sehen wir, dass Pages hier versagt, fehlt doch /Language.
In #17 wird auf den /StructTreeRoot #12 hingewiesen.
<< /Type /StructTreeRoot
/K << /Type /StructElem /S /Document /P 12 0 R /K [
<< /Type /StructElem /S /H1 /P 11 0 R /Pg 1 0 R /K 1 >>
<< /Type /StructElem /S /P /P 11 0 R /Pg 1 0 R /K 2 >>
] >>
>>
Hieraus soll nun der Screenreader sich die Struktur zusammenbauen.
Mit /S werden offenbar HTML-artige Tags angegeben, also Document, H1 und P. Das /P verweist in dem Baum wieder zum Parent und /Pg scheint auf die Seite selbst zu verweisen. Was /K ist, kann ich mir nicht erschließen. Eine Reihenfolge? Das Objekt #1 sagen jedenfalls, dass die Seite A4 ist und den Font TT1 (eingebettet als #6), abgeleitet von HelviticaNeue-Bold, enthält.
In dem PDF-Programm (in Objekt #3) müssten diese Tags wieder aufgegriffen werden. Ich nerde hier eh schon zu viel rum, da extrahiere ich jetzt nicht auch noch (per gzip) den Inhalt. Angeblich stehen da jetzt Befehle wie /P<</MCID 0>> BDC ... EMC, um andere Befehle in einen P-Tag einzuschließen.
Ich verlasse mich mal auf Claude, der sagt: PDF-1.7, _Document Information Dictionary_ muss /Title, /Language und sollte /Author, /Subject, /Creator enthalten. XMP muss pdfuaid:part=1, dc:title und dc:language enthalten. PDF muss Structure Tree (gemäß PDF-1.7 Specification §10.7) enthalten, was die größte Herausforderung ist, weil dazu neben einer hierarchischen Struktur noch die PDF-Sprache um die Tags erweitert werden muss. Was man auch immer zur Erzeugung der PDFs nutzt, dass muss jetzt ein Programm können.
Tags bilden ein Subset von HTML: H1 bis H6, P, L (für List) mit den Subtags LI, mit den Subtags Lbl (der Punkt), LBody, und Table mit THead, TBody, TR, TH und TD. Bilder sollten als Figure beschrieben werden. Außerdem kann man Teile markieren, die nicht Teil des Texts sind, etwa rein dekorative Inhalte.
Pages auf dem Mac versagt hier z.B.