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
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
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“
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úť:
/DPartsje 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ň/Endoznačuje iba skutočný viacstranový rozsah. Listový DPart smie niesť/Endiba vtedy, keď má aj/Start, a/Endmusí v poradí stromu strán nastať neskôr než/Start. Degenerovaný prípad/Start 3 0 R /End 3 0 Rteraz robí hierarchiu nepoužiteľnou namiesto toho, aby sa čítal ako jednolistový part/NodeNameListnázvy musia po unescapingu PDF mien prežiť ako XML NMTOKEN. Názov ako/Bad#20Namesa 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