Teknisk artikel

PDF's logiske objektmodel: Typer, referencer og struktur

En PDF-fil er inderst inde en samling af objekter, der peger på hinanden. Fjern komprimeringen, krydsreference-bogholderiet og byteforskydningerne, og det, der er tilbage, er en graf: et lille sæt maskinskrevne værdier, forbundet sammen af referencer, forankret i et enkelt objekt, læseren ved, hvordan man finder. Alt, hvad en PDF kan udtrykke, fra et tekstafsnit til en indlejret skrifttype til en digital signatur, er bygget af otte primitive objekttyper og reglen, der lader et objekt referere til et andet. Lær dem, og resten af formatet læses som komposition snarere end mysterium

Dette er det logiske lag af PDF, defineret i ISO 32000-1 klausul 7.3, og det sidder et niveau over det fysiske fil-layout (headeren, kroppen, krydsreferencetabellen og traileren, som er sit eget emne i den tekniske oversigt over PDF-filstruktur). Den logiske model er, hvad disse bytes betyder, når de er analyseret. En fremviser læser filen baglæns for at finde traileren, følger den til roden, og derfra folder dokumentet sig ud som objekter, der refererer til objekter. Det er den del, du ræsonnerer over, når du fejlsøger en misdannet side, skriver en parser eller stoler på et bibliotek til at samle et dokument

Otte objekttyper, og intet andet

PDF definerer præcis otte grundlæggende objekttyper. Enhver værdi i et dokument er en af dem, og det er det, der holder formatet medgørligt på trods af dets rækkevidde

Boolske værdier er nøgleordene true og false. De slår flag til og fra, såsom om en annotering udskrives

Tal kommer i to varianter, som specifikationen behandler som én type: heltal som 42 og reelle tal som 3.14 eller -0.002. PDF har ingen eksponentnotation, så du vil aldrig se 1e6 i en konform fil. Koordinater, skriftstørrelser og rotationsvinkler er alle tal

Strenge (Strings) indeholder sekvenser af bytes, skrevet enten i parentes, (Hello), eller i vinkelparenteser som hexadecimale, <48656C6C6F>. Begge notationer indkoder identisk indhold; hex er nødudgangen for bytes, der er akavede inde i parenteser. Strenge bærer tekst, men de er først og fremmest bytes, hvilket betyder noget i det øjeblik, du håndterer andet end ASCII

Navne (Names) er atomare tokens introduceret af en skråstreg: /Type, /Pages, /MediaBox. Et navn er ikke en streng; det er en identifikator, der bruges som en ordbogsnøgle eller en optalt værdi, og to navne er kun ens, hvis de matcher byte for byte. Skråstregen er syntaks, ikke en del af navnet. Dette spænder ben for nytilkomne, der behandler /Times-Roman og strengen (Times-Roman) som udskiftelige; det gør formatet ikke

Arrays er ordnede, heterogene lister i firkantede parenteser: [0 0 612 792] er et siderektangel, og et array kan blande typer frit, herunder referencer til andre objekter. Ordbøger (Dictionaries) er arbejdshesten. Skrevet mellem << og >> knytter en ordbog navnenøgler til værdier af enhver type, og næsten enhver meningsfuld struktur i PDF, side, katalog, skrifttype, annotering, er en ordbog med en /Type nøgle, der erklærer, hvad det er

Strømme (Streams) er ordbøger med en hale af rå bytes mellem stream og endstream-nøgleordene. Ordbogen beskriver bytes (deres længde og eventuelle filtre såsom FlateDecode, der komprimerer dem), og bytene bærer den voluminøse nyttelast: sideindholdsinstruktioner, indlejrede skrifttypeprogrammer, billeder. En strøm er dér, hvor PDF placerer alt for stort eller for binært til at sidde inline

