Tehnički članak

Stabla strukture označenog PDF-a u Delphiju uz PDF Library for Delphi

Pristupačan PDF oslanja se na strukturu koju vidljiva stranica nikada ne prikazuje: stablo strukture definirano u normi ISO 32000-1 §14.7. To je logička hijerarhija naslova, odlomaka, tablica i slika, postavljena preko nacrtanog sadržaja i preslikana u standardne uloge putem tablice preslikavanja uloga (role map). Čitač zaslona čita to stablo, a ne oznake na stranici. Bez njega je generirani račun koji izgleda besprijekorno semantički prazan, jer tok sadržaja (content stream) bilježi samo redoslijed crtanja i ništa više. Ukupni iznos može se pročitati prije pojedinačnih stavki, podnožje može prekinuti odlomak, a tablica stavki može se spojiti u jedan neprekinuti niz riječi. Trošak sprječavanja toga uvelike ide u vašu korist. Stvaranje strukture tokom crtanja zahtijeva samo nekoliko linija koda, dok je njezino naknadno dodavanje u gotove dokumente mukotrpan projekt sanacije. losLab PDF Library (PDF Library for Delphi) izlaže stablo Delphiju i C++Builderu kroz mali skup poziva koji svaku operaciju crtanja omotavaju u njezinu logičku ulogu

Kako se označeni sadržaj povezuje sa stablom strukture

Dva sloja surađuju. U toku sadržaja, operacije crtanja grupiraju se u sekvence označenog sadržaja (marked-content sequences), od kojih svaka nosi cijeli broj MCID. U katalogu dokumenta, stablo strukture preslikava te MCID-ove u hijerarhiju tipiziranih elemenata (H1, P, Table, Figure) s atributima kao što su alternativni tekst i jezik. Prilagođeni tipovi elemenata su dopušteni, ali se svaki mora razriješiti u standardnu ulogu putem tablice preslikavanja uloga (ISO 32000-1 §14.8.4). Sadržaj koji nema nikakvo značenje, poput linija razdvajanja, pozadina i ponavljajućih elemenata stranice, označava se kao artefakt kako bi ga pomoćna tehnologija preskočila umjesto da ga čita usred rečenice

PDF Library for Delphi održava oba sloja iza jednog para zagrada (bracket pair). BeginTag otvara element strukture i pokreće sekvencu označenog sadržaja, pozivi za crtanje smještaju se unutar njega, a EndTag zatvara oboje. Evidencija koja često stvara probleme kod ručnog označavanja, kao što su MCID-ovi, roditeljsko stablo i reference stranica, odvija se interno gdje ne možete pogriješiti

PDF Library for Delphi dijagram koji veže nizove označenog sadržaja s cjelobrojnim MCID-ovima na stablo strukture H1, P i Figure kroz mapu uloga, uz artefakte isključene iz redoslijeda čitanja
Cjelobrojni MCID-ovi vežu marked-content nizove na tipizirano stablo strukture, dok mapa uloga razrješava prilagođene uloge i artefakti ostaju izvan redoslijeda čitanja

Dva prekidača na razini dokumenta definiraju rad prije otvaranja bilo koje oznake. SetMarkInfo zapisuje zastavicu kataloga koja deklarira dokument kao označen, a IsTaggedPDF je čita natrag, što je jednostavan prvi korak pri odlučivanju ima li dolazna datoteka strukturu vrijednu čuvanja. Jezik ima dvije ulazne točke. SetDocumentLanguage samostalno postavlja zadani jezik dokumenta, dok ga SetPDFUAMode postavlja kao dio omogućavanja potpunog PDF/UA izlaza. Datoteka može biti korisno označena i bez tvrdnje o usklađenosti s PDF/UA standardom, a postupno uvođenje često počinje upravo tu

Označavanje tijekom crtanja, a ne naknadno

Učinkovit obrazac generiranja je tretiranje zagrade oznake (tag bracket) kao dijela potpisa svakog poziva za crtanje, a nikada kao kasnijeg prolaza:

var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetOrigin(1);                          // ishodište gore lijevo
    Lib.SetPDFUAMode('en-US');                 // podiže verziju spremanja na PDF 1.7
    Lib.SetInformation(1, 'Service Manual');   // /Title je obavezan za PDF/UA
    Lib.AddRoleMap('ManualTitle', 'H1');       // prilagođeni tip -> standardna uloga
    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');               // ukras stranice: isključen iz čitanja
    // ... crtaj pravila i pozadinsku nijansu ...
    Lib.EndArtifact;
    Lib.SaveToFile('manual.pdf');
  finally
    Lib.Free;
  end;
end;

