Транзакційна друкарня повертає ваш 80 000-сторінковий запуск виписок з однорядковою відмовою: "не PDF/VT, RIP не може кешувати". Файл без проблем відкривається в кожній програмі перегляду на вашому столі, кольори правильні, дані об'єднано коректно. Утім, саме цього й не просив цифровий друкарський прес. Високошвидкісний друк зі змінними даними живе або гине залежно від того, чи може прес розпізнати, що блок з логотипом клієнта на сторінці 1 є тим самим байт-у-байт об'єктом, що й на сторінці 40 000, відрендерити його один раз і повторно використати. PDF/VT - це стандарт, який робить цю обіцянку придатною для машинної перевірки, а "виглядає правильно" - саме та пастка, бо структура, яку читає RIP, невидима на екрані
PDFiumPas відкриває цю структуру через невеликий поверхневий API на TPdf: SaveAsPdfVT записує її, ValidatePdfVT перевіряє її. Ця стаття про те, що саме ці два методи реально записують на диск і перевіряють, де ISO 16612-2 суворіший, ніж здається спочатку, і які частини є справжніми структурними якорями, а не повною попередньою перевіркою, за яку можна виставити рахунок клієнту
Що стандартизує PDF/VT і чому PDF/X іде першим
PDF/VT (ISO 16612-2:2010) - це не новий файловий формат. Це шар метаданих оптимізації, накладений поверх файла PDF/X, і такий порядок є критично важливим. Стандарт визначає три рівні відповідності, але лише два з них називають PDF-файл: PDF/VT-1, один самодостатній документ, і PDF/VT-2, модель набору файлів, де сторінки посилаються на спільні зовнішні ресурси. Третій токен, який ви можете побачити, PDF/VT-2s, зовсім не є значенням на рівні файла; він живе в заголовку MIME-потоку, описаному в Додатку A. Якщо ви знайдете код, який записує GTS_PDFVTVersion = "PDF/VT-2s" у XMP документа, такий код є помилковим
Непорушне правило для одного файла - це база PDF/X. ISO 16612-2 §6.2.1 вимагає, щоб кожен файл PDF/VT-1 також був дійсним файлом PDF/X-4. Набір файлів PDF/VT-2 за §6.2.2, навпаки, має спиратися на PDF/X-4p, PDF/X-5g або PDF/X-5pg. Саме тому записувач PDF/VT не може просто додати кілька ключів ідентифікатора: він має нести з собою весь набір маркерів PDF/X-4, а це означає OutputIntent, вбудований ICC-профіль призначення, відповідні записи XMP і Document Info, трейлер /ID, і без шифрування. Пропустіть будь-який із цих елементів, і ви отримаєте файл, що заявляє PDF/VT, але провалиться в момент, коли сумісний споживач перевірить базу. PDFiumPas вважає шар PDF/X-4 частиною збереження PDF/VT, тому вам не потрібно окремо викликати SaveAsPdfX спочатку; інжектор записує обидва шари за один прохід
Запис файла за допомогою SaveAsPdfVT
Мінімальний виклик потребує лише активного документа, тому що TPdfVTSaveOptions.Default надає вбудований ICC-профіль sRGB і відповідність pvc1. Збереження виконує три кроки всередині: воно прибирає будь-який захист (впровадження відкритих маркерів у зашифрований потік об'єктів пошкодило б його), воно переносить наявний словник Info документа та трейлер /ID до набору маркерів, щоб значення XMP і Info збігалися, а потім додає об'єкти PDF/X-4 і PDF/VT через інкрементальне оновлення
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;
Для реального виробничого виводу ви майже завжди захочете перевизначити OutputIntent характеристикою вашого друкарського преса, а не універсальним резервним варіантом sRGB. Передайте байти ICC і ідентифікатори умови через 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;
Одна деталь у цьому фрагменті є навмисним запобіжником, а не обмеженням, з яким можна сперечатися. Встановлення Opt.Conformance := pvc2 не створює файл PDF/VT-2. Записувач нормалізує будь-який запит, відмінний від pvc1 назад до pvc1, тому що PDF/VT-2 є форматом набору файлів, а записувач одного файла, який додає один вихідний документ, фізично не може зібрати зовнішній набір ресурсів, потрібний §6.2.2. Значення pvc2 існує для шляху читання, тож ValidatePdfVT може розпізнати й повідомити про наявний документ-набір файлів; це не ціль запису
Дерево DPart: структура, яку RIP реально читає
Серце PDF/VT - це ієрархія Document Part (DPart). Саме вона дає змогу пресі ділити довгий тираж на записи, групувати записи за отримувачами або поштовими пакетами й додавати Document Part Metadata, щоб наступне обладнання могло маршрутизувати й обліковувати кожен елемент. ISO 16612-2 §6.5 описує розводку: каталог містить /DPartRoot, кореневий вузол DPart містить /DPartRootNode і /NodeNameList з назвами кожного рівня ієрархії, листові DPart охоплюють діапазони дерева сторінок, а кожна сторінка, що належить частині, вказує назад на свій лист через поле рівня сторінки /DPart entry
Коли вихідний документ уже містить придатну ієрархію, SaveAsPdfVT її зберігає. Якщо ні, записувач синтезує мінімальну: один DPart на рівні документа, що послідовно охоплює поточне дерево сторінок, з /DPart зворотним посиланням, доданим до кожного активного об'єкта сторінки, і однорівневим /NodeNameList [/Document]. Будьте чесними із собою щодо того, чим є це мінімальне дерево. Це структурна опора, що задовольняє вимоги §6.5 до форми; це не бізнес-метадані. Воно не може вигадати одержувачів, межі поштових відправлень або партії продуктів, бо цієї інформації ніколи не було у вихідних даних. Якщо у вас є дані на рівні окремого одержувача, слід самостійно побудувати глибше DPart-дерево і розширити /NodeNameList його так, щоб воно відповідало рівням, які ви створюєте
Перевірка, що виходить за межі наявності ключів
ValidatePdfVT повертає TPdfVTValidationResult запис із трьома речами: виявленою Conformance, набором Issues, і IsCompliant допоміжною ознакою, яка дорівнює true лише тоді, коли рівень відповідності є справжнім і набір проблем порожній. Перелік проблем навмисно деталізований, тому невдалий результат підказує, яке саме положення ви пропустили, а не просто "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;
Два перевірки, які варто глибоко зрозуміти, це поєднання відповідності та обхід DPart, бо обидві раніше були надто поблажливими і їх посилили, щоб вони відповідали специфікації. У частині поєднання валідатор виконує точне зіставлення, а не "будь-який PDF/X підійде": файл PDF/VT-1 приймається лише на PDF/X-4 базі, а файл PDF/VT-2 лише на PDF/X-4p, PDF/X-5g, або PDF/X-5pg. Маркер PDF/VT-1, розміщений на базі PDF/X-1a, фіксується як помилка, а не пропускається далі
Саме в обході DPart міститься основна суворість. Недостатньо, щоб каталог мав /DPartRoot ключ, бо підроблений порожній об'єкт або об'єкт без посилань на сторінки все одно не можна використати. HasValidDPartHierarchy і рекурсивний ValidateDPartNode обходять усю структуру: вони слідують за батьківськими посиланнями, відхиляють дублікати дочірніх елементів і цикли, вимагають, щоб /Start і /DParts були взаємовиключними, а також вимагають, щоб діапазони сторінок листів покривали дерево сторінок у порядку обходу в глибину, а /DPart кожна сторінка вказувала на лист, який її містить. Усі ці внутрішні помилки зводяться до одного прапорця pvviMissingDPartRoot проблеми, а не розширюють публічний enum, тож сприймайте цей один прапорець як "ієрархію DPart неможливо використати", а не буквально як "root key відсутній"
Три синтаксичні пастки, які тепер примусово перевіряє валідатор
Послідовні перевірки за §6.5 Table 4 виявили форми, які попередні версії приймали, але стандарт ні. Саме такі речі часто помиляються у вручну зібраному DPart-дереві, тож на них варто звернути увагу окремо:
/DPartsце масив масивів, а не плоский масив. Кожен елемент зовнішнього масиву сам має бути масивом непрямих посилань. Плоский/DParts [9 0 R]відхиляється; відповідна форма така:/DParts [[9 0 R] [10 0 R]]/Endпозначає лише справжній багатосторінковий діапазон. Лист DPart може містити/Endлише тоді, коли також має/Start, а/Endмає йти пізніше за/Startу порядку дерева сторінок. Вироджений/Start 3 0 R /End 3 0 Rтепер робить ієрархію непридатною для використання, а не сприймається як односторінкова частина/NodeNameListімена мають проходити PDF name unescaping як XML NMTOKENs. Ім'я на кшталт/Bad#20NameІм'я на кшталт.,-,_,:, а також байти поза ASCII) виявляє помилки у пробілах і роздільниках, не відкидаючи легітимні локалізовані або специфічні для постачальника назви
Позначки XMP: два способи записати ту саму властивість
Ідентифікація PDF/VT міститься в XMP у pdfvtid просторі імен, зокрема GTS_PDFVTVersion and GTS_PDFVTModDate, поряд зі стандартними xmp:CreateDate and xmp:ModifyDate. Нюанс, який у наївних читачів спричиняє хибні звіти про "відсутність", полягає в тому, що будь-який із цих елементів можна серіалізувати двома способами: як текст елемента (<pdfvtid:GTS_PDFVTVersion>PDF/VT-1</pdfvtid:GTS_PDFVTVersion>) або як атрибут RDF в елементі description. PDFiumPas читає обидві форми, тож файл, який інший інструмент записав у стилі атрибутів, не отримує штрафу. Також він застосовує правило узгодженості §6.3, згідно з яким GTS_PDFVTModDate має дорівнювати xmp:ModifyDate; невідповідність спричиняє pvviModDateMismatch
Ще одне правило з того самого пункту: невідоме GTS_PDFVTVersion значення зберігається як pvcUnknown замість того щоб бути зведеним назад до pvcNone. Ця відмінність має операційне значення. pvcNone означає "жодної позначки PDF/VT взагалі, звичайний PDF", тоді як pvcUnknown означає "щось із проставленою версією, яку цей валідатор не розпізнає" (це PDF/VT-2s із таких випадків). Змішування цих двох приховало б пошкоджений файл у тій самій категорії, що й звичайний документ
Де закінчується гарантія
Варто точно окреслити межі того, що обіцяють ці методи, бо відповідність друку змінних даних має цілком реальну фінансову вагу. Перевірки DPart і парності - це структурна валідація на рівні байтів. Вони підтверджують, що наявні й узгоджені між собою каркас оптимізації, базові маркери PDF/X-4, OutputIntent і XMP. Це не попередня перевірка PDF/X-4 на рівні вмісту: вони не перевіряють, чи кожен колір входить у заявлений вихідний стан, чи всі шрифти вбудовані, або чи не проскочив якийсь заборонений крайовий випадок змішування прозорості. Для завдання, яке ви передаєте до контрактної друкарні, поєднуйте структурну валідацію PDFiumPas із окремим механізмом переддрукової перевірки PDF/X і пробним відбитком, так само як ви перевірили б будь-яку іншу заяву про відповідність. Структурний рівень ловить збої, що непомітно ламають кешування RIP; це одна половина повної перевірки, а не вся перевірка
Якщо ви вбудовуєте ці перевірки у ширший release gate, той самий підхід до сканування на рівні байтів лежить в основі іншої стандартизаційної роботи бібліотеки, зокрема перевірку потоків об’єктів і cross-reference-потоків ще до того, як файл потрапить на preflight, а також дисципліну спільних об’єктів, що стоїть за багаторазово використовуваними штампами сторінок із Form XObjects які з самого початку роблять документ дружнім до RIP. Описані тут API збереження та валідації PDF/VT і PDF/X є частиною PDFium VCL component для Delphi і C++Builder, сторінка продукту якого містить повну довідку щодо відповідності