Teknisk artikkel

Den logiske objektmodellen for PDF: Typer, referanser og struktur

En PDF-fil er dypest sett en samling objekter som peker på hverandre. Hvis du fjerner komprimeringen, bokføringen av kryssreferansene og byteforskyvningene, sitter du igjen med en graf: et lite sett med typede verdier, koblet sammen av referanser, med rot i et enkelt objekt som leseren vet hvordan man finner. Alt en PDF kan uttrykke, fra et tekstavsnitt til en innebygd skrifttype til en digital signatur, er bygget av åtte primitive objekttyper og regelen som lar ett objekt referere til et annet. Lær deg disse, og resten av formatet leses som en komposisjon i stedet for et mysterium

Dette er det logiske laget i PDF, definert i ISO 32000-1 klausul 7.3, og det sitter ett nivå over det fysiske filoppsettet (overskriften, brødteksten, kryssreferansetabellen og avslutningen, som er sitt eget emne i teknisk oversikt over PDF-filstruktur). Den logiske modellen er hva disse bytene betyr når de er tolket. En visningsprogramvare leser filen baklengs for å finne avslutningen (trailer), følger den til roten, og derfra folder dokumentet seg ut som objekter som refererer til objekter. Dette er delen du må forholde deg til når du feilsøker en feilformet side, skriver en parser, eller stoler på at et bibliotek setter sammen et dokument

Åtte objekttyper, og ingenting annet

PDF definerer nøyaktig åtte grunnleggende objekttyper. Hver verdi i et dokument er en av dem, noe som er det som holder formatet håndterbart til tross for omfanget

Boolske verdier (Booleans) er nøkkelordene true og false. De slår flagg av og på, som for eksempel om en kommentar skrives ut

Tall (Numbers) kommer i to varianter som spesifikasjonen behandler som én type: heltall som 42 og reelle tall som 3.14 eller -0.002. PDF har ingen eksponentnotasjon, så du vil aldri se 1e6 i en i samsvar værende fil. Koordinater, skriftstørrelser og rotasjonsvinkler er alle tall

Strenger (Strings) inneholder sekvenser av byter, skrevet enten i parentes, (Hello), eller i vinkelparenteser som heksadesimalt, <48656C6C6F>. Begge notasjonene koder identisk innhold; heksadesimalt er nødutgangen for byter som er problematiske inni parenteser. Strenger bærer tekst, men de er først og fremst byter, noe som har betydning i det øyeblikket du håndterer noe utover ASCII

Navn (Names) er atomiske symboler introdusert av en skråstrek: /Type, /Pages, /MediaBox. Et navn er ikke en streng; det er en identifikator, brukt som en ordboksnøkkel (dictionary key) eller en opplistet verdi, og to navn er bare like hvis de samsvarer byte for byte. Skråstreken er syntaks, ikke en del av navnet. Dette forvirrer nybegynnere som behandler /Times-Roman og strengen (Times-Roman) som utskiftbare; formatet gjør ikke det

Matriser (Arrays) er ordnede, heterogene lister i firkantparenteser: [0 0 612 792] er et siderektangel, og en matrise kan blande typer fritt, inkludert referanser til andre objekter. Ordbøker (Dictionaries) er arbeidshesten. Skrevet mellom << og >>, tilordner en ordbok navnenøkler til verdier av hvilken som helst type, og nesten hver meningsfulle struktur i PDF, side, katalog, skrifttype, kommentar, er en ordbok med en /Type-nøkkel som erklærer hva det er

Strømmer (Streams) er ordbøker med en hale av rå byter mellom nøkkelordene stream og endstream. Ordboken beskriver bytene (lengden deres, og eventuelle filtre som FlateDecode som komprimerer dem), og bytene bærer den voluminøse lasten: instruksjoner for sideinnhold, innebygde skrifttypeprogrammer, bilder. En strøm er hvor PDF legger alt som er for stort eller for binært til å sitte på linje (inline)

Den åttende typen er null-objektet, nøkkelordet null. Det er en reell verdi, adskilt fra at en nøkkel mangler. En ordboksoppføring satt til null behandles som om den ikke var til stede, og en referanse som løser seg til et ikke-eksisterende objekt gir også null i stedet for en feil. Den tilgivende oppførselen er bevisst: den lar en skadet fil brytes ned i stedet for å nekte å åpne. Det er ingen niende type; alt PDF uttrykker kommer fra hvordan disse åtte kombineres

