Technical Article

Ručné vytvorenie minimálneho PDF: Päť objektov, ktoré potrebujete

PDF je vo svojej podstate kontajner s otvoreným textom. Otvorte väčšinu súborov v hexadecimálnom editore a začiatok bude čitateľný: komentár s verziou, za ním séria očíslovaných objektov, potom malý index a úplne naspodku ukazovateľ, ktorý čítačke hovorí, kde má začať. Ak odhliadneme od kompresie, formát je dostatočne prístupný na to, aby ste mohli funkčný dokument napísať v textovom editore a otvoriť ho v prehliadači. Ak si to raz vyskúšate, naučí vás to o štruktúre PDF viac než akékoľvek čítanie špecifikácie. Objekty totiž musíte prepojiť ručne a súbor sa neodmietne otvoriť, kým nie je všetko zapojené správne.

Tento sprievodca zostaví najmenšie možné PDF, ktoré skutočne niečo vykreslí: jednu stranu s textom „Hello, World!“ vo vstavanom písme na papieri formátu US Letter. Hotový súbor vyžaduje presne päť objektov a niekoľko riadkov sprievodných údajov. Najprv zapíšeme samotné objekty, a potom zostavíme hlavičku, tabuľku krížových odkazov (cross-reference table) a trailer, ktoré ich spoja do súboru akceptovateľného čítačkou.

Päť objektov, ktoré prehliadač vyžaduje

Čítačka neprechádza PDF zhora nadol a nehľadá v ňom obsah. Začína na konci v časti trailer, nasleduje odkaz na katalóg dokumentu a odtiaľ prechádza reťazec objektov. Každý objekt v tomto reťazci musí existovať, inak otvorenie zlyhá. Pre jednostranový dokument je tento reťazec krátky a každý článok má jednu úlohu:

  • Catalog je koreňový objekt. Smeruje naň trailer a jeho jedinou povinnou položkou v tomto prípade je odkaz na strom stránok.
  • Pages reprezentuje uzol stromu stránok. Zoznamuje strany v dokumente a udáva ich celkový počet.
  • Page popisuje jednu fyzickú stranu: jej rozmery, prostriedky (zdroje), s ktorými kreslí, a to, ktorý obsahový prúd (content stream) ju vykresľuje.
  • Content stream (obsahový prúd) obsahuje operátory kreslenia, čo sú postfixové príkazy umiestňujúce na stránku text a grafiku.
  • Font deklaruje písmo, na ktoré sa odkazuje obsahový prúd. Použitím jedného zo 14 štandardných písiem odpadá potreba čokoľvek do súboru vkladať.

Každý objekt je očíslovaný a adresovateľný. Nepriamy objekt sa zapisuje ako N 0 obj ... endobj, kde N je číslo objektu a 0 je číslo jeho generácie (v novovytvorenom súbore je to vždy 0). Kdekoľvek inde v súbore odkazujete na tento objekt pomocou referencie: 5 0 R znamená „objekt 5“. Tieto referencie predstavujú samotné prepojenie. Katalóg obsahuje hodnotu 2 0 R (podľa nášho číslovania), aby sa dostal k stromu stránok, strom stránok odkazuje späť na stranu atď. Ak pomýlite číslo, čítačka prejde po neplatnom odkaze do prázdna.

Názvy, slovníky a prúdy

Takmer všetko v syntaxi nesú tri prvky. Názov (name) začína lomkou: /Type, /Page, /F0. Názvy sú identifikátory citlivé na veľkosť písmen, nie reťazce, a PDF ich používa ako kľúče slovníkov a na označenie typu objektu. Slovník (dictionary) je sada dvojíc kľúč-hodnota uzavretá v dvojitých lomených zátvorkách, kde každý kľúč je názov: << /Type /Page /MediaBox [0 0 612 792] >>. Hodnotami môžu byť čísla, názvy, polia v hranatých zátvorkách, referencie alebo vnorené slovníky. Väčšina objektov v PDF sú slovníky.

A prúd (stream) je slovník nasledovaný blokom bajtov medzi kľúčovými slovami stream a endstream. Tu sa nachádzajú operátory kreslenia strany a v reálnych súboroch aj komprimované obrázky či vložené písma. Slovník prúdu popisuje tieto bajty. V produkčnom súbore musí obsahovať položku /Length s presným počtom bajtov a často aj /Filter, napríklad /FlateDecode, ak sú údaje komprimované. Pri dopĺňaní hodnoty /Length sa spoľahneme na nástroj, pretože ručné počítanie bajtov nemá žiadny vzdelávací prínos a nesie so sebou vysoké riziko chyby o jeden bajt, ktorá by súbor znefunkčnila.

Zápis objektov

