Esitarkastuksesi raportoi tiedoston olevan PDF/UA-standardin mukainen. veraPDF avaa saman tiedoston ja ilmoittaa kohdan 7.3 mukaisesta Figure-elementistä ilman vaihtoehtoista tekstiä (Alt). Molemmat työkalut ovat oikeassa, ja niiden välinen kuilu kuvaa koko ongelmaa saavutettavuuden tarkistamisessa pelkkiä tavuja skannaamalla. Tavutason tarkistus varmistaa tiedoston sanovan olevansa tagitettu: se löytää /StructTreeRoot- ja /MarkInfo /Marked true -määritykset, pdfuaid:part-arvon XMP-paketista, asiakirjan otsikon ja kielen. Nämä ovat muotomerkintöjä, ja ne ovat välttämättömiä. Ne eivät kuitenkaan kerro mitään siitä, sisältääkö sivulla neljä oleva kuva kuvauksen, jonka ruudunlukuohjelma voi lukea ääneen. Tuo vastaus asuu tagipuussa, ja saadaksesi sen sinun on käytävä puu läpi
PDFium-komponentti on alkuperäinen VCL PDF -kirjasto Delphille ja C++Builderille, ja sen ValidatePdfUa-metodi suorittaa molemmat tarkistukset. Tavutason tarkistus käsittelee muotomerkinnät. Sen päällä istuu rakennetarkistus, joka lataa reaaliaikaisen tagitetun puun, käy läpi jokaisen elementin ja tarkistaa pienen joukon luotettavia sisältösääntöjä, joissa puuttuva määrite tarkoittaa todellista saavutettavuusvirhettä eikä tyylillistä valintaa. Tämä artikkeli käsittelee tuota toista vaihetta: mitä se tarkistaa, miksi sääntölogiikka on puhdas funktio ilman taustalla olevaa DLL-kirjastoa ja mihin se tietoisesti pysähtyy
Miksi tavutason tarkistus ei näe puuttuvaa Alt-tekstiä
ISO 14289-1 (PDF/UA-1) on vaatimuskerros standardin ISO 32000 päällä. Jotkut näistä vaatimuksista ovat rakenteellisia ja nähtävissä raakatiedostossa: luettelon (catalog) on ilmoitettava rakennevuoka (structure tree), katseluohjelman asetusten on oltava DisplayDocTitle ja fonttien on oltava upotettuja. Tunnusskanneri, joka riisuu virtojen rungot ja täsmää nimitunnuksia erotinrajojen kanssa, voi varmistaa nämä kaikki, ja PDFiumin ValidatePdfUaCompliance tekee juuri niin pykälille kuten 7.1, 7.18 ja 7.21
Mutta sääntö "jokaisella Figure-elementillä on vaihtoehtoinen teksti" ei ole tiedoston syntaksin ominaisuus. Se on loogisen rakenteen ominaisuus – tagitettujen elementtien puu, joka kartoittaa sisällön merkitykseen. Kuvion Alt-merkintä voi sijaita rakenne-elementin sanakirjassa, tulla annetuksi /ActualText-alueen kautta tai olla peräisin roolikartoitetusta mukautetusta tyypistä. Et voi luotettavasti löytää sitä etsimällä merkkijonoa /Alt tavuvirrasta, koska tuo merkkijono esiintyy asiaankuulumattomissa yhteyksissä, voi olla pakattuna objektivirran sisällä eikä kerro mitään siitä, mille rakenne-elementti se kuuluu. Rehellinen tapa vastata kysymykseen on kysyä sitä asiakirjan omalta rakenne-elementtipuulta, elementti elementiltä, samalta rajapinnalta jota veraPDF ja PAC arvioivat. Tämä on se raja, jonka ympärille PDFiumin Tier-1-tarkistukset on rakennettu: tavuskannaus muotoa varten, puun läpikäynti sisältöä varten
Reaaliaikaisen tagipuun lukeminen
Raaka-aine on TPdf.GetStructureElements (saatavilla myös StructureElements-propertynä), joka palauttaa TPdfStructureElements-kokoelman eli litteän taulukon TPdfStructureElement-tietueita asiakirjan järjestyksessä. Jokainen tietue on yhden rakenne-elementin projektio PDFiumin saantifunktioiden läpi, sisältäen kentät, joita saavutettavuussäännöt todella tarvitsevat:
type
TPdfStructureElement = record
Level: Integer; // depth in the tag tree
ParentIndex: Integer; // index of parent element, or -1
TypeName: WString; // standard /S name: Figure, Formula, Note...
Title: WString; // /T
AlternateText: WString; // /Alt (FPDF_StructElement_GetAltText)
ActualText: WString; // /ActualText
Expansion: WString; // /E
ID: WString; // /ID (FPDF_StructElement_GetID)
Language: WString; // /Lang
MarkedContentIDs: TPdfIntegerArray;
// ... child bookkeeping fields
end;
TypeName-kenttä on se, jonka ympärillä validaattori pyörii. Se saadaan funktiosta FPDF_StructElement_GetType, joka palauttaa elementin standardin rakennetyypin – sen /S-nimen – sen jälkeen, kun PDFium on selvittänyt roolikartan. AlternateText tulee funktiosta FPDF_StructElement_GetAltText, ActualText funktiosta FPDF_StructElement_GetActualText ja ID funktiosta FPDF_StructElement_GetID. Koska taulukko on litteä ja järjestetty, validaattori voi päätellä koko asiakirjan tilan kerralla rekursion sijaan – mikä on tärkeää ainoalle säännölle, joka on globaali eikä elementtikohtainen
Tarkistus on puhdas funktio, ja se on tarkoituksellista
Sääntölogiikka ei asu DLL-kirjaston kanssa kommunikoivan metodin sisällä. Se on erillinen, julkinen, puhdas funktio:
function ValidatePdfUaStructureElements(
const Elements: TPdfStructureElements): TPdfUaValidationIssues;
Se ottaa litteän elementtitaulukon ja palauttaa joukon ongelmia. Se ei kutsu PDFium-funktioita, ei avaa asiakirjaa eikä koske globaaliin tilaan. Tämä erottelu on tietoinen valinta, ja se maksaa itsensä takaisin kahdesti. Ensinnäkin testattavuus: voit rakentaa synteettisen TPdfStructureElements-taulukon yksikkötestissä – Figure ilman Alt-tekstiä, Formula jonka ainoa saavutettava teksti on ActualText-kentässä, kaksi Notea jotka jakavat saman ID:n – ja tehdä väitteitä tulosjoukosta ilman, että pdfium.dll on lainkaan paikalla. Sääntölogiikka todennetaan offline-tilassa; DLL-läpikäynti todennetaan erikseen reaaliaikaisella asiakirjan savutestillä (smoke test), joka ohitetaan kirjaston puuttuessa
Toiseksi vastuun selkeys. TPdf.ValidatePdfUa omistaa monimutkaisemman osan – kunkin sivun lataamisen, sen elementtien hakemisen ja niiden keräämisen – ja antaa sitten puhtaan taulukon tarkistimelle. 'Hae tieto' (DLL, sivuvaikutukset, elinkaari) ja 'arvioi säännöt' (puhdas, deterministinen) eivät koskaan sekoitu keskenään. Kun sääntöä on muutettava, muutat funktiota, jossa ei ole I/O-toimintoja
Mitä nämä kolme sääntöä todellisuudessa tarkistavat
Rakennetarkistus nostaa kolme ongelma-arvoa, jotka on lisätty TPdfUaValidationIssues-luettelon loppuun, jotta enum säilyy ABI-stabiilina olemassa oleville kutsujille: pvuaiFigureMissingAlt, pvuaiFormulaMissingAlt ja pvuaiNoteMissingId. Runko on tarpeeksi pieni kokonaisuudessaan ymmärrettäväksi:
for I := 0 to High(Elements) do
begin
T := string(Elements[I].TypeName);
if T = 'Figure' then
begin
// §7.3 — a Figure needs an alternate representation:
// an Alt entry OR ActualText. Flag only when BOTH are empty.
if (Elements[I].AlternateText = '') and (Elements[I].ActualText = '') then
Include(Result, pvuaiFigureMissingAlt);
end
else if T = 'Formula' then
begin
// §7.7 — same rule as Figure: Alt OR ActualText.
if (Elements[I].AlternateText = '') and (Elements[I].ActualText = '') then
Include(Result, pvuaiFormulaMissingAlt);
end
else if T = 'Note' then
begin
// §7.9 — every Note must have a unique ID.
NoteId := string(Elements[I].ID);
if NoteId = '' then
Include(Result, pvuaiNoteMissingId)
else
for J := 0 to I - 1 do
if (string(Elements[J].TypeName) = 'Note') and
(string(Elements[J].ID) = NoteId) then
begin
Include(Result, pvuaiNoteMissingId);
Break;
end;
end;
end;
Kohta 7.3 säätelee kuvioita: Figure-elementin on tarjottava tekstivaihtoehto. Tämän tarkistuksen varhainen versio katsoi vain Alt-merkintää, mikä teki siitä tiukemman kuin viitevalidaattorit. PDF/UA hyväksyy kuvion, jonka saavutettava teksti tarjotaan ActualText-kentässä – korvaava teksti on kelvollinen vaihtoehtoinen esitysmuoto – joten sääntö liputtaa Figuren vain, kun molemmat Alt ja ActualText ovat tyhjiä. Kohta 7.7 kattaa kaavat (Formulas), ja saman korjauksen jälkeen se käyttää identtistä Alt-tai-ActualText-testiä; yhteensopivuusmateriaalin näyte, joka antoi Formulalle sen saavutettavan tekstin pelkän ActualText-kentän kautta, hylättiin virheellisesti, kunnes Formula-haara tuotiin linjaan Figure-haaran kanssa
Kohta 7.9 on erilainen. Note-elementillä on oltava /ID, ja tuon ID-tunnuksen on oltava yksilöllinen koko asiakirjassa. Puuttuva ID on elementtikohtainen virhe. Kaksoiskappale-ID on kahden elementin välinen suhde, minkä vuoksi litteä taulukko on tärkeä: jokaisen Noten kohdalla tarkistaja skannaa taaksepäin jo nähtyjen elementtien yli ja liputtaa törmäyksen minkä tahansa aiemman saman ID:n omaavan Noten kanssa. Kustannus on ilmeinen O(n²) Note-elementtien määrän yli, mikä on merkityksetöntä mille tahansa todelliselle asiakirjalle ja pitää funktion yhtenä luettavana silmukkana ilman synkronoitavia apuindeksejä
Kerääminen sivujen yli, jotta yksilöllisyys on globaalia
PDFium tuo rakenne-elementit näkyville sivuittain, ei asiakirjoittain, joten ValidatePdfUa-metodin hallinnan on kerättävä ne ennen sääntöjen ajamista. Se käy läpi jokaisen sivun kutsuilla FPDF_LoadPage / GetStructureElementsForPage / FPDF_ClosePage riippumatta siitä, mikä sivu komponentilla on parhaillaan auki, ja lisää kunkin sivun elementit yhteen taulukkoon. Vasta sitten se kutsuu puhdasta tarkistinta:
// inside TPdf.ValidatePdfUa, after the byte-level pass
if (FDocument <> nil) and
(not (pvuaiMissingStructTreeRoot in Result.Issues)) then
begin
AllElems := nil;
PageTotal := FPDF_GetPageCount(FDocument);
for I := 0 to PageTotal - 1 do
begin
Page := FPDF_LoadPage(FDocument, I);
if Page = nil then Continue;
try
PageElems := GetStructureElementsForPage(Page);
finally
FPDF_ClosePage(Page);
end;
// append PageElems into AllElems ...
end;
Result.Issues := Result.Issues + ValidatePdfUaStructureElements(AllElems);
end;
Kerääminen on se, mikä tekee kohdan 7.9 yksilöllisyystarkistuksesta oikean. Kaksi Notea eri sivuilla voivat jakaa saman ID-tunnuksen; jos validoisit sivu sivulta, et koskaan näkisi törmäystä, koska kunkin sivun elementtijoukko näyttää sisäisesti johdonmukaiselta. Yhden asiakirjan laajuisen taulukon rakentaminen on ainoa tapa tuoda kaksoiskappale näkyviin. Alussa oleva suojajoukko on myös huomion arvoinen: puun läpikäynti ajetaan vain silloin, kun tavutason tarkistus ei raportoinut arvoa pvuaiMissingStructTreeRoot. Tagittamattomassa asiakirjassa ei ole puuta läpikäytäväksi, ja se on ja liputettu puuttuvan rakennejuuren vuoksi, joten sivukohtaiset lataukset ohitetaan kokonaan. Syvä tarkistus ei maksa mitään asiakirjoissa, jotka eivät voi hyötyä siitä
Suunnittelultaan konservatiivinen: ohita hiljaisesti, älä koskaan anna turhaa hälytystä
Tämän validaattorin tärkein ominaisuus on se, mitä se kieltäytyy tekemästä. Se vertaa vain standardeja /S-tyyppinimiä, joita FPDF_StructElement_GetType palauttaa suoraan – Figure, Formula, Note. Asiakirja, joka määrittelee mukautetun tyypin ja kartoittaa sen roolin Figureksi, ilmoittaa oman nimensä riippuen siitä, miten PDFium selvittää tyypin. Kun näin tapahtuu, tarkistaja ei tunnista sitä ja pysyy hiljaa. Tämä on väärä negatiivinen havainto, ja se on tarkoituksellista käyttäytymistä. Suunnittelusääntönä on mieluummin aliraportoida kuin koskaan tuottaa väärää positiivista, koska esitarkistustyökalu, joka antaa turhia hälytyksiä yhteensopivista tiedostoista, opettaa käyttäjänsä jättämään sen huomiotta – ja huomiotta jätetty validaattori on huonompi kuin ei mitään. Koristeelliset kuvat elävät artifact-virrassa, eivät rakennekuvauksessa, joten ne eivät koskaan näyttäydy Figure-elementteinä; et saa "puuttuva Alt" -valitusta taustaviivasta, joka on oikein merkitty artifactiksi
Tästä syystä laajuus on rajattu kolmeen sääntöön. Otsikkotason sisäkkäisyys (kohta 7.4), taulukon otsikkolaajuus (7.5) ja roolikartan silmukkatunnistus (7.1) ovat kaikki legitiimejä PDF/UA-vaatimuksia, mutta niiden hyvä tarkistaminen vaatisi todellista graafi- ja attribuuttianalyysiä, ja niiden naiivi tarkistaminen tuottaisi juuri niitä vääriä positiivisia, jotka suunnittelu kieltää – PDF/UA sallii otsikkokaavat kuten H1, H2, H3, H3, H3, jotka yksinkertainen "on kasvettava tiukasti" -sääntö hylkäisi virheellisesti. Nämä tarkistukset jätetään erikoistuneille yhteensopivuustyökaluille. Tier-1-joukko on se osajoukko, jossa puuttuva määrite on yksiselitteinen
Rajat selkeästi sanottuna
Kaksi rajoitusta on hyvä tietää ennen kuin kytket tämän osaksi julkaisuputkea. Ensinnäkin tarkistaja on vain niin hyvä kuin mitä PDFium pystyy lukemaan rakenne-elementistä. Kourallinen yhteensopivuusmateriaalin tiedostoja, jotka viitevalidaattorit päästävät läpi, käyttää sellaista vaihtoehtoisen tekstin mekanismia, jota PDFium ei tuo esille, jolloin FPDF_StructElement_GetAltText palauttaa tyhjää, vaikka tiedosto on aito ja yhteensopiva. Puhdas tarkistaja liputtaa tällöin "oikein" puuttuvan Alt-tekstin puutteellisen tiedon vuoksi – tämä on väärä positiivinen, joka johtuu DLL:n saantifunktioista, ei sääntölogiikasta. Säännön löysääminen näiden tapausten huomioimiseksi sokeuttaisi sen myös todellisille virheille, joita sen on tarkoitus havaita, joten ne dokumentoidaan tunnettuna PDFium-rajoituksena sen sijaan, että ne peiteltäisiin
Toiseksi tämä on esitarkastus, ei sertifiointi. Tier-1 havaitsee ne luotettavat sisältövirheet, joita tavuskannaus ei rakenteellisesti pysty näkemään, ja tekee sen ilman turhia hälytyksiä. Kuitenkin täysi PDF/UA-yhteensopivuus, mukaan lukien otsikkosemantiikka, taulukkorakenne ja lukujärjestyksen oikeellisuus, kuuluu edelleen täydelliselle validaattorille ja viime kädessä ihmisarvioijalle. Käytä ValidatePdfUa-metodia hylätäksesi ilmeiset virheet nopeasti ja edullisesti omassa putkessasi, ja anna sitten veraPDF- tai PAC-työkalun sanoa viimeinen sana. Sama rakennekuvauksen läpikäynti on pohjana saavutettavan PDF-lukijan rakentamisessa Delphissä, jossa tagipuu ohjaa lukujärjestystä ja puhetta, ja se täydentää metatietotason työtä PDF-huomautusten tarkastelussa Delphistä käsin
Tässä esitetyt rakennekartta-API:t ja ValidatePdfUa-validaattori toimitetaan PDFium-komponentin mukana Delphille ja C++Builderille (VCL) sekä Lazarus/FPC:lle (LCL). Tuotesivu linkittää täyden API-viitteen, mukaan lukien täydellisen TPdfStructureElement-tietueen rakenteen ja näiden tarkistusten taustalla olevan ongelmaluettelon