Technischer Artikel

PDF/A- und PDF/UA-Preflight in Delphi mit PDFlibPas

Die Entdeckung kam sechs Jahre nach der Auslieferung: Achtunddreißigtausend archivierte Rechnungen erklärten in ihren XMP-Metadaten PDF/A-1b, und ein Stichprobenlauf durch veraPDF ließ fast alle durchfallen — geräteabhängiges RGB ohne OutputIntent, zwei nie eingebettete Schriften, Annotation-Flags, die ISO 19005-1 verbietet. Die erzeugende Anwendung hatte den Konformitätsbezeichner geschrieben, ohne je gegen den Standard zu validieren, und jedes nachgelagerte System vertraute dem Label. Selbstdeklarierte Compliance ist genau so viel wert, wie sie zu schreiben kostet. Die dauerhafte Alternative ist Preflight in dem Moment, in dem eine Datei ins Archiv gelangt, und losLab PDF Library (PDFlibPas) macht das für Delphi- und C++Builder-Systeme praktikabel, indem PDF/A- und PDF/UA-Validatoren direkt in die Bibliothek eingebaut sind, ohne externen Dienst in der Schleife.

Zwei Standards, die Dateien aus entgegengesetzten Gründen scheitern lassen

ISO 19005 (PDF/A) ist ein Reproduktionsvertrag: Eine konforme Datei muss Jahrzehnte später identisch rendern, auf Software, die das Ursprungssystem nie gesehen hat. Ihre Regeln greifen daher externe Abhängigkeiten an — jede Schrift eingebettet, Farbe an ein eingebettetes ICC OutputIntent gebunden oder in geräteunabhängigen Farbräumen ausgedrückt, keine Verschlüsselung in PDF/A-1, kein JavaScript, XMP-Metadaten konsistent mit dem Dokumentinformationswörterbuch.

ISO 14289 (PDF/UA) ist ein Semantikvertrag: Assistive Technology muss das Dokument sinnvoll durchlaufen können. Seine Regeln liegen in einer ganz anderen Schicht — vollständiger Strukturbaum, Alternativtext für Abbildungen, ein für die Anzeige konfigurierter Dokumenttitel, Überschriftenebenen ohne Sprünge, Tabellenkopfbeziehungen, die auch außerhalb des Bildschirms halten.

Die beiden sind orthogonal, und die gefährlichen Dateien liegen in der Mitte. Ein archivperfektes Dokument kann für einen Screenreader unlesbar sein; ein fehlerlos getaggtes Dokument kann auf eine Desktop-Schrift verweisen, die in zehn Jahren nicht existiert. Pipelines, die beides brauchen — Veröffentlichungen im öffentlichen Sektor sind der kanonische Fall —, müssen beide Prüfungen ausführen und die Befunde an verschiedene Besitzer routen, denn nicht eingebettete Schriften sind ein Generation-Code-Fehler, während fehlender Alternativtext dem gehört, der die Templates pflegt.

Die Wahl des Teils ist so wichtig wie das Urteil. PDF/A-1, eingefroren auf PDF 1.4, weist Transparenz und JPEG2000 zurück — Features, die moderne Berichtsausgabe frei verwendet. PDF/A-2 (ISO 19005-2, auf ISO 32000-1 aufgebaut) akzeptiert beides und ist die sinnvolle Vorgabe für neue Archive. PDF/A-3 erlaubt zusätzlich eingebettete Dateien beliebigen Typs, wovon regulierte E-Invoicing-Formate abhängen. Ein Team, das 2026 noch auf PDF/A-1b standardisiert, erbt meist eine Anforderung von vor fünfzehn Jahren, und die billigste Reparatur ist oft, den Zielteil neu zu verhandeln, statt Transparenz aus jedem Diagramm zu entfernen.

Strukturierte Befunde beim Ingest

Der flache API-Einstieg ist CheckFileCompliance, mit Testselektor 1 für PDF/A und 2 für PDF/UA. Er gibt ein Stringlisten-Handle zurück, dessen Einträge einzelne Befunde sind, was die richtige Form für ein automatisiertes Gate ist:

