Un PDF accesibil se sprijină pe o structură pe care pagina vizibilă nu o arată niciodată: arborele de structură definit în ISO 32000-1 §14.7. Este o ierarhie logică de titluri, paragrafe, tabele și figuri, așezată peste conținutul desenat și pusă în corespondență cu roluri standard printr-o hartă de roluri. Un cititor de ecran citește acel arbore, nu semnele de pe pagină. Fără el, o factură generată care arată impecabil este goală semantic, fiindcă fluxul de conținut înregistrează ordinea de desenare și nimic altceva. Totalul poate fi anunțat înaintea liniilor de detaliu, subsolul poate tăia un paragraf în două, tabelul cu articole se poate prăbuși într-un singur șir nediferențiat de cuvinte. Costul prevenirii este dezechilibrat în favoarea ta. Emiterea structurii în timp ce desenezi înseamnă minute de cod; adăugarea ei ulterioară în documente deja finalizate este un proiect de remediere. losLab PDF Library (PDF Library for Delphi) expune arborele către Delphi și C++Builder printr-un set mic de apeluri care învelesc fiecare operație de desenare în rolul ei logic
Cum se leagă conținutul marcat de arborele de structură
Două straturi cooperează. În fluxul de conținut, operațiile de desenare sunt încadrate în secvențe de conținut marcat, fiecare purtând un MCID întreg. În catalogul documentului, arborele de structură pune acele MCID-uri într-o ierarhie de elemente tipizate (H1, P, Table, Figure) cu atribute precum text alternativ și limbă. Tipurile personalizate de elemente sunt permise, dar fiecare trebuie să se rezolve într-un rol standard prin harta de roluri (ISO 32000-1 §14.8.4). Conținutul care nu poartă niciun înțeles, precum liniile, fundalurile și mobilierul de pagină repetat, este marcat drept artefact, ca tehnologia asistivă să îl sară în loc să îl citească în mijlocul frazei
PDF Library for Delphi întreține ambele straturi în spatele unei singure perechi de paranteze. BeginTag deschide un element de structură și pornește secvența de conținut marcat, apelurile de desenare aterizează înăuntru, iar EndTag le închide pe amândouă. Contabilitatea care încurcă etichetarea făcută de mână, adică MCID-urile, arborele de părinți și referințele de pagină, se petrece intern, acolo unde nu ai cum să greșești
Două comutatoare la nivel de document încadrează munca înainte să se deschidă vreo etichetă. SetMarkInfo scrie indicatorul din catalog care declară documentul etichetat, iar IsTaggedPDF îl citește înapoi, adică prima sondă ieftină când decizi dacă un fișier primit are vreo structură care merită păstrată. Limba are două puncte de intrare. SetDocumentLanguage setează singură valoarea implicită a documentului, în timp ce SetPDFUAMode o setează ca parte a activării ieșirii PDF/UA complete. Un fișier poate fi etichetat util fără să pretindă conformitate PDF/UA, iar o desfășurare în etape începe adesea exact de acolo
Etichetare în timp ce desenezi, nu după
Tiparul de generare care funcționează este să tratezi perechea de etichete ca parte din semnătura fiecărui apel de desenare, niciodată ca pe o trecere ulterioară:
var
Lib: TPDFlib;
begin
Lib := TPDFlib.Create;
try
Lib.SetOrigin(1); // originea în stânga sus
Lib.SetPDFUAMode('en-US'); // urcă versiunea de salvare la PDF 1.7
Lib.SetInformation(1, 'Service Manual'); // /Title este obligatoriu pentru PDF/UA
Lib.AddRoleMap('ManualTitle', 'H1'); // tip personalizat -> rol standard
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'); // decor de pagină: exclus din citire
// ... desenează aici liniile și tenta de fundal ...
Lib.EndArtifact;
Lib.SaveToFile('manual.pdf');
finally
Lib.Free;
end;
end;
Trei apeluri din acea secvență poartă greutate de conformitate. SetPDFUAMode activează ieșirea PDF/UA și urcă tacit versiunea documentului la PDF 1.7, ceea ce intră în coliziune cu fixarea versiunii. Un document blocat pe PDF 1.4 cu LockSaveVersion refuză să se salveze și întoarce codul de eroare 602 odată ce modul UA este activ, un conflict care tinde să iasă la suprafață când profilurile de arhivare și cerințele de accesibilitate sunt configurate de echipe diferite. SetInformation(1, ...) scrie titlul documentului, pe care ISO 14289 se așteaptă ca vizualizatoarele să îl afișeze în locul numelui de fișier; absența lui este una dintre cele mai frecvente constatări PDF/UA din practică. AddRoleMap înregistrează tipul personalizat ManualTitle drept H1, iar omiterea lui lasă diagnosticele descrise mai jos să semnaleze un rol nemapat
Nivelurile de titlu merită o politică deliberată, nu alegeri ad-hoc făcute după cum arată o pagină. Utilizatorii de cititoare de ecran sar între secțiuni cu scurtătura de titluri, așa că un șablon care trece de la H1 la H3 pentru că nivelul intermediar părea prea mare în designul vizual strică pe tăcute acea navigare, și nicio revizuire vizuală nu o va prinde vreodată. Este exact defectul pe care diagnosticul HEADING-LEVEL-SKIP există ca să îl numească. Pune o singură dată, într-un singur loc, stilurile vizuale ale fiecărui șablon în corespondență cu o scară fixă de titluri, și deriva nu mai pornește niciodată
Tabele pe care un cititor de ecran chiar le poate parcurge
Liniile de grilă desenate nu înseamnă nimic în afara ecranului. Ceea ce parcurg cititoarele de ecran sunt relații structurale: care celule sunt anteturi, peste ce guvernează fiecare antet și cum se leagă celulele de date de anteturi în așezări neregulate. Apelurile de atribute pentru elementele de structură le acoperă pe toate trei:
Lib.BeginTag('Table', '', '');
Lib.BeginTag('TR', '', '');
Lib.BeginTagEx2('TH', '', '', '', '', 'col-part', '');
Lib.SetStructElemScope('Column'); // valabil doar cât acest TH este deschis
Lib.DrawText(72, 120, 'Part');
Lib.EndTag;
Lib.BeginTagEx2('TH', '', '', '', '', 'col-torque', '');
Lib.SetStructElemScope('Column');
Lib.SetStructElemColSpan(2); // antetul acoperă coloanele de valoare și de unitate
Lib.DrawText(200, 120, 'Tightening torque');
Lib.EndTag;
Lib.EndTag;
Lib.BeginTag('TR', '', '');
Lib.BeginTag('TD', '', '');
Lib.SetStructElemHeaders('col-part'); // legătură explicită pentru tabele neregulate
Lib.DrawText(72, 140, 'M8 flange bolt');
Lib.EndTag;
Lib.EndTag;
Lib.EndTag; // Table
Regula de ordonare este strictă și impusă în tăcere. Fiecare apel SetStructElem* se aplică etichetei deschise în acel moment, între BeginTag-ul și EndTag-ul ei, și întoarce 0 fără să ridice nimic atunci când nicio etichetă nu este deschisă sau când atributul nu se aplică celei curente. Un apel pus greșit pur și simplu se evaporă. Învelirea valorilor de retur în aserțiuni în timpul dezvoltării prinde deriva cât încă o poți vedea; lăsată în pace, o zonă de aplicare lipsă apare abia când un audit de accesibilitate plimbă un cititor de ecran real peste tabel. Identificatorii de element transmiși prin BeginTagEx2 alimentează arborele de ID-uri (ISO 32000-1 §14.7.4), și tocmai asta face ca legătura SetStructElemHeaders să fie rezolvabilă în primul rând
Aceeași familie de atribute acoperă și restul lucrurilor pe care se sprijină tehnologia asistivă. SetStructElemListNumbering declară cum sunt etichetate elementele unei liste, astfel încât un cititor de ecran anunță poziția în listă în loc să recite glifele de marcator. SetStructElemBBox înregistrează caseta de încadrare a figurilor și a tabelelor, pe care vizualizările cu reflow o folosesc ca să așeze conținutul. SetStructElemActualText furnizează text de înlocuire pentru secvențele ale căror glife nu corespund unor caractere lizibile, cum ar fi o inițială ornamentală asamblată din grafică vectorială. Fiecare urmează aceeași regulă: ori se leagă de eticheta deschisă, ori se evaporă
Artefacte, limbă și poarta de diagnostice dinaintea salvării
Mobilierul de pagină repetat, adică anteturile curente, semnele de pliere, filigranele și tentele de fundal, aparține între parantezele BeginArtifact și EndArtifact, ca să nu intre niciodată în fluxul de citire. Limba este moștenibilă. Valoarea implicită a documentului vine din argumentul SetPDFUAMode, iar o secvență în altă limbă o suprascrie per element prin BeginTagEx sau SetStructElemLang. Asta ține pronunțabil un citat în franceză aflat într-un manual în engleză
Înainte de salvare, GetPDFUADiagnostics rulează verificările structurale ale bibliotecii peste documentul din memorie și întoarce constatările ca text, unde un șir gol înseamnă că nu s-a găsit nimic. Codurile numesc direct greșelile clasice de autorare: FIGURE-NO-ALT pentru o imagine fără text alternativ, HEADING-LEVEL-SKIP pentru un H3 care urmează unui H1, ROLEMAP-UNMAPPED pentru un tip personalizat care nu a fost înregistrat niciodată. Leagă asta la build (generează setul de documente, pică pasul când diagnosticele nu sunt goale) și regresiile de accesibilitate devin eșecuri în stil de compilare, nu constatări de audit peste luni. Verdictul complet de conformitate rămâne totuși al preflight-ului pe fișierul salvat, tratat în preflight PDF/A și PDF/UA în Delphi, fiindcă unele normalizări se aplică abia în timpul serializării
Navigarea prin adnotări are propriul buton. PDF/UA se așteaptă ca parcurgerea de la tastatură a câmpurilor de formular și a legăturilor să urmeze ordinea din structură, iar SetTabOrderMode scrie intrarea de ordine a tabulării la nivel de pagină pe care vizualizatoarele o respectă, cu GetTabOrderMode disponibil pentru auditarea fișierelor primite. Este genul de cerință pe care nu o observă nimeni până când un utilizator care lucrează doar cu tastatura raportează problema, și costă un singur apel per document ca să fie corectă
Arborii de structură nu supraviețuiesc oricărei îmbinări
Documentele etichetate rămân etichetate doar când fiecare pas de prelucrare ulterior păstrează arborele, iar muchia ascuțită din PDF Library for Delphi este familia de îmbinare pe listă. MergeFileListFast dă păstrarea arborelui de structură la schimb pe viteză. Este schimbul corect pentru loturi de imagini scanate și cel greșit pentru rapoarte etichetate, fiindcă ieșirea se deschide fără probleme, se redă identic și și-a pierdut pe tăcute stratul de accesibilitate. Folosește MergeFileList implicit sau varianta strictă ori de câte ori vreo intrare este etichetată și fă din IsTaggedPDF parte din aserțiunile de după asamblare, ca un lot aplatizat să nu poată pleca fără ca cineva să observe. Fluxurile de asamblare pentru seturi mari de documente poartă mai multe compromisuri de acest fel, explorate în îmbinarea, divizarea și accesul direct la PDF-uri mari
Bucla de verificare se închide în afara bibliotecii: deschide ieșirea în Acrobat, inspectează panoul de etichete și citește cel puțin un document din fiecare familie de șabloane cu un cititor de ecran real. Diagnosticele prind greșelile structurale; doar o ureche omenească prinde o ordine de citire care este tehnic validă și practic derutantă. Build-urile de evaluare și referința completă a API-ului de etichetare se află pe pagina de produs losLab PDF Library for Delphi