PDFlibPas проверяет файлы PDF/UA-2 (ISO 14289-2) в Delphi через TPDFlib.CheckFilePDFUACompliance с целевой частью 2: этот вызов запускает отдельный профиль PDF/UA-2, который разрешает структурные роли через пространства имён PDF 2.0 и /RoleMapNS, трактует обрезанный или нечитаемый вход как провал и возвращает отчёт о покрытии, который никогда не заявляет о полной сертификации
Последнее важнее, чем кажется. Валидация доступности — это область, где зелёная галочка попадает в закупочную таблицу и больше никем не подвергается сомнению. Валидатор, который молча пропускает половину стандарта и всё равно печатает «pass», хуже, чем отсутствие валидатора: он превращает неизвестное в ложное знание. PDFlibPas придерживается противоположной позиции: проверять то, что проверяется механически, громко сообщать о непроверенном и трактовать каждое «не удалось завершить» как находку, а не как успех
Почему файлы PDF/UA-2 нельзя проверять правилами PDF/UA-1?
PDF/UA-2 — это не PDF/UA-1 с увеличенным номером части. PDF/UA-1 стоит на PDF 1.7 и единой плоской role map; PDF/UA-2 стоит на PDF 2.0 (ISO 32000-2), где каждый структурный элемент может принадлежать пространству имён, и у каждого пространства своя role map. Чекер UA-1, нацеленный на файл UA-2, и реальные проблемы пропустит, и выдумает ложные
Ложные срабатывания — самая неприятная часть. Валидаторы PDF/UA-1 обычно требуют, чтобы элементы Note несли ID, и регулируют применение ненумерованного заголовка H. Ни то, ни другое не переносится. В пространстве имён структуры PDF 2.0 сноски выражаются элементом FENote, а заголовки — это H1–Hn без потолка в шесть уровней. PDF/UA-2 идёт дальше и вовсе запрещает роль ненумерованного H. Примените к такому дереву логику заголовков UA-1, и вы либо будете жаловаться на то, что стандарт разрешает, либо пропустите единственное, что он запрещает
Поэтому профиль PDF/UA-2 в PDFlibPas — это отдельный кодовый путь, а не флаг на старом. Он и номера пунктов UA-1 повторно использовать отказывается. Диагностики, общие с чекером UA-1, в отчётах UA-2 привязываются к ISO 14289-2, а там, где номер пункта не сверен с текстом стандарта, отчёт оставляет поле пункта пустым вместо того, чтобы угадывать. Пустой пункт тешит меньше, чем уверенный номер, но это единственный защищаемый результат, когда никто не подтвердил соответствие
Как PDFlibPas разрешает роли через RoleMapNS
PDFlibPas сводит каждую структурную роль к стандартной роли в известном пространстве имён, проходя по цепочкам /RoleMapNS между пространствами, и сообщает о любой цепочке, которая заканчивается пользовательской ролью, зацикливается или нарушает правила отображения. Это ядро валидации структуры PDF/UA-2: вспомогательные технологии понимают только стандартные роли, и пользовательская /InvoiceLine ничего не значит, пока не приведёт к чему-то вроде P или TR
Резолвер отталкивается от имени /S элемента и его пространства имён /NS. Без пространства имён элемент неявно находится в стандартном пространстве PDF 1.7, и применяется прежний /RoleMap в корне дерева структуры. С пространством имён применяется собственный /RoleMapNS словаря пространства, а отображение может указывать на роль в другом пространстве через двухэлементный массив. Проверки, которые из этого вытекают, вполне конкретны:
- Пространство имён, на которое ссылается структурный элемент, должно быть косвенным словарём с корректным URI в
/NSи должно быть перечислено в массиве/Namespacesкорня дерева структуры (диагностика 10054) - Пользовательская роль должна в итоге сводиться к стандартной роли в пространстве PDF 1.7, в пространстве PDF 2.0 (
http://iso.org/pdf2/ssn) или в пространстве MathML; циклические цепочки и тупики сообщаются как 10055 - Стандартную роль нельзя переотображать в пользовательскую, а роль нельзя переотображать внутри её собственного явного пространства имён
- Корень структуры должен содержать ровно один элемент
Documentв пространстве имён PDF 2.0 (10057), и/Pкаждого элемента должен указывать на его фактического родителя (10058) - Ненумерованный
Hили унаследованный из PDF 1.7Note, не отображённый в роль PDF 2.0, сообщается как 10056
Деталь, которую стоит перенести в собственные инструменты: PDFlibPas проверяет каждое объявленное отображение, а не только те, что достижимы из дерева. Битая запись в role map, которую сейчас не использует ни один элемент, — всё ещё скрытый дефект, потому что ближайшее обновление контента начнёт её использовать. С MathML поступают честно: пространство имён MathML признаётся допустимой целью разрешения, но сам словарь MathML не валидируется, и отчёт о покрытии об этом прямо говорит
Запуск проверки PDF/UA-2 из Delphi
TPDFlib.CheckFilePDFUACompliance — точка входа в один вызов: передаёте файл, пароль, целевую часть и опции и получаете назад хэндл списка строк с закодированными находками. Хэндл ноль при LastErrorCode равном нулю означает, что реализованные проверки ничего не нашли; ненулевой LastErrorCode — что сам вызов не удался
var
Lib: TPDFlib;
Issues, I: Integer;
begin
Lib := TPDFlib.Create;
try
// TargetPart: 0 = определить по XMP pdfuaid:part, 1 = PDF/UA-1, 2 = PDF/UA-2
// Options: 1 = остановиться на первом провале, 0 = собрать все находки
Issues := Lib.CheckFilePDFUACompliance('report.pdf', '', 2, 0);
if Issues = 0 then
begin
if Lib.LastErrorCode <> 0 then
Writeln('Validation did not run, error ', Lib.LastErrorCode)
else
Writeln('No issues from the implemented PDF/UA-2 checks');
end
else
try
for I := 1 to Lib.GetStringListCount(Issues) do // элементы нумеруются с единицы
Writeln(Lib.GetStringListItem(Issues, I));
finally
Lib.ReleaseStringList(Issues);
end;
finally
Lib.Free;
end;
end;
Целевую часть задавайте осознанно. Цель 0 читает pdfuaid:part из XMP-метаданных и выбирает профиль по нему — это правильно для универсального валидатора входящих файлов. Для собственного конвейера вывода передавайте 2 явно. Если генератор забыл записать идентификацию, автодетект откатится к правилам UA-1, и вы проверите не тот стандарт; явная двойка вместо этого выдаёт диагностику 10050 за отсутствующую часть, плюс 10051, если pdfuaid:rev не 2024, и 10052, если действующая версия PDF не 2.0. На стороне авторинга TPDFlib.SetPDFUA2Mode записывает эту идентификацию, поднимает версию до 2.0 и назначает пространство имён структуры PDF 2.0 по умолчанию; сама модель пространств имён разобрана в обзоре пространств имён PDF 2.0, PDF/A-4 и PDF/UA-2
Что происходит, когда у валидатора кончается бюджет?
PDFlibPas работает по принципу fail-closed: когда чекер PDF/UA-2 упирается в лимит обхода, цикл, лимит парсера или документ, который не удаётся открыть, он выдаёт диагностику, и результат без диагностик никогда не повышается до «прошёл». Диагностика 10059 зарезервирована ровно под этот случай «читатель не смог закончить»
Бюджеты явные и опубликованы в coverage JSON: глубина структуры 256, 100 000 структурных объектов и 256 переходов по любой цепочке role map. Эти числа существуют потому, что деревья структуры приходят из недоверенного входа. Враждебный или просто битый файл может вкладывать элементы на тысячи уровней вглубь, направить потомка назад к предку или построить role map, протянутую через сотни пространств имён. Валидатор без бюджетов либо падает, либо зависает, а валидатор с бюджетами, но без диагностик делает кое-что тоньше и хуже: он рано останавливается, не находит ничего плохого в прочитанной части и рапортует об успехе
Тот же принцип работает и на уровень выше. Если лежащий в основе читатель возвращает счёт ниже 100 без строк находок — так бывает, когда разбор обрывается до запуска какого-либо правила, — PDFlibPas вставляет строку 10059, чтобы пустой список нельзя было принять за чистый результат. Исключение во время проверки тоже превращается в строку 10059. Это немного кода, и он закрывает самый распространённый способ, которым конвейеры валидации лгут своим владельцам
Один разбор совместно с PDF/A и сборка отчёта
Для пакетной работы API конвейера валидации разбирает файл один раз и прогоняет несколько проверок по общему разбору. RunPDFUAValidationPipelineTask — PDF/UA-член этого семейства, принимающий те же значения целевой части, что и функция одного вызова, так что preflight PDF/A плюс PDF/UA-2 не платит за два полных чтения крупного документа
var
Pipe, PDFAIssues, UAIssues: Integer;
Json: WideString;
begin
Pipe := Lib.BeginValidationPipeline('archive.pdf', '');
if Pipe = 0 then
Exit;
try
PDFAIssues := Lib.RunValidationPipelineTask(Pipe, 1, 0); // 1 = PDF/A
UAIssues := Lib.RunPDFUAValidationPipelineTask(Pipe, 2, 0); // PDF/UA-2
// ... проверить и освободить оба хэндла, как в предыдущем примере ...
Writeln('Parses: ', Lib.GetValidationPipelineParseCount(Pipe));
// ComplianceTests: 1 = PDF/A, 2 = PDF/UA, 0 = оба; ReportFormat 1 = JSON
Json := Lib.CreateValidationPipelineReport(Pipe, 0, 0, 1);
finally
Lib.ReleaseValidationPipeline(Pipe);
end;
end;
Структурированный отчёт подписывает раздел доступности как PDF/UA-2 и ISO 14289-2, когда файл идентифицирует себя как часть 2, и каждый раздел PDF/UA несёт "fullCertification": false вместе со встроенным объектом покрытия. Текстовый preflight-отчёт добавляет строку о том, что выполнились только реализованные проверки, а полная сертификация доступности требует дополнительной валидации и человеческого ревью. Более широкие форматы отчётов, ID правил и паттерны гейтирования в CI описаны в руководстве по preflight PDF/A и PDF/UA
Что отчёт о покрытии реально гарантирует?
TPDFlib.GetPDFUAReadValidationCoverageJSON гарантирует одну вещь: точный список того, что чекер PDF/UA-2 осмотрел, а что нет. Для части 2 он возвращает fullCertification, жёстко равный false, массивы checked и unverified и лимиты обхода. Он не гарантирует, что файл доступен, и устроен так, что ни один кодовый путь не может заставить его сказать обратное
var
Coverage: WideString;
begin
Coverage := Lib.GetPDFUAReadValidationCoverageJSON(2);
// Хранить его рядом с результатом валидации, а не вместо него
TFile.WriteAllText('report.coverage.json', Coverage, TEncoding.UTF8);
if Pos('"fullCertification":false', Coverage) > 0 then
Writeln('Machine checks only; schedule a human accessibility review');
end;
Список checked охватывает идентификацию (часть, ревизия, префикс), действующую версию PDF, метаданные заголовка, /MarkInfo, DisplayDocTitle, ссылки на пространства имён, разрешение role map, корень документа, родителей структуры, циклы дерева и правила H и Note. Список unverified столь же конкретен: тегирование контента и порядок чтения, вложенность структуры, шрифты и отображение Unicode, аннотации и формы, естественный язык, альтернативные описания, словарь MathML и человеческое ревью доступности. Несколько пунктов из этого вообще неразрешимы механически. Ни один парсер не скажет вам, описывает ли alt text в действительности диаграмму и есть ли смысл в порядке чтения для того, кто его слушает
Обращайтесь с coverage JSON как с частью результата. Архивируйте его с каждым прогоном валидации, и когда аудитор спросит, что значило «прошёл» на определённую дату, вы сможете передать точную область проверки, а не восстанавливать её по памяти и номеру версии
Перекрёстная проверка против veraPDF
Ядро PDF/UA-2 было перекрёстно проверено командным валидатором veraPDF 1.30.3 с его профилем ua2, и там, где покрытия пересекаются, результаты совпадают. Независимо сгенерированный валидный образец ядра прошёл оба инструмента; образец, объявляющий pdfuaid:rev 2023, и образец с ненумерованной ролью H провалили оба инструмента
Четвёртый образец — тот, о котором стоит поговорить. Он содержал рисованный контент, вообще не покрытый тегами, и veraPDF его пометил, а проверки ядра PDFlibPas — нет, потому что тегирование контента лежит вне текущего покрытия. Именно этот пробел — причина, по которой content-tagging-and-reading-order возглавляет список unverified. Второй валидатор — дешёвая страховка, и расхождение двух инструментов часто быстрейший способ нащупать границу того, что каждый из них проверяет. О стороне авторинга той же проблемы — построении деревьев, которые проходят с первого раза, — см. руководство по дереву структуры тегированного PDF
FAQ
Сертифицирует ли PDFlibPas файл как соответствующий PDF/UA-2?
Нет. PDFlibPas выполняет реализованные машинные проверки для PDF/UA-2 и возвращает отчёт о покрытии с fullCertification, всегда равным false, перечисляя каждую область, которой всё ещё нужны другие инструменты или человеческое ревью
Какое значение TargetPart передавать в CheckFilePDFUACompliance?
Передавайте 2, когда знаете, что файл обязан быть PDF/UA-2, — тогда отсутствие идентификации будет сообщено. Передавайте 0 для автодетекта части по XMP pdfuaid:part и 1, чтобы принудительно применить правила PDF/UA-1
Что означает диагностика 10059 в отчёте PDF/UA-2?
Диагностика 10059 означает, что валидатор не смог завершиться: файл не удалось разобрать или открыть, превышен лимит обхода, найден цикл или произошло исключение. Она всегда считается провалом и никогда — успехом
Валидация PDF/UA-2, общий конвейер валидации и API авторинга PDF 2.0 входят в losLab PDF Library for Delphi вместе с preflight PDF/A и структурированными отчётами валидации