function GateArchiveUpload(Pdf: TPDFlib; const FileName: string): Boolean;
var
  ListId, I: Integer;
begin
  ListId := Pdf.CheckFileCompliance(FileName, '', 1, 0);  // 1 = PDF/A
  if ListId = 0 then
  begin
    // 0 means "no findings" OR "file unreadable" -- disambiguate before passing
    Result := Pdf.LastErrorCode = 0;
    Exit;
  end;
  for I := 0 to Pdf.GetStringListCount(ListId) - 1 do
    LogFinding(FileName, Pdf.GetStringListItem(ListId, I));
  Pdf.ReleaseStringList(ListId);
  Result := False;
end;

Zwei Implementierungsdetails entscheiden, ob das unbeaufsichtigt funktioniert. Erstens gibt CheckFileCompliance sowohl dann 0 zurück, wenn die Datei vollständig konform ist, als auch dann, wenn die Datei überhaupt nicht geöffnet werden konnte — intern erzeugt eine leere Ergebnisliste in beiden Fällen 0. Ein Gate, das 0 als Bestehen liest, winkt korrupte Uploads direkt ins Archiv. Unterscheiden Sie mit LastErrorCode, wie oben gezeigt. Zweitens läuft der Checker auf dem Streaming-Reader der Bibliothek statt auf dem vollständigen Dokumentmodell: Er öffnet die Datei direkt mit Lese-Sharing und braucht nie LoadFromFile, weshalb er mehrere Gigabyte große Dateien ohne Objektbäume verarbeitet — aber er schlägt fehl, solange ein anderer Prozess die Datei noch zum Schreiben geöffnet hält, was genau der Zustand eines laufenden Uploads ist. Gaten Sie nach Abschluss der Übertragung, nicht währenddessen.

Der Durchsatz folgt aus demselben Design. Weil jede Prüfung ihre Eingabe read-only öffnet und die Datei fürs Lesen teilt, parallelisiert ein Korpus-Audit natürlich über Worker-Threads oder Prozesse, je eine TPDFlib-Instanz pro Worker. Die Stringlisten-Handles sind die Ressource, bei der Disziplin zählt: Jedes Nicht-Null-Handle aus CheckFileCompliance bleibt allokiert, bis ReleaseStringList aufgerufen wird, und ein lang laufendes Gate, das sie leckt, baut langsam ab, statt laut zu scheitern.

Berichte für Menschen, Diffs für Build-Gates

Befundlisten sind die richtige Form für ein Gate und die falsche für eine E-Mail an das Template-Team. CreatePreflightReport rendert dieselbe Analyse als lesbaren Bericht, CreatePreflightReportEx fügt einen Berichtsformatselektor hinzu, und SavePreflightReport schreibt den Bericht auf die Platte, damit er im Dokumentpaket mitgeliefert werden kann — eine vertragliche Anforderung in vielen Archivliefervereinbarungen.

Das unterschätzte Mitglied der Familie ist ComparePreflightReports. Compliance-Ergebnisse sind eine Regressionsoberfläche wie jede andere: Eine Template-Änderung, eine neue Unternehmensschrift oder ein Bibliotheksupgrade kann Befunde einführen, die im letzten Release fehlten. Halten Sie Golden Reports für ein Korpus repräsentativer Dokumente unter Versionskontrolle, erzeugen Sie sie nach jeder Änderung neu und lassen Sie ComparePreflightReports das Delta berechnen. Ein leerer Diff wird zum Release-Artefakt; ein überraschender Befund bricht den Build statt das Audit.

Ausgabe erzeugen, die beim ersten Lauf besteht

Preflight verdient sein Geld bei eingehenden Dateien; bei Dokumenten, die Ihr eigener Code erzeugt, ist Reparatur nach der Generierung rückwärts gedacht. PDFlibPas besitzt für jeden Standard einen Erzeugungsmodus, und beide lassen sich kombinieren:

var
  Pdf: TPDFlib;
  Diag: WideString;