Tu je päť objektov za sebou. Pred čítaním obsahového prúdu pamätajte na detail o súradniciach: PDF meria súradnice od ľavého dolného rohu stránky v bodoch (points), kde jeden bod je 1/72 palca a os Y rastie smerom nahor. Stránka formátu US Letter má 612 x 792 bodov, takže bod 50 700 leží blízko ľavého horného rohu, nie dolného.

1 0 obj
<< /Type /Catalog
   /Pages 2 0 R
>>
endobj

2 0 obj
<< /Type /Pages
   /Kids [3 0 R]
   /Count 1
>>
endobj

3 0 obj
<< /Type /Page
   /Parent 2 0 R
   /MediaBox [0 0 612 792]
   /Resources << /Font << /F0 4 0 R >> >>
   /Contents 5 0 R
>>
endobj

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

5 0 obj
<< /Length 44 >>
stream
BT
/F0 36 Tf
50 700 Td
(Hello, World!) Tj
ET
endstream
endobj

Keď si prečítate referencie, štruktúra je jasná. Objekt 1 (katalóg) odkazuje svojou položkou /Pages na objekt 2. Objekt 2 (strom stránok) uvádza objekt 3 v poli /Kids a deklaruje /Count 1. Objekt 3 (strana) odkazuje cez /Parent späť na objekt 2 (strom a strana na seba odkazujú navzájom, čo je povinné), definuje svoje rozmery pomocou /MediaBox, sprístupňuje písmo pod lokálnym názvom /F0 vo svojich zdrojoch /Resources a označuje objekt 5 ako svoj obsah. Objekt 4 je samotné písmo: /BaseFont /Helvetica vyberá jeden zo 14 štandardných rezov písma, ktoré má každá kompatibilná čítačka k dispozícii, takže netreba nič vkladať. Objekt 5 je obsahový prúd.

Čo v skutočnosti hovorí obsahový prúd

Telo prúdu je malý program v jazyku na popis stránky PDF, ktorý je postfixový: najprv idú operandy a potom operátor, ktorý ich spracuje. Celú prácu odvedie päť riadkov. BT a ET otvárajú a zatvárajú textový objekt. Všetko, čo umiestňuje alebo zobrazuje text, sa musí nachádzať medzi nimi. /F0 36 Tf nastavuje aktuálne písmo na zdroj s názvom /F0 s veľkosťou 36 bodov (Tf znamená „nastaviť písmo a veľkosť textu“). 50 700 Td posunie pozíciu textu na súradnice (50, 700). (Hello, World!) Tj zobrazí reťazec, ktorý sa v PDF zapisuje do zátvoriek ako literál, a vykreslí ho na aktuálnej pozícii pomocou operátora Tj. Ak vynecháte BT/ET, striktná čítačka textové operátory odmietne. Ak zabudnete nastaviť písmo pred volaním Tj, nebude k dispozícii žiadne aktívne písmo na kreslenie.

Hodnota /Length 44 v slovníku prúdu je presný počet bajtov medzi kľúčovými slovami stream a endstream. Toto číslo musíte uviesť presne. Ide o hodnotu, ktorú sa oplatí prenechať nástroju namiesto ručného počítania koncov riadkov, najmä preto, že to, či váš editor zapisuje konce riadkov ako LF alebo CRLF, mení celkový výsledok.

Hlavička, xref a trailer

Objekty tvoria samotný obsah. Na to, aby sa z nich stal súbor, sú potrebné tri štrukturálne časti. Prvou je hlavička na úplne prvom riadku, ktorá udáva formát a verziu:

%PDF-1.7

Znak % začína v syntaxi PDF komentár, ale čítačka považuje tento konkrétny komentár za podpis formátu a číta z neho verziu. Skutočný zapisovač ho okamžite nasleduje druhým riadkom komentára s bajtmi s vysokým bitom (high-bit bytes), čo slúži ako nápoveda pre nástroje na prenos súborov, že súbor je binárny a nesmie sa s ním narábať ako s čistým textom.

Na konci súboru sa nachádza tabuľka krížových odkazov (cross-reference table), čo je index umožňujúci priamy prístup k objektom. Zaznamenáva bajtový posun (offset) každého objektu od začiatku súboru, takže čítačka môže skočiť priamo na objekt 3 bez toho, aby musela najprv parsovať objekty 1 a 2. Tabuľka je striktne definovaná: položky majú pevnú šírku 20 bajtov vrátane konca riadku, formátované ako 10-miestny posun, 5-miestna generácia, kľúčové slovo (n pre aktívne, f pre voľné) a dvojbajtové zakončenie. Správna tabuľka pre našich šesť položiek (objekt 0 je vždy na začiatku zoznamu voľných objektov) vyzerá takto:

xref
0 6
0000000000 65535 f
0000000009 00000 n
0000000058 00000 n
0000000115 00000 n
0000000235 00000 n
0000000308 00000 n
trailer
<< /Size 6
   /Root 1 0 R
