Tehnički članak

PDF logički objektni model: Vrste, reference i struktura

PDF datoteka je, u svojoj srži, zbirka objekata koji pokazuju jedan na drugog. Uklonite kompresiju, knjigovodstvo unakrsnih referenci i pomake (byte offsets), i ono što ostaje jest graf: mali skup tipiziranih vrijednosti (typed values), ožičenih referencama, ukorijenjen u jednom objektu kojeg čitač zna pronaći. Sve što PDF može izraziti, od odlomka teksta do ugrađenog fonta i digitalnog potpisa, izgrađeno je od osam primitivnih vrsta objekata i pravila koje omogućuje jednom objektu da se odnosi na drugi. Naučite to, i ostatak formata čitat će se kao kompozicija umjesto kao misterij

Ovo je logički sloj PDF-a, definiran u ISO 32000-1 klauzuli 7.3, i nalazi se jednu razinu iznad fizičkog izgleda datoteke (zaglavlje, tijelo, tablica unakrsnih referenci i trailer, što je zasebna tema u tehničkom pregledu strukture PDF datoteke). Logički model jest ono što ti bajtovi znače nakon što su raščlanjeni (parsed). Preglednik čita datoteku unatrag kako bi pronašao trailer, slijedi ga do korijena, a od tamo se dokument odvija (unfolds) kao objekti koji se referenciraju na druge objekte. Ovo je dio o kojem razmišljate kada otklanjate greške na neispravnoj stranici, pišete parser ili vjerujete biblioteci da sastavi dokument

Osam vrsta objekata i ništa više

PDF definira točno osam osnovnih vrsta objekata. Svaka vrijednost u dokumentu je jedna od njih, što održava format rješivim usprkos njegovom dometu

Logičke vrijednosti (Booleans) su ključne riječi true i false. One uključuju i isključuju zastavice (flags), primjerice hoće li se napomena (annotation) ispisati

Brojevi (Numbers) dolaze u dvije varijante koje specifikacija tretira kao jednu vrstu: cijeli brojevi (integers) poput 42 i realni poput 3.14 ili -0.002. PDF nema eksponencijalnu notaciju, tako da nikada nećete vidjeti 1e6 u sukladnoj datoteci. Koordinate, veličine fonta i kutovi rotacije sve su brojevi

Nizovi (Strings) sadrže sekvence bajtova, napisane u zagradama (Hello) ili u kutnim zagradama kao heksadecimalne <48656C6C6F>. Obje notacije kodiraju identičan sadržaj; heksadecimalni prikaz je izlaz u nuždi (escape hatch) za bajtove koji su nezgodni unutar zagrada. Nizovi nose tekst, ali oni su primarno bajtovi, što je važno onog trenutka kada rukujete s bilo čim izvan ASCII-ja

Imena (Names) su atomski tokeni započeti s kosom crtom: /Type, /Pages, /MediaBox. Ime nije niz (string); ono je identifikator, koji se koristi kao ključ u rječniku ili nabrojena vrijednost (enumerated value), a dva su imena jednaka samo ako se podudaraju bajt po bajt. Kosa crta je sintaksa, a ne dio imena. Ovo zbunjuje pridošlice koji /Times-Roman i niz (Times-Roman) tretiraju kao zamjenjive (interchangeable); format to ne čini

Nizovi (Arrays) su uređene, heterogene liste u uglatim zagradama: [0 0 612 792] je pravokutnik stranice, a niz može slobodno miješati vrste, uključujući reference na druge objekte. Rječnici (Dictionaries) su radni konji (workhorse). Napisani između << i >>, rječnik preslikava ime ključa u vrijednost bilo koje vrste, i gotovo svaka smislena struktura u PDF-u, na stranici, u katalogu, fontu, napomeni (annotation), jest rječnik s tipkom /Type koja izjavljuje što on jest

Tokovi (Streams) su rječnici s repom (tail) sirovih bajtova između ključnih riječi stream i endstream. Rječnik opisuje bajtove (njihovu dužinu, i sve filtere kao što je FlateDecode koji ih komprimiraju), a bajtovi nose veliki korisni teret (bulky payload): instrukcije za sadržaj stranice, programe s ugrađenim fontovima, slike. Stream je mjesto gdje PDF stavlja sve preveliko ili previše binarno da bi sjedilo u liniji (inline)

Osma vrsta je null objekt, ključna riječ null. To je stvarna vrijednost, različita od izostanka ključa (key being absent). Unos u rječnik postavljen na null tretira se kao da nije prisutan, a referenca koja se rješava u nepostojeći objekt također daje null umjesto pogreške. Takvo popustljivo (forgiving) ponašanje je namjerno: ono dopušta degradaciju oštećene datoteke umjesto odbijanja otvaranja. Nema devete vrste; sve što PDF izražava dolazi od toga kako se ovih osam kombinira