Tri poziva u tom nizu nose težinu usklađenosti. SetPDFUAMode omogućuje PDF/UA izlaz i tiho povećava verziju dokumenta na PDF 1.7, što se kosi s fiksiranjem verzije. Dokument zaključan na PDF 1.4 pomoću LockSaveVersion odbija se spremiti i vraća šifru pogreške 602 kada je aktivan UA način rada, što je sukob koji obično izbija na površinu kada arhivske profile i zahtjeve pristupačnosti konfiguriraju različiti timovi. SetInformation(1, ...) zapisuje naslov dokumenta, za koji ISO 14289 očekuje da ga preglednici prikazuju umjesto naziva datoteke; njegov nedostatak je jedan od najčešćih propusta u vezi s PDF/UA standardom. AddRoleMap registrira prilagođeni tip ManualTitle kao H1, a njegovo preskakanje ostavlja dijagnostiku opisanu u nastavku da označava nepovezanu ulogu

Razine naslova zaslužuju promišljeno pravilo, a ne ad-hoc izbore donesene radi izgleda stranice. Korisnici čitača zaslona skaču s odjeljka na odjeljak pomoću prečaca za naslove, pa predložak koji ide s H1 na H3 jer je srednja razina izgledala prevelika u vizualnom dizajnu tiho narušava tu navigaciju, a nijedan vizualni pregled to nikada neće primijetiti. To je upravo nedostatak koji dijagnostika HEADING-LEVEL-SKIP prepoznaje. Preslikajte vizualne stilove svakog predloška u fiksnu ljestvicu naslova jednom, na jednom mjestu, i odstupanja se nikada neće pojaviti

Tablice kojima čitač zaslona doista može navigirati

Nacrtane linije mreže ne znače ništa izvan zaslona. Ono čime čitači zaslona navigiraju su strukturni odnosi: koje su ćelije zaglavlja, čime svako zaglavlje upravlja i kako se ćelije podataka povezuju sa zaglavljima u nepravilnim rasporedima. Pozivi atributa strukturnog elementa upravljaju sva tri slučaja:

Lib.BeginTag('Table', '', '');
Lib.BeginTag('TR', '', '');
Lib.BeginTagEx2('TH', '', '', '', '', 'col-part', '');
Lib.SetStructElemScope('Column');          // valjano samo dok je ovaj TH otvoren
Lib.DrawText(72, 120, 'Part');
Lib.EndTag;
Lib.BeginTagEx2('TH', '', '', '', '', 'col-torque', '');
Lib.SetStructElemScope('Column');
Lib.SetStructElemColSpan(2);               // zaglavlje obuhvaća stupce vrijednosti i jedinice
Lib.DrawText(200, 120, 'Tightening torque');
Lib.EndTag;
Lib.EndTag;
Lib.BeginTag('TR', '', '');
Lib.BeginTag('TD', '', '');
Lib.SetStructElemHeaders('col-part');      // eksplicitno povezivanje za nepravilne tablice
Lib.DrawText(72, 140, 'M8 flange bolt');
Lib.EndTag;
Lib.EndTag;
Lib.EndTag; // Tablica

Pravilo redoslijeda je strogo i provodi se u tišini. Svaki poziv SetStructElem* primjenjuje se na oznaku koja je otvorena u tom trenutku, između njezinih poziva BeginTag i EndTag, te vraća 0 bez ikakvog upozorenja kada nijedna oznaka nije otvorena ili se atribut ne odnosi na trenutnu. Pogrešno postavljen poziv jednostavno nestaje. Omotavanje povratnih vrijednosti u asercije (assertions) tijekom razvoja hvata odstupanje dok ga još možete uočiti; ako se ostavi kako jest, nedostatak ispisnog područja (scope) pojavit će se tek kada se provede stvarna provjera pristupačnosti s čitačem zaslona na tablici. ID-ovi elemenata proslijeđeni putem BeginTagEx2 pune stablo ID-ova (ISO 32000-1 §14.7.4), i upravo to čini povezivanje SetStructElemHeaders uopće razrješivim

Ista obitelj atributa pokriva i ostatak onoga na što se oslanja pomoćna tehnologija. SetStructElemListNumbering deklarira kako su stavke popisa označene, pa čitač zaslona najavljuje položaj unutar popisa umjesto recitiranja znakova grafema (bullets). SetStructElemBBox bilježi granični okvir (bounding box) slika i tablica, što prikazi s prilagodbom toka (reflow views) koriste za smještaj sadržaja. SetStructElemActualText pruža zamjenski tekst za dijelove čiji se grafemi ne mapiraju u čitljive znakove, kao što je ukrasno početno slovo (drop cap) sastavljeno od vektorske grafike. Svaki od njih slijedi isto pravilo: povezuje se s otvorenom oznakom ili nestaje

