Technischer Artikel

PDF/VT-Variablendaten-Druck in Delphi mit PDFium VCL

Eine Transaktionsdruckerei schickt Ihren 80.000-seitigen Kontoauszugslauf mit einer einzeiligen Ablehnung zurück: „Kein PDF/VT, RIP kann nicht cachen.“ Die Datei lässt sich in jedem Viewer auf Ihrem Schreibtisch einwandfrei öffnen, die Farben stimmen, die Daten wurden korrekt zusammengeführt. Nichts davon ist das, was die Digitaldruckmaschine verlangt hat. Der Hochgeschwindigkeits-Variablendatendruck steht und fällt damit, ob die Druckmaschine erkennen kann, dass der Kunden-Logo-Block auf Seite 1 Byte für Byte dasselbe Objekt ist wie der auf Seite 40.000, dieses einmal rendert und dann wiederverwendet. PDF/VT is der Standard, der dieses Versprechen maschinell überprüfbar macht, und „sieht korrekt aus“ ist genau die Falle, da die Struktur, die der RIP liest, auf dem Bildschirm unsichtbar ist

PDFiumPas legt diese Struktur über eine kleine Schnittstelle auf TPdf offen: SaveAsPdfVT schreibt sie, ValidatePdfVT prüft sie. In diesem Artikel geht es darum, was diese beiden Methoden tatsächlich auf der Festplatte ablegen und untersuchen, an welchen Stellen ISO 16612-2 strenger ist, als es auf den ersten Blick aussieht, und welche Teile ehrliche strukturelle Verankerungen anstelle eines vollständigen Preflights sind, gegen den Sie einen Kunden abrechnen können

Was PDF/VT standardisiert und warum PDF/X an erster Stelle steht

PDF/VT (ISO 16612-2:2010) ist kein neues Dateiformat. Es handelt sich um eine Ebene von Optimierungs-Metadaten, die auf eine PDF/X-Datei aufgesetzt wird, und diese Reihenfolge ist tragend. Der Standard definiert drei Konformitätsstufen, aber nur zwei davon bezeichnen eine PDF-Datei: PDF/VT-1, ein einzelnes in sich geschlossenes Dokument, und PDF/VT-2, ein Dateisatz-Modell, bei dem Seiten auf gemeinsam genutzte externe Ressourcen verweisen. Das dritte Token, dem Sie begegnen können, PDF/VT-2s, ist überhaupt kein Wert auf Dateiebene; es lebt in einem in Anhang A beschriebenen MIME-Stream-Header. Wenn Sie Code finden, der GTS_PDFVTVersion = "PDF/VT-2s" in die XMP einer Datei schreibt, ist dieser Code fehlerhaft

Die nicht verhandelbare Regel für eine einzelne Datei ist die PDF/X-Basis. ISO 16612-2 §6.2.1 verlangt, dass jede PDF/VT-1-Datei auch eine gültige PDF/X-4-Datei sein muss. Der Dateisatz von PDF/VT-2 muss gemäß §6.2.2 stattdessen auf PDF/X-4p, PDF/X-5g oder PDF/X-5pg basieren. Aus diesem Grund kann ein PDF/VT-Writer nicht einfach ein paar Identifikationsschlüssel anhängen: Er muss den gesamten PDF/X-4-Markersatz mit sich führen, was einen OutputIntent, ein eingebettetes ICC-Zielprofil, die passenden XMP- und Dokument-Info-Einträge, eine Trailer-/ID und keine Verschlüsselung bedeutet. Wenn Sie eines dieser Kriterien auslassen, haben Sie eine Datei, die PDF/VT beansprucht und in dem Moment fehlschlägt, in dem ein konformer Verbraucher die Basis überprüft. PDFiumPas behandelt die PDF/X-4-Ebene als Teil der PDF/VT-Speicherung, sodass Sie nicht zuerst ein separates SaveAsPdfX aufrufen müssen; der Injektor schreibt beide Ebenen in einem einzigen Durchgang

Schreiben einer Datei mit SaveAsPdfVT

Der minimale Aufruf benötigt nichts weiter als ein aktives Dokument, da TPdfVTSaveOptions.Default ein integriertes sRGB-ICC-Profil und die Konformität pvc1 bereitstellt. Das Speichern führt intern drei Schritte aus: Es entfernt jegliche Sicherheitsbeschränkungen (das Injizieren von Klartext-Markern in einen verschlüsselten Objekt-Stream würde diesen beschädigen), es gleicht das vorhandene Info-Dictionary des Dokuments und die Trailer-/ID mit dem Markersatz ab, sodass die XMP- und Info-Werte übereinstimmen, und hängt dann die PDF/X-4- und PDF/VT-Objekte durch ein inkrementelles Update an

