Az 1-es számú objektum nem az 1. oldal. Ez az egyetlen tény a formátum bármely más aspektusánál több PDF-feldolgozó kódot akaszt meg, és annak megértéséhez, hogy miért van ez így, túl kell tekintenünk azon, amit egy megjelenítő mutat nekünk, és bele kell néznünk abba az objektumgráfba, amelyet a megjelenítő valójában olvas
Egy PDF fájl számozott közvetett objektumok gyűjteménye. Az oldalak is ezen objektumok közé tartoznak, de a megjelenítési sorrendjüknek semmi köze ahhoz, hogy hol helyezkednek el a fájlban, vagy milyen számot viselnek. A megjelenítési sorrendet teljes egészében a /Pages fa határozza meg, amely egy láncolt struktúra, és a dokumentum katalógusában gyökerezik. Ha figyelmen kívül hagyja a fát, és numerikusan pásztázza az objektumokat, a valós fájlok jelentős részénél rossz sorrendben fogja összeállítani az oldalakat
Az oldalfa: ami valójában meghatározza a sorrendet
Minden PDF egy dokumentumkatalógussal kezdődik (ISO 32000-2 §7.7.2). A katalógus tartalmaz egy /Pages bejegyzést, amely az oldalfa gyökércsomópontjára mutat. Ez a gyökércsomópont egy szótár, amely tartalmazza a /Type /Pages bejegyzést, egy közvetett hivatkozásokat tartalmazó /Kids tömböt, és egy /Count értéket, amely megadja az alatta lévő levéloldalak teljes számát. A megjelenítési sorrend ezen fa mélységi, balról jobbra történő bejárása, pont
Egy minimális, háromoldalas fájl ezt kézzelfoghatóvá teszi:
%PDF-1.7
1 0 obj
<< /Type /Catalog /Pages 2 0 R >>
endobj
2 0 obj
<< /Type /Pages /Kids [20 0 R 4 0 R 9 0 R] /Count 3 >>
endobj
% Object 4 is stored third in the file but is page 2 in display order
4 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]
/Contents 5 0 R /Resources << /Font << /F1 6 0 R >> >> >>
endobj
% Object 9 is stored fourth but is page 3
9 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]
/Contents 10 0 R /Resources << /Font << /F1 6 0 R >> >> >>
endobj
% Object 20 is stored last but is page 1; Kids[0] decides, not object number
20 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792]
/Contents 21 0 R /Resources << /Font << /F1 6 0 R >> >> >>
endobj
A /Kids tömb így néz ki: [20 0 R 4 0 R 9 0 R], tehát a 20-as objektum az 1. oldal, a 4-es objektum a 2. oldal, a 9-es objektum pedig a 3. oldal. Az objektumok számozása lényegtelen. Bármely olyan kód, amely számsorrendben iterál az objektumokon, és összegyűjti a /Type /Page típusúakat, hibás sorrendet fog eredményezni ennél a fájlnál
Miért hoznak létre a generátorok nem szekvenciális elrendezéseket? Számos oka van. Egy olyan könyvtár (library), amely előre lefoglalja az objektumszámokat minden oldal számára a tartalmuk megírása előtt, a létrehozás sorrendjében fogja megszámozni őket, majd a tényleges bájtokat olyan sorrendben írja ki, ahogy az a szerializálónak megfelel. Egy egyesítő eszköz, amely dokumentumokat fűz össze, átszámozza az egyes forrásdokumentumok objektumait az ütközések elkerülése érdekében; az átszámozott oldalobjektumok végül szétszórva jelennek meg az egyesített objektumtáblában, miközben az új gyökér /Kids tömb a helyes megjelenítési sorrendet tartalmazza. A növekményes frissítések a fájl végére fűzik az új objektumokat friss számokkal, így egy revízióként hozzáadott oldal a bájtfolyam vége felé helyezkedik el, még akkor is, ha a megjelenítési sorrendben az 1. pozícióba tartozik
Lapos fák és beágyazott részfák
A specifikáció két alakzatot engedélyez az oldalfa számára. Az egyszerű generátorok lapos szerkezetet hoznak létre: egy gyökér /Pages csomópontot, amelynek /Kids tömbje nem tartalmaz mást, csak /Page levélobjektumokat. Ezt könnyű bejárni: egyszintű mélység, egy menet
A nagy dokumentumok ezzel szemben rutinszerűen kiegyensúlyozott fát használnak. A gyökér /Pages csomópont /Kids tömbje köztes /Pages csomópontokat tartalmaz, amelyek mindegyike szintén egy saját /Kids tömböt foglal magában. Minden köztes csomóponton a /Count jelentést tesz a részfájában található levéloldalak teljes számáról, így a megjelenítő átugorhat teljes részfákat, amikor egy oldalra ugrik az index alapján, anélkül, hogy minden objektumot elemezne. Egy 1000 oldalas dokumentum, amely kiegyensúlyozott faként van strukturálva, levélcsomópontonként 10 oldallal, három vagy négy szótárkereséses bináris kereséssel megtalálhatja a 750. oldalt, ahelyett, hogy 750 /Kids bejegyzést vizsgálna át
Ennek következménye a feldolgozó kódra nézve: nem feltételezheti, hogy a /Kids első szintje /Page objektumokat tartalmaz. Minden gyermeket ellenőrizni kell. Ha a /Type értéke /Pages, akkor rekurzívan bele kell lépni. Ha a /Type értéke /Page, akkor az egy levél. Az első szintnél való megállás csendben teljes részfákat dob el minden olyan dokumentumnál, ahol a generátor a beágyazás mellett döntött
Öröklött oldaltulajdonságok
Az oldalfa egy erőforrás-megosztási mechanizmust is hordoz. Bizonyos oldaltulajdonságok: a /MediaBox, /CropBox, /Resources és a /Rotate örökölhetők (ISO 32000-2 §7.7.3.4). Ha egy /Page szótárból hiányzik valamelyik, az olvasó felfelé halad a /Parent láncon, amíg meg nem találja az adott attribútumot, vagy el nem éri a gyökeret. Ha egy megosztott betűtípus-szótárat a gyökér /Pages csomópontba helyezünk ahelyett, hogy minden levéloldalba bemásolnánk, észrevehetően csökkenthetjük a fájl méretét olyan dokumentumok esetében, amelyek végig ugyanazokat a betűtípusokat használják
Az öröklési szabály egy finomságot teremt a kód számára, amely beolvassa az oldaltulajdonságokat. Helytelen közvetlenül a /Page objektumból kiolvasni a /MediaBox értékét, és a hiányzó kulcsot hibaként kezelni; a kulcs egyszerűen öröklődhetett is. A kódnak, amely helyesen oldja fel az oldalgeometriát, követnie kell a szülőláncot. Szüksége van egy cikluskapu-védelemre (cycle guard) is: egy sérült fájlnak lehet olyan /Parent hivatkozása, amely egy már meglátogatott csomópontra mutat vissza, ami végtelen ciklust eredményezne egy meglátogatott objektum ellenőrzése nélkül
Az xref tábla és a kereszthivatkozási adatfolyamok
A közvetett objektumok keresése a kereszthivatkozási táblán (vagy annak utódján, a PDF 1.5-ben bevezetett kereszthivatkozási adatfolyamon) keresztül történik. Az xref minden objektumszámot hozzárendel a fájlon belüli bájtofszethez. Egy szabványkövető olvasó az xref-et használja arra, hogy közvetlenül bármelyik objektumhoz ugorjon; nem szekvenciálisan pásztázza a fájlt. Ez a véletlenszerű hozzáférésű kialakítás teszi lehetővé a gyors oldalugrást: a megjelenítő beolvassa a katalógusokat, feloldja a /Pages hivatkozást az xref-en keresztül, beolvassa a gyökér /Pages csomópontot, felold egy /Kids bejegyzést, és így tovább, csak azokat az objektumokat érintve, amelyekre szüksége van
A növekményes frissítések egy új xref szakaszt adnak a fájl végéhez egy trailerrel, amely visszaláncol az előzőhöz. Egy revízióban frissített objektum új bejegyzést kap a hozzáfűzött xref szakaszban; az eredeti bájtok a helyükön maradnak, de érvényüket vesztik (felülíródnak). Így maradnak a digitálisan aláírt PDF-ek ellenőrizhetők még az annotációs vagy űrlapkitöltési revíziók hozzáadása után is: az aláírt bájttartományt soha nem érintik, és az új tartalom a hozzáfűzött szakaszban él. Az oldalfa is frissíthető, így egy revízió során az oldalak hozzáadása vagy törlése egy új /Pages gyökeret hoz létre egy átdolgozott /Kids tömbbel, miközben a régi gyökérobjektum továbbra is az eredeti pozícióját foglalja el a fájlban
Mi romlik el fa bejárása nélkül
Az objektumpásztázó (object-scan) megközelítések hibamódja csendes. A kimeneti dokumentum hihetőnek tűnik: megfelelő számú oldala van, és minden oldal felismerhető tartalmat tartalmaz. A sorrend egyszerűen csak rossz, és olyan módon rossz, ami a generátortól, a revíziók számától, valamint attól függ, hogy egyesítettek-e oldalakat külső forrásokból. Egyetlen eszközzel előállított fájlok tesztkorpusza hibátlanul átmehet; míg egy másik eszközből vagy egyesítési munkafolyamatból származó fájlok elbuknak. Ez az inkonzisztencia az oka annak, hogy a heurisztikus javítások soha nem működnek megbízhatóan
A növekményes frissítéssel (incremental-update) rendelkező fájlok különösen hajlamosak erre, mivel a későbbi revíziókban hozzáadott vagy átrendezett oldalak magas objektumszámokat kapnak, miközben a megjelenítési sorrendet a frissített /Kids tömb szabályozza. Egy olyan vizsgálat, amely az objektumokat numerikus sorrendben dolgozza fel, a végére fogja helyezni ezeket a késői számozású oldalakat függetlenül attól, hogy a fa szerint hová tartoznak
A javítás nem bonyolult. Kezdje a katalógusnál, oldja fel a /Pages hivatkozást, járja be rekurzívan a /Kids tömböt, és bocsássa ki a leveleket a találkozásuk sorrendjében. Ez a definíció szerinti megjelenítési sorrend, függetlenül az objektumszámoktól, bájtofszetektől vagy a fájlszerkezettől. A legtöbb kiforrott PDF-könyvtár elérhetővé tesz egy oldalszámot és egy indexelt oldalelérőt, amelyek ezt már helyesen végzik el; a kockázat az olyan kódban van, amely megkerüli a könyvtár oldalmodelljét, és közvetlenül érinti az objektumréteget
Egy szerkezeti anomáliát érdemes kifejezetten kezelni: egy köztes /Pages csomópont /Count értéke hibás lehet a rosszul formázott fájlokban. Ha a határellenőrzéshez a /Count-ra hagyatkozunk, és emiatt idő előtt leállítjuk a teljes bejárást, akkor csendben oldalakat hagyunk ki, amikor a szám alul van becsülve. A /Count-ot csak teljesítménybeli tippként érdemes használni a kapacitás előzetes lefoglalásához vagy a bináris kereséshez, és a tényleges oldalszám levezetése a bejárásból a biztonságosabb minta a fontos dokumentumok esetében
A frissített oldalfa-áttekintés megmutatja, hogy a sorrendet a fa bejárása adja, hogyan működnek a részfák, mely attribútumok öröklődnek, és miért nem elég önmagában az xref tábla