Tekninen artikkeli

Tagitetut PDF-rakennepuut Delphissä PDFlibPasilla

Saavutettava PDF nojaa yhteen rakenteeseen, jota näkyvä sivu ei koskaan näytä: ISO 32000-1 §14.7:n määrittelemään rakennepuuhun. Se on looginen hierarkia otsikoista, kappaleista, taulukoista ja kuvista, kerrostettu maalatun sisällön päälle ja kuvattu standardirooleihin role mapin kautta. Ruudunlukuohjelma lukee tuon puun, ei merkkejä sivulla. Ilman sitä generoitu lasku, joka näyttää virheettömältä, on semanttisesti tyhjä, koska sisältövirta tallentaa piirtojärjestyksen eikä mitään muuta. Yhteissumma voidaan ilmoittaa ennen rivejä, alatunniste voi tunkeutua kappaleeseen, ja rivit-taulukko voi romahtaa yhdeksi eriytymättömäksi sanajuoksuvaksi. Sen estämisen kustannus on epätasaisesti sinun eduksesi. Rakenteen emittoiminen piirron aikana on minuuttien koodia; sen jälkiasentaminen valmiisiin dokumentteihin on korjausprojekti. losLab PDF Library (PDF Library for Delphi) paljastaa puun Delphille ja C++Builderille pienen joukon kutsujen kautta, jotka kietovat kunkin piirto-operaation sen loogiseen rooliin

Kuinka merkitty sisältö sitoutuu rakennepuuhun

Kaksi kerrosta tekevät yhteistyötä. Sisältövirrassa piirto-operaatiot suljetaan merkityn sisällön sekvensseihin, joista kukin kantaa kokonaisluku-MCID:tä. Dokumentin luettelossa rakennepuu kuvaa nuo MCID:t tyypitettyjen elementtien (H1, P, Table, Figure) hierarkiaksi ominaisuuksineen kuten vaihtoehtoinen teksti ja kieli. Mukautetut elementtityypit ovat laillisia, mutta jokaisen on ratkaistava standardirooliin role mapin kautta (ISO 32000-1 §14.8.4). Sisältö, joka ei lainkaan kanna merkitystä — kuten viivat, taustat ja toistuva sivukaluste — merkitään artefaktiksi, jotta avustava teknologia ohittaa sen lauseen keskellä lukemisen sijaan

PDF Library for Delphi ylläpitää molempia kerroksia yhden sulku-parin takana. BeginTag avaa rakenneelementin ja aloittaa merkityn sisällön sekvenssin, piirtokutsut laskeutuvat sen sisään, ja EndTag sulkee molemmat. Kirjanpito, joka kompastelee käsin tehtyä tagitusta — MCID:t ja parent-puu ja sivuviitteet — tapahtuu sisäisesti, missä et voi saada sitä väärin

PDF Library for Delphi -kaavio, joka sitoo kokonaislukisia MCID:tä kantavat merkityn sisällön ajot H1-, P- ja Figure-rakennepuuhun roolikartan kautta, kun taas artefaktit jätetään lukujärjestyksen ulkopuolelle
Kokonaislukuarvoiset MCID:t sitovat merkittyjen sisältöjen ajot tyyppitettyyn rakennepuuhun, kun taas roolimäärittely selvittää mukautetut roolit ja artefaktit jäävät lukujärjestyksen ulkopuolelle

Kaksi dokumenttitason kytkintä kehystävät työn ennen kuin yksikään tagi avautuu. SetMarkInfo kirjoittaa luettelolipun, joka julistaa dokumentin tagitetuksi, ja IsTaggedPDF lukee sen takaisin, mikä on halpa ensitarkistus päätettäessä, onko saapuvalla tiedostolla lainkaan säilyttämisen arvoista rakennetta. Kielellä on kaksi tulokohtaa. SetDocumentLanguage asettaa dokumentin oletuksen yksinään, kun taas SetPDFUAMode asettaa sen osana täyden PDF/UA-tuotoksen kytkemistä. Tiedosto voi olla hyödyllisesti tagitettu väittämättä PDF/UA-vaatimustenmukaisuutta, ja vaiheittainen käyttöönotto alkaa usein juuri sieltä

