A PDF alapjában véve egy egyszerű szöveges (plain-text) konténer. Nyissa meg a legtöbb fájlt egy hexaszerkesztőben, és a teteje olvasható: egy verzió megjegyzés, majd egy sor számozott objektum, aztán egy kis index és egy mutató a legalján, amely megmondja az olvasónak (reader), hol kezdje. Vegye le a tömörítést, és a formátum elég megközelíthető ahhoz, hogy beírhasson egy működő dokumentumot egy szövegszerkesztőbe, és egy megjelenítő (viewer) megnyissa azt. Ha ezt egyszer megteszi, többet tanul arról, hogyan áll össze a PDF, mint amennyit a specifikáció olvasása adhatna, mert kézzel kell összekötnie (wire) az objektumokat egymással, és a fájl nem hajlandó megnyílni, amíg nem csinálja meg jól a kábelezést
Ez az útmutató felépíti a legkisebb PDF-et, amely ténylegesen renderel valamit: egy oldal, a "Hello, World!" szavak egy beépített betűtípussal, US Letter méretű papíron. A kész fájlhoz pontosan öt objektumra és néhány sornyi könyvelésre (bookkeeping) van szükség körülöttük. Először megírjuk az objektumokat, majd összeállítjuk a fejlécet, a kereszthivatkozási táblát (cross-reference table) és a trailert, amelyek olyan fájlba kötik őket, amelyet az olvasó elfogad
Az öt objektum, amelyhez a megjelenítő ragaszkodik
Az olvasó (reader) nem fentről lefelé olvassa a PDF-et tartalmat keresve. A trailernél kezdődik, követi a dokumentum katalógusra (document catalog) mutató hivatkozást, és onnan egy objektumláncon halad végig. Annak a láncnak minden objektumának léteznie kell, különben a megnyitás sikertelen lesz. Egy egyoldalas dokumentum esetében a lánc rövid, és minden láncszemnek (link) egyetlen feladata van:
- A Catalog a gyökér. Ez az az objektum, amelyre a trailer mutat, és egyetlen kötelező bejegyzése (entry) itt egy hivatkozás az oldalfára (page tree)
- A Pages az oldalfa csomópont (page tree node). Felsorolja a dokumentum oldalait, és jelenti, mennyi van belőlük
- A Page leír egy fizikai oldalt: a méretét, az erőforrásokat, amelyekkel rajzol, és hogy melyik tartalomfolyam (content stream) festi meg
- A Content stream (tartalomfolyam) tartalmazza a rajzoló operátorokat, a postfix parancsokat, amelyek szöveget és grafikát helyeznek el az adott oldalon
- A Font deklarálja a betűtípust, amelyre a tartalomfolyam hivatkozik. Használja a 14 szabványos betűtípus egyikét, és nem kell beágyaznia semmit
Minden objektum számozott és címezhető. Egy indirekt objektum (indirect object) a következőképpen van felírva: N 0 obj ... endobj, ahol N az objektum száma, a 0 pedig a generációs száma (mindig 0 egy újonnan írt fájlban). A fájlban bárhol máshol egy hivatkozással (reference) mutat rá arra az objektumra: az 5 0 R azt jelenti, hogy "5. objektum". Ezek a hivatkozások a kábelezések. A katalógus a mi számozásunk szerint 2 0 R-t tartalmaz az oldalfa eléréséhez, az oldalfa tartalmaz egy hivatkozást vissza az oldalra, és így tovább. Elront egy számot, és az olvasó egy lógó mutatót (dangling pointer) követ a semmibe
Nevek, szótárak (dictionaries) és adatfolyamok (streams)
A szintaxis három darabja hordozza szinte az egészet. Egy név perjelel kezdődik: /Type, /Page, /F0. A nevek megkülönböztetik a kis- és nagybetűket (case-sensitive) lévő azonosítók, nem karakterláncok (strings), és a PDF szótárkulcsokként használja őket, és annak címkézésére, hogy mi egy objektum. Egy szótár (dictionary) kulcs-érték párok halmaza, kettős hegyes zárójelbe csomagolva, ahol minden kulcs egy név: << /Type /Page /MediaBox [0 0 612 792] >>. Az értékek lehetnek számok, nevek, tömbök (arrays) szögletes zárójelben, hivatkozások vagy beágyazott szótárak. A legtöbb PDF objektum szótár
A stream (adatfolyam) egy szótár, amelyet egy blokknyi bájt követ a stream és az endstream kulcsszavak között. Ott élnek az oldalrajzoló operátorok, és a valódi fájlokban ott élnek a tömörített képek és a beágyazott betűtípusok is. Az adatfolyam szótára (stream dictionary) leírja a bájtokat; egy éles fájlban hordoznia kell egy /Length bejegyzést, amely megadja a pontos bájtszámlálást, és gyakran egy /Filter-t, például /FlateDecode-ot, amikor az adatok tömörítve vannak. Arra fogunk támaszkodni, hogy egy eszköz (tool) kitölti a /Length-et, mert a bájtok kézi számlálása ennek a gyakorlatnak az a része, amelynek nincs oktatási hozadéka, és nagy az esélye egy egyel-elcsúszott (off-by-one) hibának, ami elrontja a fájlt
Az objektumok megírása
Itt van az öt objektum sorban. A koordináta részlet, amit szem előtt kell tartani a tartalomfolyam olvasása előtt: A PDF az oldal bal alsó sarkától mér pontokban, ahol egy pont 1/72 hüvelyk (inch), és az Y felfelé nő. Egy US Letter oldal 612 x 792 pont, tehát a 50 700 a bal felső sarok közelében ül, nem pedig az alján
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
Olvassa el a hivatkozásokat, és a struktúra kibontakozik. Az 1. objektum, a katalógus, a /Pages bejegyzését a 2. objektumra irányítja. A 2. objektum, az oldalfa, felsorolja a 3. objektumot a /Kids-ben, és deklarálja a /Count 1-et. A 3. objektum, az oldal, a /Parent-tel visszamutat a 2. objektumra (a fa és az oldal hivatkoznak egymásra, ami kötelező), méretezi magát a /MediaBox-szal, kiteszi a betűtípust a helyi /F0 név alatt a /Resources-ban, és az 5. objektumot nevezi meg tartalmaként (content). A 4. objektum a betűtípus: a /BaseFont /Helvetica kiválaszt egyet a 14 szabványos betűtípus közül, amely minden megfelelő (conforming) olvasóban már megvan, így nincs mit beágyazni. Az 5. objektum a tartalomfolyam
Mit is mond valójában a tartalomfolyam
Az adatfolyam törzse egy apró program a PDF oldal-leíró nyelvében (page-description language), amely postfix: az operandusok jönnek először, majd az operátor, amely felhasználja őket. Öt sor végzi el a munkát. A BT és ET megnyit és bezár egy szöveg objektumot; mindennek, ami szöveget pozicionál vagy jelenít meg, közéjük kell kerülnie. Az /F0 36 Tf az aktuális betűtípust az /F0 nevű erőforrásra állítja 36 ponton (a Tf a "szöveg betűtípusának és méretének beállítása"). A 50 700 Td a szöveg pozícióját a (50, 700) oldal koordinátákra mozgatja. A (Hello, World!) Tj megjeleníti a karakterláncot (string), amelyet a PDF literális (literal) szövegként ír zárójelek között, a Tj használatával az aktuális pozícióba festi. Hagyja ki a BT/ET-t, és egy szigorú olvasó elutasítja a szöveg operátorokat; elfelejt beállítani egy betűtípust a Tj előtt, és nincs aktuális betűtípus a rajzoláshoz
A /Length 44 a stream szótárban a stream és az endstream közötti bájtok száma, és pontosnak kell lennie. Ez az az érték, amit érdemes átadni egy eszköznek, ahelyett, hogy kézzel számolnánk az újsorokat (newlines), különösen mivel az, hogy a szerkesztője a sorvégződéseket (line endings) LF-ként vagy CRLF-ként írja, megváltoztatja a végösszeget
Fejléc (Header), xref és trailer
Az objektumok a tartalom. Három strukturális darab alakítja őket fájllá. Az első a fejléc (header), a legelső sor, amely megnevezi a formátumot és a verziót:
%PDF-1.7
A % egy megjegyzést (comment) indít a PDF szintaxisban, de egy olvasó ezt a bizonyos megjegyzést formátum aláírásként (format signature) kezeli, és kiolvassa belőle a verziót. Egy igazi író (writer) ezt azonnal egy második megjegyzés sorral követi magas bitű (high-bit) bájtokból, tippként a fájlátviteli eszközöknek (file-transfer tools), hogy a fájl bináris, és nem szabad szövegként összeroncsolni (mangle)
A fájl végén jön a kereszthivatkozási tábla (cross-reference table), az index, amely lehetővé teszi a véletlenszerű elérést (random access). Rögzíti minden objektum bájt eltolását (byte offset) a fájl elejétől, így egy olvasó (reader) egyenesen a 3. objektumhoz ugorhat (seek) anélkül, hogy először elemezné (parsing) az 1. és a 2. objektumot. A táblázat merev: a bejegyzések rögzített szélességűek, egyenként 20 bájt, beleértve a sorvégződést, 10 jegyű eltolásként (offset), 5 jegyű generációként, egy kulcsszóként (n a használatban lévő - in-use, f a szabad - free), és egy két bájtos lezáróként formázva. Egy helyes táblázat a hat bejegyzésünkhöz (a 0. objektum mindig a szabadlista (free-list) feje) így néz ki:
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
Ezek az eltolások (offsets) a kézi PDF írás törékeny (brittle) részei. Mindegyik pontosan az a bájtpozíció, ahol a megfelelő N 0 obj kezdődik, és minden eltolás eltolódik abban a pillanatban, hogy hozzáad egy karaktert bárhol felette. A trailer az a belépési pont (entry point), amelyet egy olvasó utoljára és először használ: a /Root 1 0 R megnevezi a katalógust, a /Size 6 megadja az objektumszámot, és a startxref 408 magának az xref szónak a bájt eltolását adja meg. Egy olvasó megnyitja a fájlt, a végére ugrik, beolvassa a startxref-et, a kereszthivatkozási táblához ugrik (seeks), és onnan eléri a katalógust és mindent az alatt. Az %%EOF jelöli az utolsó bájtot
Hagyja, hogy egy eszköz javítsa a bájtszámokat
A fenti eltolások illusztrációk; a gyakorlatban helytelenek lesznek, mire befejezi a gépelést, mert a fájl pontos bájt elrendezésétől (byte layout) függenek. Ahelyett, hogy újraszámolná őket, írja meg a struktúrát helyőrző értékekkel (placeholder values), és hagyja, hogy egy segédprogram (utility) újraépítse a kereszthivatkozási táblát és az adatfolyam hosszak (stream lengths). Az ingyenes, platformfüggetlen (cross-platform) pdftk ezt egy lépésben (one pass) elvégzi:
pdftk hello-draft.pdf output hello.pdf
Elemzi (parses) az objektumait, újraszámol minden bájt eltolást, kitölti a helyes /Length értékeket, ír egy érvényes xref táblát és trailert, és kiadja a hello.pdf-et. Nyissa meg bármilyen megjelenítőben (viewer), és kap egy oldalt "Hello, World!" felirattal 36 pontos Helvetica betűtípussal a tetejénél. A Qpdf ugyanezt a munkát végzi el, és sok megjelenítő menet közben (on the fly) is kijavít egy enyhén hibás formátumú (malformed) fájlt. Az eszközre való támaszkodás lényege itt nem a lustaság; hanem az, hogy az eltolás aritmetika a formátum egyetlen olyan része, amely nulla fogalmi tartalommal (conceptual content) és a legmagasabb hibaaránnyal rendelkezik, tehát az automatizálása lehetővé teszi, hogy a struktúra maradjon az a dolog, amit éppen tanul
Miért skálázódik ez a valódi dokumentumokra
Egy százoldalas jelentés semmin sem változtat abból az alakból (shape), amelyet éppen felépített. A katalógus még mindig a gyökérben ül, az oldalfa még mindig összegyűjti az oldalakat, és minden oldal még mindig az erőforrásaira és egy tartalomfolyamra mutat. Ami nő, az a szélesség, nem a gerinc (spine): az oldalfa elágazik, így egy olvasó egész részfákat (subtrees) hagyhat ki, a tartalomfolyamok (content streams) több száz operátort hordoznak öt helyett, a betűtípusok saját adatfolyam objektumokként (stream objects) vannak beágyazva szélességi táblákkal (width tables) és kódolásokkal (encodings), a képek pedig kép-specifikus szűrőkkel (filters) ellátott adatfolyamokként (streams) érkeznek. A modern fájlok hajlamosak arra is, hogy sok objektumot tömörített objektum folyamokba (object streams) csomagoljanak, és a sima xref táblát egy kereszthivatkozási folyammal (cross-reference stream) cseréljék le, ezért egy valódi PDF megnyitása egy szövegszerkesztőben általában egy bináris falat (wall of binary) mutat. Az alatta lévő modell megegyezik a kézzel készített fájlban lévővel. A szélesebb objektum gráffal (object graph), és azzal kapcsolatban, hogyan viszonyul a katalógus, az oldalfa és az erőforrás szótárak egy nagyobb dokumentumban, a PDF dokumentum struktúra mélyreható bemutatása ott veszi fel a fonalat, ahol ez befejeződik, és a fájl-struktúra áttekintés foglalkozik az inkrementális frissítésekkel (incremental updates) és azzal, hogyan láncolódik a trailer a revíziókon (revisions) keresztül
A kézírástól a könyvtárig
Az objektumok kézzel történő beírása tanulási gyakorlat, nem pedig termelési (production) technika. Abban a pillanatban, amint valódi betűtípusokra, tördelt szövegre (wrapped text), képekre, vagy egy triviális oldalnál többre van szüksége, a bájt könyvelés (byte bookkeeping), amit a pdftk javított (patched) Ön helyett, az egész munkává válik, és egy olyan könyvtárat szeretne, amelyik birtokolja (owns) ezt. Ugyanazt az öt objektumot továbbra is meg kell írni, de egy könyvtár kiszámít minden eltolást, kezeli a betűtípus és erőforrás szótárakat (resource dictionaries), és tömöríti a tartalomfolyamokat anélkül, hogy Ön egyetlen bájtot is követne (tracking). A Delphi-ben és a C++Builder-ben a HotPDF Component ezt az egész fájlt egy maroknyi hívásra csökkenti: állítsa be a dokumentumot, hívja meg a BeginDoc, SetFont és TextOut parancsokat, hogy ugyanazt az üdvözlést elhelyezze, majd az EndDoc parancsot egy helyes katalógus, oldalfa, xref és trailer írásához. Az alattuk lévő objektumok megértése az, ami lehetővé teszi, hogy érveljen a kimenetről, amikor egy dokumentum nem úgy renderelődik, ahogy azt várta