Tehnički članak

Redoslijed PDF stranica: Kako stablo stranica kontrolira redoslijed stranica

Objekt broj 1 nije stranica 1. Ta jedna činjenica ometa više koda za obradu PDF-a nego bilo koji drugi aspekt formata, a razumijevanje razloga zahtijeva da pogledamo dalje od onoga što vam preglednik (viewer) prikazuje i uđemo u graf objekata koji preglednik zapravo čita

PDF datoteka je skup numeriranih neizravnih objekata. Stranice su među tim objektima, ali njihov redoslijed prikaza nema nikakve veze s tim gdje se nalaze u datoteci ili koje brojeve nose. Redoslijed prikaza u potpunosti je određen stablom /Pages, povezanom strukturom ukorijenjenom u katalogu dokumenata. Ako zanemarite stablo i skenirate objekte numerički, sastavit ćete stranice pogrešnim redoslijedom za značajan dio stvarnih datoteka

Stablo stranica: što zapravo postavlja redoslijed

Svaki PDF počinje katalogom dokumenata (ISO 32000-2 §7.7.2). Katalog sadrži unos /Pages koji pokazuje na korijenski čvor stabla stranica. Taj korijenski čvor je rječnik s /Type /Pages, nizom /Kids neizravnih referenci, i /Count koji daje ukupan broj "list" (leaf) stranica ispod njega. Redoslijed prikaza je obilazak (traversal) tog stabla u dubinu s lijeva na desno, točka

Minimalna datoteka od tri stranice čini to konkretnim:

%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

Niz /Kids glasi [20 0 R 4 0 R 9 0 R], pa je objekt 20 stranica 1, objekt 4 je stranica 2, a objekt 9 je stranica 3. Numeriranje objekata je nebitno. Svaki kod koji iterira objekte po numeričkom redoslijedu i prikuplja one s /Type /Page proizvest će pogrešan redoslijed u ovoj datoteci

Zašto generatori proizvode nesekvencijalne izglede (layouts)? Nekoliko je razloga. Knjižnica (library) koja unaprijed dodjeljuje brojeve objekata za sve stranice prije pisanja njihovog sadržaja, numerirat će ih redoslijedom stvaranja, a zatim će upisati stvarne bajtove bilo kojim redoslijedom koji odgovara serijalizatoru. Alat za spajanje koji spaja dokumente prenumerira objekte iz svakog izvornog dokumenta kako bi izbjegao kolizije; prenumerirani objekti stranica završe razbacani po kombiniranoj tablici objekata dok novi korijenski /Kids niz drži ispravan redoslijed prikaza. Inkrementalna ažuriranja dodaju nove objekte na kraj datoteke sa svježim brojevima, pa stranica dodana kao revizija živi blizu kraja toka bajtova čak i ako pripada na poziciju 1 redoslijeda prikaza

Ravna stabla i ugniježđena podstabla

Specifikacija dopušta dva oblika za stablo stranica. Jednostavni generatori proizvode ravnu (flat) strukturu: jedan korijenski /Pages čvor čiji /Kids niz ne sadrži ništa osim "list" objekata /Page. To je lako obići: jedna razina duboko, jedan prolaz

Veliki dokumenti umjesto toga rutinski koriste balansirano stablo. Niz /Kids u korijenskom /Pages čvoru sadrži posredne /Pages čvorove, od kojih svaki drži vlastiti niz /Kids. /Count na svakom posrednom čvoru izvješćuje o ukupnom broju "list" stranica u njegovom podstablu, pa preglednik može preskočiti cijela podstabla kada preskače na stranicu pomoću indeksa bez parsiranja svakog objekta. Dokument od 1 000 stranica strukturiran kao uravnoteženo stablo s 10 stranica po "list" čvoru može locirati stranicu 750 binarnom pretragom kroz tri ili četiri pretrage rječnika, umjesto skeniranja 750 /Kids unosa

Posljedica za kod za obradu: ne možete pretpostaviti da prva razina /Kids sadrži /Page objekte. Svako dijete (child) se mora provjeriti. Ako je njegov /Type /Pages, treba se rekurzivno ući u njega. Ako je njegov /Type /Page, on je list (leaf). Zaustavljanje na prvoj razini tiho ispušta cijela podstabla u svakom dokumentu u kojem je generator odabrao ugniježđivanje

Naslijeđeni atributi stranice

Stablo stranica također nosi mehanizam za dijeljenje resursa. Određeni atributi stranice: /MediaBox, /CropBox, /Resources i /Rotate mogu se naslijediti (ISO 32000-2 §7.7.3.4). Ako ih /Page rječnik izostavi, čitač se kreće uz /Parent lanac dok ne pronađe atribut ili ne dosegne korijen. Postavljanje dijeljenog rječnika fontova u korijenski /Pages čvor, umjesto njegovog kopiranja na svaku stranicu, može primjetno smanjiti veličinu datoteke za dokumente koji koriste ista pisma kroz cijeli dokument