Den ottende type er nul-objektet (null object), nøgleordet null. Det er en reel værdi, der adskiller sig fra, at en nøgle er fraværende. En ordbogspost, der er sat til null, behandles, som om den ikke var til stede, og en reference, der løses til et ikke-eksisterende objekt, giver også null frem for en fejl. Denne tilgivende adfærd er bevidst: den lader en beskadiget fil forringes i stedet for at nægte at åbne. Der er ingen niende type; alt hvad PDF udtrykker, kommer fra, hvordan disse otte kombineres

Direkte værdier, indirekte objekter og referencer

Enhver af disse otte typer kan optræde på to måder. Et direkte objekt er skrevet på plads, ligesom 612 inde i et MediaBox-array. Et indirekte objekt får en identitet, så andre objekter kan pege på det: to heltal, et objektnummer og et generationsnummer, der indpakker definitionen i obj og endobj:

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

Dette er objekt 12, generation 0, en skrifttypeordbog. Hvor som helst ellers i filen refererer et andet objekt til det med en indirekte reference: de samme to numre efterfulgt af nøgleordet R, 12 0 R. Referencen er en pointer. Når en sides ressourceordbog siger /Font << /F1 12 0 R >>, navngiver den objekt 12 som skrifttypen bag ressourcenavnet /F1, uden at kopiere skrifttypens definition ind på siden

Generationsnummeret findes til sletninger og genbrug. Når et objekt frigøres og dets slot genbruges, stiger generationen, så en forældet 12 0 R ikke kan løses til den nye lejer af slot 12. Nyskrevne filer er næsten alle generation 0, men en stærkt redigeret fil kan bære højere tal, og en parser, der ignorerer generationen, vil i sidste ende læse det forkerte objekt

Indirektion er det, der gør PDF effektiv og redigerbar. Ét skrifttype-, billed- eller farverum kan defineres én gang og refereres fra hundrede sider. En lille ændring kan tilføjes som en ny revision, der erstatter et enkelt objekt i stedet for at omskrive filen. Krydsreferencetabellen er det indeks, der forvandler et objektnummer til en byteforskydning, så læseren hopper direkte til 12 0 obj uden at scanne, men det er en fysisk optimering. Logisk set behøver du kun at vide, at 12 0 R betyder "objektet identificeret som 12 0."

Kataloget: Hvor ethvert dokument begynder

Løsning af referencer skal starte et sted, og det sted er trailerens /Root-post, som peger på dokumentkataloget: roden af objektgrafikken, en ordbog med /Type /Catalog. Læseren når den først, fordi traileren findes først, og derfra kan enhver anden del af dokumentet nås ved at følge referencer

Kataloget indeholder kun to strengt krævede poster: dens /Type og /Pages, en indirekte reference til roden af sidetræet. Resten er valgfrie og beskriver dokumentomfattende adfærd snarere end indhold: /Outlines peger på bogmærketræet, /Names rummer navnetræer nøglet af streng, /Metadata refererer til en XMP-metadatastrøm, og /PageMode og /PageLayout foreslår, hvordan en fremviser skal åbne dokumentet. Ingen af dem er nødvendige for at gengive en side; de konfigurerer oplevelsen omkring siderne. Bogmærke-, metadata- og annoteringsstrukturerne, der hænger ud fra kataloget, tages op i artiklen om PDF-metadata, bogmærker og annoteringer

Diagrammet nedenfor viser, hvor objektkroppen sidder i den omgivende fil. Kataloget og sidetræet lever inde i den krop som almindelige indirekte objekter; headeren, krydsreferencetabellen og traileren omkring dem er det fysiske stillads, der lader en læser lokalisere dem

Diagram af en PDF-fils fire fysiske sektioner: en versionsoverskrift, en krop der indeholder dokumentobjekterne inklusiv kataloget og sidetræet, en krydsreferencetabel over objektforskydninger, og en trailer der peger på roden

Sidetræet: Et afbalanceret hierarki af sider

