En transaktionsudbyder sender din 80.000-siders udsendelse tilbage med en enlinjes afvisning: "not PDF/VT, RIP cannot cache." Filen åbner fint i alle fremvisere på skrivebordet, farverne er rigtige, data er flettet korrekt. Intet af det er, hvad det digitale trykpresse bad om. Højhastigheds-variabeldata-print lever og dør på, om pressen kan genkende, at kundelogo-blokken på side 1 er byte-for-byte det samme objekt som den på side 40.000, rendre det én gang og genbruge det. PDF/VT er standarden, der gør det løfte maskinkontrollerbart, og "ser rigtigt ud" er netop fælden, fordi strukturen, RIP'en læser, er usynlig på skærmen
PDFiumPas eksponerer den struktur gennem en lille overflade på TPdf: SaveAsPdfVT skriver den, ValidatePdfVT tjekker den. Denne artikel handler om, hvad de to metoder rent faktisk lægger på disk og inspicerer, hvor ISO 16612-2 er strengere, end det ser ud ved første øjekast, og hvilke dele der er ærlige strukturelle ankre frem for en fuld preflight, du kan fakturere en klient for
Hvad PDF/VT standardiserer, og hvorfor PDF/X kommer først
PDF/VT (ISO 16612-2:2010) er ikke et helt nyt filformat. Det er et lag af optimeringsmetadata boltet på en PDF/X-fil, og den rækkefølge er bærende. Standarden definerer tre overensstemmelsesniveauer, men kun to af dem navngiver en PDF-fil: PDF/VT-1, et enkelt selvstændigt dokument, og PDF/VT-2, en filsæt-model, hvor sider refererer til delte eksterne ressourcer. Det tredje token, du måske ser, PDF/VT-2s, er slet ikke en fil-niveau-værdi; det bor i en MIME-stream-header beskrevet i Annex A. Finder du kode, der stempler GTS_PDFVTVersion = "PDF/VT-2s" ind i et dokuments XMP, er den kode forkert
Den ikke-til-forhandling-regel for en enkelt fil er PDF/X-basen. ISO 16612-2 §6.2.1 kræver, at hver PDF/VT-1-fil også er en gyldig PDF/X-4-fil. PDF/VT-2's filsæt skal ifølge §6.2.2 i stedet hvile på PDF/X-4p, PDF/X-5g eller PDF/X-5pg. Det er derfor, en PDF/VT-writer ikke bare kan tilføje et par identifikatornøgler: den skal bære hele PDF/X-4-markørsættet med sig, hvilket betyder en OutputIntent, en indlejret ICC-destinationsprofil, de matchende XMP- og dokument-Info-poster, en trailer /ID, og ingen kryptering. Springer du et af dem over, har du en fil, der hævder PDF/VT og fejler, i det øjeblik en konform forbruger tjekker basen. PDFiumPas behandler PDF/X-4-laget som en del af PDF/VT-gemningen, så du kalder ikke en separat SaveAsPdfX først; injektoren skriver begge lag i ét pas
Skriv en fil med SaveAsPdfVT
Det minimale kald behøver intet andet end et aktivt dokument, fordi TPdfVTSaveOptions.Default leverer en indbygget sRGB ICC-profil og overensstemmelse pvc1. Gemningen kører tre trin internt: den fjerner enhver sikkerhed (at injicere klartekstmarkører i en krypteret objektstream ville korrumpere den), den bygger bro mellem dokumentets eksisterende Info-dictionary og trailer /ID ind i markørsættet, så XMP- og Info-værdierne stemmer overens, og tilføjer så PDF/X-4- og PDF/VT-objekterne gennem en trinvis opdatering
var
Pdf: TPdf;
begin
Pdf := TPdf.Create(nil);
try
Pdf.FileName := 'statements-merged.pdf';
Pdf.Active := True;
// Standardindstillinger: indbygget sRGB OutputIntent, PDF/VT-1, syntetiseret 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;
Til reelt produktionsoutput vil du næsten altid overstyre OutputIntenten med din presses karakterisering, ikke det generiske sRGB-fallback. Levér ICC-bytesene og betingelsesidentifikatorerne gennem 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'); // din egen loader
Opt := TPdfVTSaveOptions.Default;
Opt.Conformance := pvc1; // pvc2 normaliseres til pvc1 ved skrivning
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-tilstand
Pdf.SaveAsPdfVT('directmail-pdfvt.pdf', Opt);
finally
Pdf.Free;
end;
end;
Én detalje i det udsnit er et bevidst rækværk frem for en begrænsning, du kan diskutere. At sætte Opt.Conformance := pvc2 producerer ikke en PDF/VT-2-fil. Writeren normaliserer enhver ikke-pvc1-anmodning tilbage til pvc1, fordi PDF/VT-2 er et filsæt-format, og en enkeltfil-writer, der tilføjer ét outputdokument, fysisk ikke kan samle det eksterne ressourcesæt, §6.2.2 kræver. pvc2-værdien findes til læsestien, så ValidatePdfVT kan genkende og rapportere et eksisterende filsæt-dokument; den er ikke et skrivemål
DPart-træet: struktur som RIP faktisk læser
Hjertet i PDF/VT er Document Part (DPart)-hierarkiet. Det er, hvad der lader en presse opdele en lang kørsel i poster, gruppere poster i modtagere eller postbundter, og vedhæfte Document Part Metadata, så nedstrøms-udstyr kan rute og fakturere hvert stykke. ISO 16612-2 §6.5 lægger ledningsføringen ud: kataloget bærer en /DPartRoot, DPart-rodknuden bærer /DPartRootNode og en /NodeNameList, der navngiver hvert hierarkiniveau, blad-DParts dækker intervaller af sidetræet, og hver side, der hører til en del, peger tilbage på sit blad gennem en side-niveau /DPart-post
Når dit kildedokument allerede indeholder et brugbart hierarki, bevarer SaveAsPdfVT det. Når det ikke gør, syntetiserer writeren et minimalt: én dokument-niveau-DPart, der spænder over det aktuelle sidetræ i rækkefølge, med en /DPart-tilbagereference tilføjet til hvert levende sideobjekt og en ét-niveau /NodeNameList [/Document]. Vær ærlig med dig selv om, hvad det minimale træ er. Det er et strukturelt anker, der opfylder §6.5's formkrav; det er ikke forretningsmetadata. Det kan ikke opfinde modtagere, postgrænser eller produktbatches, fordi den information aldrig var i kilden. Har du data pr. modtager, forventes det, at du selv bygger et dybere DPart-træ og udvider /NodeNameList til at matche de niveauer, du opretter
Validering, der går ud over nøgle-tilstedeværelse
ValidatePdfVT returnerer en TPdfVTValidationResult-record med tre ting: den registrerede Conformance, et sæt Issues, og en IsCompliant-hjælper, der kun er true, når overensstemmelsen er et reelt niveau, og problemsættet er tomt. Problem-enumeringen er bevidst specifik, så et fejlet resultat fortæller dig, hvilken klausul du missede, frem for bare "ugyldig":
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 to tjek, det er værd at forstå i dybden, er overensstemmelsesparringen og DPart-gennemgangen, fordi begge plejede at være for slappe og blev strammet til at matche specifikationen. På parringssiden gør validatoren eksakt matchning, ikke "enhver PDF/X duer": en PDF/VT-1-fil accepteres kun på en PDF/X-4-base, og en PDF/VT-2-fil kun på PDF/X-4p, PDF/X-5g eller PDF/X-5pg. En PDF/VT-1-markør, der sidder på en PDF/X-1a-base, rapporteres, ikke vinkes igennem
DPart-gennemgangen er, hvor det meste af stringensen bor. Det er ikke nok, at kataloget har en /DPartRoot-nøgle, fordi et forfalsket tomt objekt eller ét uden sidelinks stadig ikke kan konsumeres. HasValidDPartHierarchy og den rekursive ValidateDPartNode sporer hele strukturen: de følger forælder-links, afviser duplikerede børn og cyklusser, håndhæver at /Start og /DParts gensidigt udelukker hinanden, og kræver, at blad-sideintervaller dækker sidetræet i depth-first-rækkefølge, med hver sides /DPart pegende på det blad, der indeholder den. Alle de interne fejl kollapser til det ene pvviMissingDPartRoot-problembit frem for at udvide den offentlige enum, så behandl det ene flag som "DPart-hierarkiet er ubrugeligt", ikke bogstaveligt "rodnøglen mangler."
Tre syntaktiske fælder, som validatoren nu håndhæver
Successive gennemløb mod §6.5 Tabel 4 afslørede former, som tidligere versioner accepterede, men som standarden ikke gør. Det er den slags, et håndbygget DPart-træ får galt, så de er værd at fremhæve eksplicit:
/DPartser et array af arrays, ikke et fladt array. Hvert element i det ydre array skal selv være et indirekte-reference-array. Et fladt/DParts [9 0 R]afvises; den konforme form er/DParts [[9 0 R] [10 0 R]]. Det forhindrer en ikke-hierarkisk struktur i at udgive sig for et gyldigt niveau/Endmarkerer kun et ægte flersidet interval. En blad-DPart må kun bære/End, når den også har/Start, og/Endskal falde senere end/Starti sidetræ-rækkefølge. Et degenereret/Start 3 0 R /End 3 0 Rgør nu hierarkiet ubrugeligt i stedet for at læses som en ét-sides del/NodeNameList-navne skal overleve PDF-navne-unescaping som gyldige XML NMTOKENs. Et navn som/Bad#20Nameudvides til ét, der indeholder et mellemrum, hvilket ikke er et gyldigt token. Implementeringen gør et let ASCII-tjek (bogstaver, cifre,.,-,_,:, plus ikke-ASCII-bytes), der fanger whitespace- og afgrænser-fejl uden at afvise legitime lokaliserede eller leverandørspecifikke navne
XMP-markører: to måder at skrive den samme egenskab
PDF/VT-identifikation bor i XMP under pdfvtid-namespacet, specifikt GTS_PDFVTVersion og GTS_PDFVTModDate, sammen med de standard xmp:CreateDate og xmp:ModifyDate. En finesse, der forårsager falske "manglende"-rapporter i naive læsere, er, at hver af disse kan serialiseres på to måder: som elementtekst (<pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion>) eller som en RDF-attribut på description-elementet. PDFiumPas læser begge former, så en fil, et andet værktøj skrev i attributstil, straffes ikke. Den håndhæver også §6.3-konsistensreglen om, at GTS_PDFVTModDate skal være lig med xmp:ModifyDate; en uoverensstemmelse rejser pvviModDateMismatch
Endnu en regel fra samme klausul: en ukendt GTS_PDFVTVersion-værdi bevares som pvcUnknown frem for at blive foldet tilbage til pvcNone. Den skelnen betyder noget operationelt. pvcNone betyder "intet PDF/VT-mærke overhovedet, en almindelig PDF," mens pvcUnknown betyder "noget stemplede en version, denne validator ikke genkender" (PDF/VT-2s-tilfældet blandt dem). At sammenblande de to ville skjule en fejlformet fil i den samme spand som et almindeligt dokument
Hvor garantien stopper
Det er værd at være præcis om grænsen for, hvad disse metoder lover, fordi variabeldata-printoverensstemmelse har rigtige penge knyttet til sig. DPart- og parringstjekkene er strukturel validering på byte-niveau. De bekræfter, at optimeringsskelettet, PDF/X-4-basemarkørerne, OutputIntenten og XMP'en er til stede og internt konsistente. De er ikke en indholds-niveau PDF/X-4-preflight: de verificerer ikke, at hver farve er inden for den deklarerede outputbetingelse, at alle fonte er indlejrede, eller at intet forbudt transparens-blending-kanttilfælde sneg sig ind. Til et job, du sætter på en kontraktpresse, skal du parre PDFiumPas's strukturelle validering med en dedikeret PDF/X-preflight-motor og en testprint, på samme måde som du ville sundhedstjekke enhver anden overensstemmelsespåstand. Det strukturelle lag fanger de fejl, der stille bryder RIP-caching; det er den ene halvdel af et komplet tjek, ikke det hele
Bygger du disse tjek ind i en bredere udgivelsesport, understøtter den samme byte-niveau-scanningstilgang bibliotekets øvrige standardarbejde, inklusive validering af objekt- og krydsreferencestreams, før en fil nogensinde når preflight, og den delt-objekt-disciplin bag genbrugelige sidestempler med Form XObjects, der overhovedet gør et dokument RIP-venligt. PDF/VT- og PDF/X-gemme- og valideringsAPI'erne beskrevet her er en del af PDFium VCL-komponenten til Delphi og C++Builder, hvis produktside indeholder den fulde overensstemmelsesreference