Odborný článok

PDF/VT Variable Data Printing in Delphi with PDFium VCL

Transakčná tlačiareň vám vráti 80 000-stranový beh výpisov s jednovetovým odmietnutím: „not PDF/VT, RIP cannot cache“. Súbor sa na každom prehliadači na stole otvorí správne, farby sú v poriadku a dáta sa zlúčili korektne. Presne to však digitálny tlačový stroj nechcel. Vysokorýchlostná variable-data tlač stojí a padá na tom, či tlačový RIP dokáže rozpoznať, že blok s logom zákazníka na strane 1 je bajt po bajte ten istý objekt ako na strane 40 000, vyrenderovať ho raz a znovu použiť. PDF/VT je štandard, ktorý tento sľub robí strojovo kontrolovateľným, a práve „vyzerá to správne“ je pasca, pretože štruktúra, ktorú RIP číta, je na obrazovke neviditeľná

PDFiumPas túto štruktúru sprístupňuje cez malý povrch na TPdf: SaveAsPdfVT ju zapisuje, ValidatePdfVT ju kontroluje. Tento článok vysvetľuje, čo tieto dve metódy skutočne zapisujú na disk a čo kontrolujú, v čom je ISO 16612-2 prísnejšie, než sa na prvý pohľad zdá, a ktoré časti sú poctivé štrukturálne kotvy namiesto plného preflightu, ktorý by ste mohli fakturovať klientovi

Čo PDF/VT štandardizuje a prečo musí najprv prísť PDF/X

PDF/VT (ISO 16612-2:2010) nie je nový formát súboru. Je to vrstva optimalizačných metadát priskrutkovaná na PDF/X súbor a toto poradie nesie váhu. Štandard definuje tri úrovne zhody, ale iba dve z nich pomenúvajú PDF súbor: PDF/VT-1, samostatný dokument, a PDF/VT-2, model file setu, v ktorom strany odkazujú na zdieľané externé zdroje. Tretí token, s ktorým sa môžete stretnúť, PDF/VT-2s, vôbec nie je hodnota na úrovni súboru; žije v MIME stream headeri opísanom v Annex A. Ak nájdete kód, ktorý zapisuje GTS_PDFVTVersion = "PDF/VT-2s" do XMP dokumentu, taký kód je nesprávny

Neobídateľné pravidlo pre samostatný súbor je základ PDF/X. ISO 16612-2 §6.2.1 vyžaduje, aby každý PDF/VT-1 súbor bol zároveň platný PDF/X-4 súbor. File set PDF/VT-2 musí podľa §6.2.2 namiesto toho stáť na PDF/X-4p, PDF/X-5g alebo PDF/X-5pg. Preto writer PDF/VT nemôže iba pripojiť pár identifikačných kľúčov: musí so sebou niesť celý markerový set PDF/X-4, teda OutputIntent, vložený ICC destination profile, zodpovedajúce záznamy v XMP a Info, trailer /ID a žiadne šifrovanie. Ak čokoľvek z toho vynecháte, máte súbor, ktorý tvrdí PDF/VT a zlyhá v okamihu, keď kompatibilný konzument skontroluje jeho základ. PDFiumPas považuje vrstvu PDF/X-4 za súčasť ukladania PDF/VT, preto najprv nevoláte samostatné SaveAsPdfX; injektor zapisuje obe vrstvy v jednom prechode

Diagram zásobníka PDF/VT postaveného v Delphi, kde optimalizačné metadáta sedí na povinnej základni PDF/X-4, s presným párovaním zhody pre PDF/VT-1, PDF/VT-2 a token PDF/VT-2s len-MIME
Metadáta PDF/VT sú nosné len na vrchu kompletnej základne PDF/X

Zápis súboru pomocou SaveAsPdfVT

