Technischer Artikel

PDF-Preflight-Prüfungen in Delphi mit HotPDF automatisieren

Die Datei öffnet sich auf Ihrem Rechner sauber. Acrobat zeigt sie an, die Druckvorschau sieht richtig aus, jede Seite ist da. Dann geht sie an die Druckerei oder in das Archivsystem, das Ihren Monatsstapel aufnimmt, und kommt abgelehnt zurück: RGB-Bilder in einem CMYK-Auftrag, kein /Trapped-Schlüssel, ein Output-Intent, der nicht zur Druckmaschine passt. Am Dokument war nichts falsch, was irgendjemand hätte sehen können. Es war falsch gegenüber einem Profil, und das Profil wurde an einem Ort geprüft, an dem Sie nicht waren. Preflight ist der Druckvorstufen-Name für diese Prüfung, und die eigentliche Frage lautet, wohin sie gehört, wenn die PDFs aus Ihrem eigenen Delphi-Code kommen statt vom Desktop eines Designers

HotPDF gibt Ihnen keine Preflight-Funktion zum Aufrufen. Die Komponente enthält in ihrer GUI-Demo ein Preflight-Berichtsfenster, aber dahinter steht keine API, die ein Dienst oder ein Build-Skript aufrufen könnte, und etwas anderes zu behaupten würde Sie nach einer Methode suchen lassen, die es nicht gibt. Das klingt nach einer Lücke, bis man bemerkt, dass für selbst erzeugte Dateien der Aufruf eines Validators auf der eigenen Ausgabe ohnehin die falsche Form hat. Sie kontrollieren bereits jede Eigenschaft, die ein Validator prüfen würde. Die sinnvolle Aufteilung besteht darin, den Generator unfähig zu machen, eine fehlerhafte Datei auszugeben, und das dann mit einem Werkzeug zu beweisen, das Sie nicht selbst geschrieben haben

Diagramm einer Delphi-Preflight-Pipeline, in der HotPDF-Compliance-Einstellungen fehlerhafte PDFs bei der Erzeugung verhindern und veraPDF plus Acrobat Preflight das Ergebnis extern nachweisen
Prävention verankert PDF/A- und PDF/X-Regeln in der HotPDF-Erzeugung selbst, während externe Validatoren das Urteil liefern, das der Generator sich nicht selbst ausstellen kann

Warum Sie Ihre eigene Ausgabe anders prüfen

Traditionelles Preflight geht von der Datei eines Fremden aus. Irgendein Designer, irgendeine andere Anwendung, irgendeine unbekannte Kette von Bearbeitungen hat sie erzeugt, und Sie untersuchen sie, weil Sie keine Ahnung haben, was darin steckt. Ein Dokument, das Ihr Code erzeugt hat, ist kein Fremder. Schrifteinbettung, Farbraum, Output-Intent, der Metadatenblock: Ihr Programm hat all das wenige Millisekunden entschieden, bevor die Datei auf die Platte kam. Sie hinterher zu untersuchen, um Entscheidungen zu entdecken, die Sie gerade getroffen haben, ist Beschäftigungstherapie. Der günstigere Zug ist, diese Entscheidungen so einzuschränken, dass eine nicht konforme Datei nie existiert, um erwischt zu werden

Es gibt auch einen Glaubwürdigkeitsgrund, die Verifikation extern zu halten. Eine Bibliothek, die ihre eigene Ausgabe absegnet, benotet ihre eigene Prüfung. Wenn das Archivsystem eines Kunden oder der RIP einer Druckerei Ihre Datei ablehnt, hat „unsere Komponente sagt, sie ist in Ordnung“ kein Gewicht. Ein Urteil von veraPDF oder Acrobat hat es, weil die Gegenseite dieselben Werkzeuge einsetzt

Konformität zu einer Einstellung machen, nicht zu einer Checkliste

Die Präventionsschicht ist reine Konfiguration. Setzen Sie PDFACompliance oder PDFXCompliance vor BeginDoc, und HotPDF hält die entsprechenden Regeln für den gesamten Erzeugungsdurchlauf ein: Es bettet Schriften ein, überwacht die Verwendung von DeviceRGB und DeviceCMYK gegen den von Ihnen deklarierten Output-Intent und verweigert Funktionen, die das Profil verbietet. Die Widersprüche treten bei EndDoc zutage, wo die Compliance-Gates eine Exception auslösen, statt still etwas auszuliefern, das nachgelagert scheitern wird. Sobald die Datei gespeichert ist, lesen dieselben Eigenschaften zurück, was tatsächlich durchgesetzt wurde, und das ist der eine Fakt, den Ihr Pipeline-Log am dringendsten braucht:

// Nach EndDoc: die durchgesetzten Profile mit den Laufmetadaten protokollieren
if Pdf.PDFACompliance <> '' then
  Log('Generated as PDF/A level ' + Pdf.PDFACompliance);
