En PDF-läsare börjar inte i början av filen. Den börjar i slutet. De sista byten håller adressen till allt annat, och en tolk (parser) som inte förstår den ordningen kommer att feltolka formatet från första raden. Så det mest användbara sättet att lära sig PDF på disk är att lära sig det på samma sätt som en läsare gör: svansen (tail) först, sedan hoppa bakåt till kartan, sedan lösa upp (resolve) de objekt kartan pekar på
Själva byten är tillräckligt enkla att läsa i en textredigerare när inget är komprimerat. Ett minimalt ensidigt dokument som ritar "Hello, World!" ryms på under femhundra byte, och varje strukturellt element i formatet syns i det. Här är hela filen, med de fyra delarna markerade:
%PDF-1.0 % Header
%âãÏÓ
1 0 obj % Body: the object sequence
<<
/Kids [2 0 R]
/Count 1
/Type /Pages
>>
endobj
2 0 obj
<<
/Rotate 0
/Parent 1 0 R
/Resources 3 0 R
/MediaBox [0 0 612 792]
/Contents [4 0 R]
/Type /Page
>>
endobj
3 0 obj
<< /Font << /F0 << /BaseFont /Times-Italic /Subtype /Type1 /Type /Font >> >> >>
endobj
4 0 obj
<< /Length 65 >>
stream
1. 0. 0. 1. 50. 700. cm BT
/F0 36. Tf
(Hello, World!) Tj
ET
endstream
endobj
5 0 obj
<< /Pages 1 0 R /Type /Catalog >>
endobj
xref % Cross-reference table
0 6
0000000000 65535 f
0000000015 00000 n
0000000074 00000 n
0000000192 00000 n
0000000291 00000 n
0000000409 00000 n
trailer % Trailer
<<
/Root 5 0 R
/Size 6
>>
startxref
459
%%EOF
Fyra delar, alltid i denna ordning ner genom filen: en header (huvud), en body (kropp) av objekt, en korsreferenstabell (cross-reference table) och en trailer. Haken är att du läser dem i nästan omvänd ordning. ISO 32000-2 §7.5.1 lägger fram samma fyrdelade anatomi, och anledningen till den bak-och-framvända åtkomsten är rent praktisk: en läsare som hoppar direkt till det objekt den behöver är mycket snabbare än en som skannar varje byte från toppen, och den slumpmässiga åtkomsten (random access) är precis vad trailern och korsreferenstabellen existerar för att tillhandahålla
Headern är två rader, och den andra spelar roll
Den första raden är %PDF-1.0. Procenttecknet gör det till en kommentar vad syntaxen beträffar, men läsare behandlar det som filsignaturen och drar ut versionsnumret ur den. Versionshantering är slapp i praktiken. En läsare byggd för PDF 2.0 öppnar gladeligen en fil som hävdar 1.0, och de flesta läsare gör ett försök med en fil vars deklarerade version är fel eller vars versionsrad ligger begravd en bit in i filen i stället för vid byte noll. Numret är en ledtråd om vilka funktioner som kan förväntas, inte en grind
Den andra raden är den som folk av misstag tar bort och sedan spenderar en eftermiddag med att felsöka. Det är också en kommentar, men dess nyttolast (payload) är fyra byte över ASCII 127. De existerar så att allt som flyttar filen i "textläge" känner igen den som binär och slutar skriva om radslut. En PDF bär på komprimerade strömmar vars byten kan matcha en vagnretur (carriage return) eller nyrad (line feed) av en slump; om ett överföringsverktyg skriver om dessa matchar den strömlängd som registrerats i ordboken inte längre de byten som finns på disken och filen blir korrupt. Den höga-byte-kommentaren är ett fyrtio år gammalt försvar mot FTP i ASCII-läge, och den finns fortfarande i varje fil som ett seriöst verktyg skriver eftersom felet det förhindrar är tyst och totalt
Kroppen håller objekten, var och en numrerad
Allt som utgör dokumentet lever i kroppen som en platt sekvens av indirekta objekt. Varje objekt öppnas med två heltal och nyckelordet obj, håller sitt innehåll och stängs med endobj. Objekt 1 i exemplet ovan är sidträdsnoden (page-tree node): 1 0 obj, sedan en ordbok (dictionary), sedan endobj. Det första heltalet är objektnumret, det andra är generationsnumret. Generationen är nästan alltid noll i en nyskriven fil; det stiger bara när ett objektnummer återanvänds över redigeringar, vilket är tillräckligt ovanligt för att du kan behandla en nollskild generation som ett tecken på att filen har gått igenom inkrementella uppdateringar. Innehållet mellan nyckelorden är en ordbok här, skriven mellan << och >>, men det kan lika gärna vara ett nummer, en sträng, en array eller en ström
Vad som gör detta till en graf snarare än en lista är referens-token 2 0 R. Det betyder "objekt 2, generation 0, var det än råkar bo i filen". Sidträdsnoden ovan innehåller inte sin sida; den pekar på objekt 2, som pekar på sina resurser och sin innehållsström genom samma mekanism. Kroppen är upplagd i den ordning som författaren fann bekväm, och referenserna syr ihop den till ett träd rotat i katalogen. Positionen i filen har ingen betydelse. Identitet kommer från objektnumret, och platsen kommer från korsreferenstabellen
Korsreferenstabellen är ett index över byte-offsets
Xref-tabellen är vad som förvandlar objektnummer till filpositioner. Det är anledningen till att en läsare kan öppna ett tusensidigt dokument och rendera sida 850 utan att tolka (parse) de 849 sidorna före den. Varje post registrerar exakt var dess objekt börjar, räknat i byte från början av filen:
xref
0 6 % 6 entries, starting at object 0
0000000000 65535 f % entry 0: head of the free list
0000000015 00000 n % object 1 begins at byte 15
0000000074 00000 n % object 2 begins at byte 74
0000000192 00000 n % object 3 begins at byte 192
0000000291 00000 n % object 4 begins at byte 291
0000000409 00000 n % object 5 begins at byte 409
Den fasta bredden är avsiktlig. Varje post är exakt tjugo byte: en tiosiffrig offset, ett mellanslag, en femsiffrig generation, ett mellanslag, en en-teckenstyp, och två byten för radslut. Eftersom raderna är enhetliga kan en läsare indexera rakt till posten för objekt n genom aritmetik i stället för att skanna, så tabellen som ger slumpmässig åtkomst till kroppen är i sig slumpmässigt åtkomlig. Raden 0 6 är en underavdelningsheader: den säger att nästa poster beskriver sex objekt med start på nummer 0
Objekt 0 är speciellt och alltid närvarande. Dess typ är f för gratis (free), dess generation är 65535, och den leder den länkade listan över gratis objektnummer. I en fil som aldrig har redigerats är gratislistan bara denna ena post, en formalitet. Den förtjänar sitt uppehälle under inkrementella uppdateringar, när borttagning av ett objekt lägger dess nummer till den listan så att en senare redigering kan återta det. De andra posterna är av typen n för i bruk, och deras tiosiffriga nummer är den offset du skulle söka till för att läsa det objektets definition
Trailern är startpunkten, och den sitter på slutet
Trailern är det första en läsare faktiskt konsumerar, även om den skrivs sist. En tolk öppnar filen, söker sig till slutet och går bakåt och letar efter %%EOF. Precis ovanför den sitter startxref följt av ett enda nummer, och det numret är byte-offseten för nyckelordet xref. Med det hoppar läsaren direkt till korsreferenstabellen utan att ha skannat ett enda objekt:
trailer
<<
/Root 5 0 R % the document catalog
/Size 6 % one more than the highest object number
>>
startxref
459 % byte offset of the xref table
%%EOF
Trailerordboken bär de två värden en läsare behöver innan den kan göra något annat. /Root pekar på dokumentkatalogen, objekt 5 här, vilket är toppen av objektgrafen och vägen till sidträdet. /Size är antalet poster som korsreferenstabellen ska innehålla, vilket är en mer än det högsta objektnumret på grund av gratisposten på plats noll. Från %%EOF faller hela lässekvensen ut: hitta markören, läs startxref för att lokalisera tabellen, ladda tabellen för att lära dig var varje objekt lever, läs /Root för att hitta katalogen, och lös upp objekt vid behov därifrån. Headern, som sitter högst upp, konsulteras knappt förrän sent. Kartan i botten är vad läsaren behöver först
Inkrementell uppdatering lägger till en andra karta i stället för att skriva om
Den svans-först (tail-first) designen lönar sig när en fil ändras. En PDF kan redigeras utan att skriva om några av de byten som redan finns på disk. Nya och modifierade objekt bifogas i slutet, följt av en ny korsreferenssektion och en ny trailer, och originalfilen därunder lämnas orörd. Den enda nya bokföringsdetaljen är en /Prev-post i den nya trailern, som håller byte-offseten för den föregående korsreferenstabellen:
% ... original file, unchanged, ends here ...
6 0 obj % an object added by this edit
<< /Type /Annot /Subtype /Text /Rect [100 700 120 720] >>
endobj
xref % a second xref section, for the new object only
6 1
0000000612 00000 n
trailer
<<
/Root 5 0 R
/Size 7
/Prev 459 % byte offset of the earlier xref table
>>
startxref
680 % offset of this new xref section
%%EOF
En läsare börjar fortfarande vid den slutliga %%EOF, följer fortfarande startxref till den senaste tabellen, men följer nu /Prev-kedjan bakåt till äldre tabeller, och sammanfogar dem så att den nyaste posten för varje objektnummer vinner. Korsreferenssektionerna bildar en länkad lista ner genom filen, där varje överstyr den före den för de objekt den rör. Ett objekt som en redigering ersatte existerar fortfarande fysiskt på sin gamla offset; den är helt enkelt inte längre nåbar, eftersom en senare xref-post pekar någonstans nyare
Detta är mekanismen som gör signerade PDF-filer verifierbara. En digital signatur täcker ett byte-intervall av filen, och eftersom en inkrementell uppdatering bara någonsin lägger till på slutet (appends), flyttas de signerade byten aldrig. Signaturen validerar fortfarande mot det ursprungliga intervallet medan senare revisioner sitter bortom det, var och en med sin egen xref och trailer. Det är också därför en PDF kan bära på återställbar historia: varje ersatt objekt finns fortfarande på disk under en tidigare korsreferenssektion, vilket är en funktion för versionsspårning och en nackdel för den som trodde att "ta bort" betydde att byten var borta
Kostnaden är tillväxt. Varje redigering lägger till; ingenting återtas på plats, så en fil som reviderats många gånger ackumulerar döda objekt och en lång kedja av xref-sektioner. Botemedlet är en fullständig omskrivning: ladda dokumentet och spara det på nytt, vilket omnumrerar de överlevande objekten, kastar de onåbara, och ger ifrån sig en enda ren korsreferenstabell. De två strategierna vägs direkt mot varandra. Att lägga till på slutet är snabbt och bevarar signaturer och historia; omskrivning är långsammare och kasserar båda, i utbyte mot en kompakt fil
Att läsa de fyra delarna i praktiken
Att känna till upplägget (layouten) är tillräckligt för att felsöka de flesta "den här filen kan inte öppnas"-problem för hand. Om en läsare avvisar en PDF är de vanliga syndarna i de två ändarna, inte i mitten. En avbruten nedladdning förlorar trailern, så startxref eller %%EOF saknas och läsaren har ingen startpunkt; toleranta läsare faller tillbaka på att skanna hela filen för att bygga om xref:en, vilket är precis den långsamma väg tabellen var tänkt att undvika. En misslyckad textlägesöverföring korrumperar strömbyten eller offseterna slutar matcha verkligheten, och objekt laddas från fel position. När offseterna i tabellen inte längre pekar på riktiga obj-nyckelord är filen strukturellt trasig även om varje enskilt objekt är bra
För ny kod är läxan av layouten att låta ett bibliotek äga byte-bokföringen. Offseterna i korsreferenstabellen måste överensstämma med de faktiska positionerna för varje objekt ner till exakt byte, trailern måste peka på rätt tabell, och inkrementella uppdateringar måste kedja korrekt genom /Prev. En inbyggd (native) komponent som HotPDF-komponenten för Delphi och C++Builder hanterar allt detta när den skriver en fil, inklusive valet mellan att lägga till en inkrementell revision och att skriva om en kompakt sådan. Om du vill se samma struktur byggas upp från ingenting i stället för dissekerad, går det tillhörande stycket om att bygga ett PDF-dokument från grunden igenom hur man ger ut headern, objekt, xref och trailern i ordning