Pravilo nasljeđivanja stvara sitnicu na koju treba paziti pri izradi koda koji čita svojstva stranice. Izravno čitanje /MediaBox iz objekta /Page i tretiranje ključa koji nedostaje kao greške je pogrešno; ključ se jednostavno može naslijediti. Kod koji ispravno rješava geometriju stranice mora pratiti roditeljski (parent) lanac. Također treba zaštitu od ciklusa (cycle guard): korumpirana datoteka može imati /Parent referencu koja ukazuje natrag na čvor koji je već posjećen, što bi uzrokovalo beskonačnu petlju bez provjere je li objekt već posjećen

Tablica xref-a i tokovi unakrsnih referenci

Pronalaženje neizravnih objekata odvija se kroz tablicu unakrsnih referenci (ili njezina nasljednika, tok unakrsnih referenci - cross-reference stream uveden u PDF 1.5). Xref mapira svaki broj objekta na pomak u bajtovima unutar datoteke. Usklađen čitač (conforming reader) koristi xref za izravan skok na bilo koji objekt; ne skenira datoteku sekvencijalno. Taj dizajn nasumičnog pristupa je ono što omogućuje brzo preskakanje stranica: preglednik čita katalog, rješava /Pages referencu preko xref-a, čita korijenski /Pages čvor, rješava unos /Kids i tako dalje, dodirujući samo objekte koje treba

Inkrementalna ažuriranja dodaju novi odjeljak xref-a na kraju datoteke s trailerom koji je povezan unatrag na prethodni. Objekt ažuriran u reviziji dobiva novi unos u priloženom odjeljku xref-a; izvorni bajtovi ostaju na mjestu, ali su zamijenjeni. Ovako digitalno potpisani PDF-ovi ostaju provjerljivi čak i nakon dodavanja bilješki ili revizija s ispunjavanjem obrazaca: potpisani raspon bajtova se nikada ne dira, a novi sadržaj živi u priloženom odjeljku. Stablo stranica također se može ažurirati, pa dodavanje ili brisanje stranica u reviziji stvara novi /Pages korijen (root) s revidiranim /Kids nizom, dok stari korijenski objekt još uvijek zauzima svoju izvornu poziciju u datoteci

Što pođe po zlu bez obilaženja stabla

Kvar kod pristupa skeniranju objekata obično je neprimjetan. Izlazni dokument izgleda uvjerljivo: ima točan broj stranica i svaka stranica sadrži prepoznatljiv sadržaj. Redoslijed je samo pogrešan, i to na način koji ovisi o generatoru, broju revizija i jesu li ikoje stranice spojene iz vanjskih izvora. Testni korpus datoteka koje je proizveo jedan alat može proći u potpunosti; datoteke iz drugog alata ili tijekom spajanja (merge) će propasti. Zbog te nedosljednosti heuristička rješenja nikada ne funkcioniraju

Datoteke s inkrementalnim ažuriranjem posebno su podložne ovome jer stranice dodane ili preuređene u kasnijim revizijama nose visoke brojeve objekata, dok se redoslijedom prikaza upravlja ažuriranim nizom /Kids. Skeniranje koje obrađuje objekte po numeričkom redu smjestit će te kasnije numerirane stranice na kraj bez obzira gdje im je mjesto po stablu

Popravak nije kompliciran. Započnite s katalogom, riješite referencu /Pages, prođite rekurzivno kroz niz /Kids, te prikažite listove onim redoslijedom kojim ih susrećete. To je redoslijed prikaza po definiciji, bez obzira na brojeve objekata, pomake u bajtovima ili strukturu datoteke. Većina zrelih (mature) PDF biblioteka izlaže broj stranica i indeksiran pristupnik stranicama (page accessor) koji to već rade ispravno; rizik je u kodu koji zaobilazi model stranice biblioteke i direktno dotiče sloj objekta

Postoji jedna strukturna anomalija s kojom vrijedi raditi eksplicitno: /Count vrijednost na posrednom (intermediate) /Pages čvoru može biti pogrešna u oštećenim datotekama. Ako se oslonite na /Count za provjeru granica i zaustavite bez potpunog prelaženja stabla, stranice će tiho izostati kad je broj podcijenjen. Korištenje opcije /Count samo kao informacije za pre-alokaciju (pre-allocation) kapaciteta performansi ili binarno pretraživanje, te dobivanje stvarnog broja putem prelaženja stabla, sigurniji je obrazac za važne dokumente

 Sljedeći članak