Technical Article

Izrada minimalnog PDF-a ručno: pet objekata koji su vam potrebni

PDF je u svojoj biti kontejner običnog teksta. Otvorite većinu datoteka u heksadecimalnom uređivaču i vrh je čitljiv: komentar s verzijom, zatim niz numeriranih objekata, pa mali indeks i pokazivač na samom dnu koji čitatelju govori odakle početi. Uklonite kompresiju i format je dovoljno pristupačan da možete utipkati funkcionalni dokument u uređivač teksta i otvoriti ga u pregledniku. Učinite li to jednom, naučit ćete više o tome kako se PDF drži zajedno nego bilo kakvo čitanje specifikacije, jer morate ručno povezati objekte jedne s drugima i datoteka se odbija otvoriti dok ne posložite to povezivanje kako treba.

Ovaj vodič gradi najmanji PDF koji zapravo nešto prikazuje: jednu stranicu, riječi 'Hello, World!' u ugrađenom fontu, na papiru formata US Letter. Dovršena datoteka treba točno pet objekata i nekoliko redaka evidencije oko njih. Prvo ćemo napisati objekte, a zatim sastaviti zaglavlje, tablicu unakrsnih referenci (cross-reference) i trailer koji ih povezuju u datoteku koju će čitatelj prihvatiti.

Pet objekata na kojima preglednik inzistira

Čitatelj ne skenira PDF od vrha do dna tražeći sadržaj. Počinje od trailera, prati referencu do kataloga dokumenta (Catalog) i odatle prolazi kroz lanac objekata. Svaki objekt u tom lancu mora postojati ili otvaranje ne uspijeva. Za dokument od jedne stranice lanac je kratak i svaka karika ima jedan zadatak:

  • Catalog je korijen. To je objekt na koji trailer ukazuje, a njegov jedini obvezni unos ovdje je referenca na stablo stranica.
  • Pages je čvor stabla stranica. Navodi stranice u dokumentu i javlja koliko ih ima.
  • Page opisuje jednu fizičku stranicu: njezinu veličinu, resurse kojima crta i koji tok sadržaja (content stream) je iscrtava.
  • Content stream (tok sadržaja) sadrži operatore crtanja, postfiksne naredbe koje postavljaju tekst i grafiku na tu stranicu.
  • Font deklarira pismo na koje se tok sadržaja odnosi. Upotrijebite jedan od 14 standardnih fontova i ne morate ništa ugrađivati.

Svaki objekt je numeriran i adresabilan. Neizravni objekt piše se kao N 0 obj ... endobj, gdje je N broj objekta, a 0 je njegov broj generacije (uvijek 0 u datoteci koju pišete iznova). Bilo gdje drugdje u datoteci ukazujete na taj objekt referencom: 5 0 R znači 'objekt 5'. Te reference su povezivanje. Katalog sadrži 2 0 R u našem numeriranju kako bi dosegnuo stablo stranica, stablo stranica sadrži referencu natrag na stranicu i tako dalje. Pogriješite li broj, čitatelj prati viseći pokazivač u ništa.

Nazivi, rječnici i tokovi

Tri dijela sintakse nose gotovo sve. Naziv (name) počinje s kosom crtom: /Type, /Page, /F0. Nazivi su identifikatori osjetljivi na velika i mala slova, a ne nizovi znakova, i PDF ih koristi za ključeve rječnika i za označavanje onoga što objekt jest. Rječnik (dictionary) je skup parova ključ-vrijednost omotanih u dvostruke uglate zagrade (<< >>), gdje je svaki ključ naziv: << /Type /Page /MediaBox [0 0 612 792] >>. Vrijednosti mogu biti brojevi, nazivi, polja u uglatim zagradama, reference ili ugniježđeni rječnici. Većina PDF objekata su rječnici.

Tok (stream) je rječnik iza kojeg slijedi blok bajtova između ključnih riječi stream i endstream. Tamo žive operatori crtanja stranice, a u stvarnim datotekama i komprimirane slike i ugrađeni fontovi. Rječnik toka opisuje bajtove; u produkcijskoj datoteci mora sadržavati unos /Length koji daje točan broj bajtova, a često i /Filter poput /FlateDecode kada su podaci komprimirani. Oslonit ćemo se na alat koji će popuniti /Length, jer je ručno brojanje bajtova dio ove vježbe bez ikakve edukativne vrijednosti i s velikom šansom za pogrešku odstupanja za jedan (off-by-one) koja kvari datoteku.