Direkte verdier, indirekte objekter og referanser

Hvilken som helst av de åtte typene kan vises på to måter. Et direkte objekt skrives på plass, som 612 inni en MediaBox-matrise. Et indirekte objekt gis en identitet slik at andre objekter kan peke på det: to heltall, et objektnummer og et generasjonsnummer, som pakker definisjonen inn i obj og endobj:

12 0 obj
<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>
endobj

Dette er objekt 12, generasjon 0, en ordbok for skrifttyper. Hvor som helst ellers i filen, refererer et annet objekt til det med en indirekte referanse: de samme to tallene etterfulgt av nøkkelordet R, 12 0 R. Referansen er en peker. Når en sides ressursordbok sier /Font << /F1 12 0 R >>, navngir den objekt 12 som skrifttypen bak ressursnavnet /F1, uten å kopiere skrifttypens definisjon inn på siden

Generasjonsnummeret eksisterer for slettinger og gjenbruk. Når et objekt frigjøres og plassen gjenbrukes, økes generasjonen slik at en foreldet 12 0 R ikke kan løse seg til den nye leietakeren av plass 12. Nyskrevne filer er nesten alle generasjon 0, men en tungt redigert fil kan bære høyere tall, og en parser som ignorerer generasjonen vil til slutt lese feil objekt

Indireksjon er det som gjør PDF effektivt og redigerbart. En skrifttype, et bilde eller et fargerom kan defineres én gang og refereres fra hundre sider. En liten endring kan legges til som en ny revisjon som erstatter et enkelt objekt i stedet for å skrive om filen. Kryssreferansetabellen er indeksen som gjør et objektnummer til en byteforskyvning, slik at leseren hopper rett til 12 0 obj uten å skanne, men det er en fysisk optimalisering. Logisk sett er alt du trenger å vite at 12 0 R betyr "objektet identifisert som 12 0."

Katalogen: der hvert dokument begynner

Å løse referanser må starte et sted, og det stedet er trailerens /Root-oppføring, som peker på dokumentkatalogen (document catalog): roten til objektgrafen, en ordbok med /Type /Catalog. Leseren når den først fordi traileren blir funnet først, og derfra er alle andre deler av dokumentet tilgjengelig ved å følge referanser

Katalogen bærer bare to strengt nødvendige oppføringer: dens /Type, og /Pages, en indirekte referanse til roten av sidetreet. Resten er valgfrie og beskriver dokumentomfattende oppførsel i stedet for innhold: /Outlines peker på bokmerketreet, /Names inneholder navnetrær nøklet av streng, /Metadata refererer til en XMP-metadatastrøm, og /PageMode og /PageLayout foreslår hvordan en visningsprogramvare skal åpne dokumentet. Ingen av disse er nødvendige for å gjengi en side; de konfigurerer opplevelsen rundt sidene. Bokmerke-, metadata- og kommentarstrukturene som henger av katalogen, tas opp i artikkelen om PDF-metadata, bokmerker og kommentarer

Diagrammet nedenfor viser hvor objektkroppen sitter i den omkringliggende filen. Katalogen og sidetreet lever inni den kroppen som vanlige indirekte objekter; overskriften, kryssreferansetabellen og traileren rundt dem er det fysiske stillaset som lar en leser finne dem

Diagram av en PDF-fils fire fysiske seksjoner: en versjonsoverskrift, en kropp som inneholder dokumentobjektene inkludert katalogen og sidetreet, en kryssreferansetabell av objektforskyvninger, og en trailer som peker på roten

Sidetreet: et balansert hierarki av sider

Fra /Pages forgrener dokumentet seg inn i sidetreet, der PDF-ens valg av en graf fremfor en flat liste lønner seg. Sider er ikke lagret som en enkel sekvens; de henger fra et tre der de indre nodene er sidetrenoder (/Type /Pages) og bladene er sideobjekter (/Type /Page). En indre node lister opp barna sine i en /Kids-matrise og registrerer i /Count hvor mange bladsider som bor under den. Hver node unntatt roten bærer en /Parent-referanse tilbake opp, slik at treet kan navigeres i begge retninger

