Et transaksjonsbasert trykkeri sender tilbake en utskriftsrunde på 80 000 sider med ett linjesvar: "not PDF/VT, RIP cannot cache." Filen åpnes fint i alle visningsprogrammer på skrivebordet ditt, fargene stemmer, og dataene ble slått sammen riktig. Ingenting av det er det den digitale pressen ba om. Høyhastighets utskrift med variable data lever eller dør etter om pressen kan kjenne igjen at kundelogo-blokken på side 1 er samme objekt byte for byte som den på side 40 000, rendere det én gang og gjenbruke det. PDF/VT er standarden som gjør dette løftet maskinverifiserbart, og "ser riktig ut" er nettopp fellen, fordi strukturen RIP-en leser er usynlig på skjermen
PDFiumPas eksponerer denne strukturen gjennom et lite API på TPdf: SaveAsPdfVT skriver den, ValidatePdfVT kontrollerer den. Denne artikkelen handler om hva disse to metodene faktisk legger på disk og inspiserer, hvor ISO 16612-2 er strengere enn den først ser ut, og hvilke deler som er ærlige strukturelle anker i stedet for en full preflight du kan fakturere en kunde for
Hva PDF/VT standardiserer, og hvorfor PDF/X kommer først
PDF/VT (ISO 16612-2:2010) er ikke et nytt filformat. Det er et lag med optimaliseringsmetadata som legges oppå en PDF/X-fil, og rekkefølgen er bærende. Standarden definerer tre samsvarsnivåer, men bare to av dem navngir en PDF-fil: PDF/VT-1, et enkelt selvstendig dokument, og PDF/VT-2, en filsettmodell der sider refererer til delte eksterne ressurser. Det tredje tokenet du kan se, PDF/VT-2s, er slett ikke en filnivåverdi; det ligger i en MIME-strømheader beskrevet i vedlegg A. Hvis du finner kode som stempler GTS_PDFVTVersion = "PDF/VT-2s" inn i en dokuments XMP, er den koden feil
Den ikke-forhandlingsbare regelen for en enkelt fil er PDF/X-basisen. ISO 16612-2 §6.2.1 krever at hver PDF/VT-1-fil også skal være en gyldig PDF/X-4-fil. PDF/VT-2s filsett må derimot, ifølge §6.2.2, ligge på PDF/X-4p, PDF/X-5g eller PDF/X-5pg. Derfor kan ikke en PDF/VT-skriver bare legge til et par identifikatornøkler: den må bære hele PDF/X-4-markørsettet med seg, som betyr et OutputIntent, en innebygd ICC-destinasjonsprofil, de samsvarende XMP- og dokumentInfo-oppføringene, en trailer /ID og ingen kryptering. Hopper du over noen av disse, ender du med en fil som påstår PDF/VT, men feiler i det øyeblikket en samsvarende konsument sjekker basen. PDFiumPas behandler PDF/X-4-laget som en del av PDF/VT-lagringen, så du kaller ikke først en separat SaveAsPdfX; injektoren skriver begge lag i ett pass
Skrive en fil med SaveAsPdfVT
Den minste kallsekvensen trenger ikke annet enn et aktivt dokument, fordi TPdfVTSaveOptions.Default leverer en innebygd sRGB ICC-profil og samsvar pvc1. Lagringen kjører tre trinn internt: den fjerner all sikkerhet (å injisere klartekstmarkører i en kryptert objektstrøm ville ødelegge den), den kobler dokumentets eksisterende Info-ordbok og trailer /ID inn i markørsettet slik at XMP- og Info-verdiene stemmer overens, og deretter legger den til PDF/X-4- og PDF/VT-objektene gjennom en inkrementell oppdatering
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;
For reell produksjonsutskrift vil du nesten alltid overstyre OutputIntent med pressens egen karakterisering, ikke den generiske sRGB-reserven. Send ICC-bytene og betingelsesidentifikatorene via 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;
En detalj i dette eksempelet er et bevisst sikkerhetsrekkverk, ikke en begrensning du kan argumentere mot. Å sette Opt.Conformance := pvc2 gir ikke en PDF/VT-2-fil. Skriveren normaliserer enhver forespørsel som ikke er pvc1, tilbake til pvc1, fordi PDF/VT-2 er et filsettformat, og en enkeltfilskriver som legger ved ett utdata-dokument fysisk ikke kan sette sammen det eksterne ressurssettet §6.2.2 krever. Verdien pvc2 finnes for lesestien, slik at ValidatePdfVT kan kjenne igjen og rapportere et eksisterende filsett-dokument; den er ikke et skrivemål
DPart-treet: strukturen RIP-en faktisk leser
Kjernen i PDF/VT er hierarkiet for Document Part (DPart). Det er dette som lar en presse dele en lang kjørejobb i poster, gruppere poster i mottakere eller postpakker, og knytte Document Part Metadata til dem slik at nedstrøms utstyr kan rute og fakturere hver enkelt del. ISO 16612-2 §6.5 beskriver koblingen: katalogen bærer en /DPartRoot, rot-DPart-noden bærer /DPartRootNode og en /NodeNameList som navngir hvert hierarkinivå, blad-DParter dekker områder av sidetreet, og hver side som hører til en del peker tilbake på bladet sitt gjennom en sidelevel /DPart-oppføring
Når kildedokumentet ditt allerede inneholder et brukbart hierarki, bevarer SaveAsPdfVT det. Når det ikke gjør det, syntetiserer skriveren et minimalt ett: en enkelt dokumentnivå-DPart som spenner over det gjeldende sidetreet i rekkefølge, med en /DPart-tilbakepekning lagt til hvert levende sideobjekt og en ett-nivå /NodeNameList [/Document]. Vær ærlig med deg selv om hva dette minimale treet er. Det er et strukturelt anker som oppfyller formkravene i §6.5; det er ikke forretningsmetadata. Det kan ikke finne opp mottakere, poststykkegrenser eller produktbatcher, fordi den informasjonen aldri fantes i kilden. Hvis du har data per mottaker, forventes det at du bygger et dypere DPart-tre selv og utvider /NodeNameList slik at den samsvarer med nivåene du lager
Validering som går lenger enn nøkkeltilstedeværelse
ValidatePdfVT returnerer en TPdfVTValidationResult-record med tre ting: den oppdagede Conformance, et sett med Issues og en IsCompliant-hjelper som bare er sann når samsvaret er et reelt nivå og issuesettet er tomt. Issue-enumerasjonen er bevisst spesifikk, så et mislykket resultat forteller deg hvilken klausul du bommet på, i stedet for bare "ugyldig":
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;
De to kontrollene det er verdt å forstå i dybden, er samsvarskoblingen og DPart-gjennomgangen, fordi begge tidligere var for slakke og ble strammet inn for å matche spesifikasjonen. På koblingssiden gjør validereren eksakt matching, ikke "enhver PDF/X går": en PDF/VT-1-fil aksepteres bare på en PDF/X-4-base, og en PDF/VT-2-fil bare på PDF/X-4p, PDF/X-5g eller PDF/X-5pg. En PDF/VT-1-markør som ligger på en PDF/X-1a-base blir rapportert, ikke sluppet gjennom
DPart-gjennomgangen er der mesteparten av strengheten ligger. Det er ikke nok at katalogen har en /DPartRoot-nøkkel, fordi et forfalsket tomt objekt eller et objekt uten sidelenker fortsatt ikke kan forbrukes. HasValidDPartHierarchy og den rekursive ValidateDPartNode følger hele strukturen: de følger foreldrelinker, avviser dupliserte barn og sykler, håndhever at /Start og /DParts er gjensidig utelukkende, og krever at blad-sideområder dekker sidetreet i dybde-først-rekkefølge, med at hver sides /DPart peker på bladet som inneholder den. Alle disse interne feilene kollapser til den ene pvviMissingDPartRoot-feilbiten, i stedet for å utvide den offentlige enumen, så behandl den ene flaggen som "DPart-hierarkiet er ubrukelig", ikke bokstavelig "rot-nøkkelen mangler"
Tre syntaktiske feller som validereren nå håndhever
Gjennomganger av §6.5 tabell 4 i flere omganger avdekket former som tidligere versjoner aksepterte, men som standarden ikke gjør. Dette er den typen ting et håndbygd DPart-tre ofte gjør feil, så de er verdt å trekke fram eksplisitt:
/DPartser en array av arrays, ikke en flat array. Hvert element i den ytre arrayen må selv være en indirekte referansearray. En flat/DParts [9 0 R]blir avvist; den samsvarende formen er/DParts [[9 0 R] [10 0 R]]. Dette hindrer at en ikke-hierarkisk struktur utgir seg for å være et gyldig nivå/Endmarkerer bare et ekte område med flere sider. En blad-DPart kan bare ha/Endnår den også har/Start, og/Endmå ligge senere enn/Starti sidetre-rekkefølge. En degenerert/Start 3 0 R /End 3 0 Rgjør nå hierarkiet ubrukelig i stedet for å bli lest som en del med én side/NodeNameList-navn må overleve PDF-navneavkoding som XML NMTOKEN-er. Et navn som/Bad#20Nameutvides til et navn som inneholder et mellomrom, som ikke er et gyldig token. Implementasjonen gjør en lett ASCII-kontroll (bokstaver, sifre,.,-,_,:, pluss ikke-ASCII-bytes) som fanger opp mellomroms- og skilletegnsfeil uten å avvise legitime lokaliserte eller leverandørspesifikke navn
XMP-markører: to måter å skrive samme egenskap på
PDF/VT-identifikasjon ligger i XMP under pdfvtid-navnerommet, nærmere bestemt GTS_PDFVTVersion og GTS_PDFVTModDate, sammen med standardfeltene xmp:CreateDate og xmp:ModifyDate. En subtilitet som gir falske "mangler"-rapporter i naive lesere, er at alle disse kan serialiseres på to måter: som elementtekst (<pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion>) eller som et RDF-attributt på description-elementet. PDFiumPas leser begge formene, så en fil som et annet verktøy skrev i attributtstil blir ikke straffet. Den håndhever også konsistensregelen i §6.3 om at GTS_PDFVTModDate må være lik xmp:ModifyDate; et avvik utløser pvviModDateMismatch
En regel til fra samme klausul: en ukjent GTS_PDFVTVersion-verdi bevares som pvcUnknown i stedet for å foldes tilbake til pvcNone. Den forskjellen betyr noe i praksis. pvcNone betyr "ingen PDF/VT-markør i det hele tatt, en vanlig PDF", mens pvcUnknown betyr "noe stemplet en versjon som denne validereren ikke kjenner igjen" (inkludert tilfellet PDF/VT-2s). Å slå sammen de to ville skjule en feilaktig fil i samme kategori som et rent dokument
Der garantien stopper
Det er verdt å være presis om grensen for hva disse metodene lover, fordi samsvar for variable data faktisk har økonomiske konsekvenser. DPart- og paringskontrollene er strukturell validering på bytenivå. De bekrefter at optimaliseringsskjelettet, PDF/X-4-basismarkørene, OutputIntent og XMP finnes og er internt konsistente. De er ikke en preflight på innholdsnivå for PDF/X-4: de verifiserer ikke at alle farger ligger innenfor den deklarerte utgangsbetingelsen, at alle fonter er innebygd, eller at ingen forbudt transparensblandingskant-case har sneket seg inn. For en jobb du skal sende til et kontrakttrykkeri, bør du kombinere PDFiumPas sin strukturelle validering med en dedikert PDF/X-preflightmotor og en testutskrift, på samme måte som du ville gjort en helsesjekk av enhver annen samsvarspåstand. Det strukturelle laget fanger feilene som stille ødelegger RIP-caching; det er den ene halvdelen av en fullstendig kontroll, ikke hele kontrollen
Hvis du bygger disse kontrollene inn i en bredere release-gate, er den samme skanningen på bytenivå grunnlaget for bibliotekets andre standardarbeid, inkludert validering av objekt- og kryssreferansestrømmer før en fil noen gang når preflight, og disiplinen for delte objekter bak gjenbrukbare sidestempler med Form XObjects som gjør et dokument RIP-vennlig i utgangspunktet. API-ene for lagring og validering av PDF/VT og PDF/X som beskrives her, er en del av PDFium VCL-komponenten for Delphi og C++Builder, og produktsiden deres har hele samsvarsreferansen