En PDF-læser starter ikke i begyndelsen af filen. Den starter til sidst. De sidste par bytes indeholder adressen på alt andet, og en parser, der ikke forstår den rækkefølge, vil fejlfortolke formatet fra den første linje. Så den mest nyttige måde at lære PDF på disken på er at lære det, som en læser gør: hale først, derefter hoppe baglæns til kortet og derefter løse objekterne, som kortet peger på
Selve bytene er tydelige nok til at læse i et tekstredigeringsprogram, når intet er komprimeret. Et minimalt et-sides dokument, der tegner "Hello, World!", passer ind på under fem hundrede bytes, og hvert strukturelt element i formatet er synligt i det. Her er hele filen med de fire dele markeret:
%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
Fire dele, altid i denne rækkefølge ned ad filen: en overskrift (header), en krop af objekter, en krydsreferencetabel (xref) og en trailer. Hagen er, at du læser dem i næsten den omvendte rækkefølge. ISO 32000-2 §7.5.1 præsenterer den samme firedelte anatomi, og årsagen til back-to-front adgang er rent praktisk: en læser, der hopper direkte til det objekt, den har brug for, er meget hurtigere end en, der scanner hver byte fra toppen, og den tilfældige adgang er præcis, hvad traileren og krydsreferencetabellen eksisterer for at levere
Headeren er to linjer, og den anden betyder noget
Den første linje er %PDF-1.0. Procenttegnet gør det til en kommentar for så vidt angår syntaksen, men læsere behandler den som filsignaturen og trækker versionsnummeret ud af den. Versionshåndtering er løs i praksis. En læser bygget til PDF 2.0 vil med glæde åbne en fil, der hævder at være 1.0, og de fleste læsere vil forsøge at åbne en fil, hvis erklærede version er forkert, eller hvis versionslinje er begravet lidt nede i filen i stedet for ved byte nul. Tallet er et vink om, hvilke funktioner man kan forvente, ikke en port
Den anden linje er den, som folk sletter ved et uheld og derefter bruger en eftermiddag på at fejlsøge. Den er også en kommentar, men dens nyttelast er fire bytes over ASCII 127. De findes, for at alt, der flytter filen i "teksttilstand", genkender den som binær og stopper med at omskrive linjeafslutninger. En PDF bærer komprimerede strømme, hvis bytes kan matche et vognretur (CR) eller linjeskift (LF) ved et tilfælde; hvis et overførselsværktøj omskriver disse, stemmer strømlængden, der er optaget i ordbogen, ikke længere overens med bytene på disken, og filen er korrupt. High-byte kommentaren er et fyrre år gammelt forsvar mod FTP i ASCII-tilstand, og den er stadig i hver fil, et seriøst værktøj skriver, fordi den fejl, det forhindrer, er tavs og total
Kroppen holder objekterne, hver nummereret
Alt, hvad der udgør dokumentet, lever i kroppen som en flad sekvens af indirekte objekter. Hver åbner med to heltal og obj-nøgleordet, holder dens indhold og lukkes med endobj. Objekt 1 i eksemplet ovenfor er sidetræknuden: 1 0 obj, så en ordbog, så endobj. Det første heltal er objektnummeret, det andet er generationsnummeret. Generation er næsten altid nul i en nyskreven fil; den klatrer kun, når et objektnummer bliver genbrugt på tværs af redigeringer, hvilket er sjældent nok til, at du kan behandle en generation, der ikke er nul, som et tegn på, at filen har været igennem trinvise opdateringer. Indholdet mellem nøgleordene er en ordbog her, skrevet mellem << og >>, men det kunne lige så godt være et tal, en streng, et array eller en strøm
Det, der gør dette til en graf i stedet for en liste, er reference-token 2 0 R. Det betyder "objekt 2, generation 0, hvor det end måtte opholde sig i filen." Sidetræknudepunktet ovenfor indeholder ikke sin side; den peger på objekt 2, som peger på dets ressourcer og indholdsstrøm ved samme mekanisme. Kroppen er lagt ud i den rækkefølge, forfatteren fandt praktisk, og referencerne syer den ind i et træ, der er forankret i kataloget. Position i filen har ingen betydning. Identitet kommer fra objektnummeret, og placering kommer fra krydsreferencetabellen
Krydsreferencetabellen er et indeks over byteforskydninger
Xref-tabellen er det, der gør objektnumre til filpositioner. Det er grunden til, at en læser kan åbne et tusinde siders dokument og gengive side 850 uden at analysere de 849 sider før det. Hver post registrerer præcis, hvor dens objekt begynder, talt i bytes fra starten af 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 faste bredde er bevidst. Hver post er på præcis tyve bytes: en ticifret forskydning (offset), et mellemrum, en femcifret generation, et mellemrum, en encifret type og en to-byte 'end-of-line'. Fordi rækkerne er ensartede, kan en læser indeksere direkte til posten for objekt n ved hjælp af aritmetik snarere end at scanne, så tabellen, der giver tilfældig adgang til kroppen, i sig selv er tilfældig tilgængelig. Linjen 0 6 er en undersektionshoved (subsection header): den siger, at de næste poster beskriver seks objekter, der starter ved nummer 0
Objekt 0 er specielt og altid til stede. Dens type er f for fri, dens generation er 65535, og den leder den linkede liste over gratis objektnumre. I en fil, der aldrig er blevet redigeret, er den gratis liste blot denne ene post, en formalitet. Den fortjener sin plads under trinvise opdateringer, når sletning af et objekt tilføjer dets nummer til den liste, så en senere redigering kan genvinde det. De andre poster er type n for 'i-brug' (in-use), og deres ti-cifrede nummer er den forskydning (offset), du ville søge efter for at læse dette objekts definition
Traileren er startpunktet, og den sidder til sidst
Traileren er den første ting, en læser faktisk forbruger, selvom den er skrevet sidst. En parser åbner filen, søger mod slutningen og går baglæns for at lede efter %%EOF. Lige over den sidder startxref efterfulgt af et enkelt tal, og det tal er byteforskydningen for nøgleordet xref. Med det springer læseren direkte til krydsreferencetabellen uden at have scannet et enkelt 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
Trailerordbogen bærer de to værdier, en læser har brug for, før den kan gøre noget andet. /Root peger på dokumentkataloget, objekt 5 her, som er toppen af objektgrafen og ruten til sidetræet. /Size er optællingen af poster, som krydsreferencetabellen skal indeholde, hvilket er én mere end det højeste objektnummer på grund af den gratis post i slot nul. Fra %%EOF falder hele læsesekvensen ud: find markøren, læs startxref for at lokalisere tabellen, indlæs tabellen for at lære, hvor hvert objekt lever, læs /Root for at finde kataloget, og løs objekter on-demand derfra. Overskriften, der sidder øverst, konsulteres knap nok før sent. Kortet i bunden er det, læseren har brug for først
Trinvise opdateringer tilføjer et andet kort i stedet for at omskrive
Det hale-først-design betaler sig, når en fil ændres. En PDF kan redigeres uden at omskrive nogen af bytene allerede på disken. Nye og ændrede objekter føjes til slutningen, efterfulgt af en ny krydsreferencesektion og en frisk trailer, og den originale fil nedenunder efterlades uberørt. Det eneste nye stykke bogføring er en /Prev-post i den nye trailer, der holder byteforskydningen fra den tidligere krydsreferencetabel:
% ... 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æser starter stadig ved det sidste %%EOF, følger stadig startxref til den seneste tabel, men følger nu /Prev kæden tilbage til ældre tabeller og fletter dem, så den nyeste post for et hvilket som helst objektnummer vinder. Krydsreferencesektionerne danner en lænket liste ned gennem filen, der hver især tilsidesætter den før sig for de objekter, den rører. Et objekt, som en redigering erstattede, eksisterer stadig fysisk ved dens gamle offset; den kan bare ikke længere nås, fordi en senere xref-post peger et nyere sted hen
Dette er mekanismen, der gør underskrevne PDF'er verificerbare. En digital signatur dækker et byteområde af filen, og fordi en trinvis opdatering kun tilføjer, flytter de signerede bytes sig aldrig. Signaturen validerer stadig mod det originale interval, mens senere revisioner sidder ud over det, hver med sin egen xref og trailer. Det er også grunden til, at en PDF kan bære gendannelseshistorik: hvert overgået objekt er stadig på disken under en tidligere krydsreferencesektion, hvilket er en funktion til versionssporing og et ansvar for alle, der troede, at "slet" betød, at bytes var væk
Omkostningerne er vækst. Hver redigering tilføjer; intet genvindes på plads, så en fil, der er revideret mange gange, akkumulerer døde objekter og en lang kæde af xref-sektioner. Løsningen er en fuld omskrivning: Indlæs dokumentet og gem det frisk, hvilket omnummererer de overlevende objekter, sletter de utilgængelige objekter og udsender en enkelt ren krydsreferencetabel. De to strategier handler direkte mod hinanden. Tilføjelse er hurtig og bevarer signaturer og historie; omskrivning er langsommere og kasserer begge, til gengæld for en kompakt fil
Aflæsning af de fire dele i praksis
At kende layoutet er nok til at fejlsøge de fleste "denne fil vil ikke åbne" problemer i hånden. Hvis en læser afviser en PDF, er de sædvanlige skyldige i de to ender, ikke i midten. En afkortet download mister traileren, så startxref eller %%EOF mangler, og læseren har intet indgangspunkt; tolerante læsere falder tilbage til at scanne hele filen for at genopbygge xref'en, hvilket er præcis den langsomme vej, tabellen var beregnet til at undgå. En mislykket teksttilstandsoverførsel korrumperer strøm-bytes, eller offsets holder op med at matche virkeligheden, og objekter indlæses fra den forkerte position. Når forskydningerne i tabellen ikke længere peger på rigtige obj-nøgleord, er filen strukturelt ødelagt, selvom hvert objekt er individuelt i orden
For ny kode er lektien af layoutet at lade et bibliotek eje byte-bogholderiet. Offsets i krydsreferencetabellen skal stemme overens med de faktiske positioner af hvert objekt i byten, traileren skal pege på den rigtige tabel, og trinvise opdateringer skal kædes korrekt gennem /Prev. En indbygget komponent som HotPDF Component til Delphi og C++Builder håndterer alt det, når den skriver en fil, inklusive valget mellem at tilføje en trinvis revision og at omskrive en kompakt. Hvis du ønsker at se den samme struktur bygget op fra ingenting frem for at blive dissekeret, gennemgår følgeartiklen om at bygge et PDF-dokument fra bunden udsendelse af header, objekter, xref og trailer i rækkefølge