Pisanje objekata

Evo pet objekata po redu. Detalj o koordinatama koji treba imati na umu prije čitanja toka sadržaja: PDF mjeri od donjeg lijevog kuta stranice u točkama (points), pri čemu je jedna točka 1/72 inča, a os Y raste prema gore. Stranica formata US Letter velika je 612 puta 792 točke, pa se točka 50 700 nalazi blizu gornjeg lijevog kuta, a ne donjeg.

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

Pročitajte reference i struktura se sama otkriva. Objekt 1, katalog (Catalog), usmjerava svoj unos /Pages na objekt 2. Objekt 2, stablo stranica, navodi objekt 3 u /Kids i deklarira /Count 1. Objekt 3, stranica (Page), usmjerava /Parent natrag na objekt 2 (stablo i stranica upućuju jedno na drugo, što je obvezno), postavlja svoju veličinu pomoću /MediaBox, izlaže font pod lokalnim nazivom /F0 u svojim resursima (/Resources) i imenuje objekt 5 kao svoj sadržaj. Objekt 4 isporučuje se kao font: /BaseFont /Helvetica bira jedno od 14 standardnih pisama koje svaki usklađeni preglednik već ima, pa nema potrebe ništa ugrađivati. Objekt 5 je tok sadržaja.

Što tok sadržaja zapravo govori

Tijelo toka je mali program na PDF jeziku za opis stranice, koji je postfiksni: operandi dolaze prvi, a zatim operator koji ih troši. Pet redaka obavlja posao. BT i ET otvaraju i zatvaraju tekstualni objekt; sve što pozicionira ili prikazuje tekst mora stajati između njih. /F0 36 Tf postavlja trenutni font na resurs pod nazivom /F0 na 36 točaka (Tf je 'postavi font teksta i veličinu'). 50 700 Td pomiče poziciju teksta na (50, 700) u koordinatama stranice. (Hello, World!) Tj prikazuje niz znakova, što PDF zapisuje kao doslovni tekst u zagradama, koristeći Tj da ga nacrta na trenutnoj poziciji. Izostavite BT/ET i strogi preglednik odbacuje tekstualne operatore; zaboravite postaviti font prije Tj i nećete imati trenutni font kojim možete crtati.

/Length 44 u rječniku toka označava točan broj bajtova između stream i endstream i mora biti precizan. To je vrijednost koju vrijedi prepustiti alatu umjesto ručnog brojanja novih redaka, pogotovo zato što promjena načina na koji vaš uređivač zapisuje krajeve redaka (LF ili CRLF) mijenja ukupan broj.

Header, xref i trailer

Objekti su sadržaj. Tri strukturna dijela pretvaraju ih u datoteku. Prvi je zaglavlje (header), sam prvi redak, koji navodi format i verziju:

%PDF-1.7

Znak % započinje komentar u PDF sintaksi, ali preglednik tretira ovaj specifični komentar kao potpis formata i iz njega čita verziju. Stvarni pisač odmah iza njega stavlja drugi redak komentara s bajtovima visokog bita, što je naznaka alatima za prijenos datoteka da je datoteka binarna i da se ne smije izobličiti kao običan tekst.

Na kraju datoteke dolazi tablica unakrsnih referenci (cross-reference table, xref), indeks koji omogućuje nasumičan pristup. Ona bilježi pomak bajtova (offset) svakog objekta od početka datoteke, tako da preglednik može skočiti izravno na objekt 3 bez prethodne analize objekata 1 i 2. Tablica je kruta: unosi su fiksne širine, od kojih svaki ima 20 bajtova uključujući kraj retka, oblikovani kao 10-znamenkasti pomak, 5-znamenkasta generacija, ključna riječ (n za u upotrebi, f za slobodno) i dvobajtni terminator. Ispravna tablica za naših šest unosa (objekt 0 je uvijek na čelu popisa slobodnih objekata) izgleda ovako:

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

