Technischer Artikel

PDF/X-Validierung in Delphi mit der PDFium-Komponente

Die PDFium-Komponente für Delphi validiert druckfertige PDF/X-Dokumente über TPdf.ValidatePdfX. Diese Methode implementiert die ISO 15930-Prüfung in zwei Schichten: acht Inhaltsprüfungen auf Byte-Ebene (Ausschluss von LZW-Komprimierung, JavaScript, Formularfeldern, OPI-Referenzen, einer fehlenden TrimBox, einem fehlenden Trapped-Schlüssel und mehr) sowie einen Objektdurchlauf über die PDFium-API. Dieser nutzt FPDFFont_GetIsEmbedded, um die Schrifteinbettung für jedes Textobjekt auf jeder Seite zu verifizieren. Das Ergebnis ist ein TPdfXValidationResult-Datensatz, der die erkannte Konformitätsstufe nennt und jeden Fehler als Enum auflistet. So kann Ihre Delphi-Anwendung Kunden exakt erklären, warum eine Datei in der Druckerei abgelehnt werden wird — noch bevor Platten belichtet werden

Wenn Sie jemals einen Auftrag an eine Druckerei gesendet und eine Ablehnung mit einer einzigen Zeile wie „keine TrimBox“, „Schriftarten nicht eingebettet“ oder „Trapped nicht gesetzt“ erhalten haben, kennen Sie die Kosten einer späten Entdeckung. PDF/X ist das Druckvorstufen-Pendant zu PDF/A: Während das archivierbare PDF/A garantiert, dass ein Dokument in Jahrzehnten exakt identisch gerendert wird, garantiert PDF/X, dass ein Dokument morgen früh auf dem RIP (Raster Image Processor) eines anderen Unternehmens fehlerfrei separiert, belichtet und zugeschnitten wird. Beide Standards teilen sich technische Grundlagen (XMP-Identifikation, OutputIntents, eingebettete ICC-Profile), beantworten aber unterschiedliche Fragen. Aus diesem Grund liefert die Komponente separate Validierer für beide Zwecke — die PDF/A-Seite wird im Artikel PDF/A-Preflight-Validierung mit der PDFium-Komponente behandelt

Was fordert die ISO 15930 tatsächlich von einer druckfertigen PDF?

Die ISO 15930 existiert, um einen blinden Austausch (Blind Exchange) zu ermöglichen: Ein Designer übergibt eine Datei an eine Druckerei, ohne mit ihr gesprochen zu haben, und die Druckerei kann eine fehlerfreie Ausgabe erzeugen — ohne Rückfragen, ohne E-Mails wegen fehlender Schriftarten und ohne verknüpfte Bilder, die auf dem Laptop des Designers verblieben sind. Jede Regel in dieser Norm dient diesem Zweck. Schriftarten müssen eingebettet sein, da nicht davon ausgegangen werden kann, dass das empfangende RIP diese besitzt. Externe Referenzen sind verboten, da die Datei in sich geschlossen sein muss. Interaktive Funktionen sind ausgeschlossen, da Druckfarbe keinen Onclick-Handler besitzt

Die PDFium-Komponente erkennt drei Konformitätsfamilien und meldet diese über das Enum TPdfXConformance im Validierungsergebnis: pxc1a für PDF/X-1a:2001 (ISO 15930-1, die strikte CMYK-plus-Sonderfarben-Basislinie auf PDF 1.3/1.4), pxc3 für PDF/X-3:2002 (ISO 15930-3, die auch RGB, Lab und ICC-verwaltete Farben zulässt) und pxc4 für PDF/X-4:2010 (ISO 15930-7, die schließlich Transparenzen und Ebenen auf Basis von PDF 1.6 erlaubt). Eine Datei, die keinerlei PDF/X-Kennzeichnung trägt, wird als pxcNone zurückgegeben. Dies ist an sich schon eine nützliche Antwort: Das Dokument hat nie behauptet, druckfertig zu sein, und die weiteren Fehlerberichte erklären, was für diesen Status noch fehlt

