Technical Article

Logický objektový model PDF: Typy, referencie a štruktúra

Súbor PDF je vo svojej podstate kolekcia objektov, ktoré na seba vzájomne odkazujú. Ak odhliadneme od kompresie, tabuliek krížových odkazov a bajtových posunov, zostane nám graf: malá množina typových hodnôt prepojených referenciami, ktorej koreňom je jediný objekt, ktorý čítačka vie nájsť. Všetko, čo dokáže PDF vyjadriť (od odseku textu cez vložené písmo až po digitálny podpis), je postavené na ôsmich primitívnych typoch objektov a na pravidle, ktoré umožňuje jednému objektu odkazovať na druhý. Spoznajte ich a zvyšok formátu sa stane jasnou kompozíciou namiesto záhady.

Ide o logickú vrstvu PDF definovanú v norme ISO 32000-1 v článku 7.3, ktorá sa nachádza o úroveň vyššie ako fyzické rozloženie súboru (hlavička, telo, tabuľka krížových odkazov a trailer, čo je téma sama o sebe v technickom prehľade štruktúry súborov PDF). Logický model definuje, čo tieto bajty znamenajú po analýze. Prehliadač číta súbor od konca, aby našiel trailer, prejde z neho ku koreňu a odtiaľ sa dokument rozvinie ako objekty odkazujúce na iné objekty. Toto je tá časť, o ktorej uvažujete pri odstraňovaní chýb na poškodenej stránke, písaní parsera alebo pri dôvere knižnici, že správne zostaví dokument.

Osem typov objektov a nič iné

PDF definuje presne osem základných typov objektov. Každá hodnota v dokumente je jedným z nich, čo robí tento formát dobre zvládnuteľným napriek jeho širokému záberu.

Booleovské hodnoty (Booleans) sú kľúčové slová true a false. Zapínajú a vypínajú príznaky, napríklad to, či sa má vytlačiť anotácia.

Čísla (Numbers) prichádzajú v dvoch variantoch, ktoré špecifikácia považuje za jeden typ: celé čísla (napr. 42) and reálne čísla (napr. 3.14 alebo -0.002). PDF nepodporuje exponenciálny zápis, takže v kompatibilnom súbore nikdy neuvidíte hodnoty typu 1e6. Súradnice, veľkosti písma a uhly rotácie sú čísla.

Reťazce (Strings) obsahujú sekvencie bajtov zapísané buď v zátvorkách (Hello), alebo v lomených zátvorkách ako hexadecimálne hodnoty <48656C6C6F>. Oba zápisy kódujú identický obsah. Šestnástková sústava slúži ako úniková cesta pre bajty, ktoré sa v zátvorkách zapisujú komplikovane. Reťazce nesú text, no v prvom rade sú to bajty, čo začne byť dôležité hneď, ako pracujete s čímkoľvek mimo ASCII.

Názvy (Names) sú atomické tokeny začínajúce lomkou: /Type, /Pages, /MediaBox. Názov nie je reťazec. Je to identifikátor používaný ako kľúč slovníka alebo enumerovaná hodnota. Dva názvy sú zhodné iba vtedy, ak sa zhodujú bajt po bajte. Lomka je syntaktický prvok, nie súčasť samotného názvu. To často mätie začiatočníkov, ktorí považujú názov /Times-Roman a reťazec (Times-Roman) za zameniteľné. Formát ich však rozlišuje.

Polia (Arrays) sú usporiadané, heterogénne zoznamy v hranatých zátvorkách. Zápis [0 0 612 792] predstavuje obdĺžnik strany. Pole môže ľubovoľne kombinovať typy vrátane odkazov na iné objekty. Slovníky (Dictionaries) sú hlavným pracovným nástrojom. Zapisujú sa medzi << a >> a mapujú názvové kľúče na hodnoty ľubovoľného typu. Takmer každá významná štruktúra v PDF (strana, katalóg, písmo, anotácia) je slovník s kľúčom /Type, ktorý deklaruje, o aký objekt ide.

Prúdy (Streams) sú slovníky s príveskom surových bajtov medzi kľúčovými slovami stream a endstream. Slovník popisuje tieto bajty (ich dĺžku a filtre ako FlateDecode na kompresiu) a bajty nesú samotný objemný obsah: inštrukcie na vykreslenie stránky, programy vložených písma alebo obrázky. Do prúdu ukladá PDF všetko, čo je príliš veľké alebo binárne na to, aby to bolo zapísané priamo.