if Pdf.PDFXCompliance <> '' then
  Log('Generated as PDF/X profile ' + Pdf.PDFXCompliance);

Schreiben Sie diese Flags in dieselbe Logzeile wie den Hash der Eingabedaten und die HotPDF-Version. An dem Tag, an dem ein Validator und Ihr Generator sich über eine Datei uneins sind, sagt Ihnen diese Zeile, welche Vorlage sie erzeugt hat und welcher Build der Bibliothek geladen war, und die Diskussion, die sonst einen Nachmittag verschlingen würde, wird zu einem grep. Die Output-Intents, ICC-Profile und das Tagging hinter diesen Flags sind in dem Leitfaden zur PDF/A-, PDF/X- und PDF/UA-Ausgabe mit HotPDF ausführlich beschrieben

Ein günstiges erstes Gate für Dateien, die Sie nicht erzeugt haben

Nicht jede Pipeline ist rein generativ. Kunden laden PDFs hoch, Scanner legen sie in einem Ordner ab, Partner hängen sie an E-Mails an. Jede davon durch einen vollständigen Strukturvalidator zu schicken, verschwendet Warteschlangenzeit an Dateien, die sich nicht einmal öffnen lassen. Die Direct File API von HotPDF liest genug von der Struktur einer Datei, um die Frage „Ist das überhaupt ein brauchbares PDF?“ zu beantworten, ohne den gesamten Objektbaum zu laden, was sie zu einem guten Ort macht, um früh zu scheitern:

function TriagePdf(Pdf: THotPDF; const FileName: string): Boolean;
var
  Handle, Pages: Integer;
begin
  Result := False;
  Handle := Pdf.DAOpenFileReadOnly(FileName, '');
  if Handle <= 0 then
    Exit;  // strukturell unlesbar: Quarantäne, nicht validieren
  try
    Pages := Pdf.DAGetPageCount(Handle);
    Result := Pages > 0;
  finally
    Pdf.DACloseFile(Handle);
  end;
end;

Zwei Fakten zu dieser API bestimmen, wie Sie sie kapseln. Die Flat-Memory-Abkürzung gilt nur für unverschlüsselte Eingaben; übergeben Sie DAOpenFileReadOnly ein Passwort, fällt es still auf ein vollständiges Parsen zurück, sodass eine Datei, von der Sie wissen, dass sie verschlüsselt ist, vor der Triage über DecryptFile in eine unverschlüsselte Arbeitskopie überführt werden sollte. Und DAGetPageCount bedeutet nichts auf einem Handle, das sich nicht sauber geöffnet hat, daher bleibt die Handle-Prüfung strikt, und ein nicht-positives Ergebnis ist eine Ablehnung, kein Wiederholungsversuch. Weitere dieser Muster finden sich in dem Artikel zur Direct File API für große PDF-Workflows

veraPDF, als Teil des Builds ausgeführt

Für alles, was Sie als PDF/A oder PDF/UA deklarieren, ist veraPDF der Validator, den es einzubinden gilt. Er läuft headless, nimmt einen Stapel entgegen, gibt XML oder JSON aus und benennt jeden Fehler nach seiner ISO-Klausel, sodass ein Regelverstoß gegen ISO 19005-1 Klausel 6.2.2 direkt auf eine Generatoreinstellung zurückzeigt, statt Sie raten zu lassen. Ihn von Delphi aus anzusteuern ist einfache Prozesssteuerung:

function RunVeraPdf(const PdfFile, ReportFile: string): Cardinal;
var
  Cmd: string;
  SI: TStartupInfo;
  PI: TProcessInformation;
begin
  Cmd := Format('cmd /c verapdf.bat --format xml "%s" > "%s"',
    [PdfFile, ReportFile]);
  FillChar(SI, SizeOf(SI), 0);
  SI.cb := SizeOf(SI);
  if not CreateProcess(nil, PChar(Cmd), nil, nil, False,
      CREATE_NO_WINDOW, nil, nil, SI, PI) then
    RaiseLastOSError;
  try
    WaitForSingleObject(PI.hProcess, 120000);  // die Wartezeit pro Datei begrenzen
    GetExitCodeProcess(PI.hProcess, Result);
  finally
    CloseHandle(PI.hThread);
    CloseHandle(PI.hProcess);
  end;
end;

Dieser Timeout verdient seinen Platz. Eine fehlerhafte Datei kann jeden Parser in eine Ecke treiben, aus der er nie wieder herauskommt, und ein unbegrenztes Warten in einem Queue-Worker zieht den Rest der Warteschlange mit hinunter. Begrenzen Sie die Wartezeit, geben Sie einem Timeout einen eigenen Fehlercode und legen Sie die Datei für einen Menschen beiseite. Wenn Sie das Ergebnis lesen, parsen Sie das XML nach Regelbezeichnern, nicht nach dem menschenlesbaren Text. Regel-IDs überleben Validator-Upgrades; der Wortlaut der Meldungen nicht, und ein stabiler Code ist etwas, wonach ein Support-Ingenieur alte Tickets durchsuchen kann