Ti pomaci (offsets) su najosjetljiviji dio ručnog pisanja PDF-a. Svaki od njih je točan položaj bajta na kojem počinje odgovarajući N 0 obj, i svaki se pomak mijenja čim dodate znak bilo gdje iznad njega. Trailer je ulazna točka koju čitatelj koristi zadnju i prvu: /Root 1 0 R imenuje katalog, /Size 6 navodi broj objekata, a startxref 408 daje pomak bajta same riječi xref. Preglednik otvara datoteku, skače na kraj, čita startxref, traži tablicu unakrsnih referenci i odatle dolazi do kataloga i svega ispod njega. Oznaka %%EOF označava zadnji bajt.

Neka alat popravi broj bajtova

Gornji pomaci su ilustrativni; u praksi će biti pogrešni do trenutka kada završite tipkanje jer ovise o točnom rasporedu bajtova vaše datoteke. Umjesto da ih ponovno računate, napišite strukturu s privremenim vrijednostima (placeholders) i pustite uslužni program da ponovno izgradi tablicu unakrsnih referenci i duljine toka. Besplatni program pdftk, koji radi na više platformi, obavlja ovaj posao u jednom prolazu:

pdftk hello-draft.pdf output hello.pdf

On analizira vaše objekte, ponovno izračunava svaki pomak bajta, popunjava ispravne vrijednosti /Length, zapisuje valjanu xref tablicu i trailer te generira hello.pdf. Otvorite to u bilo kojem pregledniku i dobit ćete jednu stranicu s tekstom 'Hello, World!' u 36-točkovnoj Helvetici blizu vrha. Qpdf radi isti posao, a mnogi će preglednici popraviti blago neispravnu datoteku u letu. Svrha oslanjanja na alat ovdje nije lijenost; stvar je u tome što je aritmetika pomaka onaj dio formata s nula konceptualnog sadržaja i najvećom stopom pogreške, pa automatizacija omogućuje da struktura ostane ono što učite.

Zašto se ovo prenosi na stvarne dokumente

Ništa u izvješću od stotinu stranica ne mijenja oblik koji ste upravo izgradili. Katalog se i dalje nalazi u korijenu, stablo stranica i dalje okuplja stranice, a svaka stranica i dalje ukazuje na svoje resurse i tok sadržaja. Ono što raste jest širina, a ne kralježnica dokumenta: stablo stranica se grana kako bi preglednik mogao preskočiti čitava podstabla, tokovi sadržaja nose stotine operatora umjesto pet, fontovi se ugrađuju kao vlastiti objekti toka s tablicama širine i kodiranjima, a slike stižu kao tokovi s filtrima specifičnim za slike. Moderne datoteke također teže pakiranju mnogih objekata u komprimirane tokove objekata i zamjenjuju običnu xref tablicu tokom unakrsnih referenci, zbog čega otvaranje stvarnog PDF-a u uređivaču teksta obično prikazuje zid binarnih podataka. Model ispod toga identičan je onom u vašoj ručno izrađenoj datoteci. Za širi graf objekata i način na koji se odnose katalog, stablo stranica i rječnici resursa u većem dokumentu, detaljan obilazak strukture PDF dokumenta nastavlja se tamo gdje ovaj članak staje, a tehnički pregled strukture datoteka pokriva inkrementalna ažuriranja i način na koji se trailer povezuje kroz revizije.

Od ručnog pisanja do knjižnice

Ručno tipkanje objekata je vježba za učenje, a ne produkcijska tehnika. Onog trenutka kada zatrebate stvarne fontove, prelomljeni tekst, slike ili više od trivijalne stranice, vođenje evidencije bajtova koje je pdftk zakrpao za vas postaje cijeli posao i tada trebate knjižnicu koja to preuzima na sebe. Istih pet objekata i dalje se zapisuje, ali knjižnica izračunava svaki pomak, upravlja rječnicima fontova i resursa te komprimira tokove sadržaja bez da vi pratite ijedan bajt. U Delphiju i C++Builderu, komponenta HotPDF smanjuje cijelu ovu datoteku na nekoliko poziva: postavite dokument, pozovite BeginDoc, SetFont i TextOut da postavite isti pozdrav, a zatim EndDoc da zapišete ispravan katalog, stablo stranica, xref i trailer. Razumijevanje objekata ispod toga omogućuje vam da logički zaključite o izlazu kada se dokument ne prikaže onako kako ste očekivali.