Rečnik PDF kataloga (PDF Catalog dictionary) ima tačno jedan obavezni navigacioni ključ: /Pages. Taj ključ mora ukazivati na indirektni objekat tipa /Pages, koji zauzvrat sadrži niz /Kids i ukupan broj stranica (/Count). Ako uklonite taj pokazivač, nijedan usaglašeni čitač ne može locirati nijednu stranicu u datoteci. Standard ISO 32000-1 §7.7.2 je nedvosmislen po ovom pitanju: Catalog mora imati unos /Pages, a referencirani objekat mora biti tipa /Pages. Datoteke koje krše ovaj zahtev nisu samo neusaglašene, već su strukturno oštećene na način koji većina parsera loše podnosi.
Šta specifikacija zapravo kaže
Minimalni usaglašeni PDF ima najmanje tri objekta. Objekat 1 je Catalog, objekat 2 je koren stabla Pages (Pages root), a objekti od broja 3 pa nadalje su pojedinačni rečnici Page. Catalog ukazuje na koren Pages; koren Pages navodi svoju decu u nizu /Kids; svaka stranica (Page) nosi povratnu referencu /Parent. Ceo lanac je po dizajnu dvosmeran, tako da parser može početi sa bilo kog kraja i proći do bilo koje stranice u vremenu složenosti O(log n) kod balansiranih stabala.
% Minimal conforming structure (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
Stablo Pages može biti ugnježdeno. Dokument sa hiljadama stranica obično grupiše stranice u međučvorne objekte koji takođe nose tip /Pages, pri čemu svaki ima svoj /Kids i /Count koji odražava podstablo ispod njega. Unos /Count u korenskom čvoru uvek je jednak ukupnom broju stranica. Taj broj je ono što pregledači prikazuju u polju za broj stranice pre nego što analiziraju ijednu stranicu, jer je čitanje jednog celog broja iz objekta 2 mnogo brže od prolaska kroz celo stablo.
Kako izgleda datoteka bez Pages rečnika
Datoteke kojima nedostaje rečnik Pages obično potiču od PDF generatora koji direktno upisuju objekte stranica bez njihovog sklapanja u stablo, ili usled oštećenja koje uklanja korenski čvor dok objekte stranica (listove stabla) ostavlja netaknutim. Katalog u takvoj datoteci ili uopšte nema ključ /Pages, ili sadrži referencu na objekat koji više ne postoji u tabeli unakrsnih referenci.
% Non-conforming: Catalog with no /Pages reference
1 0 obj
<< /Type /Catalog >>
endobj
% Page objects exist but are unreachable from the Catalog
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
Parser koji prati specifikaciju pročitaće Catalog, pokušati da razreši ključ /Pages, neće pronaći ništa (ili će naići na nevažeću referencu) i ili će prijaviti grešku ili prijaviti nula stranica. Ono što ne sme da uradi jeste da nastavi rad kao da datoteka ima nula stranica i tiho uspe; to stvara prazan izlaz koji automatizovanim alatima izgleda ispravno, a svakom čoveku koji ga otvori pogrešno.
Zašto parseri padaju
Većina PDF parsera alocira svoju internu tabelu stranica u trenutku učitavanja na osnovu vrednosti /Count iz korena Pages. Kada taj koren nedostaje, parser ili čita nulu, ne alocira ništa i potom dereferencira null pokazivač kada kod prvi put zatraži prvu stranicu, ili čita nevažeće podatke i alocira pogrešan bafer. Nijedan od ovih ishoda nije povoljan. Narušavanje pristupa (access violation) na adresi 0x008E5D78 koje se pojavljuje u izveštajima o padu programa pri obradi takve datoteke jeste upravo ovo: dereferenciranje null pokazivača unutar putanje za pristup stranicama, izazvano nedostatkom strukture za koju je parser pretpostavio da će uvek biti prisutna.
Osnovna pretpostavka u dizajnu je razumna. Velika većina postojećih PDF datoteka ima rečnik Pages. Parseri koji preskaču proveru postojanja kako bi uštedeli nekoliko instrukcija nisu neoprezni; oni vrše optimizaciju za uobičajeni scenario. Datoteke koje kažnjavaju tu optimizaciju dovoljno su retke da ih produkcioni kod možda nikada neće sresti, sve dok se to ne desi, u kom trenutku je pad i ponovljiv i zbunjujući ako inženjer nije pročitao odeljak §7.7.2.
Oporavak bez Pages stabla
Ako parser mora da obradi ove datoteke umesto da ih odbaci, oporavak prati predvidljiv put: skenirati svaki indirektni objekat u tabeli unakrsnih referenci, prikupiti one koji imaju tip /Type /Page i sortirati ih po broju objekta. Specifikacija ne garantuje da redosled brojeva objekata odgovara redosledu čitanja, ali u praksi generatori koji izostavljaju stablo Pages obično pišu stranice sekvencijalno, tako da je redosled po broju objekta tačan u većini slučajeva.
Sama provera je brza. Pre nego što pratite pokazivač /Pages iz objekta Catalog, potvrdite da taj pokazivač postoji, da se razrešava na stvarni objekat i da je tip razrešenog objekta /Pages. Ako bilo koji od ova tri uslova ne uspe, pređite na linearno skeniranje. Skeniranje je sporije od prolaska kroz stablo kod velikih dokumenata, jer čita zaglavlje svakog objekta umesto da prati balansiranu putanju, ali funkcioniše, a za datoteku koja je već neispravna, tačnost je važnija od brzine.
Jedan granični slučaj koji linearno skeniranje ne rešava automatski jeste redosled stranica. Bez niza /Kids koji definiše redosled, „ispravan“ redosled je nedefinisan specifikacijom. Redosled po broju objekta je pragmatično podrazumevano rešenje; ako je datoteka dovoljno važna da se pažljivo obradi, provera da li objekti Page nose eksplicitan unos /StructParents ili reference na napomene (annotations) koje impliciraju redosled čitanja vredi dodatnog truda.
Implikacije za PDF generatore
Za svakoga ko piše PDF generator umesto parsera, lekcija je jasna: uvek upišite koren Pages pre zatvaranja datoteke. Catalog bez unosa /Pages nije važeći PDF ni prema jednoj verziji specifikacije. Generatori koji grade objekte stranica u letu i sklapaju stablo pri finalizaciji (pristup koji koristi većina strimujućih generatora) rade ispravno sve dok se finalizacija zaista izvrši. Uobičajeni scenario otkazivanja je izuzetak ili rani povratak koji prekida upisivanje pre nego što se trailer završi, ostavljajući iza sebe datoteku koja se otvara u nekim pregledačima (koji imaju heuristiku oporavka), a ne uspeva u drugima (koji je nemaju).
Standardi PDF/A i PDF/UA nameću dodatna ograničenja na stablo stranica u odnosu na osnovnu specifikaciju, ali nijedan od njih ne ublažava zahtev za unosom /Pages. Validator koji proverava usaglašenost sa ISO 19005 ili ISO 14289 prepoznaće nedostatak rečnika Pages kao kršenje osnovne specifikacije pre nego što uopšte stigne do pravila specifičnih za profil.