Minimálne volanie nepotrebuje nič viac než aktívny dokument, pretože TPdfVTSaveOptions.Default dodáva vstavaný ICC profil sRGB a úroveň zhody pvc1. Ukladanie interne prebehne v troch krokoch: odstráni akékoľvek zabezpečenie (vloženie čistotextových markerov do šifrovaného object streamu by ho poškodilo), premostí existujúci slovník Info dokumentu a trailer /ID do markerovej sady tak, aby hodnoty v XMP aj Info súhlasili, a potom pripojí PDF/X-4 aj PDF/VT objekty cez inkrementálnu aktualizáciu

var
  Pdf: TPdf;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'statements-merged.pdf';
    Pdf.Active := True;
    // Predvolené voľby: vstavaný OutputIntent sRGB, PDF/VT-1, syntetizovaný DPart
    if Pdf.SaveAsPdfVT('statements-pdfvt.pdf') then
      Writeln('PDF/VT-1 written')
    else
      Writeln('Save failed (document not active?)');
  finally
    Pdf.Free;
  end;
end;

Pri reálnom produkčnom výstupe takmer vždy chcete prepísať OutputIntent vlastnou charakteristikou tlačového stroja, nie všeobecným fallbackom sRGB. ICC bajty a identifikátory podmienok odovzdajte cez TPdfVTSaveOptions:

var
  Pdf: TPdf;
  Opt: TPdfVTSaveOptions;
  Icc: TBytes;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'directmail-merged.pdf';
    Pdf.Active := True;
    Icc := LoadIccProfile('GRACoL2013_CRPC6.icc');  // vlastný loader

    Opt := TPdfVTSaveOptions.Default;
    Opt.Conformance := pvc1;            // pvc2 sa pri zápise normalizuje na pvc1
    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;           // stav /Trapped v PDF/X Info

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

Jeden detail v tomto snippete je zámerná ochranná lišta, nie obmedzenie, o ktorom sa dá polemizovať. Nastavenie Opt.Conformance := pvc2 nevytvorí súbor PDF/VT-2. Writer normalizuje každú požiadavku inú než pvc1 späť na pvc1, pretože PDF/VT-2 je formát file setu a jednosúborový writer, ktorý pripája jeden výstupný dokument, fyzicky nedokáže zostaviť externú sadu zdrojov, akú vyžaduje §6.2.2. Hodnota pvc2 existuje pre read path, aby ValidatePdfVT vedelo rozpoznať a nahlásiť existujúci file-set dokument; nie je to cieľ zapisovania

DPart strom: štruktúra, ktorú RIP skutočne číta

Srdcom PDF/VT je hierarchia Document Part, teda DPart. Umožňuje tlačovému stroju rozdeliť dlhý beh na záznamy, zoskupiť ich do príjemcov alebo poštových balíkov a pripojiť Document Part Metadata tak, aby downstream zariadenia vedeli smerovať a účtovať každý kus. ISO 16612-2 §6.5 opisuje zapojenie takto: katalóg nesie /DPartRoot, koreňový uzol DPart nesie /DPartRootNode a /NodeNameList pomenúvajúci každú úroveň hierarchie, listové DParty pokrývajú rozsahy stromu strán a každá strana, ktorá patrí do nejakej časti, sa naspäť odkazuje na svoj list cez záznam /DPart na úrovni strany

Diagram PDFium Component hierarchie DPart PDF/VT konzumovanej RIP: DPartRoot katalógu, NodeNameList koreňového uzla, listové DParts pokrývajúce rozsahy strán a spätné referencie DPart na úrovni strany
Strom DPart je štruktúra, ktorú RIP číta, aby rozdelil záznamy a cachoval zdieľaný obsah

Keď už zdrojový dokument použiteľnú hierarchiu obsahuje, SaveAsPdfVT ju zachová. Keď ju neobsahuje, writer syntetizuje minimálnu verziu: jediný document-level DPart, ktorý v poradí pokrýva aktuálny strom strán, so spätným odkazom /DPart pripojeným ku každej živej page object a jednoúrovňovým /NodeNameList [/Document]. Buďte k sebe úprimní, čo taký minimálny strom predstavuje. Je to štrukturálna kotva, ktorá spĺňa tvarové požiadavky §6.5. Nie sú to business metadáta. Nedokáže vymyslieť príjemcov, hranice zásielok ani produktové dávky, pretože tieto informácie v zdroji nikdy neboli. Ak máte dáta po príjemcoch, očakáva sa, že hlbší DPart strom vytvoríte sami a rozšírite /NodeNameList tak, aby zodpovedal úrovniam, ktoré vytvoríte

