Hvis du fjerner sidebeskrivelserne, står du tilbage med et tyndt lag af struktur, som ingen udskriver, men som enhver læser, indekseringsværktøj og arkiveringssystem afhænger af. Et sideobjekt ved intet om det kapitel, det tilhører, forfatteren der skrev det, eller den fodnote, der linker et andet sted hen. Den viden lever et niveau højere oppe, i tre strukturer, der er knyttet til dokument-cataloget: metadata-streams, outline-træet og de sidespecifikke annotations-arrays. De deler et træk, der gør det nemt at lave fejl. Ingen af dem bærer synlige mærker på siden, så en fil kan gengives perfekt og alligevel mangle sine bogmærker, modsige sit eget forfatterfelt eller pege et link mod et sideobjekt, der ikke længere findes
Dette er det lag, et PDF-bibliotek eksponerer som dokumentegenskaber, bogmærke-API'er og link- eller annotationskald, og det lag, som en søgecrawler læser for at beslutte, hvad dit dokument handler om. Objektmodellen under det dækkes i gennemgangen af PDF-dokumentstruktur. Her er fokus udelukkende på, hvad der hænger på cataloget
Alle tre strukturer knyttes til cataloget. Et komplet catalog, der forbinder dem, ser således ud:
1 0 obj
<< /Type /Catalog
/Pages 2 0 R
/Outlines 3 0 R
/Names << /EmbeddedFiles 4 0 R >>
/Metadata 5 0 R
>>
endobj
Fire indgange, fire uafhængige undersystemer. /Pages er det synlige dokument; /Outlines er bogmærketræet; /Metadata peger på XMP-streamen; /Names når dokumentets navneordbog, som blandt andet indeholder indlejrede filvedhæftninger. Hver er valgfri, og en læser, der ikke finder nogen af dem, viser stadig siderne. Denne valgfrihed er præcis grunden til, at navigationslaget er det første, der rådner, når en fil redigeres af værktøjer, der kun forstår sider
To metadata-lagre, der er uenige
PDF bærer dokumentmetadata to steder på én gang, og problemerne starter, når de siger forskellige ting. Den oprindelige mekanisme er dokumentinformationsordbogen, som refereres af /Info i traileren: et fladt sæt af nøgle-værdi-par til /Title, /Author, /Subject, /Keywords, /Creator, /Producer og de to datoer. Den er enkel, og enhver fremviser læser den. PDF 2.0 udfaser det meste af den til fordel for den anden mekanisme, XMP-metadata-streamen
XMP er et selvstændigt XML-dokument, skrevet i RDF, gemt som en stream, som cataloget når via /Metadata, og markeret med /Type /Metadata /Subtype /XML. I modsætning til Info-ordbogen, der er begravet inde i PDF-objektstrukturen, er en XMP-pakke designet til at blive udtrukket og parset alene af værktøjer, der ikke ved noget om PDF. Her er en repræsentativ pakke:
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
Tre detaljer i den blok afgør, om metadataene overlever kontakt med rigtige værktøjer. xpacket-behandlingsinstruktionerne er ikke dekoration: De indrammer pakken, så en udtrækker kan finde den inde i en større byte-stream, og en skribent, der udelader den afsluttende <?xpacket end="w"?>, producerer en fil, der åbner fint, men udløser fejl i strenge validatorer. Egenskabernes datatyper betyder også noget. dc:title er et sprogalternativ indpakket i rdf:Alt, mens dc:creator er en ordnet liste og tager rdf:Seq; at udsende en af dem som en simpel tekstnode er den mest almindelige XMP-fejl, som tolereres af de fleste fremvisere lige indtil den, der ikke gør. Navnerum-præfikserne er konventionelle, men de URI'er, de binder til, er normative: En parser bruger URI'en som nøgle, ikke præfikset
Den hårde regel med to lagre er, at de skal være enige. Hvis /Info siger, at forfatteren er én person, og dc:creator navngiver en anden, har du sendt et dokument, der besvarer det samme spørgsmål på to måder, og hvilket svar der vinder, afhænger af, hvilket felt det forbrugende værktøj læser. Et bibliotek skriver normalt begge for dig, men det øjeblik du redigerer en i hånden eller fletter filer fra forskellige generatorer, driver de to fra hinanden. Behandl Info-ordbogen som arv for kompatibilitet og XMP som kilden til sandhed, og regenerer begge ud fra ét sæt værdier i stedet for at rette dem uafhængigt. For PDF/A bliver dette et konformitetskrav: ISO 19005 påkræver XMP og forbyder enhver Info-egenskab, der modsiger sin XMP-modpart
Outline-træet bag bogmærkepanelet
Det, en fremviser viser som et bogmærkepanel, er i filen et dobbeltkædet træ af ordbøger kaldet document outline. Cataloget peger på en rod-outline-ordbog via /Outlines; roden peger på sine første og sidste topniveau-elementer; og hvert element er trådet til dets naboer og dets forælder. Der er intet array af bogmærker nogen steder. Hele strukturen rekonstrueres ved at følge referencer, hvilket netop er grunden til, at et enkelt brudt link kan få en hel gren til at forsvinde fra panelet uden nogen fejl
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
Læs linksene, og invarianterne bliver indlysende. Hvert element peger tilbage på sin /Parent. Søskende danner en kæde via /Prev og /Next, hvor det første element udelader /Prev og det sidste udelader /Next. En forælder navngiver sine første og sidste børn via /First og /Last, og børnene derimellem kan kun nås ved at gå langs søskendekæden. Hvis du får én forkert, er fejlen tavs: Et forældet /Next afkorter et kapitel, en forælder, hvis /Last ikke afslutter kæden, efterlader elementer forældreløse, og fremviseren gengiver alt, hvad den kan nå
/Count-feltet bærer en tilstand, der overrasker folk. På roden og på ethvert udvidet element indeholder det antallet af efterkommere, der i øjeblikket er synlige; på et sammenklappet element er det et negativt tal, hvis størrelse er, hvor mange efterkommere der ville vises ved udvidelse. Så /Count er ikke et fast strukturelt faktum om træet, det er panelets gemte åbne eller lukkede tilstand, og en generator, der hårdkoder det som en positiv total, genåbner hver gren, som forfatteren havde tænkt sig skulle forblive lukket
Hvert element tjener sin plads ved at pege et sted hen. /Title er det, panelet viser; /Dest er der, hvor et klik lander. En destination kan være indlejret i elementet, som ovenfor, eller et navn, der løses gennem dokumentets navneordbog, hvilket er det bedre valg, når mange bogmærker og links sigter mod de samme steder, fordi du retter et flyttet mål ét sted. Et bibliotek skjuler generelt dette træ bag et outline-rod-handle og metoder, der tilføjer underordnede indgange; i HotPDF eksponerer dokumentet en OutlineRoot af typen THPDFDocOutlineObject og tråder linkene /Prev, /Next, /Parent og /Count for dig, når du tilføjer elementer. Det er værd at drage fordel af, fordi håndtering af disse invarianter på tværs af redigeringer er der, hvor outlines brydes
Destinationer: Grammatikken i, hvor et klik fører hen
Både bogmærker og linkannotationer peger på destinationer, og en destination er mere end et sidetal. Det er et array, der navngiver et sideobjekt og derefter, via et udsagnsord på andenpladsen, angiver, hvordan fremviseren skal indramme det. Det mest almindelige og mest misbrugte er /XYZ, i formen [page /XYZ left top zoom]. Dets tre operander er uafhængige, og enhver kan være null for at betyde "lad dette være, som læseren havde det". Så [page /XYZ null null null] springer til siden uden at røre rulningspositionen eller zoome, hvilket normalt er det, du vil have fra et "gå til side"-link. Tallene er i standard user space, målt fra bund-venstre, hvor y øges opad, det samme koordinatsystem som sideindholdet bruger. Forfattere, der kommer fra skærmlayout, måler refleksivt fra toppen og sender læseren til den forkerte ende af siden
/Fit-familien bytter præcis positionering mod modstandsdygtighed. [page /Fit] skalerer hele siden ind i vinduet, [page /FitH top] tilpasser sidebredden med en given overkant, og [page /FitR l b r t] zoomer et rektangel for at fylde visningen. Fordi disse beregner skala ud fra sidegeometri snarere end faste koordinater, gør en /Fit-destination stadig det fornuftige, efter at siden har ændret størrelse, hvorimod en /XYZ-destination med en indbygget zoom kan efterlade læseren stirrende på margen. For en indholdsfortegnelse ældes /FitH med sektionens topkoordinat bedre end /XYZ med en gættet zoom
Annotationer: Alt interaktivt, der ikke er sideindhold
En annotation er et objekt, der ligger hen over siden uden at være en del af dens content stream. Links, sticky notes, fremhævninger, formular-widgets, ikoner til filvedhæftning, stempler: Alt dette er annotationer, der er opført i /Annots-arrayet for den side, de sidder på. Hvis man fjerner en annotation fra det array, fjernes den fra siden, selvom det underliggende indhold ikke er rørt. Det er hele pointen: Annotationer er et redigeringslag, adskilt fra de mærker, de sidder over
Hver annotation deler en lille rygrad. /Subtype navngiver typen, /Rect angiver dens afgrænsningsboks (bounding box) i sidekoordinater, og /Contents indeholder tekst, der fungerer som den tilgængelige beskrivelse. Linkannotationen er værd at studere, fordi den findes i to former: en bar destination og en handling (action)
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
/Rect er et hotspot; et klik inde i det sender læseren til destinationen og genbruger den samme grammatik, som outline bruger. /Border [0 0 0] gør et reelt stykke arbejde med at undertrykke det grimme standardrektangel, som fremvisere tegner omkring links. Den anden form bytter den bare /Dest ud med en /A-handling, hvis /S-subtype vælger adfærden: /GoTo inden for denne fil, /GoToR til en anden fil, /URI til en webadresse, /Launch for at køre et eksternt program. Den sidste fortjener mistanke. En /Launch, der starter en eksekverbar fil, er den adfærd, der gør PDF'er til en malware-vektor, så konforme fremvisere blokerer den eller prompter højlydt, og linket fejler for de fleste læsere. Grib ud efter /URI og /GoTo og lad /Launch være
Markup-annotationer såsom fremhævninger og sticky notes samt formannotationer såsom /Square tilføjer en krølle: Deres udseende på skærmen er ikke underforstået af deres type. En fremviser gengiver sin egen version, medmindre du fastgør udseendet med en appearance stream, /AP-indgangen, som refererer til et form-XObject, der indeholder tegneoperatørerne. Springer du det over, kan den samme fremhævning se forskellig ud i to læsere, eller før og efter en runde gennem en editor. Til alt, hvis nøjagtige udseende er en del af dokumentet, skal du levere /AP. Filvedhæftninger genbruger i øvrigt denne samme maskineri: en indlejret fil-stream og en filspecifikationsordbog, der overfladeres enten som en /FileAttachment-annotation eller via /EmbeddedFiles-navnetræet under catalogets /Names
Hvor dette lag brydes, og hvordan man fanger det
Den tilbagevendende fejl på tværs af alt dette er den hængende reference (dangling reference). Bogmærker holder op med at vises, når cataloget ikke har nogen /Outlines-indgang, eller en søskendekæde brydes midt i træet; metadata ignoreres, når XMP-streamen mangler sin /Type /Metadata /Subtype /XML-markering, eller xpacket-indpakningen er deformeret. I alle tilfælde er sideindholdet fint, så en afslappet åbning ser korrekt ud, og fejlen dukker kun op i panelet, som ingen tjekkede
To billige vaner fanger det meste af det. Åbn den færdige fil i en rigtig fremviser og klik dig gennem bogmærkepanelet og et udsnit af links, hvilket motionerer referencegrafen på samme måde som en læser vil. Læs derefter metadataene tilbage med et separat værktøj og bekræft, at Info-ordbogen og XMP er enige, den ene uenighed som ingen mængde af klik afslører. Generer dette lag via et bibliotek, der ejer link-bogføringen, og de fleste af disse fælder vil aldrig åbne sig. HotPDF Component til Delphi og C++Builder eksponerer outline-, annotations- og metadatastrukturerne via API'er på dokumentniveau, så du beskriver bogmærkehierarkiet og linksene og lader det tråde referencerne. For objektmodellen, som disse strukturer er knyttet til, dækker den tekniske oversigt over PDF-filstrukturen det catalog og den krydsreferencetabel, de afhænger af