Fra /Pages forgrener dokumentet sig i sidetræet, hvor PDF's valg af en graf over en flad liste betaler sig. Sider gemmes ikke som en simpel sekvens; de hænger fra et træ, hvis indre noder er sidetræknuder (/Type /Pages), og hvis blade er sideobjekter (/Type /Page). En indre knude lister sine børn i et /Kids-array og registrerer, i /Count, hvor mange bladsider, der bor under den. Hver knude undtagen roden bærer en /Parent reference tilbage op, så træet går 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 roden med tre sider under den: bladsiden 3, plus to mere, der kan nås gennem den indre knude 4. Rodens /Count på 3 skal svare til de samlede blade under den, og en optælling, der er uenig med den faktiske struktur, er en almindelig måde en håndredigeret fil går galt. Pointen med træet er lokalitet for adgang. En læser, der åbner side 900 i et dokument på tusinde sider, går ikke gennem 900 objekter; den går ned ad en håndfuld noder, fordi et velformet træ forbliver overfladisk og afbalanceret. At bygge et sådant træ i hånden er besværligt nok til at være værd at se ende-til-ende, hvilket gennemgangen af at bygge et PDF-dokument fra bunden gør

Træet tjener sin anden opgave gennem arv (inheritance). En håndfuld sideattributter, /Resources, /MediaBox, /CropBox og /Rotate, kan indstilles på en indre knude og udelades fra de enkelte sider, som derefter arver den nærmeste forfaders værdi. Sæt /MediaBox én gang på roden, og hvert blad får den samme sidestørrelse uden at gentage den; en side, der skal afvige, erklærer sin egen. Dette er det eneste sted i objektmodellen, hvor en værdis betydning afhænger af et objekts position i træet, ikke kun af dets eget indhold

Hvad en bladside faktisk rummer

Et sideobjekt er forbindelsespunktet mellem den strukturelle model og det synlige indhold. Dens /Contents-post refererer til en eller flere indholdsstrømme, tegneoperatørerne, der maler tekst og grafik ind på siden. Dens /Resources-ordbog navngiver de skrifttyper, billeder og farverum, som disse operatører er afhængige af, hver post en indirekte reference til et objekt delt på tværs af sider. /MediaBox angiver siderektanglet i punkter (1/72 tomme), og indtastninger som /Rotate og /CropBox justerer, hvordan det præsenteres

Den arbejdsdeling er hele modellen i miniature. Sideordbogen er struktur: indtastede indlæg og referencer, der siger, hvad siden er, og hvad den tegner med. Indholdsstrømmen er instruktioner: en separat, komprimerbar klat (blob), der siger, hvordan man tegner. Skrifttypen bag /F1 er en delt ressource, defineret én gang og peget på, hvor end den bruges. Ordbog, strøm og reference samarbejder for at gengive én side, og de samme mønstre skalerer til hele dokumentet. Indholdsstrømoperatørerne inde i den blob dækkes separat for tekst og skrifttyper og for grafik og visuelle elementer

Hvorfor denne model er værd at kende

De fleste udviklere møder kun objektmodellen, når noget går i stykker: en side gengives tom, fordi dens /Contents-reference dingler, tekst kommer ud som kasser, fordi en skrifttyperessource aldrig blev indlejret, et værktøj rapporterer en /Count, der ikke matcher de sider, det kan finde. Hver af disse er et udsagn om grafen, og at læse grafen direkte slår gætterier. De otte typer og referencereglen er et lille nok ordforråd til at holde i hovedet, og når du først ser en PDF som objekter, der peger på objekter, holder fejlformede filer op med at være uigennemskuelige

Når det er sagt, er det sjældent det rigtige valg at skrive modellen i hånden udover at lære det. At holde krydsreferenceforskydninger, generationsnumre, sidetræoptællinger og strømlængder konsistente på tværs af redigeringer er den slags bogholderi, et bibliotek eksisterer til at håndtere. I produktion håndterer et modent PDF-udviklingsbibliotek objektgrafen, mens du overlader det til dig at tænke i sider og indhold. At kende modellen betaler sig stadig: Du forstår, hvad biblioteket bygger nedenunder, og hvorfor