Wat een PDF eigenlijk is
ISO 32000-2 §7 Evidence: Standard-backed
In het kort
Sectie met titel “In het kort”Een PDF is geen paginabeschrijving die toevallig in een bestand staat. Het is een kleine grafendatabase met een printer eraan vastgekoppeld. Deze pagina beschrijft de vier onderdelen waaruit elke PDF bestaat — header, body, cross-referencetabel, trailer — en hoe NextPDF ze schrijft, zodat een lezer elk object kan vinden zonder te gokken.
Waarom dit van belang is
Sectie met titel “Waarom dit van belang is”De meeste PDF-bugs zijn geen weergavebugs, maar structuurbugs: een byte-offset die één teken voorbij het bedoelde object wijst, een trailer die de verkeerde root benoemt, een cross-referenceregel die niet overeenkomt met waar het object werkelijk staat. Geen van deze fouten verandert hoe een pagina eruitziet, totdat een lezer een andere route door het bestand neemt en voorbij het einde ervan terechtkomt.
Als je een PDF als een ondoorzichtig geheel behandelt, lijken die fouten willekeurig. Als je het objectmodel kent, zie je ze voor wat ze zijn: een getal dat niet overeenkomt met een positie. Het formaat kunnen lezen maakt het verschil tussen „de PDF is beschadigd” en „de offset van object 14 is verouderd omdat de writer die heeft gemeten voordat de stream-lengte werd afgerond.”
De korte versie
Sectie met titel “De korte versie”Een PDF heeft vier onderdelen, in bestandsvolgorde:
- Een header — één regel die de versie benoemt (
%PDF-2.0). - Een body — een reeks genummerde indirecte objecten: dictionaries, streams, arrays, getallen, strings, names.
- Een cross-referencetabel (of, in PDF 2.0, een cross-reference stream) — een opzoektabel van objectnummer naar byte-offset, zodat elk object bereikbaar is zonder het bestand te scannen.
- Een trailer — een kleine dictionary die het rootobject van het document benoemt en aangeeft waar de cross-referencesectie begint.
Een lezer leest een PDF niet van voor naar achter. De lezer leest eerst de laatste regel, vindt startxref, springt naar de cross-referencesectie en gebruikt die als index in de body. Het formaat is gebouwd om achterstevoren te worden gelezen. Dat ene feit verklaart het grootste deel van het ontwerp.
Hoe NextPDF het aanpakt
Sectie met titel “Hoe NextPDF het aanpakt”NextPDF bouwt een PDF op in dezelfde volgorde waarin het formaat wordt gelezen: eerst het object schrijven, daarna de offset vastleggen en als laatste de tabel schrijven.
Elk indirect object krijgt een nummer van één registry (src/Core/ObjectRegistry.php). De registry deelt opeenvolgende nummers uit via allocate() en legt via register() de byte-offset vast, nadat de bytes van een object naar de uitvoerbuffer zijn geschreven. Offsets worden nooit vooraf geraden. Ze worden afgelezen van BinaryBuffer::getOffset() op het moment waarop de objectheader wordt uitgeschreven. Daarom kan een cross-referenceregel van NextPDF niet afwijken van het object dat de regel beschrijft: de offset is precies de werkelijke positie van de buffer.
Zodra de body compleet is, schrijft een versiespecifieke serialisatiestrategie (src/Writer/PdfSerializationStrategy.php) de cross-referencesectie en de trailer:
Pdf20StreamStrategygenereert een gecomprimeerde cross-reference stream (/Type /XRef) — de PDF 2.0-standaard.Pdf17TableStrategyenPdf14TableStrategygenereren een traditionele cross-reference tabel met regels van 20 bytes plus een aparte trailer-dictionary — vereist door de PDF/A-profielen die een oudere bestandsstructuur voorschrijven.
De strategie wordt gekozen door het uitvoerprofiel, niet afgeleid. Welke strategie ook wordt gebruikt, de laatste bytes hebben dezelfde vorm: de cross-referencesectie, dan startxref, dan de byte-offset, dan %%EOF. Dat staartstuk vindt een lezer als eerste.
- Step 1 of 4: ISO 32000-2 §7.5.5 — %%EOF and startxref at the file end
- Step 2 of 4: ISO 32000-2 §7.5.4 / §7.5.8 — the cross-reference section maps object number to offset
- Step 3 of 4: ISO 32000-2 §7.5.5 — the trailer names /Root, the document catalog
- Step 4 of 4: ISO 32000-2 §7.3.10 — each indirect object is reached at its recorded offset
Wat het bewijs zegt
Sectie met titel “Wat het bewijs zegt”De vierdelige structuur is geen NextPDF-conventie, maar de bepaling over de bestandsstructuur uit Spec: ISO 32000-2, §7.5 ISO 32000-2 §7.5 . De standaard definieert een PDF als een header, een body met objecten, een cross-referencetabel en een trailer, en stelt dat een lezer moet parsen vanaf het einde van het bestand. De laatste regel is %%EOF, en de twee regels ervoor zijn het sleutelwoord startxref en de byte-offset naar de cross-referencesectie.
Een indirect object wordt gedefinieerd als een objectnummer en een generatienummer, gescheiden door witruimte, gevolgd door de waarde van het object tussen de sleutelwoorden obj en endobj. De combinatie van objectnummer en generatienummer identificeert het object uniek; een indirecte referentie ernaar wordt geschreven als het objectnummer, het generatienummer en het sleutelwoord R. De ObjectRegistry van NextPDF volgt dit precies: een opeenvolgend nummer, generatie 0 voor nieuw geschreven objecten, en een vastgelegde offset.
Vanaf PDF 1.5 mogen objecten ook in een object stream staan; daarin worden ze opgeslagen zonder de sleutelwoorden obj/endobj en moeten ze generatie nul hebben. De cross-reference stream (/Type /XRef,
Spec: ISO 32000-2, §7.5.8 ISO 32000-2 §7.5.8 ) is het PDF 2.0-mechanisme
dat zowel gewone objecten als deze gecomprimeerde objecten indexeert.
De CrossReferenceStream van NextPDF bouwt deze stream met een /W-array met veldbreedtes en
FlateDecode-compressie.
Praktijkvoorbeeld
Sectie met titel “Praktijkvoorbeeld”Dit is de vorm van een minimale PDF-body en de bijbehorende trailer. De getallen in de cross-referencesectie zijn byte-offsets. Ze moeten exact kloppen; daarom legt NextPDF ze vast vanuit de buffer in plaats van ze te berekenen.
%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 n0000000122 00000 ntrailer<< /Size 4 /Root 1 0 R >>startxref196%%EOFEen lezer leest dit van onderaf: %%EOF, dan startxref 196, dan springt de lezer naar byte 196, waar xref begint. Daar leest de lezer dat object 1 op byte 9 staat, volgt /Root 1 0 R naar de catalog en doorloopt vandaaruit de paginaboom. Object 0 is altijd de kop van de vrije lijst met generatie 65535 — een eigenaardigheid uit het vroegste ontwerp van het formaat die getrouw wordt gereproduceerd omdat lezers dat verwachten.
Veelvoorkomend misverstand
Sectie met titel “Veelvoorkomend misverstand”De valkuil is denken dat een PDF van boven naar beneden wordt gelezen, zoals broncode. Dat is niet zo. De body kan de objecten in elke willekeurige volgorde bevatten. Objectnummers hoeven in het bestand niet opeenvolgend te zijn; een lezer vertrouwt er nooit op dat dit wel zo is. De enige gezaghebbende index is de cross-referencesectie, en de enige manier om die te vinden is de trailer aan het einde. Een PDF met een volkomen geldige body en één enkel verkeerd getal in startxref is onleesbaar. Een PDF met objecten in een door elkaar gehusselde volgorde, maar met een correcte cross-referencetabel, is prima. Positie is betekenisloos; de vastgelegde positie is alles.
Grenzen en beperkingen
Sectie met titel “Grenzen en beperkingen”Deze pagina beschrijft de bestandsstructuur, niet de pagina-inhoud. Hoe markeringen op een pagina terechtkomen — content streams, grafische operatoren, tekstweergave — is een apart onderwerp. Deze pagina behandelt ook niet wat er gebeurt wanneer een bestand wordt gewijzigd nadat het is geschreven. Dat is de taak van incrementele updates, waarbij de writer een tweede cross-referencesectie toevoegt en de trailer terugkoppelt naar de vorige.
NextPDF is een writer. Het gedrag dat hier wordt beschreven, is de manier waarop NextPDF een document serialiseert dat het zelf heeft opgebouwd. NextPDF is geen algemene PDF-parser of reparatietool. NextPDF belooft niet dat het een willekeurig bestand van derden met een beschadigde cross-referencetabel kan lezen, reconstrueren of redden. De garantie is beperkt en bewust gekozen. De bestanden die NextPDF schrijft, hebben correcte offsets, omdat ze worden gemeten en niet voorspeld.
Mini-FAQ
Sectie met titel “Mini-FAQ”Waarom gebruiken nieuwe bestanden altijd 0 als generatienummer? Generatienummers bestaan voor hergebruik van objecten over updates heen. In een net geschreven bestand heeft elk object generatie 0. Generaties die niet nul zijn, verschijnen pas wanneer een bestand incrementeel is bijgewerkt en een objectnummer wordt hergebruikt.
Kunnen twee objecten hetzelfde nummer hebben? Niet binnen één cross-referencesectie. Over meerdere incrementele updates heen kan een bestand fysiek meerdere kopieën van hetzelfde objectnummer bevatten. De meest recente cross-referenceregel wint. Dat is het onderwerp van de volgende pagina.
Maakt de objectvolgorde in het bestand uit voor de uitvoer? Nee. NextPDF schrijft objecten in een deterministische volgorde voor reproduceerbare builds, maar een lezer lost alles op via de cross-referencesectie; de fysieke volgorde is dus semantisch niet van betekenis.
Gerelateerde documentatie
Sectie met titel “Gerelateerde documentatie”- Incrementele updates en waarom ze van belang zijn — wat er gebeurt wanneer een geschreven PDF wordt gewijzigd: extra secties en een gekoppelde trailer.
- Streams en filters — hoe stream-objecten in de body worden gecomprimeerd en gecodeerd.
- PDF 2.0: wat er is veranderd — hoe de bestandsstructuur verschilt tussen 1.7 en de 2.0-baseline waarop NextPDF is gericht.
Verklarende woordenlijst
Sectie met titel “Verklarende woordenlijst”- Indirect object — een genummerd object in de body, geschreven als
N G obj … endobj, waarbijNhet objectnummer is enGhet generatienummer. - Indirect reference — een verwijzing naar een indirect object, geschreven als
N G R. - Cross-reference table (xref) — de index van objectnummer naar byte-offset. In PDF 2.0 is dit doorgaans een cross-reference stream (
/Type /XRef) in plaats van de klassieke teksttabel van 20 bytes per regel. - Trailer — de dictionary aan het einde van een cross-referencesectie die
/Root(de document catalog) en/Sizenoemt en via destartxref-offset wordt gevonden. - Object stream — een stream-object dat zelf andere indirecte objecten bevat (samen gecomprimeerd); leden hebben geen
obj/endobjen generatie nul. - Document catalog — het object dat door
/Rootwordt aangewezen; het toegangspunt tot de paginaboom en al het andere in het document.