PDFlibPas validiert PDF/UA-2-Dateien (ISO 14289-2) in Delphi über TPDFlib.CheckFilePDFUACompliance mit einer Ziel-Part von 2; dahinter läuft ein eigenes PDF/UA-2-Profil: Es löst Strukturrollen über PDF-2.0-Namespaces und /RoleMapNS auf, meldet abgeschnittene oder unlesbare Eingaben als Fehler und liefert einen Coverage-Bericht, der nie volle Zertifizierung behauptet
Dieser letzte Teil ist wichtiger, als er klingt. Bei der Barrierefreiheits-Validierung wird ein grüner Haken gern in eine Beschaffungstabelle kopiert und nie wieder hinterfragt. Ein Validator, der stillschweigend die Hälfte des Standards überspringt und trotzdem „pass“ ausgibt, ist schlechter als gar keiner, denn er verwandelt ein Unbekanntes in ein falsches Bekanntes. PDFlibPas macht das Gegenteil: prüfen, was sich mechanisch prüfen lässt; laut sagen, was nicht geprüft wurde; und jedes „konnte nicht abgeschlossen werden“ als Fund werten, nicht als Erfolg
Warum Sie PDF/UA-2 nicht mit PDF/UA-1-Regeln prüfen können
PDF/UA-2 ist nicht PDF/UA-1 mit hochgezählter Part-Nummer. PDF/UA-1 baut auf PDF 1.7 und einer einzigen flachen Role Map auf; PDF/UA-2 baut auf PDF 2.0 (ISO 32000-2) auf, wo jedes Strukturelement zu einem Namespace gehören kann und jeder Namespace seine eigene Role Map mitbringt. Ein UA-1-Checker, den Sie auf eine UA-2-Datei loslassen, übersieht echte Probleme und erfindet nebenbei falsche
Die falschen sind der peinliche Teil. PDF/UA-1-Validatoren erzwingen typischerweise Regeln dazu, dass Note-Elemente eine ID tragen, und dazu, wie die unnummerierte H-Überschrift verwendet werden darf. Keine dieser Regeln lässt sich übertragen. Im PDF-2.0-Struktur-Namespace werden Fußnoten als FENote ausgedrückt, und Überschriften heißen H1 bis Hn ohne Sechs-Ebenen-Deckel. PDF/UA-2 geht weiter und verbietet die unnummerierte H-Rolle kurzerhand ganz. Wenden Sie UA-1-Überschriftenlogik auf diesen Baum an, bemängeln Sie entweder Dinge, die der Standard erlaubt, oder winken genau das eine durch, das er verbietet
Deshalb ist das PDFlibPas-PDF/UA-2-Profil ein separater Code-Pfad, kein Flag auf dem alten. Er recycelt auch keine UA-1-Klauselnummern. Diagnosen, die der UA-1-Checker ebenfalls ausgibt, werden in UA-2-Berichten auf ISO 14289-2 umgebunden, und wo eine Klauselnummer nicht gegen den Standardtext verifiziert wurde, lässt der Bericht das Klauselfeld leer, statt zu raten. Eine leere Klausel befriedigt weniger als eine selbstbewusste Nummer, aber sie ist die einzige vertretbare Ausgabe, solange niemand das Mapping bestätigt hat
Wie PDFlibPas Rollen über RoleMapNS auflöst
PDFlibPas löst jede Strukturrolle zu einer Standardrolle in einem bekannten Namespace auf, folgt dabei /RoleMapNS-Ketten über Namespace-Grenzen hinweg und meldet jede Kette, die auf einer Custom-Rolle endet, eine Schleife bildet oder die Mapping-Regeln bricht. Das ist der Kern der PDF/UA-2-Strukturvalidierung, denn Assistive Technology versteht nur Standardrollen; ein Custom-/InvoiceLine bedeutet nichts, solange es nicht bei etwas wie P oder TR landet
Der Resolver startet beim /S-Namen des Elements und seinem /NS-Namespace. Ohne Namespace liegt das Element implizit im PDF-1.7-Standard-Namespace, und die altbekannte /RoleMap an der Wurzel des Strukturbaums greift. Mit Namespace greift die eigene /RoleMapNS des Namespace-Dictionary, und ein Mapping kann über ein zweielementiges Array auf eine Rolle in einem anderen Namespace zeigen. Die Prüfungen, die daraus entstehen, sind konkret:
- Ein von einem Strukturelement referenzierter Namespace muss ein indirektes Dictionary mit gültiger
/NS-URI sein und im/Namespaces-Array der Strukturbaumwurzel gelistet sein (Diagnose 10054) - Eine Custom-Rolle muss irgendwann zu einer Standardrolle im PDF-1.7-Namespace, im PDF-2.0-Namespace (
http://iso.org/pdf2/ssn) oder im MathML-Namespace aufgelöst werden; kreisförmige Ketten und Sackgassen werden als 10055 gemeldet - Eine Standardrolle darf nicht auf eine Custom-Rolle umgemappt werden, und eine Rolle darf nicht innerhalb ihres eigenen expliziten Namespace umgemappt werden
- Die Strukturwurzel muss genau ein
Document-Element im PDF-2.0-Namespace enthalten (10057), und das/Pjedes Elements muss auf sein tatsächliches Eltern-Element zeigen (10058) - Ein unnummeriertes
Hoder ein Legacy-Noteaus PDF 1.7, das nicht auf eine PDF-2.0-Rolle gemappt ist, wird als 10056 gemeldet
Ein Detail, das sich für das eigene Tooling abschreiben lohnt: PDFlibPas validiert jedes deklarierte Mapping, nicht nur die vom Baum aus erreichbaren. Ein kaputter Eintrag in einer Role Map, die derzeit kein Element nutzt, bleibt trotzdem ein latenter Defekt, denn das nächste Content-Update wird ihn verwenden. MathML wird ebenso ehrlich behandelt: Der MathML-Namespace gilt als gültiges Auflösungsziel, aber das MathML-Vokabular selbst wird nicht validiert, und der Coverage-Bericht sagt das auch so
Eine PDF/UA-2-Prüfung aus Delphi heraus ausführen
TPDFlib.CheckFilePDFUACompliance ist der Ein-Aufruf-Einstiegspunkt: Datei, Passwort, Ziel-Part und Optionen übergeben, und Sie bekommen ein String-List-Handle mit codierten Findings zurück. Ein Handle von null bei gleichzeitig null in LastErrorCode heißt: Die implementierten Prüfungen haben nichts gefunden; ein LastErrorCode ungleich null bedeutet, dass der Aufruf selbst fehlgeschlagen ist
var
Lib: TPDFlib;
Issues, I: Integer;
begin
Lib := TPDFlib.Create;
try
// TargetPart: 0 = aus XMP pdfuaid:part erkennen, 1 = PDF/UA-1, 2 = PDF/UA-2
// Options: 1 = beim ersten Fehler abbrechen, 0 = jedes Finding sammeln
Issues := Lib.CheckFilePDFUACompliance('report.pdf', '', 2, 0);
if Issues = 0 then
begin
if Lib.LastErrorCode <> 0 then
Writeln('Validation did not run, error ', Lib.LastErrorCode)
else
Writeln('No issues from the implemented PDF/UA-2 checks');
end
else
try
for I := 1 to Lib.GetStringListCount(Issues) do // Items sind 1-basiert
Writeln(Lib.GetStringListItem(Issues, I));
finally
Lib.ReleaseStringList(Issues);
end;
finally
Lib.Free;
end;
end;
Wählen Sie die Ziel-Part bewusst. Eine 0 liest pdfuaid:part aus den XMP-Metadaten und wählt das Profil danach, was für einen generischen Inbox-Validator richtig ist. Für die eigene Output-Pipeline übergeben Sie explizit 2. Hat der Generator vergessen, die Identifikation zu schreiben, fällt die Auto-Erkennung auf die UA-1-Regeln zurück, und Sie validieren den falschen Standard; eine explizite 2 erzeugt stattdessen die Diagnose 10050 für die fehlende Part, dazu 10051, wenn pdfuaid:rev nicht 2024 ist, und 10052, wenn die effektive PDF-Version nicht 2.0 ist. Auf der Erzeuger-Seite schreibt TPDFlib.SetPDFUA2Mode genau diese Identifikation, hebt die Version auf 2.0 und wählt den PDF-2.0-Struktur-Namespace als Default; das Namespace-Modell selbst erklärt der Walkthrough zu PDF-2.0-, PDF/A-4- und PDF/UA-2-Namespaces
Was passiert, wenn dem Validator das Budget ausgeht?
PDFlibPas arbeitet fail-closed: Trifft der PDF/UA-2-Checker auf ein Traversallimit, einen Zyklus, ein Parser-Limit oder ein Dokument, das er nicht öffnen kann, gibt er eine Diagnose aus, und ein Ergebnis ohne Diagnosen wird nie zu einem Pass hochgestuft. Diagnose 10059 ist genau für diesen Fall „der Reader konnte nicht zu Ende lesen“ reserviert
Die Budgets sind explizit und im Coverage-JSON publiziert: Strukturtiefe 256, 100.000 Strukturobjekte und 256 Hops entlang jeder Role-Map-Kette. Diese Zahlen existieren, weil Strukturbäume aus nicht vertrauenswürdiger Eingabe stammen. Eine feindliche oder schlicht kaputte Datei kann Elemente tausende Ebenen tief verschachteln, ein Kind zurück auf einen Vorfahren zeigen lassen oder eine Role Map bauen, die sich durch hunderte Namespaces zieht. Ein Validator ohne Budgets stürzt ab oder hängt sich auf, und ein Validator mit Budgets, aber ohne Diagnosen, tut etwas Subtileres und Schlimmeres: Er hört früh auf, findet in dem gelesenen Teil nichts Falsches und meldet Erfolg
Dasselbe Prinzip greift eine Ebene höher. Liefert der zugrundeliegende Reader einen Score unter 100, aber keine Finding-Zeilen zurück – was passieren kann, wenn das Parsen abbricht, bevor irgendeine Regel läuft –, fügt PDFlibPas eine 10059-Zeile ein, damit die leere Liste nicht mit einem sauberen Ergebnis verwechselt werden kann. Eine Exception während der Prüfung wird ebenfalls zu einer 10059-Zeile. Das ist wenig Code, und es schließt den häufigsten Weg, auf dem Validierungspipelines ihre Betreiber belügen
Einen Parse mit PDF/A teilen und einen Report bauen
Für Batch-Arbeit parst die Validation-Pipeline-API eine Datei einmal und fährt mehrere Prüfungen gegen den geteilten Parse. RunPDFUAValidationPipelineTask ist das PDF/UA-Mitglied dieser Familie und nimmt dieselben Ziel-Part-Werte wie die Ein-Aufruf-Funktion, sodass ein PDF/A-plus-PDF/UA-2-Preflight nicht zwei komplette Reads eines großen Dokuments bezahlt
var
Pipe, PDFAIssues, UAIssues: Integer;
Json: WideString;
begin
Pipe := Lib.BeginValidationPipeline('archive.pdf', '');
if Pipe = 0 then
Exit;
try
PDFAIssues := Lib.RunValidationPipelineTask(Pipe, 1, 0); // 1 = PDF/A
UAIssues := Lib.RunPDFUAValidationPipelineTask(Pipe, 2, 0); // PDF/UA-2
// ... beide Handles wie im vorigen Beispiel auswerten und freigeben ...
Writeln('Parses: ', Lib.GetValidationPipelineParseCount(Pipe));
// ComplianceTests: 1 = PDF/A, 2 = PDF/UA, 0 = beides; ReportFormat 1 = JSON
Json := Lib.CreateValidationPipelineReport(Pipe, 0, 0, 1);
finally
Lib.ReleaseValidationPipeline(Pipe);
end;
end;
Der strukturierte Report labelt den Barrierefreiheits-Abschnitt als PDF/UA-2 und ISO 14289-2, wenn sich die Datei als Part 2 identifiziert, und jeder PDF/UA-Abschnitt trägt "fullCertification": false samt dem eingebetteten Coverage-Objekt. Der Text-Preflight-Report ergänzt eine Zeile, dass nur implementierte Prüfungen gelaufen sind und dass eine vollständige Barrierefreiheits-Zertifizierung zusätzliche Validierung und manuelles Review braucht. Die weiteren Report-Formate, Rule-IDs und CI-Gating-Muster beschreibt der PDF/A- und PDF/UA-Preflight-Guide
Was garantiert der Coverage-Bericht tatsächlich?
TPDFlib.GetPDFUAReadValidationCoverageJSON garantiert genau eines: eine exakte Liste dessen, was der PDF/UA-2-Checker untersucht hat und was nicht. Für Part 2 liefert er fullCertification fix auf false, ein checked-Array, ein unverified-Array und die Traversallimits. Er garantiert nicht, dass eine Datei barrierefrei ist, und er ist so gebaut, dass kein Code-Pfad ihn zum Gegenteil bringen kann
var
Coverage: WideString;
begin
Coverage := Lib.GetPDFUAReadValidationCoverageJSON(2);
// Legen Sie es neben das Validierungsergebnis, nicht an dessen Stelle
TFile.WriteAllText('report.coverage.json', Coverage, TEncoding.UTF8);
if Pos('"fullCertification":false', Coverage) > 0 then
Writeln('Machine checks only; schedule a human accessibility review');
end;
Die Checked-Liste deckt Identifikation (Part, Revision, Prefix), effektive PDF-Version, Titel-Metadaten, /MarkInfo, DisplayDocTitle, Namespace-Referenzen, Role-Map-Auflösung, die Dokumentwurzel, Struktur-Eltern, Baum-Zyklen sowie die H- und Note-Regeln ab. Die Unverified-Liste ist genauso konkret: Content-Tagging und Lesereihenfolge, Struktur-Kapselung, Fonts und Unicode-Mapping, Annotationen und Formulare, natürliche Sprache, alternative Beschreibungen, das MathML-Vokabular und menschliches Barrierefreiheits-Review. Einiges davon ist nicht einmal mechanisch entscheidbar. Kein Parser kann Ihnen sagen, ob ein Alt-Text das Diagramm tatsächlich beschreibt oder ob die Lesereihenfolge für jemanden Sinn ergibt, der sie vorgelesen bekommt
Behandeln Sie das Coverage-JSON als Teil des Ergebnisses. Archivieren Sie es mit jedem Validierungslauf, und wenn ein Auditor fragt, was an einem bestimmten Datum „bestanden“ bedeutete, können Sie den exakten Scope aushändigen, statt ihn aus Erinnerung und einer Versionsnummer zu rekonstruieren
Gegencheck gegen veraPDF
Der PDF/UA-2-Kern wurde gegen den veraPDF-1.30.3-Kommandozeilen-Validator mit dessen ua2-Profil gegengecheckt, und die Ergebnisse passen dort zusammen, wo sich die Coverage überschneidet. Eine unabhängig erzeugte gültige Core-Probe bestand beide Tools; eine Probe mit pdfuaid:rev 2023 und eine Probe mit der unnummerierten H-Rolle fielen bei beiden durch
Über die vierte Probe lohnt sich das Reden. Sie enthielt Zeichnen-Content, der gar nicht getaggt war, und veraPDF markierte sie, während die PDFlibPas-Core-Prüfungen es nicht taten, denn Content-Tagging liegt außerhalb der aktuellen Coverage. Genau diese Lücke ist der Grund, warum content-tagging-and-reading-order die Unverified-Liste anführt. Ein zweiter Validator ist billige Versicherung, und eine Meinungsverschiedenheit zwischen zwei Tools ist oft der schnellste Weg, die Grenze des jeweils Geprüften zu finden. Für die Erzeuger-Seite desselben Problems – Bäume bauen, die von Anfang an durchgehen – siehe den Guide zum Tagged-PDF-Strukturbaum
FAQ
Zertifiziert PDFlibPas eine Datei als PDF/UA-2-konform?
Nein. PDFlibPas fährt die implementierten Maschinen-Prüfungen für PDF/UA-2 aus und liefert einen Coverage-Bericht, in dem fullCertification stets false ist und jeder Bereich aufgelistet steht, der noch andere Tools oder menschliches Review braucht
Welchen TargetPart-Wert sollte ich an CheckFilePDFUACompliance übergeben?
Übergeben Sie 2, wenn die Datei PDF/UA-2 sein muss, damit eine fehlende Identifikation gemeldet wird. Eine 0 erkennt die Part automatisch aus XMP pdfuaid:part, eine 1 erzwingt die PDF/UA-1-Regeln
Was bedeutet Diagnose 10059 in einem PDF/UA-2-Bericht?
Diagnose 10059 heißt, der Validator konnte nicht fertig werden: Die Datei ließ sich nicht parsen oder öffnen, ein Traversallimit wurde überschritten, ein Zyklus gefunden oder eine Exception geworfen. Sie zählt immer als Fehler, nie als Bestehen
Die PDF/UA-2-Validierung, die geteilte Validation-Pipeline und die PDF-2.0-Authoring-APIs kommen in der losLab PDF Library for Delphi, zusammen mit PDF/A-Preflight und strukturierten Validierungs-Reports