A preflight vizsgálata a fájlt PDF/UA szempontból tisztának jelenti. A veraPDF megnyitja ugyanazt a fájlt, és a 7.3-as záradék alapján jelez egy alternatív szöveg nélküli ábrát (Figure). Mindkét eszköznek igaza van, és a köztük lévő különbség a bájtok vizsgálatával történő akadálymentességi ellenőrzés egész problémája. A bájtszintű vizsgálat megerősíti, hogy a fájl azt állítja, hogy címkézett: megtalálja a /StructTreeRoot-ot, a /MarkInfo /Marked true-t, a pdfuaid:part-ot az XMP csomagban, a dokumentum címét és a nyelvet. Ezek formátumjelzők (format markers), és szükségesek. Ugyanakkor semmit sem mondanak arról, hogy a negyedik oldalon található tényleges ábra tartalmaz-e olyan leírást, amelyet a képernyőolvasó fel tud olvasni. Ez a válasz a címkefában rejlik, és ahhoz, hogy megkapja, be kell járnia a fát
A PDFium komponens egy natív VCL PDF könyvtár Delphihez és C++Builderhez, és a ValidatePdfUa metódusa mindkét vizsgálatot elvégzi. A bájtszintű vizsgálat kezeli a formátumjelzőket. Ezen felül található a struktúrafa-vizsgálat, amely betölti a működő címkézett fát, bejár minden elemet, és ellenőrzi a nagy biztonságú tartalmi szabályok kis csoportját, ahol a hiányzó attribútum valós akadálymentességi hibát jelent, nem pedig stílusbeli preferenciát. Ez a cikk erről a második vizsgálatról szól: mit ellenőriz, miért tiszta függvény a szabálylogika DLL nélkül alatta, és hol áll meg szándékosan
Miért nem látja a bájtszkennelés a hiányzó alternatív szöveget (Alt)
Az ISO 14289-1 (PDF/UA-1) az ISO 32000 tetején elhelyezkedő követelményrendszer. Ezen követelmények némelyike strukturális és látható a nyers fájlban: a katalógusnak struktúrafát kell deklarálnia, a nézőke beállításainak meg kell adniuk a DisplayDocTitle-t, és a betűtípusokat be kell ágyazni. Egy token-szkenner, amely lecsupaszítja az adatfolyam-testeket és elhatárolja a név-tokeneket, mindezeket ellenőrizni tudja, és a PDFium ValidatePdfUaCompliance funkciója pontosan ezt teszi az olyan záradékok esetében, mint a 7.1, 7.18 és 7.21
De az, hogy „minden ábrának van alternatív szövege”, nem a fájl szintaxisának tulajdonsága. Ez a logikai struktúra tulajdonsága — a címkézett elemek fája, amely a tartalmat a jelentéshez rendeli. Egy ábra (Figure) Alt bejegyzése elhelyezkedhet a struktúraelem-szótárban, megadható egy /ActualText tartományon keresztül, vagy származhat egy szerephez rendelt egyedi típusból. Nem lehet megbízhatóan megtalálni a /Alt kifejezést keresve a bájtfolyamban, mert ez a karakterlánc független környezetekben is megjelenik, lehet tömörítve egy objektumfolyamon belül, és semmit sem mond el arról, hogy melyik struktúraelemhez belongs to. A kérdés megválaszolásának hiteles módja az, ha lekérdezzük a dokumentum saját struktúrafáját, elemről elemre, ugyanazon a felületen, amelyet a veraPDF és a PAC is értékel. Ez az a határvonal, amely köré a PDFium Tier-1 ellenőrzései épülnek: bájtszkennelés a formátumhoz, fabejárás a tartalomhoz
A működő címkefa beolvasása
A nyers anyag a TPdf.GetStructureElements (a StructureElements tulajdonságon keresztül is elérhető), amely egy TPdfStructureElements-t ad vissza — ez a struktúraelemek lapos tömbje dokumentumsorrendben. Minden rekord egy struktúraelem vetülete a PDFium elosztó funkcióin keresztül, azokkal a mezőkkel, amelyekre az akadálymentességi szabályoknak valójában szükségük van:
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;
A TypeName mező az, amelyre az ellenőrző támaszkodik. Ez az FPDF_StructElement_GetType-ból származik, amely visszaadja az elem szabványos struktúratípusát — annak /S nevét —, miután a PDFium feloldotta a szerepköri leképezést (role map). Az AlternateText az FPDF_StructElement_GetAltText-ből származik, az ActualText az FPDF_StructElement_GetActualText-ből, az ID pedig az FPDF_StructElement_GetID-ból. Mivel a tömb lapos és rendezett, az ellenőrző a rekurzió helyett egyszerre tud következtetni a teljes dokumentumra — ami fontos annál az egy szabálynál, amely inkább globális, mintsem elemenkénti
Az ellenőrző egy tiszta függvény, és ez szándékos
A szabálylogika nem a DLL-lel kommunikáló metóduson belül él. Ez egy önálló, nyilvános, tiszta függvény:
function ValidatePdfUaStructureElements(
const Elements: TPdfStructureElements): TPdfUaValidationIssues;
Ez a függvény egy lapos elemtömböt fogad, és a problémák halmazát adja vissza. Nem hív meg semmilyen PDFium függvényt, nem nyit meg dokumentumot, nem nyúl a globális állapothoz. Ez az elkülönítés szándékos, és kétszeresen is kifizetődik. Először is, a tesztelhetőség terén: létrehozhat egy szintetikus TPdfStructureElements tömböt egy egységtesztben — egy Alt nélküli ábrát (Figure), egy olyan képletet (Formula), amelynek egyetlen akadálymentes szövege az ActualText-ben van, két azonosítóval rendelkező jegyzetet (Note) —, és vizsgálhatja az eredményhalmazt anélkül, hogy a pdfium.dll jelen lenne. A szabálylogikát offline ellenőrizzük; a DLL bejárást külön ellenőrizzük egy élő dokumentumos füstteszttel, amely átugrik, ha a könyvtár hiányzik
Másodszor, a felelősség tisztasága. A TPdf.ValidatePdfUa birtokolja a bonyolultabb részt — minden oldal betöltését, elemeik kinyerését, felhalmozásukat —, majd egy tiszta tömböt ad át a tiszta ellenőrzőnek. Az „adatok lekérése” (DLL, mellékhatások, élettartam) és a „szabályok elbírálása” (tiszta, determinisztikus) soha nem keveredik össze. Ha egy szabályt módosítani kell, akkor egy olyan függvényt módosít, amelyben nincs I/O
Mit ellenőriz valójában a három szabály
A struktúrafa-vizsgálat három értékkel bővíti a TPdfUaValidationIssues-t, amelyek a felsorolás végére kerülnek, így az enum ABI-stabil marad a meglévő hívók számára: pvuaiFigureMissingAlt, pvuaiFormulaMissingAlt és pvuaiNoteMissingId. A törzs elég kicsi ahhoz, hogy teljesen átlássuk:
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;
A 7.3-as záradék szabályozza az ábrákat: egy Figure elemnek szöveges alternatívát kell biztosítania. Ezen ellenőrzés korai verziója csak az Alt bejegyzést kereste, ami szigorúbbá tette a referencia-ellenőrzőknél. A PDF/UA elfogadja az olyan ábrákat is, amelyek akadálymentes szövegét ehelyett az ActualText biztosítja — a helyettesítő szöveg érvényes alternatív ábrázolás —, így a szabály csak akkor jelzi az ábrát (Figure), ha mind az Alt, mind az ActualText üres. A 7.7-es záradék a képletekre (Formula) vonatkozik, és ugyanezen korrekció után ugyanazt az Alt-vagy-ActualText tesztet használja; egy megfelelőségi-korpusz minta, amely egy képletnek csak az ActualText-en keresztül adott akadálymentes szöveget, tévesen elutasításra került, amíg a Formula ágat össze nem hangoltuk a Figure ággyal
A 7.9-es záradék jellegében eltérő. Egy jegyzetnek (Note) rendelkeznie kell egy /ID-val, és ennek az azonosítónak egyedinek kell lennie a dokumentumban. A hiányzó azonosító elemenkénti hiba. A duplikált azonosító két elem közötti kapcsolat, ezért fontos a lapos tömb: minden Note esetében az ellenőrző visszafelé szkenneli a már látott elemeket, és jelzi az ütközést minden olyan korábbi Note-tal, amely ugyanazt a NoteId-t hordozza. A költség a Note darabszáma feletti egyértelmű O(n²), ami lényegtelen bármely valódi dokumentumnál, és a függvényt egyetlen olvasható ciklusban tartja, segédindex nélkül
Oldalakon átívelő felhalmozás, hogy az egyediség globális legyen
A PDFium laponként teszi elérhetővé a struktúraelemeket, nem pedig dokumentumszinten, így a ValidatePdfUa koordinációjának össze kell gyűjtenie őket a szabályok futtatása előtt. Végigmegy minden oldalon a FPDF_LoadPage / GetStructureElementsForPage / FPDF_ClosePage segítségével, függetlenül attól, hogy a komponensnek éppen melyik oldal van megnyitva, és minden oldal elemét egyetlen tömbhöz fűzi. Csak ezután hívja meg a tiszta ellenőrzőt:
// 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;
A felhalmozás az, ami a 7.9-es egyediség-ellenőrzést helyessé teszi. Két különböző oldalon lévő Note osztozhat egy azonosítón; ha oldalról oldalra haladva ellenőrizne, soha nem látná az ütközést, mert minden oldal elemkészlete belsőleg következetesnek tűnik. Egyetlen dokumentumszintű tömb felépítése az egyetlen módja annak, hogy a duplikátum láthatóvá váljon. Az elején lévő ellenőrzést is érdemes megjegyezni: a fabejárás csak akkor fut le, ha a bájtszintű vizsgálat nem jelentett pvuaiMissingStructTreeRoot hibát. Egy címkézetlen dokumentumnak nincs bejárható fája, és a hiányzó struktúragyökér miatt már megjelölésre került, így az oldalankénti betöltések teljesen kimaradnak. A mély vizsgálat semmibe sem kerül azokon a dokumentumokon, amelyek nem profitálhatnak belőle
Tervezésénél fogva konzervatív: csendes kihagyás, felesleges riasztás nélkül
Ennek az ellenőrzőnek a legfontosabb tulajdonsága az, amit elutasít. Csak azokat a szabványos /S típusneveket vizsgálja, amelyeket az FPDF_StructElement_GetType közvetlenül visszaad — Figure, Formula, Note. Egy olyan dokumentum, amely egyedi típust határoz meg és leképezi azt a Figure-höz, a saját nevét fogja jelenteni attól függően, hogyan oldja fel a PDFium a típust. Amikor ez megtörténik, az ellenőrző nem ismeri fel, és csendben marad. Ez egy téves negatív (false negative) eredmény, és ez a szándékos viselkedés. A tervezési szabály az, hogy inkább aluljelentsünk, semmint valaha is téves riasztást (false positive) generáljunk, mert egy preflight eszköz, amely a megfelelő fájloknál is vaklármát ad, arra neveli a felhasználókat, hogy hagyják figyelmen kívül — a figyelmen kívül hagyott ellenőrző pedig rosszabb, mint ha nincs semmi. A dekoratív képek az artifact folyamokban élnek, nem pedig a struktúrafában, így eleve nem jelennek meg ábraként (Figures); nem kap majd „hiányzó Alt” panaszt egy olyan háttérszabályról, amely helyesen artifactként van megjelölve
A címsorszintek egymásba ágyazása (7.4-es záradék), a táblázat fejlécének hatóköre (7.5) és a szerepleképezési ciklus észlelése (7.1) mind jogos PDF/UA követelmények, de helyes ellenőrzésükhöz valós gráf- és attribútumelemzésre van szükség, és a naiv ellenőrzésük pontosan azokat a téves riasztásokat eredményezi, amelyeket a tervezés tilt — a PDF/UA engedélyezi az olyan címsormintákat, mint a H1, H2, H3, H3, amelyeket egy egyszerű „szigorúan növekednie kell” szabály tévesen elutasítana. Ezek az ellenőrzések a célszerszámokra maradnak. A Tier-1 készlet az a részhalmaz, ahol a hiányzó attribútum egyértelmű
A határvonalak tisztázása
Két korlátot érdemes megismerni, mielőtt beépíti ezt egy kiadási kapuba. Először is, az ellenőrző csak annyira jó, amennyire azt a PDFium ki tudja olvasni a struktúraelemből. Néhány megfelelőségi-korpusz fájl, amelyeken a referencia-ellenőrzők átmennek, olyan alternatívszöveg-mechanizmust használ, amelyet a PDFium nem tesz elérhetővé, így az FPDF_StructElement_GetAltText üresen tér vissza, annak ellenére, hogy a fájl valójában megfelel a szabványnak. A tiszta ellenőrző ekkor „helyesen” jelzi a hiányzó Alt-ot a hiányos adatok alapján — ez egy téves riasztás, amely a DLL elérésének lefedettségéből származik, nem pedig a szabálylogikából. A szabály lazítása ezen esetek befogadására elvakítaná azt a valós hibák elől, amelyeket el kell kapnia, így ezeket ismert PDFium korlátozásként dokumentáljuk, ahelyett, hogy elfednénk őket
Másodszor, ez egy preflight ellenőrzés, nem pedig tanúsítás. A Tier-1 elkapja azokat a nagy biztonságú tartalmi hibákat, amelyeket a bájtszkennelés szerkezetileg nem tud, és ezt téves riasztások nélkül teszi — a teljes PDF/UA megfelelőség vizsgálata, beleértve a címsor-szemantikát, a táblázatszerkezetet és az olvasási sorrend helyességét, továbbra is egy teljes ellenőrzőre és végső soron emberi felülvizsgálóra tartozik. Használja a ValidatePdfUa-t a nyilvánvaló hibák gyors és olcsó kiszűrésére a saját folyamatában, majd hagyja, hogy a veraPDF vagy a PAC mondja ki a végső szót. Ugyanez a struktúrafa-bejárás képezi az alapját egy akadálymentes PDF-olvasó Delphiben történő felépítésének, ahol a címkefa határozza meg az olvasási sorrendet és a felolvasott szöveget, és ez kiegészíti a PDF annotációk Delphiből történő felülvizsgálatával kapcsolatos metaadat-szintű munkát
Az itt bemutatott struktúrafa API-k és a ValidatePdfUa ellenőrző a Delphihez és C++Builderhez (VCL), valamint a Lazarus/FPC-hez (LCL) készült PDFium komponenssel együtt kerülnek szállításra. A termékoldal linkeli a teljes API-referenciát, beleértve a teljes TPdfStructureElement rekordelrendezést és a vizsgálatok mögött álló probléma-felsorolást