Izravne vrijednosti, indirektni objekti i reference

Bilo koja od tih osam vrsta može se pojaviti na dva načina. Izravan (direct) objekt zapisuje se na mjestu, poput 612 unutar MediaBox niza (array). Indirektnom (indirect) objektu dodjeljuje se identitet kako bi drugi objekti mogli na njega pokazivati: dva cijela broja, broj objekta i broj generacije, omotavajući definiciju u obj i endobj:

12 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
endobj

Ovo je objekt 12, generacija 0, rječnik fonta. Bilo gdje drugdje u datoteci, drugi objekt referencira se na njega pomoću indirektne reference: ista dva broja iza kojih slijedi ključna riječ R, 12 0 R. Referenca je pokazivač (pointer). Kada rječnik resursa stranice kaže /Font << /F1 12 0 R >>, on imenuje objekt 12 kao font iza imena resursa /F1, bez kopiranja definicije fonta u stranicu

Broj generacije postoji za brisanja i ponovnu uporabu. Kada se objekt oslobodi i njegov utor (slot) ponovno iskoristi, generacija se povećava tako da stari 12 0 R ne može riješiti (resolve) na novog korisnika utora 12. Svježe napisane datoteke gotovo su sve generacija 0, ali jako uređivana (heavily edited) datoteka može nositi više brojeve, i parser koji ignorira generaciju će na kraju pročitati pogrešan objekt

Indirekcija (indirection) je ono što PDF čini učinkovitim i uredivim (editable). Jedan font, slika ili prostor boja može se jednom definirati i referencirati sa sto stranica. Mala promjena može biti dodana kao nova revizija (revision) koja nadomješta jedan objekt umjesto ponovnog pisanja (rewriting) datoteke. Tablica unakrsnih referenci (cross-reference table) je indeks koji broj objekta pretvara u pomak u bajtovima (byte offset), tako da čitač preskače izravno na 12 0 obj bez skeniranja, ali to je fizička optimizacija. Logički, sve što trebate znati je da 12 0 R znači "objekt identificiran kao 12 0."

Katalog: gdje počinje svaki dokument

Rješavanje (resolving) referenci mora početi negdje, a to negdje je /Root unos u traileru, koji usmjerava na katalog dokumenta (document catalog): korijen objektnog grafa, rječnik (dictionary) s /Type /Catalog. Čitač prvi dopire do njega jer je trailer prvi pronađen, a od tamo je dostupan svaki drugi dio dokumenta praćenjem referenci

Katalog nosi samo dva strogo obvezna unosa: svoj /Type i /Pages, indirektnu referencu na korijen stabla stranica (page tree). Ostali su opcijski i opisuju ponašanje na razini dokumenta umjesto samog sadržaja: /Outlines pokazuje na stablo oznaka (bookmark tree), /Names čuva stabla imena ključana stringom (keyed by string), /Metadata referencira stream XMP metapodataka, a /PageMode i /PageLayout sugeriraju kako bi preglednik trebao otvoriti dokument. Ništa od toga nije potrebno za renderiranje stranice; oni konfiguriraju iskustvo oko stranica. Strukture oznaka, metapodataka i napomena koje vise s kataloga obrađene su u članku o PDF metapodacima, oznakama i napomenama

Dijagram ispod prikazuje gdje tijelo objekta sjedi u okolnoj datoteci. Katalog i stablo stranica žive unutar tog tijela kao obični indirektni objekti; zaglavlje, tablica unakrsnih referenci i trailer oko njih fizička su skela (scaffolding) koja čitaču omogućuje lociranje

Dijagram četiri fizička dijela PDF datoteke: zaglavlje verzije, tijelo s objektima dokumenta, uključujući katalog i stablo stranica, tablica unakrsnih referenci pomaka objekata i trailer koji ukazuje na korijen

Stablo stranica: uravnotežena hijerarhija stranica

Iz /Pages dokument se grana u stablo stranica (page tree), gdje se isplati PDF-ov izbor grafa umjesto ravnog popisa (flat list). Stranice se ne pohranjuju kao jednostavna sekvenca; vise sa stabla čiji su unutarnji čvorovi čvorovi stabla stranica (/Type /Pages) a čiji su listovi objekti stranice (/Type /Page). Unutarnji čvor ispisuje svoju djecu u /Kids nizu (array) i bilježi, u /Count, koliko listova stranica živi ispod njega. Svaki čvor osim korijena nosi /Parent referencu natrag prema gore, tako da stablo prolazi u oba smjera

2 0 obj                                  % root of the page tree
<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 3 >>
endobj