2 0 obj                                  % root of the page tree
<< /Type /Pages /Kids [3 0 R 4 0 R] /Count 3 >>
endobj

3 0 obj                                  % a leaf page
<< /Type /Page /Parent 2 0 R
   /MediaBox [0 0 612 792]
   /Resources << /Font << /F1 12 0 R >> >>
   /Contents 5 0 R >>
endobj

4 0 obj                                  % an interior node grouping two more pages
<< /Type /Pages /Parent 2 0 R /Kids [6 0 R 7 0 R] /Count 2 >>
endobj

Her er objekt 2 roten, med tre sider under seg: bladsiden 3, pluss to til som kan nås gjennom indre node 4. Rotens /Count på 3 må tilsvare de totale bladene under den, og en telling som ikke stemmer overens med den faktiske strukturen er en vanlig måte en håndredigert fil går feil på. Poenget med treet er lokalitet av tilgang. En leser som åpner side 900 i et tusensiders dokument, går ikke gjennom 900 objekter; den går ned en håndfull noder, fordi et velformet tre forblir grunt og balansert. Å bygge et slikt tre for hånd er fiklete nok til at det er verdt å se fra begynnelse til slutt, noe gjennomgangen av å bygge et PDF-dokument fra bunnen av gjør

Treet tjener sin andre oppgave gjennom arv (inheritance). En håndfull sideattributter, /Resources, /MediaBox, /CropBox og /Rotate, kan settes på en indre node og utelates fra de enkelte sidene, som deretter arver den nærmeste forfaderens verdi. Sett /MediaBox én gang på roten, og hvert blad får samme sidestørrelse uten å gjenta det; en side som trenger å være annerledes, erklærer sin egen. Dette er det ene stedet i objektmodellen der en verdis mening avhenger av et objekts posisjon i treet, ikke bare av dets eget innhold

Hva en bladside faktisk inneholder

Et sideobjekt er koblingspunktet mellom den strukturelle modellen og det synlige innholdet. Dets /Contents-oppføring refererer til én eller flere innholdsstrømmer, tegneoperatørene som maler tekst og grafikk på siden. Dens /Resources-ordbok navngir skrifttypene, bildene og fargerommene som disse operatørene stoler på, der hver oppføring er en indirekte referanse til et objekt som deles på tvers av sider. /MediaBox gir siderektangelet i punkter (1/72 tomme), og oppføringer som /Rotate og /CropBox justerer hvordan den presenteres

Den arbeidsdelingen er hele modellen i miniatyr. Sideordboken er struktur: typede oppføringer og referanser som sier hva siden er og hva den tegner med. Innholdsstrømmen er instruksjoner: en separat, komprimerbar klump som sier hvordan man skal tegne. Skrifttypen bak /F1 er en delt ressurs, definert én gang og pekt på hvor enn den brukes. Ordbok, strøm og referanse samarbeider om å gjengi én side, og de samme mønstrene skaleres til hele dokumentet. Innholdsstrømoperatørene i den klumpen dekkes separat for tekst og skrifttyper og for grafikk og visuelle elementer

Hvorfor det er verdt å kjenne denne modellen

De fleste utviklere møter objektmodellen bare når noe går i stykker: en side gjengis blank fordi dens /Contents-referanse dingler, tekst kommer out som bokser fordi en skrifttyperessurs aldri ble bygd inn, et verktøy rapporterer en /Count som ikke samsvarer med sidene det kan finne. Hver av disse er en uttalelse om grafen, og å lese grafen direkte er bedre enn å gjette. De åtte typene og referanseregelen er et lite nok vokabular til å holde i hodet, og når du først ser en PDF som objekter som peker på objekter, slutter feilformede filer å være ugjennomsiktige

Når det er sagt, er det sjelden riktig å skrive modellen for hånd utover for læring. Å holde kryssreferanseforskyvninger, generasjonsnumre, sidetretellinger og strømlengder konsistente på tvers av redigeringer, er den typen bokføring et bibliotek eksisterer for å håndtere. I produksjon håndterer et modent PDF-utviklingsbibliotek objektgrafen mens det lar deg tenke i sider og innhold. Å kjenne modellen lønner seg fortsatt: du forstår hva biblioteket bygger under, og hvorfor