Verwijder de paginabeschrijvingen en er blijft een dunne laag structuur over die niemand afdrukt maar waar elke lezer, indexeerder en archiefsysteem op steunt. Een pagina-object weet niets over het hoofdstuk waartoe het behoort, de auteur die het heeft geschreven of de voetnoot die ergens anders naartoe verwijst. Die kennis bevindt zich een niveau hoger, in drie structuren die aan de documentcatalogus zijn gekoppeld: de metadatastreams, de overzichtsboom en de annotatiearray per pagina. Ze delen een eigenschap die ze gemakkelijk fout laat gaan. Geen van hen laat zichtbare markeringen op de pagina achter, zodat een bestand perfect kan renderen maar toch zijn bladwijzers mist, zijn eigen auteursfield tegenspreekt of een link naar een pagina-object verwijst dat niet meer bestaat.
Dit is de laag die een PDF-bibliotheek blootlegt als documenteigenschappen, bladwijzer-API's en link- of annotatie-aanroepen, en de laag die een zoekcrawler leest om te bepalen waar uw document over gaat. Het onderliggende objectmodel wordt behandeld in de walkthrough van de PDF-documentstructuur. Hier ligt de focus uitsluitend op wat aan de catalogus hangt.
Alle drie de structuren zijn aan de catalogus gekoppeld. Een volledige catalogus die ze samenbrengt ziet er als volgt uit:
1 0 obj
<< /Type /Catalog
/Pages 2 0 R
/Outlines 3 0 R
/Names << /EmbeddedFiles 4 0 R >>
/Metadata 5 0 R
>>
endobj
Vier vermeldingen, vier onafhankelijke subsystemen. /Pages is het zichtbare document; /Outlines is de bladwijzerboom; /Metadata verwijst naar de XMP-stream; /Names bereikt het documentbrede naamwoordenboek, dat onder meer ingesloten bestandsbijlagen bevat. Elk is optioneel en een lezer die er geen van vindt toont de pagina's toch. Die optionaliteit is precies de reden waarom de navigatielaag als eerste in verval raakt wanneer een bestand wordt bewerkt door tools die alleen pagina's begrijpen.
Twee metadataopslagplaatsen die het oneens zijn
PDF slaat documentmetadata op twee plaatsen tegelijk op, en de problemen beginnen wanneer die twee verschillende dingen zeggen. Het oorspronkelijke mechanisme is het document-informatiewoordenboek, waarnaar wordt verwezen door /Info in de trailer: een vlakke verzameling sleutel-waardeparen voor /Title, /Author, /Subject, /Keywords, /Creator, /Producer en de twee datumvelden. Het is eenvoudig en elke viewer leest het. PDF 2.0 deprecateert de meeste hiervan ten gunste van het tweede mechanisme, de XMP-metadatastream.
XMP is een op zichzelf staand XML-document, geschreven in RDF, opgeslagen als een stream die de catalogus bereikt via /Metadata en gemarkeerd als /Type /Metadata /Subtype /XML. In tegenstelling tot het Info-woordenboek dat diep verborgen zit in de PDF-objectstructuur, is een XMP-pakket ontworpen om op zichzelf te worden geextraheerd en geparseerd door tools die niets van PDF weten. Hier is een representatief pakket:
5 0 obj
<< /Type /Metadata /Subtype /XML /Length 1235 >>
stream
<?xpacket begin="" id="W5M0MpCehiHzreSzNTczkc9d"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<rdf:Description rdf:about=""
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:xmp="http://ns.adobe.com/xap/1.0/"
xmlns:pdf="http://ns.adobe.com/pdf/1.3/">
<dc:title><rdf:Alt><rdf:li xml:lang="x-default">Quarterly Report</rdf:li></rdf:Alt></dc:title>
<dc:creator><rdf:Seq><rdf:li>A. Author</rdf:li></rdf:Seq></dc:creator>
<xmp:CreateDate>2026-06-16T10:46:27+08:00</xmp:CreateDate>
<xmp:CreatorTool>Reporting Service 4.2</xmp:CreatorTool>
<pdf:Producer>losLab PDF Library</pdf:Producer>
</rdf:Description>
</rdf:RDF>
</x:xmpmeta>
<?xpacket end="w"?>
endstream
endobj
Drie details in dat blok bepalen of de metadata contact met echte tooling overleeft. De verwerkingsinstructies van xpacket zijn geen versiering: zij kaderen het pakket in zodat een extractor het kan vinden in een grotere bytestream, en een schrijver die de afsluitende <?xpacket end="w"?> weglaat produceert een bestand dat prima opent maar strikte validators laat struikelen. De eigenschapsdatatypes zijn ook van belang. dc:title is een taalalternatief gewikkeld in rdf:Alt, terwijl dc:creator een geordende lijst is en rdf:Seq vereist; een van beide als een kale tekstknoop emitteren is de meest voorkomende XMP-fout, getolereerd door de meeste viewers totdat er een is die dat niet doet. De naamruimtevoorvoegsels zijn conventioneel, maar de URI's waaraan ze gebonden zijn normatief: een parser slaat af op de URI, niet op het voorvoegsel.
De harde regel bij twee opslagplaatsen is dat ze het eens moeten zijn. Als /Info zegt dat de auteur een bepaalde persoon is en dc:creator een andere persoon noemt, heeft u een document verzonden dat dezelfde vraag op twee manieren beantwoordt, en welk antwoord wint hangt af van welk veld de verbruikende tool leest. Een bibliotheek schrijft gewoonlijk beide voor u, maar zodra u er een met de hand bewerkt of bestanden van verschillende generatoren samenvoegt, raken de twee uit de pas. Behandel het Info-woordenboek als compatibiliteit met oudere versies en XMP als de bron van waarheid, en genereer beide vanuit een reeks waarden in plaats van ze afzonderlijk te patchen. Voor PDF/A wordt dit een conformiteitsvereiste: ISO 19005 verplicht XMP en verbiedt elke Info-eigenschap die in strijd is met zijn XMP-tegenhanger.
De overzichtsboom achter het bladwijzerpaneel
Wat een viewer toont als bladwijzerpaneel is in het bestand een dubbel gelinkte boom van woordenboeken die het documentoverzicht wordt genoemd. De catalogus verwijst naar een hoofdoverzichtswoordenboek via /Outlines; het hoofditem verwijst naar zijn eerste en laatste item op het hoogste niveau; en elk item is verbonden met zijn buren en zijn bovenliggende element. Er is nergens een array van bladwijzers. De hele structuur wordt gereconstrueerd door verwijzingen te volgen, wat precies de reden is waarom een enkele kapotte link een volledig vertakking uit het paneel kan laten verdwijnen zonder enige fout.
8 0 obj % the outline root
<< /Type /Outlines /Count 4 /First 9 0 R /Last 9 0 R >>
endobj
9 0 obj % top-level: a chapter
<< /Title (Chapter 1: Results)
/Parent 8 0 R /Count 2
/First 12 0 R /Last 15 0 R >>
endobj
12 0 obj % first child
<< /Title (Introduction)
/Parent 9 0 R /Next 15 0 R
/Dest [3 0 R /XYZ 72 720 0] >>
endobj
15 0 obj % second child, last sibling
<< /Title (Methodology)
/Parent 9 0 R /Prev 12 0 R
/Dest [3 0 R /Fit] >>
endobj
Lees de koppelingen en de invarianten worden duidelijk. Elk item verwijst terug naar zijn /Parent. Broers en zussen vormen een keten via /Prev en /Next, waarbij het eerste item /Prev weglaat en het laatste /Next. Een bovenliggend element benoemt zijn eerste en laatste kinderen via /First en /Last, en de kinderen daartussenin zijn alleen bereikbaar door de broer-en-zusketen te doorlopen. Eentje fout en de mislukking is stil: een verouderde /Next kapt een hoofdstuk af, een bovenliggend element waarvan /Last de keten niet beëindigt laat items wees achter, en de viewer rendert wat het kan bereiken.
Het veld /Count bevat een stukje toestand dat mensen verrast. Op het hoofditem en op elk uitgevouwen item bevat het het aantal afstammelingen dat momenteel zichtbaar is; op een ingeklapt item is het een negatief getal waarvan de absolute waarde aangeeft hoeveel afstammelingen bij uitvouwen verschijnen. /Count is dus geen vast structureel gegeven over de boom, maar de opgeslagen open of gesloten toestand van het paneel, en een generator die het hardcodeert als een positief totaal heropent elke vertakking die de auteur gesloten wilde laten.
Elk item verdient zijn plaats door ergens naartoe te verwijzen. De /Title is wat het paneel toont; de /Dest is waar een klik terechtkomt. Een bestemming kan direct in het item staan, zoals hierboven, of een naam die via het naamwoordenboek van het document wordt opgelost; dat is de betere keuze wanneer veel bladwijzers en koppelingen naar dezelfde plaatsen verwijzen, want dan hoeft u een verplaatst doel slechts op een plek aan te passen. Een bibliotheek verbergt deze boom gewoonlijk achter een overzichtsroothandle en methoden die onderliggende vermeldingen toevoegen; in HotPDF stelt het document een OutlineRoot van het type THPDFDocOutlineObject beschikbaar en legt de /Prev-, /Next-, /Parent- en /Count-koppelingen voor u aan terwijl u items toevoegt. Dat is de moeite waard om van te profiteren, want het met de hand onderhouden van die invarianten bij bewerkingen is precies waar overzichten kapotgaan.
Bestemmingen: de grammatica van waar een klik naartoe gaat
Zowel bladwijzers als linkannotaties verwijzen naar bestemmingen, en een bestemming is meer dan een paginanummer. Het is een array die een pagina-object benoemt en vervolgens, via een werkwoord in het tweede slot, aangeeft hoe de viewer de pagina moet kadreren. De meest gebruikte en meest misbruikte is /XYZ, in de vorm [pagina /XYZ links boven zoom]. De drie operanden zijn onafhankelijk en elk kan null zijn om "laat dit zoals de lezer het had" te betekenen. Dus [pagina /XYZ null null null] springt naar de pagina zonder de scrollpositie of zoom te wijzigen; meestal is dat wat u wilt bij een "ga naar pagina"-link. De getallen zijn in de standaard gebruikersruimte, gemeten vanuit linksonder met y toenemend omhoog, hetzelfde coördinatensysteem dat de pagina-inhoud gebruikt. Auteurs die gewend zijn aan schermopmaak meten reflexmatig van boven en sturen de lezer naar het verkeerde einde van de pagina.
De /Fit-familie ruilt nauwkeurige positionering in voor veerkracht. [pagina /Fit] schaalt de hele pagina in het venster, [pagina /FitH boven] past de paginabreedte aan met een gegeven bovenrand, en [pagina /FitR l o r t] zoomt een rechthoek in om de weergave te vullen. Omdat deze de schaal berekenen op basis van paginageometrie in plaats van vaste coordinaten, doet een /Fit-bestemming nog steeds het juiste nadat de pagina is vergroot of verkleind, terwijl een /XYZ-bestemming met een ingeprikte zoom de lezer naar de marge kan laten staren. Voor een inhoudsopgave veroudert /FitH met de bovenste coordinaat van de sectie beter dan /XYZ met een geraden zoom.
Annotaties: alles wat interactief is maar geen pagina-inhoud
Een annotatie is een object dat over de pagina ligt zonder deel uit te maken van de inhoudsstream. Koppelingen, plaknotities, markeringen, formulierwidgets, pictogrammen voor bestandsbijlagen, stempels: alles zijn annotaties, opgesomd in de /Annots-array van de pagina waarop ze staan. Een annotatie uit die array verwijderen, verwijdert hem van de pagina ook al is de onderliggende inhoud ongewijzigd. Dat is het hele idee: annotaties zijn een bewerkingslaag, gescheiden van de markeringen waarover ze liggen.
Elke annotatie deelt een kleine kern. /Subtype benoemt het type, /Rect geeft het begrenzingsvak in paginacoordinaten en /Contents bevat tekst die tevens fungeert als toegankelijke beschrijving. De linkannotatie is het geval dat het bestuderen waard is, omdat die in twee vormen voorkomt: een kale bestemming en een actie.
12 0 obj % link to a destination
<< /Type /Annot /Subtype /Link
/Rect [100 200 300 250]
/Border [0 0 0]
/Dest [5 0 R /XYZ null null null] >>
endobj
13 0 obj % link that runs an action
<< /Type /Annot /Subtype /Link
/Rect [50 50 200 100]
/Border [0 0 0]
/A << /Type /Action /S /URI /URI (https://www.example.com) >> >>
endobj
De /Rect is een hotspot; klikken erin stuurt de lezer naar de bestemming, waarbij dezelfde grammatica wordt hergebruikt die het overzicht gebruikt. De /Border [0 0 0] doet echt werk door de lelijke standaardrechthoek te onderdrukken die viewers rondom koppelingen tekenen. De tweede vorm vervangt de kale /Dest door een /A-actie, waarvan het subtype /S het gedrag selecteert: /GoTo binnen dit bestand, /GoToR voor een ander bestand, /URI voor een webadres, /Launch om een extern programma te starten. Die laatste verdient wantrouwen. Een /Launch die een uitvoerbaar bestand start is het gedrag dat PDF's een malwarevector maakt, dus conforme viewers blokkeren het of geven een duidelijke waarschuwing en de koppeling werkt voor de meeste lezers niet. Gebruik /URI en /GoTo en laat /Launch met rust.
Markupaannotaties zoals markeringen en plaknotities, en vormaannotaties zoals /Square, voegen een complicatie toe: hun weergave op het scherm wordt niet bepaald door hun type. Een viewer rendert zijn eigen versie tenzij u het uiterlijk vastlegt met een uiterlijkstream, de /AP-vermelding, die verwijst naar een formulier-XObject dat de tekenprogramma's bevat. Laat u dat weg, dan kan dezelfde markering er in twee viewers anders uitzien, of voor en na een bewerkingsronde. Voor alles waarvan het exacte uiterlijk deel uitmaakt van het document: geef de /AP op. Bestandsbijlagen maken overigens gebruik van dezelfde machinerie: een ingesloten bestandsstream en een bestandsspecificatiewoordenboek, gepresenteerd als een /FileAttachment-annotatie of via de /EmbeddedFiles-naamstructuur onder de /Names van de catalogus.
Waar deze laag breekt en hoe u dat opspoort
De terugkerende oorzaak van fouten in dit alles is de verweesde verwijzing. Bladwijzers verdwijnen wanneer de catalogus geen /Outlines-vermelding heeft of een broer-en-zusketen halverwege de boom breekt; metadata wordt genegeerd wanneer de XMP-stream de markering /Type /Metadata /Subtype /XML mist of de xpacket-wrapper misvormd is. In elk geval is de pagina-inhoud in orde, dus een vluchtige blik ziet er correct uit en het gebrek komt alleen aan het licht in het paneel dat niemand heeft gecontroleerd.
Twee goedkope gewoonten vangen het meeste op. Open het voltooide bestand in een echte viewer en klik door het bladwijzerpaneel en een steekproef van koppelingen; dit oefent de verwijzingsgrafiek op de manier zoals een lezer dat doet. Lees vervolgens de metadata terug met een afzonderlijk tool en bevestig dat het Info-woordenboek en de XMP overeenkomen; dat is het ene meningsverschil dat geen enkele hoeveelheid klikken aan het licht brengt. Genereer deze laag via een bibliotheek die de koppelingenadministratie beheert en de meeste van deze valkuilen zullen nooit opengaan. De HotPDF Component voor Delphi en C++Builder stelt de overzichts-, annotatie- en metadatastructuren beschikbaar via API's op documentniveau, zodat u de bladwijzerhierarchie en de koppelingen beschrijft en de bibliotheek de verwijzingen voor u aanlegt. Voor het objectmodel waaraan deze structuren zijn gekoppeld, behandelt het technisch overzicht van de PDF-bestandsstructuur de catalogus en de kruisverwijzingstabel waarvan ze afhankelijk zijn.