begin
  Pdf := TPDFlib.Create;
  try
    Pdf.NewDocument;
    Pdf.SetPDFAMode(1);
    Pdf.LoadOutputIntentProfile('sRGB-IEC61966-2.1.icc', 'RGB');
    Pdf.SetPDFUAMode('en-US');
    Pdf.SetInformation(1, 'Quarterly Statement');  // /Title: required for PDF/UA
    // ... draw tagged content here ...
    Diag := Pdf.GetPDFUADiagnostics;
    if Diag <> '' then
      Writeln('fix before shipping: ', Diag);
    Pdf.SaveToFile('statement.pdf');
    // the preflight that counts runs on the saved file:
    Writeln(Pdf.CreatePreflightReport('statement.pdf', '', 1, 0));
  finally
    Pdf.Free;
  end;
end;

Die Falle steckt in der Speicherzeit. Mehrere Konformitätsreparaturen — das Erzwingen des Print-Flags auf Annotationen, das Schreiben des Standard-AFRelationship für eingebettete PDF/A-3-Dateien, das Normalisieren von Tab-Reihenfolge und Formularfeldbeschreibungen für PDF/UA — werden beim Serialisieren angewendet, nicht beim Aktivieren des Modus. Das In-Memory-Dokument ist nicht byteidentisch mit dem, was auf die Platte gelangt, also zählt nur das Preflight-Urteil, das aus der gespeicherten Datei berechnet wird. Validieren Sie statement.pdf; schließen Sie nie von dem Dokumentobjekt im Speicher auf Compliance.

Rechnungsszenarien, die maschinenlesbares XML neben dem visuellen Dokument einbetten — das ZUGFeRD- und Factur-X-Muster, aufgebaut auf PDF/A-3 —, sollten die Attachment-Beziehung explizit über SetPDFA3DefaultAFRelationship setzen, weil ISO 19005-3 von jeder eingebetteten Datei verlangt, ihre Rolle relativ zum Dokument zu erklären.

Unabhängige Schiedsrichter: veraPDF und Acrobat

Lassen Sie einen Producer nie die eigenen Hausaufgaben benoten. Die PDFlibPas-Checker liefern schnelle, strukturierte In-Process-Urteile; das Release-Gate für Archivstapel sollte trotzdem einen unabhängigen Validator enthalten. veraPDF ist die communitygepflegte Referenzimplementierung für PDF/A-Validierung und das Werkzeug, das die meisten Archive in ihren Abnahmekriterien nennen; Acrobats Preflight-Profile sind eine nützliche dritte Meinung, wenn zwei Werkzeuge widersprechen. Protokollieren Sie Validatorname und Version neben jedem gespeicherten Bericht — die Aussage „besteht veraPDF“ bedeutet wenig ohne das konkrete veraPDF.

Wenn Validatoren widersprechen, und an den Rändern der Standards tun sie das gelegentlich, schrumpfen Sie die Datei auf ein minimales Beispiel und entscheiden Sie gegen den Standardtext statt gegen eines der Werkzeuge. Die Übung dauert eine Stunde und deckt meist entweder einen meldenswerten Toolfehler oder eine falsch gelesene Klausel auf, die in die Compliance-Notizen des Teams gehört.

Verschlüsselte Eingaben brauchen eine Sonderregel. Beide Checker akzeptieren ein Passwortargument, aber für PDF/A-1 ist das Verschlüsselungswörterbuch selbst bereits ein Konformitätsverstoß — ISO 19005-1 verbietet es ausdrücklich —, sodass eine verschlüsselte Einreichung vor jeder tieferen Analyse zurückgewiesen werden kann. Zu auditieren, was ein Verschlüsselungswörterbuch tatsächlich erlaubt, ist eine eigene Aufgabe, behandelt in PDF-Verschlüsselung und Berechtigungen auditieren.

PDF/UA-Befunde führen fast immer darauf zurück, wie der Strukturbaum erstellt wurde; die Tagging-Techniken behandelt Tagged-PDF-Strukturbäume in Delphi bauen. Archive, die zusätzlich digitale Signaturen verlangen, sollten dieses Gate mit dem Workflow in PAdES-Signierung und -Validierung kombinieren. Die vollständige Preflight-API-Referenz steht auf der Produktseite losLab PDF Library for Delphi.