Technisch artikel

Getagde PDF-structuurbomen in Delphi met PDF Library for Delphi

Een toegankelijke PDF rust op één structuur die de zichtbare pagina nooit toont: de structuurboom gedefinieerd in ISO 32000-1 §14.7. Het is een logische hiërarchie van koppen, alinea's, tabellen en figuren, bovenop de geschilderde inhoud gelegd en via een role map aan standaardrollen gekoppeld. Een schermlezer leest die boom, niet de tekens op de pagina. Zonder die boom is een gegenereerde factuur die er onberispelijk uitziet semantisch leeg, omdat de contentstream alleen tekenvolgorde vastlegt en verder niets. Het totaal kan vóór de regelitems worden aangekondigd, de voettekst kan een alinea doorklieven, de itementabel kan inzoomen tot één ongedifferentieerde reeks woorden. De prijs om dat te voorkomen valt in uw voordeel. Structuur uitzenden terwijl u tekent is minuten aan code; het achteraf in afgewerkte documenten inpassen is een saneringsproject. losLab PDF Library (PDF Library for Delphi) toont de boom aan Delphi en C++Builder via een kleine verzameling aanroepen die elke tekenbewerking in zijn logische rol verpakken

Hoe marked content aan de structuurboom bindt

Twee lagen werken samen. In de contentstream worden tekenbewerkingen ingeklemd in marked-content-sequenties, elk met een integer-MCID. In de documentcatalog brengt de structuurboom die MCIDs onder in een hiërarchie van getypeerde elementen (H1, P, Table, Figure) met attributen zoals alternatieve tekst en taal. Custom elementtypes zijn toegestaan, maar elk moet via de role map naar een standaardrol herleidbaar zijn (ISO 32000-1 §14.8.4). Inhoud die helemaal geen betekenis draagt, zoals lijnen, achtergronden en herhaalde pagina-inrichting, wordt als artifact gemarkeerd zodat hulptechnologie deze overslaat in plaats van hem midden in een zin voor te lezen

PDF Library for Delphi houdt beide lagen bij achter één haakjespaar. BeginTag opent een structuurelement en start de marked-content-sequentie, tekenaanroepen landen erin, en EndTag sluit beide. Het boekhouden dat handmatige tagging de das omdoet, de MCIDs en de parent tree en de paginareferenties, gebeurt intern waar u het niet fout kunt doen

Diagram van PDF Library for Delphi dat marked-content-runs met integer-MCID's bindt aan een H1-, P- en Figure-structuurboom via de role map, waarbij artefacten buiten de leesvolgorde vallen
Gehele MCIDs verbinden marked-content-reeksen met een getypeerde structuurboom, terwijl de role map aangepaste rollen oplost en artifacts buiten de leesvolgorde blijven

Twee schakelaars op dokumentniveau kaderen het werk voordat een tag opent. SetMarkInfo schrijft de catalogusvlag die het document als getagd declareert, en IsTaggedPDF leest die terug, wat de goedkope eerste proef is bij de beslissing of een inkomend bestand enige structuur heeft die het bewaren waard is. Taal heeft twee ingangen. SetDocumentLanguage stelt de documentstandaard op zichzelf in, terwijl SetPDFUAMode dat doet als onderdeel van het inschakelen van volledige PDF/UA-uitvoer. Een bestand kan zinvol getagd zijn zonder PDF/UA-conformiteit te claimen, en een gefaseerde uitrol begint vaak precies daar

Taggen tijdens het tekenen, niet achteraf

Het generatiepatroon dat werkt is om de tag-haakjes als onderdeel van de signatuur van elke tekenaanroep te behandelen, nooit als een latere pas:

var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetOrigin(1);                          // linkerbovenhoek-oorsprong
    Lib.SetPDFUAMode('en-US');                 // verhoogt de opslagversie naar PDF 1.7
    Lib.SetInformation(1, 'Service Manual');   // /Title is verplicht voor PDF/UA
    Lib.AddRoleMap('ManualTitle', 'H1');       // aangepast type -> standaardrol
    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');               // paginadecoratie: uitgesloten van voorlezen
    // ... teken lijnen en achtergrondtint ...
    Lib.EndArtifact;
    Lib.SaveToFile('manual.pdf');
  finally
    Lib.Free;
  end;
end;

