Technical Article

Stabla strukture označenog PDF-a u Delphiju uz PDFlibPas

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 (PDFlibPas) 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.

PDFlibPas 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.

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);                          // 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;

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');          // 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

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.

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 PDFlibPas-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.

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.