En PDF er i bund og grund en ren tekstbeholder. Åbn de fleste filer i en hex-editor, og toppen er læsbar: en versionskommentar, derefter en række nummererede objekter, og så et lille indeks og en peger helt i bunden, der fortæller en læser, hvor den skal starte. Fjern komprimering, og formatet er tilgængeligt nok til, at du kan indtaste et arbejdsdokument i et tekstredigeringsprogram og få en fremviser til at åbne det. At gøre det én gang lærer dig mere om, hvordan PDF hænger sammen, end nogen mængde af at læse specifikationen, fordi du manuelt skal forbinde objekterne med hinanden, og filen nægter at åbne, indtil du får ledningsføringen rigtigt
Denne gennemgang bygger den mindste PDF, der rent faktisk gengiver noget: én side, ordene "Hello, World!" i en indbygget skrifttype, på US Letter-papir. Den færdige fil har brug for præcis fem objekter og et par linjers bogføring omkring dem. Vi vil skrive objekterne først og derefter samle header, krydsreferencetabel og trailer, der binder dem til en fil, en læser vil acceptere
De fem objekter, en fremviser insisterer på
En læser scanner ikke en PDF fra top til bund på udkig efter indhold. Den starter ved traileren, følger en reference til dokumentkataloget og går gennem en kæde af objekter derfra. Hvert objekt på den kæde skal eksistere, eller åbningen mislykkes. For et et-sides dokument er kæden kort, og hvert link har en enkelt opgave:
- Catalog (Katalog) er roden. Det er det objekt, traileren peger på, og dens eneste krævede indtastning her er en reference til sidetræet
- Pages (Sider) er sidetræknudepunktet. Den viser siderne i dokumentet og rapporterer, hvor mange der er
- Page (Side) beskriver én fysisk side: dens størrelse, de ressourcer, den tegner med, og hvilken indholdsstrøm der maler den
- Content stream (Indholdsstrøm) indeholder tegneoperatørerne, de postfix-kommandoer, der placerer tekst og grafik på den side
- Font (Skrifttype) erklærer den skrifttype, som indholdsstrømmen refererer til. Brug en af de 14 standardskrifttyper, og du behøver ikke at indlejre noget
Hvert objekt er nummereret og adresserbart. Et indirekte objekt skrives som N 0 obj ... endobj, hvor N er objektnummeret og 0 er dets generationsnummer (altid 0 i en fil, du skriver frisk). Alle andre steder i filen peger du på det objekt med en reference: 5 0 R betyder "objekt 5." Disse referencer er ledningsføringen. Kataloget rummer 2 0 R i vores nummerering for at nå sidetræet, sidetræet rummer en reference tilbage ned til siden og så videre. Få et nummer forkert, og læseren følger en dinglende peger ud i ingenting
Navne, ordbøger og strømme
Tre syntaksdele bærer næsten alt. Et navn starter med en skråstreg: /Type, /Page, /F0. Navne er store og små bogstaver følsomme identifikatorer, ikke strenge, og PDF bruger dem til ordbogsnøgler og til at tagge, hvad et objekt er. En ordbog (dictionary) er et sæt nøgle-værdi-par pakket ind i dobbelte vinkelparenteser, hvor hver nøgle er et navn: << /Type /Page /MediaBox [0 0 612 792] >>. Værdier kan være tal, navne, arrays i firkantede parenteser, referencer eller indlejrede ordbøger. De fleste PDF-objekter er ordbøger
En strøm (stream) er en ordbog efterfulgt af en blok af bytes mellem nøgleordene stream og endstream. Det er her, tegneoperatører til sider lever, og i rigtige filer, hvor komprimerede billeder og indlejrede skrifttyper også bor. Strømordbogen beskriver bytene; i en produktionsfil skal den have en /Length-indtastning, der angiver det nøjagtige antal bytes, og ofte et /Filter såsom /FlateDecode, når dataene er komprimeret. Vi kommer til at støtte os til et værktøj til at udfylde /Length, fordi at tælle bytes i hånden er den del af denne øvelse, der ikke har noget uddannelsesmæssigt afkast, og en stor chance for en 'off-by-one'-fejl, der ødelægger filen
At skrive objekterne
Her er de fem objekter i rækkefølge. Den koordinatdetalje at huske på, før du læser indholdsstrømmen: PDF måler fra nederste venstre hjørne af siden i punkter, hvor et punkt er 1/72 tomme, og Y vokser opad. En US Letter-side er 612 gange 792 punkter, så 50 700 sidder nær øverst til venstre, ikke i bunden
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
Læs referencerne, og strukturen falder ud. Objekt 1, kataloget, peger dens /Pages-post mod objekt 2. Objekt 2, sidetræet, lister objekt 3 i /Kids og deklarerer /Count 1. Objekt 3, siden, peger /Parent tilbage op til objekt 2 (træet og siden refererer til hinanden, hvilket er påkrævet), dimensionerer sig selv med /MediaBox, blotlægger skrifttypen under det lokale navn /F0 i dens /Resources, og navngiver objekt 5 som dets indhold. Objekt 4 er skrifttypen: /BaseFont /Helvetica vælger en af de 14 standard skrifttyper, hver konform læser allerede har, så der er intet at indlejre. Objekt 5 er indholdsstrømmen
Hvad indholdsstrømmen faktisk siger
Strømlegemet er et lille program i PDF'ens sidebeskrivelsessprog, som er postfix: operander kommer først, og derefter operatoren, der forbruger dem. Fem linjer gør arbejdet. BT og ET åbner og lukker et tekstobjekt; alt, hvad der placerer eller viser tekst, skal sidde imellem dem. /F0 36 Tf indstiller den aktuelle skrifttype til ressourcen ved navn /F0 ved 36 punkter (Tf er "indstil tekstskrifttype og størrelse"). 50 700 Td flytter tekstpositionen til (50, 700) i sidekoordinater. (Hello, World!) Tj viser strengen, som PDF skriver som bogstavelig tekst i parentes, ved hjælp af Tj til at male den på den aktuelle position. Udelad BT/ET og en streng læser afviser tekstoperatørerne; glem at indstille en skrifttype før Tj, og der er ingen aktuel skrifttype at tegne med
/Length 44 i strømordbogen er antallet af bytes mellem stream og endstream, og det skal være nøjagtigt. Dette er den værdi, der er værd at overlade til et værktøj frem for at tælle nylinjer i hånden, især fordi om din editor skriver linjeafslutninger som LF eller CRLF ændrer totalen
Header, xref og trailer
Objekterne er indholdet. Tre strukturelle dele gør dem til en fil. Den første er overskriften (header), den allersidste linje, der navngiver format og version:
%PDF-1.7
%'et begynder en kommentar i PDF-syntaks, men en læser behandler netop denne kommentar som formatsignaturen og læser versionen fra den. En rigtig forfatter følger den straks med en anden kommentarlinje med højbits-bytes, et tip til filoverførselsværktøjer om, at filen er binær og ikke må forvanskes som tekst
Til sidst i filen kommer krydsreferencetabellen (cross-reference table), indekset der muliggør tilfældig adgang. Den registrerer byte-forskydningen for hvert objekt fra starten af filen, så en læser kan søge direkte til objekt 3 uden at analysere objekter 1 og 2 først. Tabellen er stiv: poster har fast bredde, 20 bytes hver inklusive linjeafslutningen, formateret som en 10-cifret forskydning, en 5-cifret generation, et nøgleord (n for i-brug, f for fri), og en to-byte terminator. En korrekt tabel for vores seks poster (objekt 0 er altid fri-liste hovedet) ser sådan ud:
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
Disse offsets er den skrøbelige del af at skrive PDF i hånden. Hver enkelt er den nøjagtige byteposition, hvor det tilsvarende N 0 obj begynder, og enhver forskydning skifter i det øjeblik, du tilføjer et tegn et vilkårligt sted over det. Traileren er det indgangspunkt, en læser bruger sidst og først: /Root 1 0 R navngiver kataloget, /Size 6 angiver objektantallet, og startxref 408 giver byteforskydningen for selve ordet xref. En læser åbner filen, hopper til slutningen, læser startxref, søger til krydsreferencetabellen og derfra når kataloget og alt derunder. %%EOF markerer den sidste byte
Lad et værktøj rette bytetællingerne
Offsets ovenfor er illustrative; i praksis vil de være forkerte, når du er færdig med at skrive, fordi de afhænger af det nøjagtige bytelayout for din fil. I stedet for at genberegne dem, skriv strukturen med pladsholderværdier og lad et hjælpeprogram genopbygge krydsreferencetabellen og strømlængderne. Den gratis, cross-platform pdftk gør dette i én arbejdsgang:
pdftk hello-draft.pdf output hello.pdf
Den analyserer dine objekter, genberegner hver byteforskydning, udfylder de korrekte /Length-værdier, skriver en gyldig xref-tabel og trailer og udsender hello.pdf. Åbn den i en hvilken som helst fremviser, og du får én side med "Hello, World!" i 36-punkts Helvetica nær toppen. Qpdf gør det samme stykke arbejde, og mange seere vil også reparere en let misdannet fil i farten. Pointen med at støtte sig til et værktøj her er ikke dovenskab; det er, at offset-aritmetikken er den ene del af formatet med nul konceptuelt indhold og den højeste fejlrate, så ved at automatisere det, forbliver strukturen den ting, du lærer
Hvorfor dette skalerer til rigtige dokumenter
Intet ved en hundrede siders rapport ændrer den form, du lige har bygget. Kataloget sidder stadig ved roden, sidetræet samler stadig siderne, og hver side peger stadig på dets ressourcer og en indholdsstrøm. Det, der vokser, er bredden, ikke rygsøjlen: sidetræets grene, så en læser kan springe hele undertræer over, indholdsstrømme bærer hundreder af operatører i stedet for fem, skrifttyper bliver indlejret som deres egne strømobjekter med breddetabeller og kodninger, og billeder ankommer som strømme med billedspecifikke filtre. Moderne filer har også en tendens til at pakke mange objekter i komprimerede objektstrømme og erstatte den almindelige xref-tabel med en krydsreferencestrøm, hvilket er grunden til, at åbning af en rigtig PDF i et tekstredigeringsprogram normalt viser en væg af binær. Modellen under er identisk med den i din håndlavede fil. For den bredere objektgrafik og hvordan kataloget, sidetræet og ressourceordbøgerne relaterer sig over et større dokument, tager den dybdegående tur i PDF-dokumentstruktur fat, hvor denne slutter, og oversigten over filstruktur dækker trinvise opdateringer, og hvordan traileren kæder sig over revisioner
Fra håndskrift til et bibliotek
At skrive objekter i hånden er en læreøvelse, ikke en produktionsteknik. I det øjeblik du har brug for rigtige skrifttyper, ombrudt tekst, billeder eller mere end en triviel side, bliver det bytebogholderi, som pdftk lappede for dig, hele arbejdet, og du vil have et bibliotek, der ejer det. De samme fem objekter bliver stadig skrevet, men et bibliotek beregner hvert offset, administrerer skrifttype- og ressourceordbøgerne og komprimerer indholdsstrømmene uden at du sporer en enkelt byte. I Delphi og C++Builder reducerer HotPDF Component hele denne fil til en håndfuld opkald: konfigurer dokumentet, kald BeginDoc, SetFont og TextOut for at placere den samme hilsen, derefter EndDoc for at skrive et korrekt katalog, sidetræ, xref og trailer. At forstå objekterne nedenunder er det, der lader dig ræsonnere over outputtet, når et dokument ikke gengives på den måde, du forventede