Достъпният PDF се основава на една структура, която видимата страница никога не показва: дървото на структурата (structure tree), дефинирано в ISO 32000-1 §14.7. Това е логическа йерархия от заглавия, абзаци, таблици и фигури, разположена над изчертаното съдържание и съпоставена със стандартни роли чрез съответствие на ролите (role map). Екранните четци четат това дърво, а не символите върху страницата. Без него една генерирана фактура, която изглежда безупречно, е семантично празна, тъй като потокът от съдържание записва реда на чертане и нищо друго. Крайната сума може да бъде прочетена преди списъка с артикули, долният колонтитул може да прекъсне абзац, а таблицата с артикули може да се слее в една недиференцирана поредица от думи. Разходите за предотвратяване на това са изцяло във ваша полза. Генерирането на структура по време на чертане изисква минимален код, докато добавянето й в готови документи е цял проект за коригиране. losLab PDF Library (PDFlibPas) предоставя дървото на Delphi и C++Builder чрез набор от повиквания, които обграждат всяка операция по чертане в нейната логическа роля
Как маркираното съдържание се свързва с дървото на структурата
Два слоя си сътрудничат. В потока от съдържание операциите по чертане са групирани в маркирани последователности (marked-content sequences), всяка от които носи целочислен идентификатор MCID. В каталога на документа дървото на структурата съпоставя тези идентификатори в йерархия от типизирани елементи (H1, P, Table, Figure) с атрибути като алтернативен текст и език. Използването на персонализирани типове елементи е позволено, но всеки от тях трябва да се препраща към стандартна роля чрез съответствието на ролите (ISO 32000-1 §14.8.4). Съдържанието, което няма никакво значение, като линии, фонове и повтарящи се елементи на страницата, се маркира като артефакт, така че асистиращите технологии да го пропускат, вместо да го четат по средата на изречението
PDFlibPas поддържа и двата слоя чрез една двойка скоби. BeginTag отваря структурен елемент и стартира маркираната последователност, операциите за чертане се изпълняват вътре в нея, а EndTag затваря и двата елемента. Управлението на идентификаторите (MCIDs), родителското дърво и препратките към страници, което често води до грешки при ръчно писане, тук се случва автоматично в самата библиотека
Два параметъра на ниво документ определят работата преди отварянето на който и да е таг. SetMarkInfo записва флага в каталога, който указва, че документът е структуриран (tagged), а IsTaggedPDF го прочита обратно, което е бърз метод за проверка дали входящ файл има структура, която си струва да бъде запазена. Задаването на език има две точки на достъп: SetDocumentLanguage задава езика по подразбиране за документа, докато SetPDFUAMode го задава като част от активирането на пълния PDF/UA изход. Документът може да бъде структуриран успешно и без да декларира пълно съответствие с PDF/UA, като поетапното внедряване често започва именно оттам
Структуриране по време на чертане, а не след това
Правилният модел на генериране е структурата да се третира като част от всяко повикване за чертане, а не като допълнителна стъпка по-късно:
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1); // top-left origin
Lib.SetPDFUAMode('en-US'); // bumps the save version to PDF 1.7
Lib.SetInformation(1, 'Service Manual'); // /Title is mandatory for PDF/UA
Lib.AddRoleMap('ManualTitle', 'H1'); // custom type -> standard role
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'); // page decoration: excluded from reading
// ... draw rules and background tint ...
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'); // valid only while this TH is open
Lib.DrawText(72, 120, 'Part');
Lib.EndTag;
Lib.BeginTagEx2('TH', '', '', '', '', 'col-torque', '');
Lib.SetStructElemScope('Column');
Lib.SetStructElemColSpan(2); // header spans the value and unit columns
Lib.DrawText(200, 120, 'Tightening torque');
Lib.EndTag;
Lib.EndTag;
Lib.BeginTag('TR', '', '');
Lib.BeginTag('TD', '', '');
Lib.SetStructElemHeaders('col-part'); // explicit binding for irregular tables
Lib.DrawText(72, 140, 'M8 flange bolt');
Lib.EndTag;
Lib.EndTag;
Lib.EndTag; // Table
Правилното правило за последователност е строго и се прилага мълчаливо. Всяко повикване на SetStructElem* се прилага към тага, който е отворен в момента (между неговите BeginTag и EndTag), и връща 0 без да предизвиква изключение, ако няма отворен таг или ако атрибутът не е приложим за текущия. Грешно разположеното повикване просто се игнорира. Проверката на върнатите стойности чрез твърдения (assertions) по време на разработка помага за откриването на тези несъответствия навреме; в противен случай липсващият обхват (scope) ще бъде открит едва при одит на достъпността с реален екранен четец. Идентификаторите на елементите, предадени чрез BeginTagEx2, попълват дървото на идентификаторите (ISO 32000-1 §14.7.4) и позволяват свързването чрез SetStructElemHeaders
Същата група атрибути обхваща и останалите елементи, на които разчитат асистиращите технологии. SetStructElemListNumbering указва как се номерират елементите в списък, така че екранният четец да съобщава позицията в списъка, вместо да чете символите за водещи знаци (bullets). SetStructElemBBox записва рамката на фигурите и таблиците, която се използва при пренареждане на съдържанието. SetStructElemActualText предоставя алтернативен текст за символи, които нямат съответствие в кодовата таблица (например декоративна първа буква, сглобена от векторни линии). Всеки от тях следва същото правило: свързва се с отворения таг или се игнорира
Декоративни елементи (artifacts), език и предварителна диагностика преди запис
Повтарящите се декоративни елементи на страниците, като горни и долни колонтитули, знаци за сгъване, водни знаци и фонове, трябва да се поставят в блокове BeginArtifact и EndArtifact, за да се изключат от потока за четене. Езикът се наследява. Стойността по подразбиране за документа се задава чрез аргумента на SetPDFUAMode, а текст на друг език може да бъде указан за отделен елемент чрез BeginTagEx или SetStructElemLang. Това позволява на екранния четец да произнесе правилно френски цитат в ръководство на английски език
Преди запис GetPDFUADiagnostics стартира структурните проверки на библиотеката върху документа в паметта и връща резултатите като текст (празен низ означава липса на грешки). Кодовете указват типичните грешки при изготвянето на документи: FIGURE-NO-ALT за изображение без алтернативен текст, HEADING-LEVEL-SKIP за H3 след H1 и ROLEMAP-UNMAPPED за потребителски тип без съответствие. Интегрирането на тази стъпка в процеса на компилация (генериране на документите и прекъсване на процеса при наличие на диагностични съобщения) превръща проблемите с достъпността в грешки при компилация. Окончателното заключение за съвместимост се извършва след записване на файла съгласно описаното в PDF/A и PDF/UA проверка в Delphi, тъй като някои корекции се прилагат само по време на serialization
Навигацията по анотации също се контролира. PDF/UA изисква преминаването чрез клавиатура (tab order) през формулярни полета и връзки да следва логическата структура, като SetTabOrderMode записва съответната стойност за страницата, а GetTabOrderMode позволява одит на входящи файлове. Това е изискване, което рядко се забелязва, докато потребител, работещ само с клавиатура, не съобщи за проблем, а отстраняването му изисква само едно извикване на документ
Структурните дървета невинаги се запазват при обединяване
Структурираните документи запазват своите свойства само ако следващите стъпки по обработка съхраняват дървото на структурата. Специфична особеност в PDFlibPas са функциите за обединяване. MergeFileListFast жертва структурата в името на скоростта на работа. Това е подходящо за сканирани изображения, но не и за структурирани отчети, тъй като крайният резултат изглежда идентично, но губи слоя за достъпност. Използвайте стандартния метод MergeFileList или по-строгия му вариант, когато някой от входящите файлове е структуриран, и включете проверка с IsTaggedPDF след сглобяването. Процесите по сглобяване на големи документи включват подобни компромиси, разгледани в сливане, разделяне и директен достъп до големи PDF файлове
Проверката завършва извън библиотеката: отворете файла в Acrobat, проверете панела с тагове и тествайте с реален екранен четец. Автоматичната диагностика открива структурни грешки, но само човешкото ухо може да оцени дали редът на четене е логичен и разбираем на практика. Пробните версии и пълното описание на API за структуриране са налични на продуктовата страница за losLab PDF Library за Delphi