PDFlibPas здатний тегувати документ у процесі малювання. Увімкніть SetAutoTagMode, і звичайні виклики DrawText стають абзацами, текст одразу після RegisterHeading стає заголовком відповідного рівня, колонтитули стають артефактами, які читач оминає, зображення стають фігурами, а DrawTableRows переносить таблицю, її рядки та клітинки у дерево структури
Альтернатива — й донедавна єдиний варіант — полягала в ручному обгортанні кожного виклику малювання в BeginTag та EndTag. Це працює, і для документів з незвичайною структурою залишається правильним інструментом. Для звичайного звіту, рахунку чи виписки це означає, що доступність результату залежить від того, що ніхто ніколи не забуде пару в кожному шляху коду, де щось малюється
Що охоплюють біти режиму
SetAutoTagMode приймає бітову маску й повертає режим, що діяв раніше. AUTOTAG_TEXT (1) тегує текст як абзац або як заголовок, коли настав його час. AUTOTAG_FURNITURE (2) позначає колонтитули та номери сторінок як артефакти. AUTOTAG_FIGURE (4) перетворює намальоване зображення на фігуру або на артефакт, якщо воно оголошене декоративним. AUTOTAG_TABLE (8) переносить намальовані таблиці у дерево структури. AUTOTAG_DEFAULT дорівнює 15 — це всі чотири прапорці
Увімкнення режиму також позначає документ як тегований, і цей крок менш косметичний, ніж здається. Читач вважає документ нетегованим, якщо каталог не стверджує зворотне (ISO 32000-1 §14.7.1), тож файл із повним деревом структури, але без декларації /MarkInfo оголошується допоміжними технологіями як такий, що не має структури взагалі. Дерево на місці; просто ніхто його не читає
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1);
Lib.SetAutoTagMode(AUTOTAG_DEFAULT); // text + furniture + figures + tables
Lib.AddStandardFont(4);
Lib.SetTextSize(18);
Lib.RegisterHeading(1, 'Annual service report');
Lib.DrawText(72, 96, 'Annual service report'); // becomes H1
Lib.SetTextSize(11);
Lib.DrawText(72, 130, 'Every unit installed before 2024 was inspected.');
Lib.SaveToFile('report.pdf');
finally
Lib.Free;
end;
end;
Звідки заголовок знає, до якого тексту він належить?
RegisterHeading задає рівень для наступного тексту, що малюється, і чекає на текст. Якщо між ними малюється зображення, воно стає фігурою, а заголовок залишається очікуючим для наступного тексту. Ця поведінка навмисна: альтернатива, за якої зображення бере рівень заголовка, породжувала документи, де декоративна лінія під заголовком оголошувалася як сам заголовок
Те саме правило «витрачається на один елемент» керує фігурами. RegisterFigure задає опис, який нестиме наступне зображення, а RegisterDecoration оголошує наступне зображення лінією, рамкою чи фоном без смислового навантаження. Обидва витрачаються на одне зображення, тому пізніше зображення ніколи не успадковує опис, призначений попередньому — а саме так альтернативний текст потрапляє не на ту картинку в коді з ручним тегуванням
Опис важливіший за будь-який інший рядок у доступному документі. Незрячий читач отримує опис замість картинки, і це все, що він отримує. «Графік» — це не опис; «Щоквартальний дохід за регіонами, зі східним регіоном найвищим у Q3» — це опис
Lib.RegisterFigure('Exploded view of the gearbox assembly');
Lib.AddImageFromFile('gearbox.png', 0); // becomes a tagged Figure
Lib.RegisterDecoration; // meaningless rule
Lib.AddImageFromFile('divider.png', 0); // drawn inside a layout artifact
Таблиці, заголовки та місце рішення про повторення
Із ввімкненим бітом таблиць DrawTableRows переносить таблицю, її рядки та клітинки у дерево структури, тож читач може сказати, в якому стовпці знаходиться значення, замість того, щоб читати всю таблицю як потік незв'язаного тексту. SetTableHeaderRowCount вказує, скільки перших рядків є заголовками; ці рядки записуються як заголовкові клітинки зі стовпцевою областю дії, що й дозволяє читачу оголосити заголовок значення, на якому перебуває користувач
Заголовкові рядки, визначені таким чином, залишаються там, де є. Їх повторення вгорі кожної сторінки — це рішення компонування, і воно ним залишається: DrawTaggedTableRows приймає аргумент RepeatHeaderRows саме для цієї мети. Розділення двох речей уникає появи в дереві структури другої копії заголовка для кожного розриву сторінки, яку породило б автоматичне повторення
var
TableID: Integer;
begin
TableID := Lib.CreateTable(40, 3);
Lib.SetTableHeaderRowCount(TableID, 1); // row 1 is the header band
Lib.SetTableCellContent(TableID, 1, 1, 'Part');
Lib.SetTableCellContent(TableID, 1, 2, 'Torque');
Lib.SetTableCellContent(TableID, 1, 3, 'Unit');
// ... fill the data rows ...
// Draw rows 1..40 into a 600pt band, repeating one header row per page
Lib.DrawTaggedTableRows(TableID, 72, 150, 600, 1, 40, 1);
end;
Поєднання автоматичного та ручного тегування
Автоматичне тегування відходить убік усередині тега, відкритого вручну. Частина документа може бути описана вашим кодом, а решта залишена бібліотеці, без взаємного вкладання — і саме така схема потрібна більшості реальних документів. Титульна сторінка та блок підписів мають структуру, яку розумієте лише ви; двісті сторінок основного тексту між ними — ні
Два правила безпеки тримають результат чистим. Усередині артефакту нічого не тегується, бо вміст, позначений як артефакт, не повинен нести жодного елемента структури. І порожній текст не відкриває жодного елемента, тож випадковий DrawText з порожнім рядком не здатний породити елемент структури, який читач оголосив би порожнім. Обидві вади — саме того ґатунку, що тихо накопичуються в документах із ручним тегуванням і про які валідатор звітує масово через кілька місяців
Що автоматичне тегування все ще не вирішує за вас
Порядок читання, що відрізняється від порядку малювання, семантичні ролі поза абзацом, заголовком, фігурою чи таблицею, та декларації мови. Автоматичне тегування призначає структуру в порядку малювання вмісту — якщо ваш код компонування малює бічну панель перед основним текстом, то саме цей порядок записує дерево. Для документів, де візуальний порядок і порядок читання дійсно розходяться, API ручного тегування залишається правильним інструментом, а нарис тегованого PDF та структури доступності детально описує ролі, області дії та прив'язки заголовків
Коли документ готовий, перевіряйте, а не припускайте: нотатки префлайта PDF/A та PDF/UA показують, як отримати вердикт щодо створеної структури, а нарис експорту звітів на основі наборів даних описує, куди ці виклики вписуються в рушій звітів, що генерує компонування з даних
PDFlibPas — це рідна Pascal-бібліотека PDF для Delphi, C++Builder та Lazarus без зовнішнього PDF-рутайму, тож доступний результат створюється тим самим кодом, що малює документ — дивіться сторінку продукту PDFlibPas щодо повного API та списку платформ