Търговска печатница ви връща 80 000-страничен statement run с едноредово отхвърляне: "not PDF/VT, RIP cannot cache." Файлът се отваря нормално във всеки viewer на бюрото ви, цветовете са правилни, данните са се merged-нали коректно. Нито едно от тези неща не е това, което цифровата преса е поискала. High-speed variable-data printing живее или умира от това дали пресата може да разпознае, че блокът с клиентското лого на страница 1 е byte-for-byte същият object като този на страница 40 000, да го рендерира веднъж и да го използва повторно. PDF/VT е стандартът, който прави това обещание machine-checkable, а "изглежда правилно" е точно капанът, защото структурата, която RIP-ът чете, е невидима на екрана
PDFiumPas излага тази структура през малка повърхност на TPdf: SaveAsPdfVT записва това, ValidatePdfVT го проверява. Тази статия е за това какво тези два метода всъщност записват на диск и инспектират, къде ISO 16612-2 е по-строг, отколкото изглежда първоначално, и кои части са честни structural anchors, а не пълно preflight решение, срещу което можете да таксувате клиент
Какво стандартизира PDF/VT и защо PDF/X идва първо
PDF/VT (ISO 16612-2:2010) не е нов файлов формат. Това е слой от optimization metadata, налепен върху PDF/X файл, и този ред е критичен. Стандартът дефинира три нива на съответствие, но само две от тях назовават PDF файл: PDF/VT-1, един самостоятелен документ, и PDF/VT-2, file-set модел, при който страниците реферират към споделени външни ресурси. Третият token, който може да видите, PDF/VT-2s, изобщо не е file-level value; той живее в MIME stream header, описан в Annex A. Ако видите код, който записва GTS_PDFVTVersion = "PDF/VT-2s" в XMP на документ, този код е грешен
Неподлежащото на преговори правило за един файл е PDF/X базата. ISO 16612-2 §6.2.1 изисква всеки PDF/VT-1 файл да бъде и валиден PDF/X-4 файл. PDF/VT-2 file set-ът, по §6.2.2, трябва вместо това да стъпва върху PDF/X-4p, PDF/X-5g или PDF/X-5pg. Затова PDF/VT writer не може просто да добави няколко identifier key-а: трябва да носи със себе си целия PDF/X-4 marker set, което означава OutputIntent, вграден ICC destination profile, съответстващите XMP и document Info entries, trailer /ID и без encryption. Ако пропуснете някое от тези неща, получавате файл, който твърди PDF/VT и се проваля в момента, в който conforming consumer провери базата. PDFiumPas третира PDF/X-4 слоя като част от PDF/VT save-а, така че не викате отделен SaveAsPdfX първо; injector-ът записва и двата слоя в един проход
Записване на файл с SaveAsPdfVT
Минималното извикване не изисква нищо освен активен документ, защото TPdfVTSaveOptions.Default осигурява вграден sRGB ICC profile и conformance pvc1. Save-ът минава през три стъпки вътрешно: премахва всяка security, защото инжектиране на plaintext marker-и в encrypted object stream би го повредило; свързва съществуващия Info dictionary и trailer /ID на документа в marker set-а, така че XMP и Info стойностите да съвпадат; после добавя PDF/X-4 и PDF/VT objects чрез incremental update
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;
За реален production output почти винаги ще искате да override-нете OutputIntent с characterisation-а на вашата преса, а не с generic sRGB fallback-а. Подайте ICC байтовете и condition identifier-ите чрез 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;
Един детайл в този snippet е умишлена предпазна релса, а не ограничение, срещу което да спорите. Задаването на Opt.Conformance := pvc2 не произвежда PDF/VT-2 файл. Writer-ът нормализира всяка non-pvc1 заявка обратно към pvc1, защото PDF/VT-2 е file-set формат и writer за един файл, който добавя един output document, физически не може да събере външния resource set, изискван от §6.2.2. Стойността pvc2 съществува за read path-а, така че ValidatePdfVT може да разпознае и отчете съществуващ file-set документ; тя не е write target
DPart дървото: структурирането, което RIP-ът реално чете
Сърцето на PDF/VT е Document Part (DPart) йерархията. Тя позволява на пресата да раздели дълъг run на records, да групира records в recipients или mail bundles и да прикачи Document Part Metadata, така че downstream оборудването да може да route-ва и таксува всяко парче. ISO 16612-2 §6.5 описва окабеляването: catalog-ът носи /DPartRoot, root DPart node-ът носи /DPartRootNode и /NodeNameList за всеки hierarchy level, leaf DPart-овете покриват ranges от page tree-а, а всяка страница, която принадлежи към part, сочи обратно към своя leaf чрез page-level /DPart entry
Когато изходният ви документ вече съдържа използваема йерархия, SaveAsPdfVT я запазва. Когато не съдържа, writer-ът синтезира минимална: един document-level DPart, който обхваща текущото page tree в ред, с /DPart back-reference, добавена към всеки жив page object, и едно ниво /NodeNameList [/Document]. Бъдете честни със себе си какво представлява това минимално дърво. То е structural anchor, който покрива shape изискванията на §6.5; не е business metadata. То не може да измисли recipients, mail-piece граници или product batches, защото тази информация никога не е била в source-а. Ако имате per-recipient данни, трябва сами да изградите по-дълбоко DPart дърво и да разширите /NodeNameList така, че да съвпада с нивата, които създавате
Валидиране, което отива отвъд наличието на key
ValidatePdfVT връща TPdfVTValidationResult record с три неща: засеченото Conformance, set от Issues, и IsCompliant helper, който е true само когато conformance е реално ниво и issue set-ът е празен. Issue enumeration-ът е нарочно конкретен, така че неуспешният резултат да ви казва коя клауза сте пропуснали, вместо просто "invalid":
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;
Двете проверки, които си струва да разберете в дълбочина, са conformance pairing-ът и DPart walk-ът, защото и двете са били твърде разхлабени и са затегнати, за да съвпаднат със спецификацията. От страната на pairing-а validator-ът прави точно съвпадение, не "какъвто и да е PDF/X става": PDF/VT-1 файл се приема само върху PDF/X-4 PDF/X base, а PDF/VT-2 файл само върху PDF/X-4p, PDF/X-5g, или PDF/X-5pg. PDF/VT-1 marker върху PDF/X-1a база се отчита, не се пуска напред
DPart walk-ът е мястото, където живее по-голямата част от rigor-а. Не е достатъчно catalog-ът да има /DPartRoot key, защото фалшив празен object или такъв без page links пак не може да бъде консумиран. HasValidDPartHierarchy и рекурсивният ValidateDPartNode trace-ват цялата структура: следват parent links, отхвърлят duplicate children и cycles, налагат /Start и /DParts да са взаимно изключващи се и изискват leaf page ranges да покриват page tree-а в depth-first order, като всеки page-level /DPart сочи към leaf-а, който го съдържа. Всички тези вътрешни faults се свличат към единственото pvviMissingDPartRoot issue bit вместо да разширяват публичния enum, така че приемайте този flag като "DPart йерархията е неизползваема", а не буквално "root key-ят липсва"
Три синтактични капана, които validator-ът вече налага
Последователни passes срещу §6.5 Table 4 извадиха shapes, които по-ранни версии приемаха, а стандартът не. Това са неща, които ръчно изградено DPart дърво често обърква, така че си струва да бъдат назовани изрично:
/DPartsе array от arrays, не flat array.Всеки елемент от outer array-а сам трябва да бъде indirect-reference array. Flat/DParts [9 0 R]се отхвърля; conforming shape-ът е/DParts [[9 0 R] [10 0 R]]. Това спира non-hierarchical структура да се представя за валидно ниво/Endмаркира само истински multi-page range.Leaf DPart може да носи/Endсамо когато има и/Start, а/Endтрябва да идва по-късно от/Startв page-tree реда. Дегенерирало/Start 3 0 R /End 3 0 Rвече прави йерархията неизползваема, вместо да се чете като едностраничен part/NodeNameListnames трябва да оцелеят PDF name unescaping като XML NMTOKENs. Име като/Bad#20Nameсе разширява до такова, което съдържа space, а това не е валиден token. Имплементацията прави лек ASCII check (букви, цифри,.,-,_,:,
, плюс non-ASCII bytes) that catches whitespace и delimiter грешки без да отхвърля легитимни локализирани или vendor-specific имена
XMP marker-и: два начина да се запише едно и също свойствоpdfvtidPDF/VT identification живее в XMP под GTS_PDFVTVersion namespace-а, по-конкретно GTS_PDFVTModDate и xmp:CreateDate, редом със стандартните xmp:ModifyDate и <pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion>style) или като RDF атрибут върху description елемента. PDFiumPas чете и двата форми, така че файл, който друг инструмент е записал в attribute style, не се наказва. Той също така налага §6.3 consistency правилото, че GTS_PDFVTModDate трябва да е равно на xmp:ModifyDate; mismatch-ът вдига pvviModDateMismatch
GTS_PDFVTVersionОще едно правило от същата клауза: unknown pvcUnknown value се пази като pvcNone вместо да се сгъне обратно към pvcNone. Това различие е важно оперативно. pvcUnknown означава "няма PDF/VT marker изобщо, обикновен PDF", докато PDF/VT-2s означава "някой е поставил версия, която този validator не разпознава" (
случая сред тях). Сливането на двете би скрило дефектен файл в същата кофа като обикновен документ
Къде свършва гаранцията
Струва си да бъдем точни за границата на това, което тези методи обещават, защото variable-data print съответствието е свързано с реални пари. DPart и pairing проверките са byte-level structural validation. Те потвърждават, че optimization skeleton-ът, PDF/X-4 base marker-ите, OutputIntent и XMP са налице и вътрешно съгласувани. Те не са content-level PDF/X-4 preflight: не проверяват дали всеки цвят е в рамките на декларираното output condition, дали всички fonts са embedded или дали някой забранен transparency-blending edge case не е попаднал вътре. За job, който ще печатате на contract press, комбинирайте structural validation-а на PDFiumPas с dedicated PDF/X preflight engine и test print, както бихте проверили всяко друго claim-ване за съответствие. Structural layer-ът хваща грешките, които тихо чупят RIP caching; той е едната половина на пълна проверка, не цялата. validating object and cross-reference streams преди файлът изобщо да стигне до preflight, и shared-object дисциплината зад reusable page stamps with Form XObjects която прави документ RIP-friendly още от самото начало. PDF/VT и PDF/X save и validation APIs, описани тук, са част от PDFium VCL component за Delphi и C++Builder, чиято product page съдържа пълната compliance reference