var
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    if Pdf.LoadFromFile('statements-merged.pdf') then
    begin
      // Default options: built-in sRGB OutputIntent, PDF/VT-1, synthesised DPart
      if Pdf.SaveAsPdfVT('statements-pdfvt.pdf') then
        Writeln('PDF/VT-1 written')
      else
        Writeln('Save failed (document not active?)');
    end;
  finally
    Pdf.Free;
  end;
end;

Für echte Produktionsausgaben möchten Sie fast immer den OutputIntent durch die Charakterisierung Ihrer Druckmaschine überschreiben, anstatt den generischen sRGB-Fallback zu verwenden. Übergeben Sie die ICC-Bytes und die Bedingungs-Identifikatoren über TPdfVTSaveOptions:

var
  Pdf: TPdf;
  Opt: TPdfVTSaveOptions;
  Icc: TBytes;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.LoadFromFile('directmail-merged.pdf');
    Icc := LoadIccProfile('GRACoL2013_CRPC6.icc');  // your own loader

    Opt := TPdfVTSaveOptions.Default;
    Opt.Conformance := pvc1;            // pvc2 is normalised to pvc1 on write
    Opt.IccProfileData := Icc;
    Opt.OutputConditionIdentifier := 'CGATS21_CRPC6';
    Opt.OutputCondition := 'Commercial print, coated, CRPC6';
    Opt.RegistryName := 'http://www.color.org';
    Opt.Title := 'Spring 2026 Direct Mail Run';
    Opt.Trapped := ptvFalse;           // PDF/X Info /Trapped state

    Pdf.SaveAsPdfVT('directmail-pdfvt.pdf', Opt);
  finally
    Pdf.Free;
  end;
end;

Ein Detail in diesem Schnipsel ist eher ein bewusster Schutzmechanismus als eine Einschränkung, über die man streiten kann. Das Setzen von Opt.Conformance := pvc2 erzeugt keine PDF/VT-2-Datei. Der Writer normalisiert jede Nicht-pvc1-Anforderung zurück auf pvc1, da PDF/VT-2 ein Dateisatz-Format ist und ein Einzeildatei-Writer, der ein Ausgabedokument physisch anhängt, die vom Standard §6.2.2 geforderten externen Ressourcen nicht zusammenstellen kann. Der Wert pvc2 existiert für den Lesepfad, damit ValidatePdfVT ein vorhandenes Dateisatz-Dokument erkennen und melden kann; er ist kein Schreibziel

Der DPart-Baum: Die Struktur, die der RIP tatsächlich liest

Das Herzstück von PDF/VT ist die Document Part (DPart)-Hierarchie. Sie ermöglicht es einer Druckmaschine, einen langen Lauf in Datensätze zu unterteilen, Datensätze nach Empfängern oder Postbündeln zu gruppieren und Document Part Metadata anzuhängen, damit nachgelagerte Geräte jedes Teil sortieren und abrechnen können. ISO 16612-2 §6.5 legt die Verkabelung fest: Der Katalog trägt einen /DPartRoot, der DPart-Wurzelknoten trägt /DPartRootNode und eine /NodeNameList, die jede Hierarchieebene benennt, Blatt-DParts (Leaf DParts) decken Bereiche des Seitenbaums ab, und jede Seite, die zu einem Teil gehört, verweist über einen seitenbezogenen /DPart-Eintrag zurück auf ihr Blatt

Wenn Ihr Quelldokument bereits eine nutzbare Hierarchie enthält, bleibt diese durch SaveAsPdfVT erhalten. Wenn nicht, erzeugt der Writer eine minimale Struktur: einen einzelnen DPart auf Dokumentebene, der den aktuellen Seitenbaum in der richtigen Reihenfolge überspannt, mit einer seitenweisen /DPart-Rückreferenz, die an jedes Live-Seitenobjekt angehängt wird, und einer einstufigen /NodeNameList [/Document]. Seien Sie ehrlich zu sich selbst, was diesen minimalen Baum betrifft. Er ist eine strukturelle Verankerung, die die Formanforderungen von §6.5 erfüllt; er ist keine geschäftliche Metadatenstruktur. Er kann keine Empfänger, Poststückgrenzen oder Produktchargen erfinden, da diese Informationen niemals in der Quelle vorhanden waren. Wenn Sie empfängerspezifische Daten haben, wird von Ihnen erwartet, dass Sie selbst einen tieferen DPart-Baum aufbauen und die /NodeNameList entsprechend den von Ihnen erstellten Ebenen erweitern

