Доступний PDF спирається на одну структуру, яку видима сторінка ніколи не показує: структурне дерево, визначене в ISO 32000-1 §14.7. Це логічна ієрархія заголовків, абзаців, таблиць і рисунків, накладена поверх намальованого вмісту й зіставлена зі стандартними ролями через карту ролей. Програма читання з екрана читає саме це дерево, а не позначки на сторінці. Без нього згенерований рахунок-фактура, що виглядає бездоганно, семантично порожній, бо потік вмісту фіксує лише порядок малювання і нічого більше. Підсумок можна оголосити раніше за рядки позицій, підвал може врізатися в абзац, таблиця позицій може згорнутися в один невиразний потік слів. Ціна запобігання цьому схилена на вашу користь. Формування структури під час малювання — це кілька хвилин коду; допасування її до вже готових документів — це проєкт з усунення недоліків. losLab PDF Library (PDF Library for Delphi) відкриває це дерево для Delphi та C++Builder через невеликий набір викликів, які загортають кожну операцію малювання в її логічну роль
Як позначений вміст прив'язується до структурного дерева
Взаємодіють два шари. У потоці вмісту операції малювання беруться в дужки позначених послідовностей вмісту, кожна з яких несе цілочисельний MCID. У каталозі документа структурне дерево зіставляє ці MCID з ієрархією типізованих елементів (H1, P, Table, Figure) з атрибутами на кшталт альтернативного тексту й мови. Власні типи елементів допустимі, але кожен з них має зводитися до стандартної ролі через карту ролей (ISO 32000-1 §14.8.4). Вміст, що не несе жодного змісту, як-от лінії, тло та повторювані елементи оформлення сторінки, позначається як артефакт, тож допоміжна технологія пропускає його, а не читає посеред речення
PDF Library for Delphi веде обидва шари за однією парою дужок. BeginTag відкриває структурний елемент і починає послідовність позначеного вмісту, виклики малювання потрапляють усередину неї, а EndTag закриває обидва. Облік, на якому спотикається саморобне тегування — MCID, батьківське дерево й посилання на сторінки — відбувається внутрішньо, там, де ви не можете помилитися
Два перемикачі рівня документа окреслюють роботу ще до відкриття будь-якого тегу. SetMarkInfo записує прапорець каталогу, який оголошує документ тегованим, а IsTaggedPDF зчитує його назад, і це дешевий перший тест, коли треба вирішити, чи є у вхідному файлі структура, варта збереження. Мова має дві точки входу. SetDocumentLanguage самостійно встановлює типову мову документа, тоді як SetPDFUAMode встановлює її як частину увімкнення повноцінного виводу PDF/UA. Файл можна корисно тегувати, не претендуючи на відповідність PDF/UA, і поетапне впровадження часто починається саме звідси
Тегування під час малювання, а не постфактум
Робочий патерн генерації полягає в тому, щоб трактувати дужки тегу як частину сигнатури кожного виклику малювання, а не як пізніший прохід:
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1); // початок координат у верхньому лівому куті
Lib.SetPDFUAMode('en-US'); // піднімає версію збереження до PDF 1.7
Lib.SetInformation(1, 'Service Manual'); // /Title обов'язковий для PDF/UA
Lib.AddRoleMap('ManualTitle', 'H1'); // власний тип -> стандартна роль
Lib.AddStandardFont(4);
Lib.SetTextSize(18);
Lib.BeginTagEx2('ManualTitle', '', '', 'en-US', '', 'h1-cover', '');
Lib.DrawText(72, 96, 'Service Manual');
Lib.EndTag;
Lib.BeginTag('Figure', 'Exploded view of the gearbox assembly', '');
Lib.AddImageFromFile('gearbox.png', 0);
Lib.EndTag;
Lib.BeginArtifact('Layout'); // оформлення сторінки: виключено з читання
// ... малювання ліній і тла ...
Lib.EndArtifact;
Lib.SaveToFile('manual.pdf');
finally
Lib.Free;
end;
end;
Три виклики в цій послідовності несуть вагу відповідності. SetPDFUAMode вмикає вивід PDF/UA і мовчки піднімає версію документа до PDF 1.7, що стикається з фіксацією версії. Документ, заблокований на PDF 1.4 через LockSaveVersion, відмовляється зберігатися й повертає код помилки 602, щойно активовано режим UA, — конфлікт, який зазвичай виринає, коли профілі архівування та вимоги доступності налаштовують різні команди. SetInformation(1, ...) записує заголовок документа, який ISO 14289 очікує побачити в переглядачах замість імені файлу; його відсутність — одне з найпоширеніших зауважень PDF/UA на практиці. AddRoleMap реєструє власний тип ManualTitle як H1, і якщо це пропустити, діагностика, описана нижче, позначить незіставлену роль
Рівні заголовків заслуговують на свідому політику, а не на випадкові рішення, ухвалені заради вигляду сторінки. Користувачі програм читання з екрана переходять між розділами за допомогою скорочень для заголовків, тож шаблон, який стрибає з H1 одразу на H3, бо проміжний рівень виглядав у візуальному дизайні надто великим, тихо ламає цю навігацію, і жодна візуальна перевірка цього ніколи не помітить. Це саме той дефект, для позначення якого існує діагностика HEADING-LEVEL-SKIP. Прив'яжіть візуальні стилі кожного шаблону до фіксованої драбини заголовків один раз, в одному місці, — і дрейф ніколи не почнеться
Таблиці, якими програма читання з екрана може справді користуватися
Намальовані лінії сітки нічого не означають поза екраном. Те, чим користуються програми читання з екрана, — це структурні зв'язки: які клітинки є заголовками, чим керує кожен заголовок і як клітинки даних прив'язуються до заголовків у нерегулярних макетах. Виклики атрибутів структурних елементів охоплюють усі три випадки:
Lib.BeginTag('Table', '', '');
Lib.BeginTag('TR', '', '');
Lib.BeginTagEx2('TH', '', '', '', '', 'col-part', '');
Lib.SetStructElemScope('Column'); // дійсно лише поки цей TH відкритий
Lib.DrawText(72, 120, 'Part');
Lib.EndTag;
Lib.BeginTagEx2('TH', '', '', '', '', 'col-torque', '');
Lib.SetStructElemScope('Column');
Lib.SetStructElemColSpan(2); // заголовок охоплює колонки значення та одиниці виміру
Lib.DrawText(200, 120, 'Tightening torque');
Lib.EndTag;
Lib.EndTag;
Lib.BeginTag('TR', '', '');
Lib.BeginTag('TD', '', '');
Lib.SetStructElemHeaders('col-part'); // явна прив'язка для нерегулярних таблиць
Lib.DrawText(72, 140, 'M8 flange bolt');
Lib.EndTag;
Lib.EndTag;
Lib.EndTag; // Table
Правило порядку суворе й дотримується мовчки. Кожен виклик SetStructElem* застосовується до тегу, який відкритий у цей момент — між його BeginTag і його EndTag — і повертає 0, нічого не піднімаючи, коли жоден тег не відкритий або атрибут не застосовний до поточного. Хибно розташований виклик просто зникає безслідно. Загортання значень, що повертаються, в асертах під час розробки ловить цей дрейф, поки ви ще можете його побачити; якщо ж це залишити напризволяще, відсутня область видимості проявиться лише тоді, коли аудит доступності прожене справжню програму читання з екрана по таблиці. Ідентифікатори елементів, передані через BeginTagEx2, живлять дерево ID (ISO 32000-1 §14.7.4), і саме це робить прив'язку SetStructElemHeaders взагалі можливою для розв'язання
Та сама родина атрибутів охоплює решту того, на що спирається допоміжна технологія. SetStructElemListNumbering оголошує, як позначені елементи списку, тож програма читання з екрана оголошує позицію в списку замість того, щоб зачитувати гліфи маркерів. SetStructElemBBox фіксує обмежувальний прямокутник рисунків і таблиць, який використовують режими перекомпонування для розміщення вмісту. SetStructElemActualText надає текст-заміну для фрагментів, чиї гліфи не зіставляються з читабельними символами, як-от буквиця, зібрана з векторної графіки. Кожен з них підпорядковується тому самому правилу: він прив'язується до відкритого тегу — або зникає
Артефакти, мова та шлюз діагностики перед збереженням
Повторювані елементи оформлення сторінки, тобто наскрізні колонтитули, мітки згину, водяні знаки й тло, мають перебувати всередині дужок BeginArtifact і EndArtifact, щоб ніколи не потрапляти до потоку читання. Мова успадковується. Типова мова документа береться з аргументу SetPDFUAMode, а фрагмент іншою мовою перевизначає її для окремого елемента через BeginTagEx або SetStructElemLang. Саме це робить французьку цитату всередині англомовного посібника вимовною
Перед збереженням GetPDFUADiagnostics проганяє структурні перевірки бібліотеки над документом у пам'яті й повертає знахідки як текст, де порожній рядок означає, що нічого не знайдено. Коди прямо називають класичні помилки авторів: FIGURE-NO-ALT для зображення без альтернативного тексту, HEADING-LEVEL-SKIP для H3, що йде одразу за H1, ROLEMAP-UNMAPPED для власного типу, який так і не зареєстрували. Вбудуйте це у збірку (згенеруйте набір документів, провалюйте крок при непорожній діагностиці) — і регресії доступності стають помилками на кшталт помилок компіляції замість зауважень аудиту через місяці. Остаточний вердикт про відповідність усе одно належить preflight-перевірці збереженого файлу, що розглянута в розділі про preflight PDF/A та PDF/UA у Delphi, бо деякі нормалізації застосовуються лише під час серіалізації
Навігація анотаціями має власний важіль. PDF/UA очікує, що обхід полів форми та посилань з клавіатури відбуватиметься за порядком структури, і SetTabOrderMode записує запис порядку табуляції на рівні сторінки, який шанують переглядачі, а GetTabOrderMode доступний для аудиту вхідних файлів. Це та вимога, якої ніхто не помічає, доки користувач, що працює лише з клавіатури, не заведе баг, а виправити її коштує один виклик на документ
Структурні дерева переживають не кожне злиття
Тегований документ лишається тегованим лише тоді, коли кожен наступний крок обробки зберігає дерево, і гострий край усередині PDF Library for Delphi — це родина функцій merge-list. MergeFileListFast міняє збереження структурного дерева на швидкість. Це правильний компроміс для пакетів сканованих зображень і неправильний для тегованих звітів, бо результат відкривається нормально, відображається ідентично й тихцем втрачає свій шар доступності. Використовуйте типовий MergeFileList або строгий варіант щоразу, коли хоч один вхідний файл тегований, і зробіть IsTaggedPDF частиною перевірок після складання, щоб сплющений пакет не міг вийти в реліз непоміченим. Конвеєри складання для великих наборів документів несуть більше подібних компромісів, розглянутих у розділі про злиття, розділення та прямий доступ до великих PDF
Цикл перевірки замикається поза бібліотекою: відкрийте результат в Acrobat, огляньте панель тегів і прочитайте хоча б один документ з кожної родини шаблонів справжньою програмою читання з екрана. Діагностика ловить структурні помилки; лише людське вухо вловлює порядок читання, який технічно коректний, але практично спантеличує. Оцінні збірки та повний довідник API тегування розміщені на сторінці продукту losLab PDF Library for Delphi