Een drukkerij voor transactionele post stuurt uw run van 80.000 overzichtspagina's terug met een afwijzing van één regel: "geen PDF/VT, de RIP kan niet cachen." Het bestand opent prima in elke viewer op uw bureau, de kleuren kloppen, de gegevens zijn correct samengevoegd. Niets daarvan is wat de digitale pers vroeg. Snelle druk met variabele data staat of valt met het vermogen van de pers om te herkennen dat het blok met het klantlogo op pagina 1 byte voor byte hetzelfde object is als dat op pagina 40.000, het één keer te renderen en het te hergebruiken. PDF/VT is de standaard die die belofte machinaal controleerbaar maakt, en "ziet er goed uit" is precies de valstrik, want de structuur die de RIP leest, is op het scherm onzichtbaar
PDFiumPas stelt die structuur bloot via een klein oppervlak op TPdf: SaveAsPdfVT schrijft haar, ValidatePdfVT controleert haar. Dit artikel gaat over wat die twee methoden werkelijk op schijf zetten en inspecteren, waar ISO 16612-2 strenger is dan het op het eerste gezicht lijkt, en welke delen eerlijke structurele ankers zijn in plaats van een volledige preflight die u een klant in rekening kunt brengen
Wat PDF/VT standaardiseert, en waarom PDF/X eerst komt
PDF/VT (ISO 16612-2:2010) is geen nieuw bestandsformaat. Het is een laag optimalisatiemetagegevens die op een PDF/X-bestand wordt geschroefd, en die volgorde is dragend. De standaard definieert drie conformiteitsniveaus, maar slechts twee daarvan benoemen een PDF-bestand: PDF/VT-1, één op zichzelf staand document, en PDF/VT-2, een model met een bestandsset waarin pagina's naar gedeelde externe bronnen verwijzen. Het derde token dat u kunt tegenkomen, PDF/VT-2s, is helemaal geen waarde op bestandsniveau; het leeft in een MIME-stroomkop die in bijlage A wordt beschreven. Vindt u code die GTS_PDFVTVersion = "PDF/VT-2s" in de XMP van een document stempelt, dan is die code fout
De niet-onderhandelbare regel voor één bestand is de PDF/X-basis. ISO 16612-2 §6.2.1 vereist dat elk PDF/VT-1-bestand ook een geldig PDF/X-4-bestand is. De bestandsset van PDF/VT-2 moet volgens §6.2.2 in plaats daarvan op PDF/X-4p, PDF/X-5g of PDF/X-5pg rusten. Daarom kan een PDF/VT-schrijver niet zomaar een paar identificatiesleutels aanhangen: hij moet de volledige set PDF/X-4-markeringen meedragen, dus een OutputIntent, een ingesloten ICC-doelprofiel, de bijpassende XMP- en Info-vermeldingen van het document, een /ID in de trailer, en geen versleuteling. Sla een daarvan over en u hebt een bestand dat PDF/VT claimt en faalt zodra een conforme afnemer de basis controleert. PDFiumPas behandelt de PDF/X-4-laag als onderdeel van het PDF/VT-opslaan, dus u roept niet eerst een aparte SaveAsPdfX aan; de injector schrijft beide lagen in één doorloop
Een bestand schrijven met SaveAsPdfVT
De minimale aanroep heeft niets meer nodig dan een actief document, want TPdfVTSaveOptions.Default levert een ingebouwd sRGB-ICC-profiel en conformiteit pvc1. Het opslaan verloopt intern in drie stappen: het verwijdert eventuele beveiliging (leesbare markeringen in een versleutelde objectstroom injecteren zou die beschadigen), het brengt de bestaande Info-dictionary van het document en de /ID uit de trailer over naar de markeringsset zodat de XMP- en Info-waarden overeenstemmen, en het hangt daarna de PDF/X-4- en PDF/VT-objecten aan via een incrementele bijwerking
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'statements-merged.pdf';
Pdf.Active := True;
// Standaardopties: ingebouwde sRGB-OutputIntent, PDF/VT-1, gesynthetiseerde 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;
Voor echte productie-uitvoer wilt u de OutputIntent vrijwel altijd vervangen door de karakterisering van uw pers, niet door de generieke sRGB-terugval. Geef de ICC-bytes en de conditie-identificaties mee via 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'); // uw eigen laadroutine
Opt := TPdfVTSaveOptions.Default;
Opt.Conformance := pvc1; // pvc2 wordt bij het schrijven naar pvc1 genormaliseerd
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; // status van PDF/X Info /Trapped
Pdf.SaveAsPdfVT('directmail-pdfvt.pdf', Opt);
finally
Pdf.Free;
end;
end;
Eén detail in dat fragment is een bewuste beveiliging en geen beperking waarover te twisten valt. Opt.Conformance := pvc2 zetten levert geen PDF/VT-2-bestand op. De schrijver normaliseert elk verzoek dat niet pvc1 is terug naar pvc1, want PDF/VT-2 is een bestandssetformaat en een schrijver voor één bestand die één uitvoerdocument aanhangt, kan de externe bronnenset die §6.2.2 eist fysiek niet samenstellen. De waarde pvc2 bestaat voor het leespad, zodat ValidatePdfVT een bestaand document met een bestandsset kan herkennen en melden; het is geen schrijfdoel
De DPart-boom: de structuur die de RIP werkelijk leest
Het hart van PDF/VT is de hiërarchie van documentdelen (DPart). Zij stelt een pers in staat een lange run in records op te delen, records tot ontvangers of postbundels te groeperen en documentdeelmetagegevens toe te voegen zodat apparatuur verderop elk stuk kan routeren en factureren. ISO 16612-2 §6.5 legt de bedrading vast: de catalogus draagt een /DPartRoot, het wortelknooppunt draagt /DPartRootNode en een /NodeNameList die elk hiërarchieniveau benoemt, DPart-bladeren dekken bereiken van de paginaboom, en elke pagina die bij een deel hoort, wijst via een /DPart-vermelding op paginaniveau terug naar haar blad
Bevat uw brondocument al een bruikbare hiërarchie, dan bewaart SaveAsPdfVT die. Is dat niet zo, dan synthetiseert de schrijver een minimale: één DPart op documentniveau die de huidige paginaboom op volgorde overspant, met een /DPart-terugverwijzing die aan elk levend pagina-object wordt toegevoegd en een /NodeNameList [/Document] van één niveau. Wees eerlijk tegen uzelf over wat die minimale boom is. Het is een structureel anker dat aan de vormeisen van §6.5 voldoet; het zijn geen bedrijfsmetagegevens. Zij kan geen ontvangers, grenzen van poststukken of productiebatches verzinnen, want die informatie stond nooit in de bron. Hebt u gegevens per ontvanger, dan wordt van u verwacht dat u zelf een diepere DPart-boom bouwt en de /NodeNameList uitbreidt tot de niveaus die u aanmaakt
Validatie die verder gaat dan de aanwezigheid van sleutels
ValidatePdfVT geeft een record TPdfVTValidationResult terug met drie dingen: de gedetecteerde Conformance, een verzameling Issues, en een hulpfunctie IsCompliant die alleen waar is wanneer de conformiteit een echt niveau is en de probleemverzameling leeg is. De probleemopsomming is bewust specifiek, zodat een mislukte uitkomst u vertelt welke clausule u miste in plaats van louter "ongeldig":
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;
De twee controles die het diepgaand begrijpen waard zijn, zijn de conformiteitskoppeling en de DPart-doorloop, want beide waren ooit te soepel en zijn aangescherpt om de specificatie te volgen. Aan de koppelingskant doet de validator exacte vergelijking en geen "elke PDF/X volstaat": een PDF/VT-1-bestand wordt alleen aanvaard op een PDF/X-4-basis, en een PDF/VT-2-bestand alleen op PDF/X-4p, PDF/X-5g of PDF/X-5pg. Een PDF/VT-1-markering op een PDF/X-1a-basis wordt gemeld en niet doorgelaten
In de DPart-doorloop zit de meeste striktheid. Het volstaat niet dat de catalogus een sleutel /DPartRoot heeft, want een vervalst leeg object of een object zonder paginakoppelingen is nog altijd niet te verbruiken. HasValidDPartHierarchy en het recursieve ValidateDPartNode volgen de hele structuur: zij lopen ouderkoppelingen na, wijzen dubbele kinderen en cycli af, dwingen af dat /Start en /DParts elkaar uitsluiten, en eisen dat paginabereiken van bladeren de paginaboom in diepte-eerste volgorde dekken waarbij de /DPart van elke pagina naar het blad wijst dat haar bevat. Al die interne fouten vallen samen in het ene probleembit pvviMissingDPartRoot in plaats van de publieke opsomming uit te breiden, dus lees die ene vlag als "de DPart-hiërarchie is onbruikbaar" en niet letterlijk als "de wortelsleutel ontbreekt."
Drie syntactische valstrikken die de validator nu afdwingt
Opeenvolgende doorlopen langs §6.5 tabel 4 brachten vormen aan het licht die eerdere versies aanvaardden maar de standaard niet. Dit is het soort dingen dat een met de hand gebouwde DPart-boom fout doet, dus het loont ze expliciet te benoemen:
/DPartsis een array van arrays, geen platte array. Elk element van de buitenste array moet zelf een array van indirecte verwijzingen zijn. Een platte/DParts [9 0 R]wordt afgewezen; de conforme vorm is/DParts [[9 0 R] [10 0 R]]. Dit voorkomt dat een niet-hiërarchische structuur zich voordoet als een geldig niveau/Endmarkeert alleen een werkelijk meerpaginabereik. Een DPart-blad mag/Endalleen dragen wanneer het ook/Startheeft, en/Endmoet in de volgorde van de paginaboom later vallen dan/Start. Een ontaarde/Start 3 0 R /End 3 0 Rmaakt de hiërarchie nu onbruikbaar in plaats van als een deel van één pagina te worden gelezen- Namen in
/NodeNameListmoeten na het ontsnappen van PDF-namen als XML-NMTOKENs overeind blijven. Een naam als/Bad#20Nameontvouwt zich tot een naam met een spatie erin, wat geen geldig token is. De implementatie doet een lichte ASCII-controle (letters, cijfers,.,-,_,:, plus niet-ASCII-bytes) die fouten met witruimte en scheidingstekens opvangt zonder legitieme gelokaliseerde of leverancierseigen namen af te wijzen
XMP-markeringen: twee manieren om dezelfde eigenschap te schrijven
De identificatie van PDF/VT woont in XMP onder de naamruimte pdfvtid, specifiek GTS_PDFVTVersion en GTS_PDFVTModDate, naast de standaardwaarden xmp:CreateDate en xmp:ModifyDate. Een subtiliteit die in naïeve lezers valse meldingen van "ontbreekt" veroorzaakt, is dat elk daarvan op twee manieren kan zijn geserialiseerd: als elementtekst (<pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion>) of als RDF-attribuut op het description-element. PDFiumPas leest beide vormen, dus een bestand dat een ander gereedschap in attribuutstijl schreef, wordt niet bestraft. Het dwingt ook de consistentieregel uit §6.3 af dat GTS_PDFVTModDate gelijk moet zijn aan xmp:ModifyDate; een verschil levert pvviModDateMismatch op
Nog een regel uit dezelfde clausule: een onbekende waarde van GTS_PDFVTVersion blijft bewaard als pvcUnknown in plaats van naar pvcNone te worden teruggevouwen. Dat onderscheid doet er operationeel toe. pvcNone betekent "helemaal geen PDF/VT-markering, een gewone PDF", terwijl pvcUnknown betekent "iets heeft een versie gestempeld die deze validator niet herkent" (waaronder het geval PDF/VT-2s). De twee op één hoop gooien zou een misvormd bestand in dezelfde bak verbergen als een gewoon document
Waar de garantie ophoudt
Het loont precies te zijn over de grens van wat deze methoden beloven, want aan nalevingsclaims bij druk met variabele data hangt echt geld. De controles op DPart en koppeling zijn structurele validatie op byteniveau. Zij bevestigen dat het optimalisatieskelet, de PDF/X-4-basismarkeringen, de OutputIntent en de XMP aanwezig en onderling consistent zijn. Zij zijn geen PDF/X-4-preflight op inhoudsniveau: zij verifiëren niet dat elke kleur binnen de verklaarde uitvoerconditie valt, dat alle lettertypen zijn ingesloten, of dat er geen verboden randgeval met transparantiemenging is binnengeslopen. Zet u een taak op een contractpers, combineer dan de structurele validatie van PDFiumPas met een specialistische PDF/X-preflight-engine en een proefdruk, net zoals u elke andere nalevingsclaim zou natrekken. De structurele laag vangt de storingen op die de RIP-caching stilletjes breken; het is de helft van een volledige controle, niet het geheel
Bouwt u deze controles in een bredere uitgavepoort, dan ligt dezelfde scanaanpak op byteniveau onder het overige standaardenwerk van de bibliotheek, waaronder object- en kruisverwijzingsstromen valideren voordat een bestand ooit bij de preflight komt, en de discipline rond gedeelde objecten achter herbruikbare paginastempels met Form XObjects die een document in de eerste plaats RIP-vriendelijk maakt. De hier beschreven API voor het opslaan en valideren van PDF/VT en PDF/X maakt deel uit van de PDFium VCL-component voor Delphi en C++Builder, waarvan de productpagina de volledige nalevingsreferentie draagt