Technisch artikel

PDF/VT variabele-datadruk in Delphi met PDFium VCL

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

Diagram van de PDF/VT-stapel gebouwd in Delphi, waarbij de optimalisatiemetagegevens op een verplichte PDF/X-4-basis rusten, met exacte conformiteitskoppeling voor PDF/VT-1, PDF/VT-2 en het token PDF/VT-2s dat alleen in MIME bestaat
PDF/VT-metagegevens zijn alleen dragend boven op een volledige PDF/X-basis

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

Diagram voor PDFium Component van een PDF/VT-DPart-hierarchie die door een RIP wordt verbruikt: de DPartRoot in de catalogus, de NodeNameList van het wortelknooppunt, DPart-bladeren die paginabereiken dekken en terugverwijzingen met DPart op paginaniveau
De DPart-boom is de structuur die een RIP leest om records op te delen en gedeelde inhoud te cachen

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":

Diagram van PDF/VT-validatie in Delphi met PDFium VCL, waarbij exacte basiskoppeling, een recursieve doorloop van de DPart-boom, PDF/X-4-markeringen en datumcontroles in XMP het record TPdfVTValidationResult voeden
ValidatePdfVT meldt welke clausule faalde in plaats van een algemeen oordeel dat het bestand ongeldig is
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:

  • /DParts is 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
  • /End markeert alleen een werkelijk meerpaginabereik. Een DPart-blad mag /End alleen dragen wanneer het ook /Start heeft, en /End moet in de volgorde van de paginaboom later vallen dan /Start. Een ontaarde /Start 3 0 R /End 3 0 R maakt de hiërarchie nu onbruikbaar in plaats van als een deel van één pagina te worden gelezen
  • Namen in /NodeNameList moeten na het ontsnappen van PDF-namen als XML-NMTOKENs overeind blijven. Een naam als /Bad#20Name ontvouwt 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