Wie Sie den Stapel ausführen, ist ebenso wichtig wie die Frage, ob jede Datei besteht. Ein Prozess pro Datei, nicht einer pro Stapel, damit eine giftige Eingabe Sie den Timeout dieser Datei kostet und nichts weiter. Begrenzen Sie die Anzahl der Validator-Prozesse auf die Kernanzahl, denn der Aufbau des XML-Berichts ist CPU-gebunden, und Überbelegung führt nur zu Thrashing. Und setzen Sie beim Eingang eine Größenobergrenze, denn ein zwei Gigabyte großes gescanntes Buch wird die Warteschlange beherrschen, egal wie geduldig der Parser ist. Nichts davon ist Preflight im strengen Sinne. Es ist der Unterschied zwischen einem Gate, das das Monatsendvolumen überlebt, und einem, das in der ersten Nacht abgeschaltet wird, in der es die Pipeline um 2 Uhr morgens zum Stillstand bringt

Diagramm eines Delphi-Stapel-Gates, das einen veraPDF-Prozess pro PDF unter begrenztem Timeout ausführt, XML-Regel-IDs statt Meldungen auswertet und jeden Bericht neben seiner Datei archiviert
Ein Eingangswächter begrenzt die Warteschlangenlast, während ein frischer veraPDF-Worker pro Datei verhindert, dass eine vergiftete Eingabe den Build blockiert

Bei PDF/X stößt das an seine Grenzen. veraPDF validiert es nicht, daher ist die praktikable Prüfung nach wie vor Acrobats Preflight mit dem ISO-15930-Profil, das Ihre Druckerei genannt hat. Acrobat braucht einen Menschen, was Stichproben statt vollständiger Abdeckung bedeutet: die erste Datei aus einer neuen Vorlage plus eine kleine Zufallsauswahl aus jedem Stapel, während das automatisierte Gate alles übernimmt, was sich ohne Menschen erledigen lässt. Eine Stichprobenprüfung, die tatsächlich läuft, schlägt eine vollständige Automatisierung, die für immer halb fertig bleibt

Ein Bericht, den Sie auch in einem Jahr noch brauchen

Ein Preflight-Gate zahlt sich zweimal aus. Einmal, wenn es eine fehlerhafte Datei an der Tür aufhält, und noch einmal viel später, wenn jemand fragt, warum eine bestimmte Datei durchgelassen wurde. Dieser zweite Moment sollte das Format bestimmen, denn es ist derjenige, in dem ein dünner Bericht Sie im Stich lässt. Bewahren Sie für jede geprüfte Datei den Eingabe-Hash, die Compliance-Flags des Generators und die Bibliotheksversion aus der obigen Logzeile, Name und Version des Validators, das Profil, gegen das geprüft wurde, das Bestehen oder Scheitern sowie die fehlgeschlagenen Regel-IDs mit Seitenzahlen auf, wo immer der Validator sie liefert. Speichern Sie diesen Bericht neben der Datei, die er beschreibt. Legen Sie ihn in ein separates System, und dieses System wird stillgelegt, bevor das Archiv, das es dokumentiert

Ausnahmen müssen ebenfalls schriftlich festgehalten werden. Wenn ein Kunde darauf besteht, eine Datei auszuliefern, die das Gate nicht mag, besteht die Antwort nicht darin, die Regel für alle zu lockern. Halten Sie fest, wer diese Datei genehmigt hat, aus welchem Grund und bis zu welchem Datum, und hängen Sie diese Freigabe an ihren Bericht. Eine Freigabe mit Namen und Ablaufdatum ist eine Entscheidung, für die jemand verantwortlich ist. Eine „vorübergehend“ auskommentierte Prüfung ist ein Vorfall, der auf sein Datum wartet

Eine weitere Gewohnheit zahlt sich aus: Wenn eine Datei scheitert, kopieren Sie sie in einen benannten Regressionsordner, bevor jemand sie anfasst. Nahezu jedes Preflight-Problem, das sich zu debuggen lohnt, geht auf eine bestimmte Eingabe zurück, und die Teams, die diese Eingaben aufbewahren, beheben die Wiederholung in einer Stunde, statt darauf zu warten, dass sie in der Produktion wieder auftaucht. Die hier gezeigten Compliance-Eigenschaften und die Direct File API sind Teil der HotPDF Delphi Component für Delphi und C++Builder, deren Dokumentation jeden Aufruf vollständig beschreibt