Die Verbote sind logisch, wenn man sich in die Perspektive eines RIP-Herstellers versetzt. /LZWDecode ist in allen PDF/X-Varianten verboten, damit ein konformer Empfänger niemals von einem Filter mit einer problematischen Lizenzhistorie abhängt — Flate erledigt denselben Job ohne diesen Ballast. JavaScript, AcroForm-Felder und /AA-Verzeichnisse für zusätzliche Aktionen sind ausgeschlossen, da eine Druckdatei eine feste Beschreibung von Zeichen auf Papier sein muss. Alles, was das Aussehen beim Öffnen verändern kann, verletzt die Garantie, dass das Gedruckte dem Korrekturabzug entspricht. OPI-Platzhalter (Open Prepress Interface) sind unzulässig, da sie programmbedingt Verweise auf hochauflösende Bilder darstellen, die an einem anderen Ort gespeichert sind — und „an einem anderen Ort“ ist genau das, was der blinde Austausch verbietet

Warum lehnen Druckereien PDFs ohne TrimBox ab?

Die TrimBox definiert das fertige Endprodukt — das Rechteck, das nach dem Schneiden mit der Schneidemaschine übrig bleibt. Die MediaBox, die jede PDF-Seite besitzt, definiert lediglich den Rohbogen: Sie enthält den Anschnitt (Bleed), Schneidemarken, Passkreuze und Farbkontrollstreifen. Ausschieß-Software positioniert Seiten auf dem Druckbogen anhand ihrer TrimBoxen. Fehlt diese, muss der Operator schätzen, wo Ihre Visitenkarte tatsächlich endet — und eine falsche Schätzung schneidet Ihren Anschnitt ab oder hinterlässt einen weißen Blitz an einer Kante. Aus diesem Grund verlangt die ISO 15930 eine TrimBox (oder eine ArtBox) auf jeder Seite, und deshalb meldet ValidatePdfX den Fehler pvxiMissingTrimBox, wenn auf einer Seite des Dokuments kein /TrimBox-Schlüssel gefunden wird

Der Schlüssel /Trapped beantwortet eine andere Produktionsfrage. Trapping (Überfüllung) ist eine Technik der Druckvorstufe, bei der aneinandergrenzende Farben minimal überlappen, damit kleine Passerungenauigkeiten auf der Druckmaschine keine weißen Blitzer verursachen. Die Druckerei muss wissen, ob diese Arbeit bereits durchgeführt wurde: Das Trappen einer bereits getrappten Datei verdoppelt die Überlappungen, während das Überspringen bei einer nicht getrappten Datei sichtbare Blitzer riskiert. PDF/X verlangt daher, dass das Info-Verzeichnis explizit /Trapped /True oder /Trapped /False deklariert — ein fehlender Schlüssel oder /Unknown zwingt eine Person zur manuellen Überprüfung der Datei, was genau die Rückfragen aufwirft, die der blinde Austausch verhindern sollte. Die Komponente kennzeichnet dies als pvxiTrappedNotSet

Ausführen der zweischichtigen Validierung mit TPdf.ValidatePdfX

TPdf.ValidatePdfX benötigt keine Parameter und gibt einen Datensatz TPdfXValidationResult mit drei Elementen zurück: Conformance (die erkannte PDF/X-Variante), Issues (eine Pascal-Menge von TPdfXValidationIssue-Werten) and einen Helper IsCompliant. Intern serialisiert die Methode das geladene Dokument in einen Speicherstrom, führt die Inhaltsprüfung auf Byte-Ebene durch und durchläuft anschließend das PDFium-Objektmodell für die Schrifteinbettungsprüfung pro Schriftart. Eine einfache Preflight-Prüfung sieht wie folgt aus:

uses PDFium, FPdfPdfx;

procedure CheckPrintReadiness(const FileName: string);
var
  Pdf: TPdf;
  Res: TPdfXValidationResult;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := FileName;
    Pdf.Active := True;

    Res := Pdf.ValidatePdfX;
    Writeln('Erkannte Konformitaet: ',
      Ord(Res.Conformance)); // pxc1a, pxc3, pxc4, pxcNone...

    if Res.IsCompliant then
      Writeln('PDF/X-Pruefungen bestanden')
    else
    begin
      if pvxiMissingTrimBox in Res.Issues then
        Writeln('REJECT: Keine /TrimBox auf den Seiten vorhanden');
      if pvxiTrappedNotSet in Res.Issues then
        Writeln('REJECT: /Trapped fehlt oder ist auf /Unknown gesetzt');
      if pvxiPdfiumFontNotEmbedded in Res.Issues then
        Writeln('REJECT: Eine Seite nutzt eine nicht eingebettete Schriftart');
      if pvxiLzwForbidden in Res.Issues then
        Writeln('REJECT: LZWDecode-Filter vorhanden');
    end;
  finally
    Pdf.Free;
  end;
