PDFlibPas potrafi otagować dokument w trakcie jego rysowania. Włącz SetAutoTagMode, a zwykłe wywołania DrawText stają się akapitami, tekst narysowany bezpośrednio po RegisterHeading staje się nagłówkiem tego poziomu, powtarzające się nagłówki i stopki stają się artefaktami pomijanymi przez czytnik, obrazy stają się figurami, a DrawTableRows przenosi tabelę, jej wiersze i komórki do drzewa struktury
Alternatywą — i do niedawna jedyną opcją — było ręczne otaczanie każdego wywołania rysowania parą BeginTag i EndTag. To działa, a dla dokumentów o nietypowej strukturze wciąż jest właściwym narzędziem. Dla zwykłego raportu, faktury czy wyciągu oznacza to, że dostępność wyniku zależy wyłącznie od tego, czy nikt nigdy nie zapomni pary, na każdej ścieżce kodu cokolwiek rysującej
Czego dotyczą bity trybu
SetAutoTagMode przyjmuje maskę bitową i zwraca tryb obowiązujący wcześniej. AUTOTAG_TEXT (1) taguje tekst jako akapit albo jako nagłówek, gdy jest on aktualny. AUTOTAG_FURNITURE (2) oznacza biegnące nagłówki, stopki i numery stron jako artefakty. AUTOTAG_FIGURE (4) zamienia rysowany obraz w figurę lub w artefakt, gdy został zadeklarowany jako dekoracyjny. AUTOTAG_TABLE (8) przenosi rysowane tabele do drzewa struktury. AUTOTAG_DEFAULT to 15, czyli wszystkie cztery
Włączenie trybu oznacza także dokument jako otagowany, a ten krok jest mniej kosmetyczny, niż się wydaje. Czytnik uznaje dokument za nieotagowany, chyba że katalog mówi inaczej (ISO 32000-1 §14.7.1), więc plik niosący kompletne drzewo struktury bez deklaracji /MarkInfo jest ogłaszany przez technologię asystującą jako pozbawiony struktury. Drzewo tam jest; niczego ono nie czyta
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;
Skąd nagłówek wie, do jakiego tekstu należy?
RegisterHeading ustala poziom dla następnego rysowanego tekstu i czeka na tekst. Jeśli pomiędzy nimi zostanie narysowany obraz, obraz staje się figurą, a nagłówek pozostaje wstrzymany na tekst, który nadejdzie. Takie zachowanie jest celowe: alternatywa, w której obraz przejmuje poziom nagłówka, produkowała dokumenty, w których dekoracyjny pasek pod tytułem był ogłaszany jako tytuł
Ta sama reguła „zużyte na jeden element" rządzi figurami. RegisterFigure dostarcza opis, jaki niesie następny obraz, a RegisterDecoration deklaruje następny obraz jako pasek, obramowanie albo tło, które nie niesie znaczenia. Oba są konsumowane przez jeden obraz, więc późniejszy obraz nigdy nie dziedziczy opisu przeznaczonego dla wcześniejszego — a tak właśnie tekst alternatywny przyczepia się do niewłaściwego obrazka w ręcznie tagowanym kodzie
Opis ma większe znaczenie niż jakikolwiek inny pojedynczy ciąg znaków w dostępnym dokumencie. Czytelnik niewidomy dostaje opis w zastępstwie obrazka i to jest wszystko, co dostaje. „Wykres" nie jest opisem; „Kwartalny przychód wg regionu, przy czym region wschodni jest najwyższy w Q3" — jest
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
Tablice, nagłówki i gdzie mieszka decyzja o powtarzaniu
Przy włączonym bicie tabeli DrawTableRows przenosi tabelę, jej wiersze i komórki do drzewa struktury, więc czytelnik może powiedzieć, w której kolumnie leży wartość, zamiast czytać całą tabelę jako ciąg niezwiązanych tekstów. SetTableHeaderRowCount ustala, ile wiodących wierszy to nagłówki; te wiersze są zapisywane jako komórki nagłówka niosące zakres kolumny, co pozwala czytnikowi ogłosić nagłówek wartości, na której aktualnie znajduje się użytkownik
Wiersze nagłówka nazwane w ten sposób zostają tam, gdzie są. Powtarzanie ich na górze każdej strony to decyzja układu i taką pozostaje: DrawTaggedTableRows przyjmuje argument RepeatHeaderRows właśnie w tym celu. Utrzymanie tych dwóch rzeczy osobno zapobiega powstaniu w drzewie struktury drugiej kopii nagłówka przy każdym podziale strony — a to właśnie wyprodukowałoby automatyczne powtarzanie
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;
Łączenie tagowania automatycznego i ręcznego
Tagowanie automatyczne schodzi na bok wewnątrz tagu otwartego ręcznie. Część dokumentu może być opisana przez Twój kod, a reszta pozostawiona bibliotece, bez wzajemnego zagnieżdżania się — a to jest układ, jakiego pragnie większość realnych dokumentów. Strona tytułowa i blok podpisu mają strukturę, którą rozumiesz tylko Ty; dwieście stron tekstu głównego pomiędzy nimi — nie
Dwie reguły bezpieczeństwa utrzymują wynik w czystości. Wewnątrz artefaktu nic nie jest tagowane, ponieważ treść oznaczona jako artefakt nie może nieść żadnego elementu struktury. A pusty tekst nie otwiera żadnego elementu, więc porzucone DrawText z pustym ciągiem znaków nie może wyprodukować elementu struktury, który czytnik ogłosiłby jako pusty. To są właśnie te wady, które ręcznie tagowane dokumenty milcząco gromadzą i które walidator raportuje zbiorczo miesiące później
Czego automatyczne tagowanie wciąż dla Ciebie nie decyduje
Kolejność czytania wykraczająca poza kolejność rysowania, role semantyczne inne niż akapit, nagłówek, figura czy tablica, oraz deklaracje języka. Tagowanie automatyczne przypisuje strukturę w kolejności, w jakiej treść jest rysowana — jeśli kod układu rysuje panel boczny przed treścią główną, właśnie taka kolejność zostaje zapisana w drzewie. Dla dokumentów, w których kolejność wizualna i kolejność czytania rzeczywiście się różnią, ręczne API tagowania pozostaje właściwym narzędziem, a przewodnik po otagowanym PDF i strukturze dostępności omawia role, zakresy i powiązania nagłówków szczegółowo
Gdy dokument jest gotowy, waliduj zamiast zakładać: notatki o preflighcie PDF/A i PDF/UA pokazują, jak uzyskać werdykt na wyprodukowanej strukturze, a przewodnik po eksporcie raportu opartym na zbiorze danych omawia, gdzie te wywołania mieszczą się w silniku raportów generującym układ z danych
PDFlibPas to natywna biblioteka PDF w Pascalu dla środowisk Delphi, C++Builder i Lazarus bez zewnętrznego runtime PDF, więc dostępny wynik jest produkowany przez ten sam kod, który rysuje dokument — zobacz stronę produktu PDFlibPas, by poznać pełne API i listę platform