Drie aanroepen in die sequentie dragen conformiteitsgewicht. SetPDFUAMode schakelt PDF/UA-uitvoer in en verhoogt stilzwijgend de documentversie naar PDF 1.7, wat bots met version pinning. Een document dat met LockSaveVersion op PDF 1.4 is vastgezet weigert op te slaan en retourneert foutcode 602 zodra UA-modus actief is, een conflict dat meestal bovenkomt wanneer archiefprofielen en toegankelijkheidseisen door verschillende teams worden geconfigureerd. SetInformation(1, ...) schrijft de documenttitel, die ISO 14289 verwacht dat viewers weergeven in plaats van de bestandsnaam; de afwezigheid ervan is een van de meest voorkomende PDF/UA-bevindingen in de praktijk. AddRoleMap registreert het custom ManualTitle-type als een H1, en het overslaan ervan laat de hieronder beschreven diagnoses een niet-afgebeelde rol signaleren

Kopniveaus verdienen een doordacht beleid, geen ad-hockeuzes gemaakt voor het uiterlijk van een pagina. Schermlezer-gebruikers springen tussen secties via de kop-sneltoets, dus een sjabloon dat van H1 naar H3 gaat omdat het tussenliggende niveau er in het visuele ontwerp te groot uitzag, verbreekt stilletjes die navigatie, en geen visuele review zal het ooit vangen. Het is precies het defect dat de HEADING-LEVEL-SKIP-diagnose moet benoemen. Wijs de visuele stijlen van elk sjabloon één keer toe aan een vaste kop-ladder, op één plek, en de drift begint nooit

Tabellen die een schermlezer daadwerkelijk kan navigeren

Getekende rasterlijnen betekenen niets buiten het scherm. Wat schermlezers navigeren zijn structurele relaties: welke cellen kopteksten zijn, wat elke koptekst bestuurt, en hoe datacellen in onregelmatige lay-outs aan kopteksten binden. De structuur-element-attribuut-aanroepen behandelen alle drie:

Lib.BeginTag('Table', '', '');
Lib.BeginTag('TR', '', '');
Lib.BeginTagEx2('TH', '', '', '', '', 'col-part', '');
Lib.SetStructElemScope('Column');          // alleen geldig zolang deze TH open is
Lib.DrawText(72, 120, 'Part');
Lib.EndTag;
Lib.BeginTagEx2('TH', '', '', '', '', 'col-torque', '');
Lib.SetStructElemScope('Column');
Lib.SetStructElemColSpan(2);               // koptekst overspant de waarde- en eenheidkolommen
Lib.DrawText(200, 120, 'Tightening torque');
Lib.EndTag;
Lib.EndTag;
Lib.BeginTag('TR', '', '');
Lib.BeginTag('TD', '', '');
Lib.SetStructElemHeaders('col-part');      // expliciete binding voor onregelmatige tabellen
Lib.DrawText(72, 140, 'M8 flange bolt');
Lib.EndTag;
Lib.EndTag;
Lib.EndTag; // Tabel

De volgregel is strikt en wordt in stilte gehandhaafd. Elke SetStructElem*-aanroep is van toepassing op de tag die op dat moment open is, tussen zijn BeginTag en zijn EndTag, en retourneert 0 zonder iets te melden wanneer er geen tag open is of het attribuut niet van toepassing is op de huidige. Een verkeerd geplaatste aanroep verdwijnt gewoon. Het verpakken van de retourwaarden in assertions tijdens ontwikkeling vangt de drift terwijl u deze nog kunt zien; alleen gelaten, duikt een ontbrekende scope pas op wanneer een toegankelijkheidsaudit een echte schermlezer over de tabel laat lopen. De element-ID's die via BeginTagEx2 gaan, voeden de ID-tree (ISO 32000-1 §14.7.4), en dat is wat de SetStructElemHeaders-binding in de eerste plaats herleidbaar maakt

Dezelfde attribuutfamilie dekt de rest waarop hulptechnologie leunt. SetStructElemListNumbering declareert hoe lijstitems worden gelabeld, zodat een schermlezer positie binnen de lijst aankondigt in plaats van opsommingstekens op te dreunen. SetStructElemBBox registreert de bounding box van figuren en tabellen, die reflow-weergaven gebruiken om inhoud te plaatsen. SetStructElemActualText levert vervangende tekst voor reeksen waarvan de glyphs niet aan leesbare tekens koppelen, zoals een initiaal samengesteld uit vector-kunst. Elk volgt dezelfde regel: het bindt aan de open tag, of het verdwijnt

