Műszaki cikk

PDF Pages (oldalak) szótár nélkül: Elemzési (parsing) következmények

A PDF Catalog (katalógus) szótárnak pontosan egy kötelező navigációs kulcsa van: a /Pages. Ennek a kulcsnak egy /Pages típusú indirekt objektumra kell mutatnia, amely viszont tartalmazza a /Kids tömböt és az oldalak teljes /Count-ját (számát). Vegye el ezt a mutatót, és egyetlen megfelelő (conforming) olvasó (reader) sem tud megtalálni egyetlen oldalt sem a fájlban. Az ISO 32000-1 §7.7.2 egyértelmű ebben a pontban: a katalógusnak rendelkeznie kell egy /Pages bejegyzéssel, és a hivatkozott objektumnak /Pages típusúnak kell lennie. Azok a fájlok, amelyek megsértik ezt a követelményt, nem csupán nem megfelelőek; strukturálisan oly módon töröttek (broken), amit a legtöbb elemző (parser) rosszul kezel

Mit is mond valójában a specifikáció

Egy minimális megfelelő (conforming) PDF-nek legalább három objektuma van. Az 1. objektum a katalógus (Catalog), a 2. objektum a Pages (oldalak) gyökere, a 3. objektumtól kezdve pedig az egyedi Page (oldal) szótárak következnek. A katalógus a Pages gyökerére mutat; a Pages gyökere felsorolja a gyermekeit a /Kids-ben; minden egyes oldal hordoz egy /Parent visszahivatkozást. A teljes lánc tervezésénél fogva kétirányú, így egy elemző (parser) bármelyik végéről elindulhat, és O(log n) idő alatt bejárhat (traverse) bármelyik oldalt kiegyensúlyozott fák (balanced trees) esetén

% Minimális megfelelő struktúra (ISO 32000-1 §7.7.2)
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj

2 0 obj
<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 2 >>
endobj

3 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 5 0 R /Resources << >> >>
endobj

4 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Contents 6 0 R /Resources << >> >>
endobj

A Pages fa (oldalfa) beágyazható (nested). Egy több ezer oldalas dokumentum jellemzően közbenső csomópont objektumokba (intermediate node objects) csoportosítja az oldalakat, amelyek szintén /Pages típusúak, mindegyik saját /Kids-szel és egy /Count-tal, amely az alatta lévő részfát (subtree) tükrözi. A gyökércsomópont (root node) /Count-ja mindig megegyezik a teljes oldalszámmal. Ez az a szám, amelyet a megjelenítők (viewers) az oldalszám mezőben jelenítenek meg, még mielőtt egyetlen oldalt is elemeztek volna, mert egyetlen egész szám beolvasása a 2. objektumból sokkal olcsóbb, mint az egész fa végigjárása (walking)

Hogyan néz ki egy Pages nélküli fájl

A Pages szótárt hiányoló fájlok jellemzően olyan PDF generátorokból származnak, amelyek közvetlenül írnak oldal objektumokat anélkül, hogy azokat egy fába állítanák össze, vagy olyan korrupcióból (corruption), amely eltávolítja a gyökércsomópontot, miközben érintetlenül hagyja a levél (leaf) oldal objektumokat. A katalógus egy ilyen fájlban vagy teljesen hiányolja a /Pages kulcsot, vagy egy olyan objektumra mutató hivatkozást tartalmaz, amely már nem létezik a kereszthivatkozási táblában (cross-reference table)

% Nem megfelelő: Katalógus /Pages hivatkozás nélkül
1 0 obj
<< /Type /Catalog >>
endobj

% Az oldal objektumok léteznek, de elérhetetlenek a katalógusból
5 0 obj
<< /Type /Page /MediaBox [0 0 612 792] /Contents 6 0 R /Resources << >> >>
endobj

15 0 obj
<< /Type /Page /MediaBox [0 0 612 792] /Contents 16 0 R /Resources << >> >>
endobj

25 0 obj
<< /Type /Page /MediaBox [0 0 612 792] /Contents 26 0 R /Resources << >> >>
endobj

A specifikációt követő elemző beolvassa a katalógust, megkísérli feloldani (resolve) a /Pages-t, nem talál semmit (vagy egy halott hivatkozást), és vagy hibát jelez, vagy nulla oldalt jelent. Amit nem szabad megtennie, az az, hogy úgy folytatja, mintha a fájlnak nulla oldala lenne, és csendben sikeres lenne (silently succeed); ez üres kimenetet eredményez, ami helyesnek tűnik az automatizált eszközök számára, de hibás minden ember számára, aki megnyitja

Miért omlanak össze az elemzők (parsers)