Ukazovateľ na ôsmy typ je null objekt, reprezentovaný kľúčovým slovom null. Je to skutočná hodnota, odlišná od neprítomnosti kľúča. So slovníkovou položkou nastavenou na null sa zaobchádza tak, akoby neexistovala, a odkaz smerujúci na neexistujúci objekt tiež vráti hodnotu null namiesto chyby. Toto benevolentné správanie je zámerné: umožňuje čiastočné zobrazenie poškodeného súboru namiesto úplného zlyhania otvorenia. Žiadny deviaty typ neexistuje. Všetko, čo PDF vyjadruje, pochádza z kombinácie týchto ôsmich typov.

Priame hodnoty, nepriame objekty a referencie

Ktorýkoľvek z týchto ôsmich typov sa môže vyskytovať dvoma spôsobmi. Priamy (direct) objekt sa zapisuje priamo na mieste, napríklad číslo 612 v poli MediaBox. Nepriamy (indirect) objekt dostáva identitu, aby naň mohli odkazovať iné objekty: tvoria ho dve celé čísla (číslo objektu a číslo generácie) a samotná definícia uzavretá medzi obj a endobj:

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

Toto je objekt 12, generácia 0, slovník písma. Kdekoľvek inde v súbore naň iný objekt odkazuje pomocou nepriameho odkazu (indirect reference): ide o rovnaké dve čísla sprevádzané kľúčovým slovom R, teda 12 0 R. Odkaz je vlastne ukazovateľ. Keď slovník zdrojov stránky uvádza /Font << /F1 12 0 R >>, definuje objekt 12 ako písmo pod lokálnym názvom /F1 bez toho, aby musel celú definíciu písma kopírovať do stránky.

Číslo generácie slúži na účely odstraňovania a opätovného použitia. Keď sa nejaký objekt uvoľní a jeho slot sa použije znova, číslo generácie sa zvýši, aby starý odkaz 12 0 R neodkazoval na nového nájomníka v slote 12. Novo zapísané súbory majú takmer vždy generáciu 0, no často upravovaný súbor môže niesť aj vyššie čísla. Parser, ktorý číslo generácie ignoruje, môže nakoniec prečítať nesprávny objekt.

Nepriamy prístup (indirection) robí PDF efektívnym a upraviteľným. Jedno písmo, obrázok alebo farebný priestor možno definovať raz a odkazovať naň zo sto rôznych stránok. Druhú zmenu je možné pripojiť ako novú revíziu, ktorá nahradí iba jeden objekt, bez nutnosti prepisovať celý súbor. Tabuľka krížových odkazov je index, ktorý prevádza číslo objektu na bajtový posun, takže čítačka skočí priamo na 12 0 obj bez prehľadávania. To je však fyzická optimalizácia. Logicky vám stačí vedieť, že 12 0 R znamená „objekt identifikovaný ako 12 0“.

Katalóg: kde začína každý dokument

Vyhodnocovanie odkazov musí niekde začať a týmto miestom je položka /Root v časti trailer, ktorá ukazuje na katalóg dokumentu (document catalog): koreň grafu objektov, čo je slovník s typom /Type /Catalog. Čítačka sa k nemu dostane ako k prvému, pretože trailer nájde hneď na začiatku, a odtiaľ je prechodom cez referencie dostupná každá ďalšia časť dokumentu.

Katalóg obsahuje iba dve striktne povinné položky: svoj typ /Type a /Pages, čo je nepriamy odkaz na koreň stromu stránok. Zvyšok položiek je voliteľný a popisuje správanie celého dokumentu, nie jeho obsah: /Outlines ukazuje na strom záložiek, /Names obsahuje stromy názvov indexované reťazcami, /Metadata odkazuje na prúd metadát XMP a /PageMode spolu s /PageLayout navrhujú, ako by mal prehliadač dokument otvoriť. Nič z toho nie je potrebné na vykreslenie stránky. Nastavujú iba používateľské prostredie okolo stránok. Záložkám, metadátam a anotáciám visiacim z katalógu sa venuje článok o metadátach, záložkách a anotáciách v štruktúre PDF.

Nižšie uvedený diagram znázorňuje, kde sa v rámci súboru nachádza telo objektov. Katalóg a strom stránok žijú v tomto tele ako bežné nepriame objekty. Hlavička, tabuľka krížových odkazov a trailer okolo nich sú fyzickým lešením, ktoré čítačke umožňuje ich lokalizáciu.

Diagram štyroch fyzických častí súboru PDF: hlavička verzie, telo obsahujúce objekty dokumentu vrátane katalógu a stromu stránok, tabuľka krížových odkazov s posunmi objektov a trailer ukazujúci na koreň

Strom stránok: vyvážená hierarchia stránok