end;

Da Issues eine normale Pascal-Menge (Set) darstellt, können Sie diese nach Bedarf aufteilen — strukturelle Probleme als harte Ablehnung werten, pvxiMissingTitle (eine Empfehlung in der Norm, kein Muss) als Warnung behandeln und den Rest protokollieren. Derselbe Datensatztyp speichert auch den Bericht-Generator der Komponente. Wenn Sie also lieber einen lesbaren Bericht ausgeben möchten, als auf Enums zu prüfen, entspricht das Vorgehen dem im Artikel Erstellen eines Batch-Preflight-Berichts per CLI mit der PDFium-Komponente beschriebenen Weg

Was die Byte-Ebene erfasst — und was sie übersieht

Die Byte-Ebene ist eine Token-Prüfung der Strukturbytes des Dokuments, wobei die Stream-Inhalte ausgeblendet werden. So kann beispielsweise eine JPEG-Datei, die zufällig das Byte-Muster /JavaScript enthält, keinen Fehlalarm auslösen. Neben den Identifikationsprüfungen (XMP pdfxid:GTS_PDFXVersion, OutputIntent mit eingebettetem ICC-Profil, Trailer-/ID, Verschlüsselungsverbot) prüft der Inhaltsstrom-Durchlauf acht Punkte, jeweils mit einem eigenen Enum-Wert:

  • pvxiLzwForbidden — Ein /LZWDecode-Filter taucht irgendwo in der Datei auf (in allen PDF/X-Varianten verboten)
  • pvxiJavaScriptForbidden — Ein /JavaScript-Aktions- oder Namensbaum ist vorhanden
  • pvxiFormFieldsForbidden — Ein /AcroForm-Verzeichnis oder ein /XFA-Eintrag existiert
  • pvxiAdditionalActions — Ein /AA-Verzeichnis für zusätzliche Aktionen ist vorhanden
  • pvxiEmbeddedFilesForbidden/EmbeddedFiles oder eine /FileAttachment-Anmerkung ist vorhanden
  • pvxiOpiForbidden — Ein /OPI- oder /Alternates-Eintrag verweist auf austauschbare Bildinhalte
  • pvxiMissingTrimBox — Keine /TrimBox auf einer der Seiten gefunden
  • pvxiTrappedNotSet/Trapped fehlt oder ist auf /Unknown gesetzt

Das Scannen der Bytes arbeitet schnell und benötigt keine Rendering-Engine, besitzt aber eine systembedingte Schwachstelle bei Schriftarten: Auf dieser Ebene kann der Inspektor nur eine grobe Heuristik anwenden — er bemängelt ein Dokument, wenn er überhaupt kein eingebettetes Schriftprogramm findet. Eine Datei mit neun eingebetteten Schriftarten und einer eingeschlichenen Systemschriftart sieht für den Byte-Scan korrekt aus. Diese Lücke begründet die Notwendigkeit der zweiten Prüfschicht

Prüfung der Schrifteinbettung über das PDFium-Objektmodell

Die Objektmodell-Schicht der PDFium-Komponente beantwortet die Schriftartenfrage präzise. Nach dem Durchlauf auf Byte-Ebene durchläuft TPdf.ValidatePdfX jede Seite, fragt die Objektliste über FPDFPage_CountObjects ab, löst für jedes Textobjekt das Schriftart-Handle mittels FPDFTextObj_GetFont auf und fragt FPDFFont_GetIsEmbedded ab. Eine einzige nicht eingebettete Schriftart im gesamten Dokument fügt der Fehlermenge pvxiPdfiumFontNotEmbedded hinzu. Der Durchlauf bricht ab, sobald der Fehler bestätigt wird — so wird bei einem fehlerhaften 300-seitigen Katalog das Urteil oft schon nach der ersten Seite gefällt

