Техническа статия

Автоматично маркиране на структурата за достъпен PDF в Delphi

PDFlibPas може да маркира документ докато се чертае. Включете SetAutoTagMode и обикновените извиквания на DrawText стават абзаци, текстът начертан веднага след RegisterHeading става заглавие от съответното ниво, колонтитулите стават артефакти, които четецът прескача, изображенията стават фигури, а DrawTableRows пренася таблицата, нейните редове и клетки в дървото на структурата

Алтернативата — и до скоро единствената възможност — беше ръчно да обвиете всяко извикване за чертане в BeginTag и EndTag. Това работи и за документи с необичайна структура все още е правилният инструмент. За обикновен отчет, фактура или извлечение означава, че достъпността на изхода зависи от това никой никога да не забрави двойка, по всички кодови пътища, които чертаят каквото и да е

Какво покриват битовете на режима

SetAutoTagMode приема битовa маска и връща режима, който е бил в сила преди това. 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 runtime, така че достъпен изход се произвежда от същия код, който чертае документа — вижте страницата на продукта PDFlibPas за пълния списък с API и платформи