Validácia, ktorá ide ďalej než len k prítomnosti kľúčov

ValidatePdfVT vracia záznam TPdfVTValidationResult s tromi vecami: zistenou hodnotou Conformance, množinou Issues a pomocníkom IsCompliant, ktorý je true iba vtedy, keď je conformance reálna úroveň a množina problémov je prázdna. Výpočtový typ problémov je zámerne konkrétny, takže neúspešný výsledok vám povie, ktorú klauzulu ste zmeškali, nielen že je súbor „invalid“:

var
  Pdf: TPdf;
  Res: TPdfVTValidationResult;
begin
  Pdf := TPdf.Create(nil);
  try
    Pdf.FileName := 'statements-pdfvt.pdf';
    Pdf.Active := True;
    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;

Dve kontroly, ktoré stojí za to pochopiť do hĺbky, sú párovanie conformance a prechod stromom DPart, pretože obe bývali príliš zhovievavé a boli sprísnené tak, aby zodpovedali špecifikácii. Na strane párovania robí validátor presné porovnanie, nie „hocijaké PDF/X postačí“: súbor PDF/VT-1 sa akceptuje iba na základe PDF/X-4, a súbor PDF/VT-2 iba na PDF/X-4p, PDF/X-5g alebo PDF/X-5pg. Marker PDF/VT-1 sediaci na základe PDF/X-1a sa nahlási, nie prepustí

Najviac prísnosti sa nachádza v prechode DPart. Nestačí, aby katalóg mal kľúč /DPartRoot, pretože sfalšovaný prázdny objekt alebo objekt bez odkazov na strany stále nemožno spracovať. HasValidDPartHierarchy a rekurzívna ValidateDPartNode prejdú celú štruktúru: sledujú odkazy na rodiča, odmietajú duplicitné potomky a cykly, vynucujú, že /Start a /DParts sa vzájomne vylučujú, a vyžadujú, aby listové rozsahy strán pokrývali strom strán v poradí do hĺbky (depth-first), pričom /DPart každej strany ukazuje na list, ktorý ju obsahuje. Všetky tieto vnútorné chyby sa zbiehajú do jediného bitu problému pvviMissingDPartRoot namiesto rozširovania verejného výpočtového typu, takže tento príznak čítajte ako „DPart hierarchia je nepoužiteľná“, nie doslova ako „chýba root key“

Diagram validácie PDF/VT v Delphi s PDFium VCL, kde presné párovanie základne, rekurzívny prechod DPart, značky PDF/X-4 a kontroly dátumov XMP kŕmia záznam TPdfVTValidationResult
ValidatePdfVT hlási, ktorá klauzula zlyhala, namiesto generického verdiktu neplatné

Tri syntaktické pasce, ktoré validátor teraz vynucuje

Postupné prechody cez tabuľku 4 v §6.5 odhalili tvary, ktoré staršie verzie akceptovali, ale štandard nie. Presne na týchto miestach sa ručne stavaný DPart strom zvykne pokaziť, preto stojí za to ich vypichnúť:

  • /DParts je pole polí, nie ploché pole. Každý prvok vonkajšieho poľa musí byť sám osebe poľom nepriamych referencií. Ploché /DParts [9 0 R] sa odmieta; zhodný tvar je /DParts [[9 0 R] [10 0 R]]. Tým sa zabráni tomu, aby sa nehierarchická štruktúra tvárila ako platná úroveň
  • /End označuje iba skutočný viacstranový rozsah. Listový DPart smie niesť /End iba vtedy, keď má aj /Start, a /End musí v poradí stromu strán nastať neskôr než /Start. Degenerovaný prípad /Start 3 0 R /End 3 0 R teraz robí hierarchiu nepoužiteľnou namiesto toho, aby sa čítal ako jednolistový part
  • /NodeNameList názvy musia po unescapingu PDF mien prežiť ako XML NMTOKEN. Názov ako /Bad#20Name sa rozbalí na hodnotu obsahujúcu medzeru, čo nie je platný token. Implementácia vykonáva ľahkú ASCII kontrolu (písmená, číslice, ., -, _, :, plus neASCII bajty), ktorá zachytí whitespace a delimiter chyby bez toho, aby odmietala legitímne lokalizované alebo vendor-specific mená