Zwei Hinweise zu den Systemgrenzen: Erstens benötigt diese Schicht das Laden der PDFium-Bibliothek und setzt voraus, dass die DLL-Version FPDFFont_GetIsEmbedded exportiert. Fehlt dieser Export, wird die Prüfung übersprungen und nicht als fehlgeschlagen gewertet. So führen ältere DLLs nicht zu falschen Rejection-Meldungen. Zweitens beantwortet die Prüfung nur die Frage „eingebettet oder nicht“ — sie unterscheidet nicht zwischen vollständiger Einbettung und Subsetting und prüft auch nicht die Glyphenabdeckung. Wenn eine Datei fehlschlägt und Sie wissen müssen, welche Schriftart auf welcher Seite betroffen ist, setzt die im Artikel Analysieren von PDF-Schrifteigenschaften mit PDFium in Delphi beschriebene Methode genau dort an, wo die Ja/Nein-Antwort des Validierers aufhört

Validieren von Streams ohne Dokumentenladung — und ohne die DLL

Der Byte-Ebene-Inspektor wird auch als eigenständige Funktion bereitgestellt: ValidatePdfXCompliance(Source: TStream) in der Unit FPdfPdfx. Dies ist reiner Object-Pascal-Code ohne Abhängigkeit von der PDFium-DLL. So lässt er sich an Orten einsetzen, an denen eine Rendering-Engine nicht erwünscht ist: als einfache Upload-Prüfung auf einem Webserver, als CI-Job, der generierte Grafiken prüft, oder als Lazarus-Dienst auf einer Plattform, auf der Sie keine nativen Binärdateien ausliefern möchten. Übergeben Sie einen beliebigen suchbaren Stream:

uses Classes, FPdfPdfx;

function QuickPdfXGate(const FileName: string): Boolean;
var
  Fs: TFileStream;
  Res: TPdfXValidationResult;
begin
  Fs := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  try
    Res := ValidatePdfXCompliance(Fs);
    Result := Res.IsCompliant and (Res.Conformance <> pxcNone);
  finally
    Fs.Free;
  end;
end;

Der Kompromiss ist eindeutig: Der eigenständige Pfad führt die Identifikationsprüfungen und alle acht Inhaltsprüfungen durch, nicht jedoch die Schriftprüfung über das PDFium-Objektmodell. Bei Schriftarten greift er auf die grobe Heuristik der Byte-Ebene zurück. Eine sinnvolle Architektur nutzt ValidatePdfXCompliance als schnelle Vorprüfung und reserviert das vollständige TPdf.ValidatePdfX für Dateien, die diese Vorprüfung bestehen

Wo dieser Validierer aufhört und ein vollständiger Preflight beginnt

Eine ehrliche Beschreibung der Grenzen ist wichtig: ValidatePdfX verifiziert Identifikationsmerkmale, strukturelle Verbote, Seitengeometrie-Schlüssel, die Trapped-Deklaration und die Schrifteinbettung bis auf die Ebene einzelner Textobjekte. Die Methode misst nicht den Gesamtfarbauftrag, prüft nicht, ob jeder Farbraum für die angegebene Variante zulässig ist (z. B. die reine CMYK-Regel von X-1a), prüft die Bildauflösung nicht gegen den Rasterdruck und bewertet auch nicht das Verhalten beim Überdrucken oder der Transparenzreduzierung — diese Aufgaben erfordern eine farbverwaltete Preflight-Engine, und die Dokumentation der Unit empfiehlt, sie für eine endgültige Zertifizierung damit zu kombinieren. Die zweischichtige Prüfung liefert Ihnen die 80 % der strukturellen Fehler, die sich frühzeitig erkennen lassen — gemessen in Millisekunden in Ihrem eigenen Delphi-Code statt in der E-Mail der Druckerei am nächsten Tag

Beide Validierungsschichten, die APIs zur Erzeugung konformer PDF/X-Dateien sowie die PDF/A-, PDF/UA-, PDF/E- und PDF/VT-Validierer mit identischer Architektur werden in der PDFium Component für Delphi und C++Builder ausgeliefert — eine einzige Komponente vom Rendering bis zur Druckvorstufenprüfung