Validierung, die über das bloße Vorhandensein von Schlüsseln hinausgeht

ValidatePdfVT gibt einen TPdfVTValidationResult-Record mit drei Dingen zurück: der erkannten Conformance, einer menge von Issues und einer IsCompliant-Hilfsfunktion, die nur dann wahr ist, wenn die Konformität einer echten Stufe entspricht und die Menge der Probleme leer ist. Die Problemaufzählung ist bewusst spezifisch gehalten, sodass ein fehlerhaftes Ergebnis Ihnen sagt, welche Klausel Sie verfehlt haben, anstatt nur „ungültig“ zu melden:

var
  Pdf: TPdf;
  Res: TPdfVTValidationResult;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.LoadFromFile('statements-pdfvt.pdf');
    Res := Pdf.ValidatePdfVT;

    if Res.IsCompliant then
      Writeln('PDF/VT compliant: ', VTLevelName(Res.Conformance))
    else
    begin
      if pvviMissingDPartRoot in Res.Issues then
        Writeln('DPart hierarchy missing or unusable');
      if pvviMissingPdfXIdentifier in Res.Issues then
        Writeln('PDF/X-4 base identifier absent');
      if pvviMissingOutputIntent in Res.Issues then
        Writeln('OutputIntent / ICC profile missing');
      if pvviEncryptionPresent in Res.Issues then
        Writeln('Encrypted - PDF/X forbids this');
    end;
  finally
    Pdf.Free;
  end;
end;

Die beiden Prüfungen, die man im Detail verstehen sollte, sind die Konformitätspaarung und der DPart-Durchlauf, da beide früher zu nachsichtig waren und verschärft wurden, um dem Standard zu entsprechen. Auf der Paarungsseite führt der Validator einen exakten Abgleich durch, nicht nach dem Motto „jedes PDF/X reicht aus“: Eine PDF/VT-1-Datei wird nur auf einer PDF/X-4-Basis akzeptiert und eine PDF/VT-2-Datei nur auf PDF/X-4p, PDF/X-5g oder PDF/X-5pg. Ein PDF/VT-1-Marker, der auf einer PDF/X-1a-Basis sitzt, wird gemeldet und nicht einfach durchgewunken

Im DPart-Durchlauf liegt der größte Teil der Strenge. Es reicht nicht aus, dass der Katalog einen /DPartRoot-Schlüssel enthält, da ein gefälschtes leeres Ojekt oder eines ohne Seitenverknüpfungen immer noch nicht verarbeitet werden kann. HasValidDPartHierarchy und das rekursive ValidateDPartNode verfolgen die gesamte Struktur: Sie folgen den Elternverknüpfungen, lehnen doppelte Kinder und Zyklen ab, stellen sicher, dass /Start und /DParts sich gegenseitig ausschließen, und verlangen, dass die Blatt-Seitenbereiche den Seitenbaum in einer Tiefensuche-Reihenfolge abdecken, wobei der /DPart jeder Seite auf das Blatt zeigt, das sie enthält. Alle diese internen Fehler laufen im einzelnen Problem-Bit pvviMissingDPartRoot zusammen, anstatt das öffentliche Enum zu erweitern. Betrachten Sie dieses eine Flag also als „die DPart-Hierarchie ist unbrauchbar“ und nicht wörtlich als „der Wurzel-Schlüssel fehlt“

Drei syntaktische Fallen, die der Validator nun durchsetzt

Aufeinanderfolgende Durchgänge gegen §6.5 Tabelle 4 brachten Formen zutage, die frühere Versionen akzeptierten, der Standard jedoch nicht. Dies sind die typischen Fehler, die bei einem manuell erstellten DPart-Baum auftreten, weshalb sie hier explizit genannt werden sollen:

  • /DParts ist ein Array von Arrays, kein flaches Array. Jedes Element des äußeren Arrays muss selbst ein Array mit indirekten Referenzen sein. Ein flaches /DParts [9 0 R] wird abgelehnt; die konforme Form ist /DParts [[9 0 R] [10 0 R]]. Dies verhindert, dass sich eine nicht-hierarchische Struktur als gültige Ebene ausgibt
  • /End markiert nur einen echten mehrseitigen Bereich. Ein Blatt-DPart darf /End nur tragen, wenn es auch /Start besitzt, und /End muss in der Reihenfolge des Seitenbaums nach /Start liegen. Ein degeneriertes /Start 3 0 R /End 3 0 R führt nun dazu, dass die Hierarchie unbrauchbar wird, anstatt als einseitiger Teil interpretiert zu werden
  • Namen in /NodeNameList müssen die PDF-Namen-Dekodierung als XML-NMTOKENs überstehen. Ein Name wie /Bad#20Name expandiert zu einem Namen, der ein Leerzeichen enthält, was kein gültiges Token ist. Die Implementierung führt eine einfache ASCII-Prüfung durch (Buchstaben, Ziffern, ., -, _, : sowie Nicht-ASCII-Bytes), die Leerzeichen- und Trennzeichenfehler abfängt, ohne legitime lokalisierte oder herstellerspezifische Namen abzulehnen