Značky XMP: dva spôsoby, ako zapísať tú istú vlastnosť

Identifikácia PDF/VT žije v XMP pod namespace pdfvtid, konkrétne GTS_PDFVTVersion a GTS_PDFVTModDate, popri štandardných xmp:CreateDate a xmp:ModifyDate. Jemnosť, ktorá v naivných čítačkách spôsobuje falošné hlásenia „missing“, spočíva v tom, že každú z týchto hodnôt možno serializovať dvoma spôsobmi: ako text elementu (<pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion>) alebo ako RDF atribút na elemente description. PDFiumPas číta obe podoby, takže súbor, ktorý iný nástroj zapísal atribútovým štýlom, nie je penalizovaný. Zároveň vynucuje pravidlo konzistencie z §6.3, podľa ktorého GTS_PDFVTModDate musí byť rovné xmp:ModifyDate; nezhoda vyvolá pvviModDateMismatch

Ešte jedno pravidlo z tej istej klauzuly: neznáma hodnota GTS_PDFVTVersion sa zachová ako pvcUnknown namiesto toho, aby sa preklopila späť na pvcNone. Tento rozdiel je prevádzkovo dôležitý. pvcNone znamená „vôbec žiadny marker PDF/VT, obyčajné PDF“, zatiaľ čo pvcUnknown znamená „niečo označilo verziu, ktorú tento validátor nerozpoznáva“ (patrí sem aj prípad PDF/VT-2s). Zlievanie týchto dvoch stavov by schovalo chybný súbor do toho istého koša ako obyčajný dokument

Kde sa záruka končí

Oplatí sa byť presný v tom, čo tieto metódy sľubujú, pretože zhoda variable-data printu má reálne finančné dôsledky. Kontroly DPart a párovania sú štrukturálna validácia na úrovni bajtov. Potvrdzujú, že optimalizačná kostra, základné markery PDF/X-4, OutputIntent a XMP sú prítomné a interne konzistentné. Nie je to content-level PDF/X-4 preflight: nekontrolujú, či sú všetky farby v deklarovanej output condition, či sú vložené všetky fonty alebo či sa neprepašoval zakázaný edge case s transparentným blendovaním. Pri zákazke, ktorú posielate na zmluvnú tlač, párujte štrukturálnu validáciu PDFiumPas s dedikovaným PDF/X preflight enginom a testovacou tlačou, rovnako, ako by ste si overili akékoľvek iné tvrdenie o zhode. Štrukturálna vrstva zachytí zlyhania, ktoré ticho rozbijú RIP caching; je to len polovica kompletnej kontroly, nie celá

Ak tieto kontroly vkladáte do širšieho release gate, rovnaký prístup byte-level scanningu podkladá aj ďalšiu štandardovú prácu knižnice vrátane validácie object a cross-reference streamov ešte predtým, než súbor dôjde do preflightu, a disciplínu zdieľaných objektov za článkom reusable page stamps with Form XObjects, ktorá z dokumentu vôbec robí niečo, s čím vie RIP efektívne pracovať. API na ukladanie a validáciu PDF/VT a PDF/X opísané tu sú súčasťou PDFium VCL component pre Delphi a C++Builder, ktorého produktová stránka obsahuje plný referenčný opis zhody