Z uzla /Pages sa dokument vetví do stromu stránok. Tu sa naplno prejavuje výhoda grafu oproti plochému zoznamu. Stránky nie ojedinelé; visia zo stromu, ktorého vnútorné uzly sú uzly stromu stránok (/Type /Pages) a listami sú objekty stránok (/Type /Page). Vnútorný uzol uvádza svojich potomkov v poli /Kids a v položke /Count zaznamenáva, koľko koncových stránok (listov) sa pod ním nachádza. Každý uzol okrem koreňového obsahuje odkaz /Parent smerujúci nahor, takže strom je možné prechádzať oboma smermi.

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

V tomto príklade je objekt 2 koreňom s tromi stránkami pod ním: koncovou stránkou 3 a ďalšími dvoma dostupnými cez vnútorný uzol 4. Hodnota /Count (3) v koreni sa musí rovnať celkovému počtu listov pod ním. Nesúlad tohto počtu so skutočnou štruktúrou je častou chybou pri ručnej úprave súborov. Výhodou stromu je lokalita prístupu. Čítačka, ktorá otvára stranu 900 v tisícstranovom dokumente, neprechádza 900 objektov. Zostúpi len cez niekoľko uzlov, pretože dobre navrhnutý strom zostáva plytký a vyvážený. Ručné vytváranie takého stromu je pomerne náročné, preto sa oplatí vidieť ho spracovaný krok za krokom v článku o budovaní PDF dokumentu od nuly.

Strom prináša druhú výhodu v podobe dedičnosti. Niekoľko atribútov stránky, ako /Resources, /MediaBox, /CropBox a /Rotate, je možné nastaviť na vnútornom uzle a na jednotlivých stránkach ich vynechať. Tie potom zdedia hodnotu najbližšieho predka. Ak nastavíte /MediaBox raz na koreni, každý list získa rovnaký rozmer stránky bez opakovania. Stránka, ktorá sa líši, si zadefinuje vlastný. Toto je jediné miesto v objektovom modeli, kde význam hodnoty závisí od pozície objektu v strome, nielen od jeho vlastného obsahu.

Čo v skutočnosti obsahuje koncová stránka

Objekt stránky (page object) je spojovacím bodom medzi štrukturálnym modelom a viditeľným obsahom. Jeho položka /Contents odkazuje na jeden alebo viac obsahových prúdov (content streams), čo sú operátory kreslenia, ktoré vykresľujú text a grafiku na stránku. Slovník /Resources (zdroje) pomenováva písma, obrázky a farebné priestory, na ktoré sa tieto operátory spoliehajú. Každá položka je nepriamym odkazom na objekt zdieľaný medzi stránkami. Položka /MediaBox definuje obdĺžnik stránky v bodoch (1/72 palca) a položky ako /Rotate a /CropBox upravujú spôsob prezentácie.

Táto deľba práce je celým modelom v miniatúre. Slovník stránky je štruktúra: typové položky a referencie určujúce, čím a ako sa stránka vykresľuje. Obsahový prúd sú inštrukcie: samostatný, komprimovateľný blok určujúci postup kreslenia. Písmo za odkazom /F1 je zdieľaný zdroj definovaný raz, na ktorý sa odkazuje všade tam, kde sa používa. Slovník, prúd a referencie spolupracujú na vykreslení jednej stránky a rovnaké vzory sa škálujú na celý dokument. Operátory obsahového prúdu v tomto bloku sú samostatne popísané v článkoch o práci s textom a písmami a o grafike a vizuálnych prvkoch.

Prečo sa oplatí tento model poznať

Väčšina vývojárov sa s objektovým modelom stretne až vtedy, keď sa niečo pokazí: stránka sa vykreslí ako prázdna, pretože odkaz /Contents nikam nevedie, text sa zobrazí ako štvorčeky, pretože zdroj písma nebol vložený, prípadne nástroj nahlási počet /Count, ktorý nezodpovedá nájdeným stránkam. Každý z týchto problémov je vyjadrením o stave grafu a priame čítanie grafu je oveľa lepšie ako hádanie. Osem typov a pravidlo odkazovania sú dostatočne malou slovnou zásobou na to, aby ste ich udržali v hlave. Akonáhle uvidíte PDF ako objekty odkazujúce na iné objekty, poškodené súbory prestanú byť záhadou.

Ručné písanie objektového modelu je však okrem učenia len málokedy tou správnou cestou. Udržiavanie konzistentnosti posunov krížových odkazov, čísel generácií, počtu stromov stránok a dĺžok prúdov pri úpravách je presne tá práca, na ktorú existujú knižnice. V produkcii spravuje graf objektov zrelá knižnica pre vývoj PDF a vám umožňuje premýšľať v kategóriách stránok a obsahu. Znalosť modelu sa napriek tomu vypláca: rozumiete tomu, čo knižnica pod kapotou vytvára a prečo.