XMP-Marker: Zwei Wege, dieselbe Eigenschaft zu schreiben

Die PDF/VT-Identifizierung lebt im XMP-Bereich unter dem Namensraum pdfvtid, speziell GTS_PDFVTVersion und GTS_PDFVTModDate, neben den Standardwerten xmp:CreateDate und xmp:ModifyDate. Eine Feinheit, die bei naiven Readern zu falschen „Fehlt“-Meldungen führt, besteht darin, dass diese auf zwei Arten serialisiert werden können: als Elementtext (<pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion>) oder als RDF-Attribut im Description-Element. PDFiumPas liest beide Formen, sodass eine Datei, die von einem anderen Tool im Attribut-Stil geschrieben wurde, nicht benachteiligt wird. Es erzwingt auch die Konsistenzregel von §6.3, dass GTS_PDFVTModDate gleich xmp:ModifyDate sein muss; eine Abweichung löst pvviModDateMismatch aus

Eine weitere Regel aus derselben Klausel: Ein unbekannter GTS_PDFVTVersion-Wert wird als pvcUnknown beibehalten, anstatt zu pvcNone zurückgefaltet zu werden. Dieser Unterschied ist betrieblich wichtig. pvcNone bedeutet „überhaupt kein PDF/VT-Marker, ein gewöhnliches PDF“, während pvcUnknown bedeutet „etwas hat eine Version eingeprägt, die dieser Validator nicht erkennt“ (darunter der Fall PDF/VT-2s). Das Vermischen der beiden würde eine fehlerhafte Datei im selben Topf wie ein normales Dokument verstecken

Wo die Garantie endet

Es lohnt sich, genau zu bestimmen, wo die Grenzen dessen liegen, was diese Methoden versprechen, da mit der Einhaltung des Variablendatendrucks echtes Geld verbunden ist. Die DPart- und Paarungsprüfungen sind strukturelle Validierungen auf Byte-Ebene. Sie bestätigen, dass das Optimierungsskelett, die PDF/X-4-Basismarker, der OutputIntent und die XMP vorhanden und in sich konsistent sind. Sie sind kein Preflight auf Inhaltsebene für PDF/X-4: Sie überprüfen nicht, ob jede Farbe innerhalb der deklarierten Ausgabebedingung liegt, ob alle Schriften eingebettet sind oder ob sich kein verbotener Transparenzmischungs-Grenzfall eingeschlichen hat. Für einen Auftrag, den Sie an eine Vertragsdruckerei übergeben, kombinieren Sie die strukturelle Validierung von PDFiumPas mit einer dedizierten PDF/X-Preflight-Engine und einem Probedruck, genau so, wie Sie jede andere Konformitätsbehauptung überprüfen würden. Die strukturelle Ebene fängt die Fehler ab, die das RIP-Caching lautlos stören; sie ist eine Hälfte einer vollständigen Prüfung, nicht das Ganze

Wenn Sie diese Prüfungen in ein breiteres Release-Gate einbauen: Dieselbe Methode des Scannens auf Byte-Ebene bildet die Grundlage für die anderen Standardarbeiten der Bibliothek, einschließlich der Validierung von Objekt- und Cross-Reference-Streams, bevor eine Datei überhaupt einen Preflight erreicht, und der Disziplin der gemeinsam genutzten Objekte bei der Wiederverwendung von Seitenstempeln mit Form-XObjects, die ein Dokument überhaupt erst RIP-freundlich macht. Die hier beschriebenen PDF/VT- und PDF/X-Speicher- und Validierungs-APIs sind Teil der PDFium-VCL-Komponente für Delphi und C++Builder, deren Produktseite die vollständige Konformitätsreferenz enthält