PDF Library for Delphi dijagram tablice koji prikazuje TH opseg, colspan od dva i headers atribut vezan na podatkovne ćelije, uz pravilo da se pozivi atributa vezuju samo dok je njihova oznaka otvorena
Čitači ekrana slijede TH scope, colspan i headers veze umjesto nacrtanih linija, a pozivi atributa vežu samo dok je njihova oznaka otvorena

Artefakti, jezik i dijagnostička provjera prije spremanja

Ponavljajući elementi stranice, što znači zaglavlja stranice (running headers), oznake savijanja, vodeni žigovi i pozadinske nijanse, pripadaju unutar zagrada BeginArtifact i EndArtifact kako nikada ne bi ušli u čitač toka. Jezik je nasljedan. Zadana vrijednost dokumenta dolazi iz argumenta SetPDFUAMode, a dio u drugom jeziku nadjačava je po elementu putem BeginTagEx ili SetStructElemLang. To je ono što francuski citat unutar engleskog priručnika čini izgovorljivim

Prije spremanja, GetPDFUADiagnostics pokreće strukturne provjere knjižnice nad dokumentom u memoriji i vraća rezultate kao tekst, pri čemu prazan niz znači da ništa nije pronađeno. Kodovi izravno imenuju klasične pogreške u izradi: FIGURE-NO-ALT za sliku bez alternativnog teksta, HEADING-LEVEL-SKIP za H3 koji slijedi nakon H1, ROLEMAP-UNMAPPED za prilagođeni tip koji nikada nije registriran. Ugradite ovo u proces izgradnje (generirajte skup dokumenata, prekinite korak ako dijagnostika nije prazna) i regresije pristupačnosti postat će pogreške na razini prevođenja (compile-time) umjesto rezultata revizije mjesecima kasnije. Potpuna ocjena usklađenosti i dalje pripada provjeri (preflight) na spremljenoj datoteci, obrađenoj u članku PDF/A i PDF/UA provjera u Delphiju, jer se neke normalizacije primjenjuju tek tijekom serijalizacije

Navigacija bilješkama ima vlastiti prekidač. PDF/UA očekuje da navigacija tipkovnicom kroz polja obrasca i veze prati redoslijed strukture, a SetTabOrderMode zapisuje unos redoslijeda tabulatora na razini stranice koji preglednici poštuju, dok je GetTabOrderMode dostupan za provjeru dolaznih datoteka. To je zahtjev koji nitko ne primjećuje dok korisnik koji koristi samo tipkovnicu ne prijavi bug, a potrebno je samo jedno pozivanje po dokumentu da se to ispravno postavi

Stabla strukture ne preživljavaju svako spajanje

Oznake na dokumentima ostaju označene samo ako svaki kasniji korak obrade čuva stablo, a oštar rub unutar PDF Library for Delphi-a je obitelj funkcija za spajanje popisa. MergeFileListFast žrtvuje očuvanje stabla strukture radi brzine. To je ispravan izbor za serije skeniranih slika, ali pogrešan za označena izvješća, jer se izlaz otvara bez problema, renderira se identično, a tiho je izgubio svoj sloj pristupačnosti. Koristite zadani MergeFileList ili strogu varijantu kad god je bilo koji unos označen i učinite IsTaggedPDF dijelom asercija nakon sastavljanja kako se izravnana serija ne bi isporučila bez da itko primijeti. Cjevovodi za sastavljanje velikih skupova dokumenata donose više ovakvih kompromisa, koji su istraženi u članku spajanje, dijeljenje i izravan pristup velikim PDF datotekama

PDF Library for Delphi dijagram GetPDFUADiagnostics koji vraća prazan string ili imenovane nalaze poput FIGURE-NO-ALT koji padaju na buildu prije nego preflight presudi spremljenu datoteku
GetPDFUADiagnostics izvještava nalaze poput FIGURE-NO-ALT prije spremanja, a neprazan rezultat uključen u build odmah obara korak

Petlja provjere zatvara se izvan knjižnice: otvorite izlaz u Acrobat programu, pregledajte ploču s oznakama i pročitajte barem jedan dokument po obitelji predložaka sa stvarnim čitačem zaslona. Dijagnostika otkriva strukturne pogreške; samo ljudsko uho može primijetiti redoslijed čitanja koji je tehnički ispravan, a u praksi zbunjujući. Evaluacijske verzije i potpuna referenca API-ja za označavanje nalaze se na stranici proizvoda losLab PDF Library za Delphi