A legtöbb PDF elemző a betöltési időben (load time) foglalja le a belső oldaltábláját (internal page table) a Pages gyökér /Count értéke alapján. Ha ez a gyökér hiányzik, az elemző vagy nullát olvas, semmit sem foglal le, majd null-pointer hivatkozást old fel (dereferences a null pointer) az első alkalommal, amikor bármilyen kód az 1. oldalt kéri, vagy szemetet olvas, és egy vadul helytelen puffert foglal le. Egyik kimenetel sem elegáns (graceful). A 0x008E5D78-nál lévő hozzáférési hiba (access violation), amely az összeomlási naplókban (crash logs) jelenik meg egy ilyen fájl feldolgozásakor, pontosan ez: egy null-pointer feloldás az oldal-hozzáférési útvonalon belül, amelyet annak a struktúrának a hiánya váltott ki, amelyről az elemző feltételezte, hogy mindig ott lesz

A mögöttes tervezési feltételezés ésszerű. A létező PDF-ek túlnyomó többsége rendelkezik Pages szótárral. Azok az elemzők, amelyek kihagyják a létezés ellenőrzését, hogy megtakarítsanak néhány utasítást, nem felelőtlenek (reckless); a gyakori esetre optimalizálnak. Azok a fájlok, amelyek megbüntetik ezt az optimalizálást, elég ritkák ahhoz, hogy a termelési kód soha ne találkozzon ilyennel, amíg meg nem történik, amely ponton az összeomlás egyszerre reprodukálható és megdöbbentő (baffling), ha a mérnök nem olvasta el a §7.7.2-t

Helyreállítás egy Pages fa nélkül

Ha egy elemzőnek kezelnie kell ezeket a fájlokat ahelyett, hogy elutasítaná őket, a helyreállítás kiszámítható utat követ: átvizsgál minden indirekt objektumot a kereszthivatkozási táblában, összegyűjti a /Type /Page típusúakat, és rendezi őket az objektum szám (object number) szerint. Az objektum szám szerinti sorrend nem garantálja, hogy megegyezik a specifikáció szerinti olvasási sorrenddel, de a gyakorlatban azok a generátorok, amelyek kihagyják a Pages fát, hajlamosak az oldalakat szekvenciálisan kibocsátani, így az objektum szám szerinti sorrend az esetek többségében helyes

Maga az ellenőrzés olcsó. Mielőtt végigjárná a katalógus /Pages mutatóját, győződjön meg arról, hogy a mutató létezik, hogy egy valódi objektumra oldódik fel, és hogy a feloldott objektum /Type-ja megegyezik a /Pages értékkel. Ha e három feltétel bármelyike meghiúsul, térjen át a lineáris vizsgálatra (linear scan). A vizsgálat lassabb, mint a fa bejárása (tree traversal) nagy dokumentumok esetében, mert minden objektum fejlécét (object header) beolvassa ahelyett, hogy egy kiegyensúlyozott utat követne, de működik, és egy olyan fájl esetében, amely már eleve hibás formátumú (malformed), a helyesség felülmúlja a sebességet

Egy peremeset (edge case), amelyet a lineáris vizsgálat nem old meg automatikusan: az oldal sorrend. A szekvenciát meghatározó /Kids tömb nélkül a "helyes" sorrend definiálatlan a specifikáció szerint. Az objektum szám szerinti sorrend a pragmatikus alapértelmezés; ha a fájl elég fontos ahhoz, hogy gondosan feldolgozzák, érdemes extra munkát fektetni annak ellenőrzésébe, hogy a Page objektumok hordoznak-e explicit /StructParents-t vagy megjegyzés hivatkozásokat (annotation references), amelyek olvasási sorrendet (reading sequence) sugallnak

Következmények a PDF generátorok számára

Bárki számára, aki PDF generátort ír elemző (parser) helyett, a tanulság szűk: mindig bocsássa ki a Pages gyökeret a fájl bezárása előtt. A /Pages bejegyzés nélküli katalógus nem érvényes PDF a specifikáció egyik felülvizsgálata (revision) szerint sem. Azok a generátorok, amelyek menet közben (on the fly) építik fel az oldal objektumokat, és a véglegesítéskor (finalization) állítják össze a fát (ez az a megközelítés, amelyet a legtöbb streaming író - streaming writers - használ), rendben vannak, amíg a véglegesítés ténylegesen lefut. A gyakori hiba mód egy kivétel (exception) vagy korai visszatérés (early return), amely megszakítja az írást a trailer befejezése előtt, hátrahagyva egy olyan fájlt, amely megnyílik néhány megjelenítőben (amelyek helyreállítási heurisztikákkal - recovery heuristics - rendelkeznek), és elbukik másokban (amelyek nem)

A PDF/A és a PDF/UA további korlátozásokat (constraints) ró az oldalfára azon túl, amit az alap specifikáció megkövetel, de egyik sem enyhíti a /Pages követelményt. Egy érvényesítő (validator), amely az ISO 19005 vagy az ISO 14289 szabványnak való megfelelést ellenőrzi, a hiányzó Pages szótárat alap-specifikáció (base-spec) megsértésként fogja elkapni, még mielőtt egyáltalán elérné a profil-specifikus szabályokat