3 0 obj                                  % a leaf page
<< /Type /Page /Parent 2 0 R
   /MediaBox [0 0 612 792]
   /Resources << /Font << /F1 12 0 R >> >>
   /Contents 5 0 R >>
endobj

4 0 obj                                  % an interior node grouping two more pages
<< /Type /Pages /Parent 2 0 R /Kids [6 0 R 7 0 R] /Count 2 >>
endobj

Ovdje je objekt 2 korijen, s tri stranice ispod sebe: list-stranicom (leaf page) 3, plus još dvije dostupne putem unutarnjeg čvora 4. Korijenski /Count od 3 mora biti jednak ukupnim listovima ispod njega, a zbroj koji se ne slaže sa stvarnom strukturom uobičajen je način na koji ručno uređena datoteka pođe po zlu. Poanta drveta je lokalitet pristupa. Čitač koji otvara stranicu 900 dokumenta od tisuću stranica ne hoda kroz 900 objekata; spušta se kroz pregršt čvorova, jer dobro formirano stablo ostaje plitko (shallow) i uravnoteženo. Ručna izgradnja takvog stabla dovoljno je petljava (fiddly) da bi ju vrijedilo vidjeti od početka do kraja, što prolazak o izgradnji PDF dokumenta ispočetka (from scratch) i radi

Drvo zarađuje i na nasljeđivanju (inheritance). Šačica atributa stranice, /Resources, /MediaBox, /CropBox i /Rotate, može biti postavljena na unutarnjem čvoru i izostavljena s pojedinačnih stranica, koje zatim nasljeđuju vrijednost najbližeg pretka (ancestor). Postavite /MediaBox jednom u korijenu i svaki list (leaf) dobiva istu veličinu stranice bez ponavljanja; stranica koja se treba razlikovati deklarira svoju vlastitu. To je jedino mjesto u objektnom modelu gdje značenje vrijednosti ovisi o poziciji objekta u stablu, a ne samo o vlastitom sadržaju

Što stranica list (leaf page) zapravo drži

Objekt stranice je točka spajanja (join point) između strukturnog modela i vidljivog sadržaja. Njegov unos /Contents (sadržaj) referencira jedan ili više tokova sadržaja (content streams), operatore crtanja (drawing operators) koji iscrtavaju (paint) tekst i grafiku na stranicu. Njegov rječnik /Resources (resursi) imenuje fontove, slike i prostore boja na koje se oslanjaju ti operatori, s tim da je svaki unos indirektna referenca na objekt koji se dijeli na različitim stranicama. /MediaBox daje pravokutnik stranice u točkama (1/72 inča), a unosi kao što su /Rotate i /CropBox prilagođavaju način njezine prezentacije

Takva podjela rada čitav je model u minijaturi. Rječnik stranice je struktura (structure): tipizirani unosi i reference (typed entries and references) koji govore što je stranica i čime crta. Tok sadržaja (content stream) su upute: zaseban, kompresibilan dio (blob) koji kaže kako crtati. Font iza /F1 je zajednički resurs, jednom definiran i usmjeren na njega gdje god se koristi. Rječnik, tok i referenca surađuju na iscrtavanju (render) jedne stranice, a isti uzorci se primjenjuju i na čitav dokument. Operatori toka sadržaja unutar tog dijela posebno su obuhvaćeni za tekst i fontove i za grafiku i vizualne elemente

Zašto ovaj model vrijedi znati

Većina inženjera (developers) sretne objektni model tek kad se nešto pokvari: stranica se iscrtava prazna jer njezina /Contents referenca visi (dangles), tekst izlazi kao niz pravokutnika jer resurs fonta nikada nije bio ugrađen, alat javlja /Count (brojač) koji se ne poklapa sa stranicama koje može pronaći. Svaka od tih situacija je izjava o grafu, a izravno čitanje grafa bolje je od pogađanja. Osam tipova i pravilo referenci dovoljno su mali vokabular da ga zadržite u glavi, a kad jednom vidite PDF kao objekte koji pokazuju na objekte, neispravne datoteke prestaju biti neprozirne (opaque)

Ipak, ručno pisanje modela rijetko je ispravan potez (right call) osim za učenje. Održavanje pomaka unakrsnih referenci (cross-reference offsets), brojeva generacija (generation numbers), brojača stabla stranica i duljina toka (stream lengths) dosljednima (consistent) kroz izmjene je vrsta knjigovodstva za koje postoji knjižnica (library). U proizvodnji, zrela biblioteka za razvoj PDF-a upravlja grafom objekata, a vama ostavlja prostor da razmišljate o stranicama i sadržaju. Poznavanje modela se i dalje isplati: razumijete što biblioteka izgrađuje ispod svega (underneath) i zašto