Tagitusta piirron aikana, ei sen jälkeen

Toimiva generointimalli on käsitellä tagisulkua osana joka ikisen piirtokutsun allekirjoitusta, ei koskaan myöhempänä läpikäyntinä:

var
  Lib: TPDFlib;
begin
  Lib := TPDFlib.Create;
  try
    Lib.SetOrigin(1);                          // origo vasemmassa yläkulmassa
    Lib.SetPDFUAMode('en-US');                 // nostaa tallennusversion PDF 1.7:ään
    Lib.SetInformation(1, 'Service Manual');   // /Title on pakollinen PDF/UA:lle
    Lib.AddRoleMap('ManualTitle', 'H1');       // mukautettu tyyppi -> standardirooli
    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');               // sivun koristelu: suljettu pois lukemisesta
    // ... piirrä viivat ja taustasävy ...
    Lib.EndArtifact;
    Lib.SaveToFile('manual.pdf');
  finally
    Lib.Free;
  end;
end;

Kolme kutsua tuossa sekvenssissä kantavat vaatimustenmukaisuuspainoa. SetPDFUAMode kytkee PDF/UA-tuotoksen päälle ja hiljaisesti nostaa dokumenttiversiota PDF 1.7:ään, mikä törmää versiolukitukseen. PDF 1.4:ään LockSaveVersion:llä lukittu dokumentti kieltäytyy tallentumasta ja palauttaa virhekoodin 602 kun UA-tila on aktiivinen, yhteentörmäys joka pyrkii nousemaan esiin kun arkistoprofiilit ja saavutettavuusvaatimukset määrittävät eri tiimit. SetInformation(1, ...) kirjoittaa dokumentin otsikon, jonka ISO 14289 odottaa katseluohjelmien näyttävän tiedostonimen sijaan; sen puuttuminen on yksi yleisimmistä PDF/UA-löydöksistä luonnossa. AddRoleMap rekisteröi mukautetun ManualTitle-tyypin H1:ksi, ja sen ohittaminen jättää alla kuvatun diagnostiikan liputtamaan kuvaamattoman roolin

Otsikkotasot ansaitsevat harkitun käytännön, ei ad-hoc-valintoja, joita tehdään sivun näön vuoksi. Ruudunlukuohjelman käyttäjät hyppäävät osien välillä otsikkopikakomennolla, joten malli, joka menee H1:stä H3:een koska välitaso näytti liian suurelta visuaalisessa suunnittelussa, rikkoo hiljaisesti tuon navigoinnin, eikä yksikään visuaalinen tarkistus koskaan saa sitä kiinni. Se on juuri se vika, jonka HEADING-LEVEL-SKIP-diagnostiikka on olemassa nimeämään. Kukin mukautetun mallin visuaaliset tyylit kiinteään otsikkotikkaan kerran, yhdessä paikassa, ja harhailu ei koskaan ala

Taulukot, joita ruudunlukuohjelma todella voi navigoida

Piirretyt ruudukon viivat eivät tarkoita mitään ruudun ulkopuolella. Sitä mitä ruudunlukuohjelmat navigoivat, ovat rakenteelliset suhteet: mitkä solut ovat otsikoita, mitä kukin otsikko hallitsee, ja miten datasolut sitoutuvat otsikoihin epäsäännöllisissä asetteluissa. Rakenneelementin ominaisuuskutsut käsittelevät kaikki kolme:

