HotPDF пишет нативные документы PDF 2.0 из Delphi и C++Builder, включая три архивных профиля PDF/A-4 и доступный вывод PDF/UA-2 с элементами структуры в пространствах имён. Их выбор — вопрос двух свойств, но стандарты за этими свойствами изменились больше, чем предполагает номер версии: PDF/A-4 отбросил буквы соответствия, выученные всеми на PDF/A-2, а PDF/UA-2 ввёл пространства имён структуры, которых документ части 1 никогда не имел
Эта статья описывает, что именно меняется в генерируемом файле и какие ошибки HotPDF превращает в исключение на EndDoc, а не в документ, проваливающий валидацию на площадке заказчика
Чем идентификация PDF/A-4 отличается от частей 2 и 3
PDF/A-4 идентифицирует себя номером части и годом редакции, без буквы соответствия для базовой части. Установите PDFACompliance в '4', и HotPDF выпускает pdfaid:part=4 с pdfaid:rev=2020 и вообще без записи pdfaid:conformance. Буква не потерялась: у части 4 нет уровней A/B/U, потому что требования, ранее их разделявшие, были свёрнуты в базовую часть
Два расширения сохраняют букву. '4E' выбирает PDF/A-4e для инженерных документов и выпускает соответствие E, допускающее пути аннотаций 3D и RichMedia, которые другие профили запрещают. '4F' выбирает PDF/A-4f и выпускает соответствие F, допускающее встроенный файл любого формата. Все три принудительно задают заголовок PDF 2.0, требуют обычных проверки выходного интента и метаданных PDF/A и запрещают шифрование: зашифрованный архивный файл — противоречие, которое стандарт не допускает
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'invoice-archive.pdf';
Pdf.PDFACompliance := '4F'; // PDF/A-4f: associated files of any format
Pdf.BeginDoc;
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(50, 760, 0, 'Invoice 2026-0731');
Pdf.AddPDFAssociatedFile('invoice.xml', 'text/xml',
'Structured invoice data', 'Data', LoadInvoiceBytes);
Pdf.EndDoc;
finally
Pdf.Free;
end;
end;
AddPDFAssociatedFile встраивает файл, строит его FileSpec с /AFRelationship и регистрирует его как в массиве /AF каталога, так и в дереве имён EmbeddedFiles. Обе регистрации обязательны; файл, указанный лишь в одной из них, — самая частая причина, по которой гибридный счёт-фактура проходит быструю визуальную проверку и проваливает настоящий валидатор. Строка отношения принимает значения Source, Data, Alternative, Supplement или Unspecified, а активный профиль должен быть PDF/A-3, PDF/A-4e или PDF/A-4f: базовый профиль части 4 не допускает ассоциированных файлов. Прежнее имя AddPDFA3AssociatedFile по-прежнему работает для существующего кода
Чего требует PDF/UA-2, чего не требовал PDF/UA-1
PDF/UA-2 принудительно использует PDF 2.0 и выпускает pdfuaid:part=2 с pdfuaid:rev=2024, а также вводит пространства имён в дерево структуры. Документ части 1 имел один плоский словарь стандартных ролей. Документ части 2 может нести собственные роли, лишь бы каждая принадлежала объявленному пространству имён — именно это делает доменно-специфическую разметку читаемой для вспомогательных технологий, а не угадываемой
Этому посвящены два метода. RegisterStructureNamespace создаёт или повторно использует непрямой словарь /Type /Namespace и перечисляет его в StructTreeRoot /Namespaces, возвращая словарь для повторного применения. AddStructureElementNS создаёт элемент структуры, чья запись /NS указывает на этот словарь — именно это и лицензирует имя роли вне стандартного набора. Повторные вызовы с тем же URI повторно используют один словарь, а не накапливают дубликаты
var
Root: THPDFDictionaryObject;
begin
Pdf.PDFUACompliance := True;
Pdf.PDFUAPart := 2; // part 2 forces PDF 2.0
Pdf.Lang := 'en-US';
Pdf.BeginDoc;
Root := Pdf.AddStructureElement('Document', nil);
Pdf.AddStructureElementNS('WidgetGroup',
'https://example.com/ns/widgets', Root);
Pdf.EndDoc;
end;
Lang здесь — не украшение. Размеченный документ без объявленного естественного языка заставляет скринридер гадать о произношении, а PDF/UA трактует это упущение как дефект, а не как предпочтение
Какие ошибки структуры ловит EndDoc?
Четыре, и каждая соответствует документу, который иначе попал бы к валидатору сломанным. Корень структуры должен содержать ровно один элемент Document верхнего уровня. Каждый словарь пространства имён должен быть непрямым, типизированным как Namespace и нести уникальный непустой URI. Каждая ссылка /NS элемента структуры должна разрешаться в словарь, действительно перечисленный в корневом массиве /Namespaces. А роль без пространства имён должна быть стандартной ролью PDF 2.0 или разрешаться через RoleMap
Эти проверки срабатывают на EndDoc, потому что это последний момент, когда всё дерево целиком существует в памяти, и первый, когда оно полно. Ловить их раньше означало бы отвергать корректные промежуточные состояния; ловить позже — не ловить вовсе. Практическое следствие для вашего кода: баг структуры всплывает в конце генерации с сообщением, называющим проблему, а не спустя недели как отчёт veraPDF, который кто-то перешлёт от заказчика
Роли PDF 2.0, о которых стоит знать
Типизированное перечисление ролей пополняется DocumentFragment, Aside, Title, FENote, Sub, Em, Strong и Artifact. Три из них меняют то, как вы размечаете обычные деловые документы. Aside наконец даёт боковым панелям и врезкам дом, не являющийся злоупотреблённым Sect. FENote помечает концевые и подстрочные сноски тем, чем они являются, чтобы читалка могла предложить их, а не interleavingировать с основным текстом. Em и Strong заменяют семантическое угадывание, возникавшее из пометки выделения как форматирования уровня span
Строковая перегрузка дополнительно принимает открытую форму Hn, включая H7 и далее. PDF 1.7 останавливался на H6, что заставляло глубокие технические документы выравнивать структуру или повторно использовать уровни. Если вы генерируете документы стандартов, правовые кодексы или каталоги запчастей, одно это может стать причиной перевода вывода на PDF 2.0
Что проверить перед переключением производственного вывода
PDF 2.0 — это смена заголовка с длинным хвостом. Старые инструменты приёма архивов, некоторые печатные RIP и удивительное число бизнес-просмотрщиков принимают только вплоть до PDF 1.7 и падают на заголовке, а не на чём-то, что вы сделали неправильно. Перед переключением подтвердите совместимость принимающих систем и помните, что выбор профиля PDF/A-4 выбирает PDF 2.0, просили вы его или нет
Безопасная последовательность: держите PDF/A-3 для документов, идущих внешним неизвестным читателям, используйте PDF/A-4f для внутренних архивов, где приём контролируете вы, и принимайте PDF/UA-2 лишь там, где политика доступности называет его. Если вы сначала проходите архивную сторону, руководства по валидации PDF/A, PDF/X и PDF/UA и по гибридным счетам ZUGFeRD и Factur-X на PDF/A-3 описывают выбор профилей, значимый прежде номера версии, а заметки об автоматизированной предпечатной отчётности показывают, как сделать вердикт частью сборки, а не ручным шагом
HotPDF поставляет всю авторскую поверхность PDF 2.0 как нативный VCL-код для Delphi и C++Builder, поэтому вывод PDF/A-4 и PDF/UA-2 не требует внешнего движка или распространяемого компонента — на странице компонента HotPDF перечислены поддерживаемые профили и версии RAD Studio