De anatomie van een PDF-bestand
In een oogopslag
Sectie met titel “In een oogopslag”Open een willekeurige PDF in een gewone teksteditor en het eerste wat je ziet, is geruststellend:
een %PDF-1.x- of %PDF-2.0-header. Het laatste wat je ziet, is %%EOF.
Alles tussen die twee regels is een nette kleine machine om objecten te vinden
op nummer. Deze pagina is een ontleding. We openen het bestand, benoemen elk orgaan, en
laten zien hoe ze met elkaar verbonden zijn.
Het is de structurele tegenhanger van twee buren. Wat een PDF eigenlijk is behandelt het bestand als een object- graaf; incrementele updates gaan over hoe het in de loop der tijd groeit. Deze pagina blijft dicht bij de bytes — de fysieke regio’s die een parser doorloopt, in de volgorde waarin ze op schijf staan.
Waarom dit ertoe doet
Sectie met titel “Waarom dit ertoe doet”Je hebt dit bijna nooit nodig om een PDF te gebruiken. Je hebt het nodig op de dag dat er een misgaat. Een bestand opent in de ene viewer en niet in de andere; een validator meldt een “beschadigde cross-referencetabel”; een ondertekend document faalt plotseling bij verificatie. Geen van die zijn mysteries zodra je de anatomie kunt lezen. Ze zijn een getal dat niet langer overeenkomt met een positie, een regio op de verkeerde plek, of een staart die naar niets wijst.
De indeling kennen verandert “de PDF is corrupt” in een diagnose waar je naar kunt handelen. Het is het verschil tussen je schouders ophalen bij een black box en wijzen naar de exacte byte die liegt.
De korte versie
Sectie met titel “De korte versie”Een conforme PDF heeft vier fysieke onderdelen, in deze bestandsvolgorde (Spec: ISO 32000-2, §7.5.1ISO 32000-2 §7.5.1):
- Een header — één regel,
%PDF-2.0, die de versie benoemt. - Een body — het grootste deel van het bestand: een reeks genummerde indirecte objecten.
- Een cross-referencesectie — een index van objectnummer naar de byte- offset waar dat object staat. Klassieke PDF’s gebruiken een tekst-tabel; PDF 2.0 gebruikt een gecomprimeerde xref-stream.
- Een trailer — een kleine dictionary die het toegangspunt benoemt, gevolgd door
startxref, een offset en%%EOF.
De twist: een lezer begint niet bovenaan. Hij begint onderaan,
leest startxref om de index te vinden, en gebruikt die index om elk object
direct te bereiken. Het bestand wordt van voor naar achter geschreven maar van achter naar voren gelezen.
Hoe NextPDF dit aanpakt
Sectie met titel “Hoe NextPDF dit aanpakt”Laten we de vier regio’s op volgorde doorlopen, met de bytes voor ons.
De header is één regel. NextPDF schrijft %PDF-2.0, en conventioneel een
tweede commentaarregel van high-bit-bytes zodat naïeve transfertools het bestand
als binair behandelen, niet als tekst. Die tweede regel is waarom een PDF die als gewone tekst wordt geopend
een kleine wirwar laat zien direct na de versie.
De body is waar het document leeft. Elk indirect object is een nummer,
een generatie, het sleutelwoord obj, een waarde, en endobj
(Spec: ISO 32000-2, §7.3.10ISO 32000-2 §7.3.10). De waarde is een van enkele
vormen — maar twee dragen vrijwel het hele gewicht:
- Een dictionary,
<< /Key value … >>, is een map van names naar values. De paginaboom, de catalog, de font descriptors: allemaal dictionaries. - Een stream is een dictionary gevolgd door
stream, een blok willekeurige bytes, enendstream. Pagina-inhoud, ingesloten lettertypen en afbeeldingen zijn streams, vrijwel altijd gecomprimeerd. (Hun filters zijn een verhaal apart, verteld in streams en filters.)
Objecten wijzen naar elkaar via een indirect reference — 2 0 R betekent “object 2,
generatie 0.” Dat is de bedrading die een platte lijst objecten verandert in een
graaf.
De cross-referencesectie is het onderdeel dat de meeste mensen zich nooit correct
voorstellen. In de klassieke vorm is het gewone tekst: het sleutelwoord xref, dan
subsecties van vaste-breedte 20-byte-regels (Spec: ISO 32000-2, §7.5.4ISO 32000-2 §7.5.4). Elke regel is een tiencijferige byte-offset, een vijfcijferige
generatie, en een enkele flag — n voor in gebruik, f voor vrij — aangevuld tot
precies twintig bytes zodat een lezer naar elke vermelding kan springen met alleen rekenkunde.
PDF 2.0 vervangt dit door een cross-reference stream: dezelfde index, maar
binair en gecomprimeerd binnen een stream-object gemarkeerd /Type /XRef
(Spec: ISO 32000-2, §7.5.8ISO 32000-2 §7.5.8). Kleiner, en in staat om
objecten te beschrijven die binnen object streams zijn verpakt.
De trailer is de inhoudsopgave van het bestand (Spec: ISO
32000-2, §7.5.5ISO
32000-2 §7.5.5). Hij benoemt /Root — de document catalog, het
enige object waar al het andere aan hangt — en /Size, het objectaantal.
Dan komt de handdruk die achterwaarts lezen mogelijk maakt:
startxref, een byte-offset op een eigen regel, en %%EOF. Een lezer springt naar het
einde, leest die offset, springt direct naar de cross-referencesectie, en is
vertrokken.
- HeaderOne line, %PDF-2.0, naming the version. A binary-marker comment usually follows.
- BodyNumbered indirect objects — dictionaries and streams — referenced by N G R.
- Cross-reference sectionA text table of 20-byte entries, or a compressed /Type /XRef stream in PDF 2.0.
- TrailerNames /Root and /Size, then startxref + offset + %%EOF.
- Read orderA reader starts at %%EOF, follows startxref to the index, then reaches each object directly.
Er is een vijfde regio die een langlevend bestand laat groeien: een incrementele update. Een
wijziging wordt niet ter plaatse geschreven. De gewijzigde objecten, een verse cross-reference-
sectie en een nieuwe trailer worden toegevoegd na de eerste %%EOF, en die
nieuwe trailer draagt /Prev — de offset van de vorige cross-reference-
sectie (Spec: ISO 32000-2, §7.5.6ISO 32000-2 §7.5.6). De secties vormen een
achterwaartse keten; voor elk objectnummer wint de nieuwste vermelding. Omdat de
oorspronkelijke bytes nooit verschuiven, houdt de cryptografische controle over het bytebereik dat een
handtekening daadwerkelijk dekt nog steeds stand na een update. Een latere incrementele
update kan nog steeds wijzigen wat een validator over het document als geheel rapporteert
— of de wijzigingen na ondertekening zijn toegestaan, en wat de
handtekening geacht wordt te certificeren — maar hij kan de ondertekende bytes
zelf niet wijzigen. Die eigenschap is het hele onderwerp van incrementele
updates.
Praktijkvoorbeeld
Sectie met titel “Praktijkvoorbeeld”Hier is de hele anatomie in één minimaal bestand. De getallen onder xref zijn
byte-offsets, en ze moeten exact zijn — wijs één teken voorbij waar een
object begint en een strikte lezer geeft het op.
%PDF-2.01 0 obj<< /Type /Catalog /Pages 2 0 R >>endobj2 0 obj<< /Type /Pages /Kids [3 0 R] /Count 1 >>endobj3 0 obj<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>endobjxref0 40000000000 65535 f0000000009 00000 n0000000058 00000 n0000000115 00000 ntrailer<< /Size 4 /Root 1 0 R >>startxref186%%EOFLees het zoals een parser het doet. Laatste regel: %%EOF. Daarboven: startxref 186,
dus spring naar byte 186, waar xref begint. De tabel zegt dat object 1 op
byte 9 staat. De /Root 1 0 R van de trailer wijst daarheen — de catalog — en vanuit de
catalog loop je /Pages naar de paginaboom en vind je de enkele pagina. Object 0
is altijd de kop van de vrije lijst met generatie 65535, een fossiel uit het
eerste ontwerp van het formaat dat elke lezer nog steeds verwacht te zien.
Veelvoorkomend misverstand
Sectie met titel “Veelvoorkomend misverstand”De valkuil is een PDF lezen als een verhaal — van boven naar beneden, op volgorde. Het is geen
verhaal; het is een index met een achterwaartse wijzer. Objectnummers hoeven niet
opeenvolgend te zijn in het bestand, objecten kunnen in elke fysieke volgorde verschijnen, en een lezer
vertrouwt nooit op hun positie. De enige gezaghebbende kaart is de
cross-referencesectie, en de enige manier om die kaart te vinden is de startxref-
offset helemaal aan het einde.
Het gevolg verrast mensen. Een PDF met een vlekkeloze body en één verkeerd
cijfer in startxref is onleesbaar — de lezer kan de index niet vinden. Een PDF
met zijn objecten in door elkaar gehusselde volgorde maar een correcte cross-referencesectie is
prima. Fysieke positie draagt geen betekenis. De vastgelegde positie
draagt het allemaal.
Limieten en grenzen
Sectie met titel “Limieten en grenzen”Deze pagina beschrijft de fysieke structuur, niet de pagina-inhoud. Hoe markeringen op een
pagina belanden — content-stream-operatoren, tekstweergave, grafische status — is een apart
onderwerp. Ze beschrijft ook een welgevormd bestand. PDF’s in de echte wereld zijn
vaak een beetje kapot en overleven alleen omdat vergevingsgezinde viewers de
cross-referencetabel opnieuw opbouwen door te scannen op obj-sleutelwoorden. Die berging is een
viewergedrag, geen iets dat het formaat garandeert.
| Edition | Availability |
|---|---|
| Core | NextPDF is een writer. Het legt elke offset uit de uitvoerbuffer vast op het moment dat elk object wordt uitgestuurd, zodat de bestanden die het produceert een cross-referencesectie hebben die door constructie overeenkomt met de body. |
| Pro | Het parsen, reconstrueren of repareren van een beschadigde cross-referencetabel in een bestand dat NextPDF niet heeft geschreven, valt buiten de scope in elke editie. |
| Enterprise | Gebruik voor inspectie van de structuur van een bestaand bestand een speciale parser of validator; NextPDF garandeert correctheid voor wat het schrijft, niet voor wat het leest. |
Mini-FAQ
Sectie met titel “Mini-FAQ”Waarom heeft een PDF een binary-marker-regel na de header? Sommige oudere transfertools zouden een bestand verminken waarvan ze dachten dat het gewone tekst was. Het high-bit-commentaar laat het bestand er ondubbelzinnig binair uitzien, zodat het de reis ongewijzigd overleeft.
Is de xref-stream gewoon een kleinere tabel? Grotendeels, met één extra kracht. Naast gecomprimeerd zijn, kan een xref-stream objecten beschrijven die binnen object streams zijn opgeslagen — vermeldingen die de klassieke 20-byte- teksttabel op geen enkele manier kan uitdrukken.
Kan ik zowel een tabel als een stream in één bestand hebben? Eén revisie gebruikt het een of het ander. Maar een hybride bestand kan een klassieke tabel voor oude lezers koppelen aan een cross-reference stream voor nieuwe, zodat elk soort lezer een index vindt die hij begrijpt.
Verwante documentatie
Sectie met titel “Verwante documentatie”- Wat een PDF eigenlijk is — dezelfde vier onderdelen gezien als een objectgraaf in plaats van een fysieke indeling.
- Incrementele updates en waarom ze ertoe doen — hoe de toegevoegde vijfde regio een bestand laat groeien en handtekeningen beschermt.
- Streams en filters — wat er in de stream-objecten van de body zit en hoe ze worden gecomprimeerd.
- PDF 2.0: wat er veranderde — waarom de cross-reference stream de standaardstructuur is die NextPDF schrijft.
Verklarende woordenlijst
Sectie met titel “Verklarende woordenlijst”- Header — de eerste regel,
%PDF-2.0, die de versie benoemt; doorgaans gevolgd door een binary-marker-commentaar. - Indirect object — een genummerd object in de body, geschreven als
N G obj … endobj, waarbijNhet objectnummer is enGde generatie. - Dictionary — een
<< /Key value … >>-map van names naar values; de meest voorkomende objectvorm. - Stream — een dictionary plus een blok bytes tussen
streamenendstream, gebruikt voor inhoud, lettertypen en afbeeldingen. - Cross-reference table (xref) — de index van objectnummer naar byte-
offset; klassiek een teksttabel van 20 bytes per vermelding, een
/Type /XRef-stream in PDF 2.0. - Trailer — de dictionary die
/Rooten/Sizebenoemt, gevonden via destartxref-offset aan het einde van het bestand. - Incrementele update — gewijzigde objecten, een nieuwe cross-referencesectie, en
een nieuwe trailer toegevoegd na
%%EOF, met/Prevdie terugkoppelt naar de vorige sectie.