Lib.BeginTag('Table', '', '');
Lib.BeginTag('TR', '', '');
Lib.BeginTagEx2('TH', '', '', '', '', 'col-part', '');
Lib.SetStructElemScope('Column');          // kelvollinen vain, kun tämä TH on auki
Lib.DrawText(72, 120, 'Part');
Lib.EndTag;
Lib.BeginTagEx2('TH', '', '', '', '', 'col-torque', '');
Lib.SetStructElemScope('Column');
Lib.SetStructElemColSpan(2);               // otsikko ulottuu arvo- ja yksikkösarakkeiden yli
Lib.DrawText(200, 120, 'Tightening torque');
Lib.EndTag;
Lib.EndTag;
Lib.BeginTag('TR', '', '');
Lib.BeginTag('TD', '', '');
Lib.SetStructElemHeaders('col-part');      // eksplisiittinen sidonta epäsäännöllisille taulukoille
Lib.DrawText(72, 140, 'M8 flange bolt');
Lib.EndTag;
Lib.EndTag;
Lib.EndTag; // Table

Järjestyssääntö on tiukka ja vangitaan hiljaa. Jokainen SetStructElem*-kutsu kohdistuu tagiin, joka on sillä hetkellä auki, sen BeginTag:n ja EndTag:n välissä, ja se palauttaa 0 nostamatta mitään kun yksikään tagi ei ole auki tai ominaisuus ei koske nykyistä. Väärin sijoitettu kutsu yksinkertaisesti katoaa. Paluuarvojen kietominen assertteihin kehityksen aikana nappaa harhailun silloin kun vielä näet sen; jätettyinä, puuttuva näkyvyys nousee esiin vasta kun saavutettavuusauditointi ajaa todellisen ruudunlukuohjelman taulukon yli. Elementti-ID:t, jotka viedään BeginTagEx2:n läpi, ruokkivat ID-puuta (ISO 32000-1 §14.7.4), ja se on se, mikä tekee SetStructElemHeaders-sidonnasta ratkaistavissa ensinnä

Sama ominaisuusperhe kattaa loputkin, mihin avustava teknologia nojaa. SetStructElemListNumbering julistaa, miten luettelokohteet nimetään, joten ruudunlukuohjelma ilmoittaa sijainnin luettelossa luettelomerkin glyfien resitoinnin sijaan. SetStructElemBBox tallentaa kuvien ja taulukoiden rajaavan laatikon, jota uudelleenvirtausnäkymät käyttävät sisällön sijoittamiseen. SetStructElemActualText toimittaa korvaavan tekstin juoksuille, joiden glyfit eivät kuvaudu luettaviksi merkeiksi, kuten vektoritaiteesta koottuun alkukirjaimeen. Jokainen noudattaa samaa sääntöä: se sitoutuu avoimeen tagiin, tai se katoaa

PDF Library for Delphi -taulukkokaavio, joka näyttää TH-scopen, kaksisoluisen colspanin ja data-soluihin sidotun headers-attribuutin, rinnalla sääntö, että attribuuttikutsut sitovat vain niin kauan kuin niiden tagi on auki
Ruudunlukijat noudattavat TH-scope-, colspan- ja headers-sidontoja piirrettyjen viivojen sijaan, ja attribuuttikutsut sitovat vain, kun niiden tagi on auki

Artefaktit, kieli ja pre-save-diagnostiikkaportti

Toistuva sivukaluste — eli juoksevat otsikot, taittokomerkit, vesileimat ja taustasävyt — kuuluu BeginArtifact- ja EndArtifact-sulkujen sisään, jotta se ei koskaan mene lukuvirtaan. Kieli on perittävä. Dokumentin oletus tulee SetPDFUAMode-argumentista, ja toista kieltä oleva juoksu ohittaa sen per elementti BeginTagEx:n tai SetStructElemLang:n kautta. Se on sitä, mikä pitää ranskalaisen lainauksen englanninkielisessä käsikirjassa lausuttavana