Tabeldiagram van PDF Library for Delphi met TH-scope, een colspan van twee en een headers-attribuut gebonden aan datacellen, naast de regel dat attribuutaanroepen alleen binden zolang hun tag open is
Screenreaders volgen TH-scope-, colspan- en headers-koppelingen in plaats van getekende lijnen, en attribute-aanroepen binden alleen zolang hun tag open is

Artefacten, taal en de pre-save diagnosetest

Herhaalde pagina-inrichting, oftewel doorlopende koppen, vouwmarkeringen, watermerken en achtergrondtinten, hoort binnen BeginArtifact- en EndArtifact-haakjes zodat het nooit de leesstroom betreedt. Taal is erfelijk. De documentstandaard komt uit het argument van SetPDFUAMode, en een reeks in een andere taal overschrijft die per element via BeginTagEx of SetStructElemLang. Dat is wat een Franse aanhaling binnen een Engelstalige handleiding uitspreekbaar houdt

Voor het opslaan draait GetPDFUADiagnostics de structurele controles van de bibliotheek over het in-memory document en retourneert bevindingen als tekst, waarbij een lege string betekent dat er niets werd gevonden. De codes benoemen direct de klassieke ontwerpfouten: FIGURE-NO-ALT voor een afbeelding zonder alternatieve tekst, HEADING-LEVEL-SKIP voor een H3 na een H1, ROLEMAP-UNMAPPED voor een custom type dat nooit werd geregistreerd. Bouw dit in de build in (genereer de documentenset, faal de stap bij niet-lege diagnoses) en toegankelijkheidsregressies worden compileer-tijd-stijl-fouten in plaats van auditbevindingen maanden later. Het volledige conformiteitsvonnis behoort nog steeds toe aan preflight op het opgeslagen bestand, behandeld in PDF/A- en PDF/UA-preflight in Delphi, omdat sommige normalisaties pas tijdens serialisatie worden toegepast

Annotatienavigatie heeft zijn eigen knop. PDF/UA verwacht dat toetsenbordtraversering van formuliervelden en links de structuurvolgorde volgt, en SetTabOrderMode schrijft de paginaniveau tab-order-vermelding die viewers eren, met GetTabOrderMode beschikbaar voor het auditen van inkomende bestanden. Het is het soort eis dat niemand opmerkt tot een uitsluitend-toetsenbord-gebruiker de bug indient, en het kost één aanroep per document om het goed te krijgen

Structuurbomen overleven niet elke samenvoeging

Getagde documenten blijven alleen getagd wanneer elke latere verwerkingsstap de boom behoudt, en de scherpe rand binnen PDF Library for Delphi is de samenvoeglijst-familie. MergeFileListFast ruilt behoud van de structuurboom in voor snelheid. Dat is de juiste afweging voor gescande afbeeldingsbatches en de verkeerde voor getagde rapporten, omdat de uitvoer prima opent, identiek rendert en stilletjes zijn toegankelijkheidslaag is kwijtgeraakt. Gebruik de standaard MergeFileList of de strict-variant wanneer dan ook een invoer getagd is, en maak IsTaggedPDF deel van de post-assemblage-assertions zodat een afgevlakte batch niet kan worden verzonden zonder dat iemand het merkt. Assemblage-pipelines voor grote documentensets dragen meer afwegingen van deze soort, verkend in grote PDF samenvoegen, splitsen en direct access

Diagram van PDF Library for Delphi met GetPDFUADiagnostics die een lege string teruggeeft of benoemde bevindingen zoals FIGURE-NO-ALT die de build laten falen voordat preflight het opgeslagen bestand beoordeelt
GetPDFUADiagnostics meldt bevindingen zoals FIGURE-NO-ALT vóór het opslaan, en een niet-lege resultaat, ingebouwd in de build, laat de stap onmiddellijk mislukken

De verificatie-lus sluit zich buiten de bibliotheek: open de uitvoer in Acrobat, inspecteer het tags-paneel en lees ten minste één document per sjabloonfamilie met een echte schermlezer. Diagnoses vangen structurele fouten; alleen een menselijk oor vangt een leesvolgorde die technisch geldig is en praktisch onbegrijpelijk. Evaluatie-builds en de volledige tagging-API-referentie staan op de productpagina van losLab PDF Library for Delphi