>>
startxref
408
%%EOF

Tieto posuny (offsets) sú najchúlostivejšou časťou ručného písania PDF. Každý z nich predstavuje presnú pozíciu bajtu, na ktorej začína príslušný zápis N 0 obj, a každý posun sa zmení vo chvíli, keď pridáte čo i len jeden znak kdekoľvek nad ním. Trailer je vstupný bod, ktorý čítačka používa ako prvý aj posledný: /Root 1 0 R označuje katalóg, /Size 6 udáva počet objektov a startxref 408 určuje bajtový posun samotného slova xref. Čítačka otvorí súbor, prejde na koniec, prečíta startxref, vyhľadá tabuľku krížových odkazov a odtiaľ sa dostane ku katalógu a všetkému pod ním. Značka %%EOF označuje posledný bajt.

Nechajte nástroj opraviť počty bajtov

Vyššie uvedené posuny sú ilustračné. V praxi budú po dokončení písania nesprávne, pretože závisia od presného rozloženia bajtov vo vašom súbore. Namiesto ich prepočítavania napíšte štruktúru s dočasnými hodnotami a nechajte tabuľku krížových odkazov a dĺžky prúdov prebudovať utility. Bezplatný a multiplatformový nástroj pdftk to zvládne v jednom kroku:

pdftk hello-draft.pdf output hello.pdf

Nástroj analyzuje vaše objekty, prepočíta každý bajtový posun, doplní správne hodnoty /Length, zapíše platnú tabuľku xref a trailer a vygeneruje súbor hello.pdf. Keď ho otvoríte v ľubovoľnom prehliadači, získate jednu stranu s nápisom „Hello, World!“ v 36-bodovom písme Helvetica blízko horného okraja. Rovnakú úlohu splní aj nástroj Qpdf a mnohé prehliadače opravia mierne poškodený súbor automaticky pri otváraní. Pointa použitia nástroja nespočíva v lenivosti. Ide o to, že matematika posunov je jedinou časťou formátu s nulovým koncepčným významom a najvyššou chybovosťou, takže jej automatizácia vám umožní sústrediť sa na učenie samotnej štruktúry.

Prečo sa toto dá aplikovať na reálne dokumenty

Na štruktúre, ktorou ste práve vytvorili, sa nič nemení ani pri stostranovom reporte. Katalóg sa stále nachádza v koreni, strom stránok stále združuje strany a každá strana stále odkazuje na svoje zdroje a obsahový prúd. To, čo rastie, je rozsah, nie kostra dokumentu: strom stránok sa vetví, aby čítačka mohla preskakovať celé podstromy, obsahové prúdy nesú stovky operátorov namiesto piatich, písma sa vkladajú ako samostatné objekty prúdu s tabuľkami šírok a kódovaním a obrázky prichádzajú ako prúdy so špecifickými filtrami. Moderné súbory majú tiež tendenciu baliť viacero objektov do komprimovaných prúdov objektov (object streams) a nahradzovať textovú tabuľku xref prúdom krížových odkazov (cross-reference stream), preto otvorenie skutočného PDF v textovom editore zvyčajne zobrazí záplavu binárnych dát. Model pod tým je však identický s modelom vo vašom ručne vytvorenom súbore. Pre detailnejšie pochopenie širšieho grafu objektov a vzájomných vzťahov medzi katalógom, stromom stránok a slovníkmi zdrojov vo väčšom dokumente pokračuje podrobný sprievodca štruktúrou PDF dokumentu tam, kde tento článok končí, a prehľad štruktúry súborov sa zaoberá prírastkovými aktualizáciami a reťazením trailerov naprieč revíziami.

Od ručného písania ku knižnici

Ručné písanie objektov je vzdelávacie cvičenie, nie produkčná technika. Vo chvíli, keď potrebujete skutočné písma, zalamovaný text, obrázky alebo viac ako jednu triviálnu stranu, sa účtovníctvo bajtov, ktoré za vás vyriešil nástroj pdftk, stáva hlavnou náplňou práce. Vtedy prichádza čas na knižnicu, ktorá to prevezme za vás. Zapisuje sa síce rovnakých päť objektov, ale knižnica prepočíta každý offset, spravuje slovníky písiem a zdrojov a komprimuje obsahové prúdy bez toho, aby ste museli sledovať jediný bajt. V Delphi a C++Builderi redukuje komponent HotPDF celý tento proces na niekoľko volaní: nastavíte dokument, zavoláte BeginDoc, SetFont a TextOut na umiestnenie pozdravu a nakoniec EndDoc na zápis správneho katalógu, stromu stránok, xref tabuľky a traileru. Pochopenie objektov pod kapotou je to, čo vám umožní analyzovať výstup, keď sa dokument nevykreslí tak, ako ste očakávali.