Ennen tallennusta GetPDFUADiagnostics ajaa kirjaston rakenteelliset tarkistukset muistissa olevan dokumentin yli ja palauttaa löydökset tekstinä, missä tyhjä merkkijono tarkoittaa, ettei mitään löytynyt. Koodit nimeävät klassiset laadintavirheet suoraan: FIGURE-NO-ALT kuvalle, jolla ei ole vaihtoehtoista tekstiä, HEADING-LEVEL-SKIP H3:lle H1:n jälkeen, ROLEMAP-UNMAPPED mukautetulle tyypille jota ei koskaan rekisteröity. Sido tämä buildiin (generoi dokumenttijoukko, kaada vaihe ei-tyhjällä diagnostiikalla) ja saavutettavuusregressiot muuttuvat käännösaikaisen tyylisiksi virheiksi kuukausien auditointilöydösten sijaan. Täysi vaatimustenmukaisuustuomio kuuluu yhä tallennetun tiedoston preflightille, joka on käsitelty PDF/A- ja PDF/UA-preflight Delphissä, koska jotkut normalisoinnit tehdään vain sarjallistamisen aikana

Merkintänavigoinnilla on oma säätimensä. PDF/UA odottaa, että lomakekenttien ja linkkien näppäimistöläpikäynti noudattaa rakennejärjestystä, ja SetTabOrderMode kirjoittaa sivutason sarkainjärjestys-merkinnän, jota katseluohjelmat kunnioittavat, GetTabOrderMode ollessa saatavilla saapuvien tiedostojen auditointiin. Se on sellainen vaatimus, jota kukaan ei huomaa ennen kuin vain-näppäimistö-käyttäjä tekee virheraportin, ja se maksaa yhden kutsun per dokumentti saadakseen sen oikein

Rakennepuut eivät selviä joka yhdistämisestä

Tagitetut dokumentit pysyvät tagitettuina vain kun joka myöhempi käsittelyvaihe säilyttää puun, ja PDFlibPasin sisällä terävä reuna on merge-list-perhe. MergeFileListFast vaihtaa rakennepuun säilyttämisen nopeuteen. Se on oikea vaihto skannatuille kuvaerille ja väärä tagitetuille raporteille, koska tuloste avautuu hyvin, renderöityy identtisesti ja on hiljaa menettänyt saavutettavuuskerroksensa. Käytä oletus-MergeFileList:ä tai tiukkaa varianttia aina kun yksikään syöte on tagitettu, ja tee IsTaggedPDF:stä osa kokoonpanon jälkeisiä assertteja, jotta litistetty erä ei voi lähteä ilman että joku huomaa. Suuria dokumenttijoukkoja varten kokoonpanoputket kantavat lisää tämän kaltaisia kompromisseja, joita on tutkittu suurten PDF-tiedostojen yhdistämisessä, jakamisessa ja suorassa pääsyssä

PDF Library for Delphi -kaavio GetPDFUADiagnostics-toiminnosta, joka palauttaa tyhjän merkkijonon tai nimettyjä löytöjä kuten FIGURE-NO-ALT, jotka kaatavat käännöksen ennen kuin preflight tuomitsee tallennetun tiedoston
GetPDFUADiagnostics raportoi havainnot, kuten FIGURE-NO-ALT, jo ennen tallennusta, ja tyhjää nähden ei-tyhjä tulos kytkettynä buildiin kaataa vaiheen välittömästi

Varmistusluuppi sulkeutuu kirjaston ulkopuolella: avaa tuloste Acrobatissa, tarkista tagit-paneeli ja lue vähintään yksi dokumentti per malliperhe todellisella ruudunlukuohjelmalla. Diagnostiikka nappaa rakenteelliset virheet; vain ihmiskorva nappaa lukujärjestyksen, joka on teknisesti kelvollinen ja käytännössä hämmentävä. Evaluaatioversiot ja täydellinen tagitus-API-viite ovat losLab PDF Library for Delphi -tuotesivulla