Teknisk artikkel

PDF-metadata, disposisjoner og kommentarer forklart

Skrell bort sidebeskrivelsene, og du sitter igjen med et tynt strukturlag som ingen skriver ut, men som hver leser, indekserer og arkivsystem avhenger av. Et sideobjekt vet ingenting om kapittelet det tilhører, forfatteren som skrev det, eller fotnoten som lenker andre steder. Den kunnskapen lever ett nivå opp, i tre strukturer knyttet til dokumentkatalogen: metadatastrømmene, disposisjonstreet (outline tree) og kommentarmatrisene (annotation arrays) per side. De deler et trekk som gjør dem lette å få feil. Ingen av dem bærer synlige merker på siden, så en fil kan gjengis perfekt og fortsatt mangle bokmerkene sine, motsi sitt eget forfatterfelt, eller peke en lenke mot et sideobjekt som ikke lenger eksisterer

Dette er laget et PDF-bibliotek eksponerer som dokumentegenskaper, bokmerke-APIer og lenke- eller kommentarkall, og laget en søkerobot leser for å bestemme hva dokumentet ditt handler om. Objektmodellen under dette dekkes i gjennomgangen av PDF-dokumentstruktur. Her er fokuset strengt på det som henger fra katalogen

Alle tre strukturene festes ved katalogen. En komplett katalog som kobler dem sammen, ser slik ut:

1 0 obj
<< /Type /Catalog
   /Pages 2 0 R
   /Outlines 3 0 R
   /Names << /EmbeddedFiles 4 0 R >>
   /Metadata 5 0 R
>>
endobj

Fire oppføringer, fire uavhengige undersystemer. /Pages er det synlige dokumentet; /Outlines er bokmerketreet; /Metadata peker på XMP-strømmen; /Names når den dokumentomfattende navneordboken, som blant annet inneholder innebygde filvedlegg. Hver av dem er valgfrie, og en leser som ikke finner noen av dem, viser fortsatt sidene. Den valgfriheten er nøyaktig grunnen til at navigasjonslaget er det første som råtner når en fil redigeres av verktøy som bare forstår sider

To metadatalagre som er uenige

PDF bærer dokumentmetadata på to steder samtidig, og problemene starter når de sier forskjellige ting. Den opprinnelige mekanismen er ordboken for dokumentinformasjon, referert til av /Info i traileren: et flatt sett med nøkkel-verdi-par for /Title, /Author, /Subject, /Keywords, /Creator, /Producer og de to datoene. Den er enkel, og alle visningsprogrammer leser den. PDF 2.0 avvikler det meste av den til fordel for den andre mekanismen, XMP-metadatastrømmen

XMP er et selvstendig XML-dokument, skrevet i RDF, lagret som en strøm katalogen når gjennom /Metadata og markert /Type /Metadata /Subtype /XML. I motsetning til Info-ordboken begravet inni PDF-objektstrukturen, er en XMP-pakke designet for å trekkes ut og tolkes på egen hånd av verktøy som ikke vet noe om PDF. Her er en representativ 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 blokken avgjør om metadataene overlever kontakt med ekte verktøy. xpacket-behandlingsinstruksjonene er ikke dekorasjon: de rammer inn pakken slik at en uttrekker kan finne den inni en større bytestrøm, og en skriver som utelater den avsluttende <?xpacket end="w"?> produserer en fil som åpner fint, men feiler på strenge validatorer. Egenskapsdatatypene har også betydning. dc:title er et språkalternativ pakket inn i rdf:Alt, mens dc:creator er en ordnet liste og tar rdf:Seq; å sende ut noen av dem som en bar tekstnode er den vanligste XMP-feilen, tolerert av de fleste visningsprogrammer helt til den som ikke gjør det. Navneroms-prefiksene er konvensjonelle, men URI-ene de binder seg til er normative: en parser nøkler av URI-en, ikke prefikset

Den harde regelen med to lagre er at de må stemme overens. Hvis /Info sier at forfatteren er en person og dc:creator navngir en annen, har du levert et dokument som svarer på samme spørsmål på to måter, og hvilket svar som vinner avhenger av hvilket felt det konsumerende verktøyet leser. Et bibliotek skriver vanligvis begge for deg, men i det øyeblikket du redigerer et av dem for hånd, eller slår sammen filer fra forskjellige generatorer, driver de fra hverandre. Behandle Info-ordboken som arvekompatibilitet og XMP som sannhetskilden, og regenerer begge fra ett sett med verdier i stedet for å lappe dem uavhengig av hverandre. For PDF/A blir dette et samsvarskrav: ISO 19005 pålegger XMP og forbyr enhver Info-egenskap som motsier dens XMP-motpart

Disposisjonstreet bak bokmerkepanelet

Det et visningsprogram viser som et bokmerkepanel, er, i filen, et dobbeltlenket tre av ordbøker kalt dokumentdisposisjonen. Katalogen peker på en rotdisposisjonsordbok gjennom /Outlines; roten peker på sine første og siste elementer på toppnivå; og hvert element er trådet til sine naboer og sin forelder. Det er ingen matrise av bokmerker noe sted. Hele strukturen rekonstrueres ved å følge referanser, noe som er akkurat grunnen til at én enkelt ødelagt lenke kan få en hel gren til å forsvinne fra panelet uten noen feilmelding

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

Les lenkene, og invariantene blir åpenbare. Hvert element peker tilbake til sin /Parent. Søsken danner en kjede gjennom /Prev og /Next, der det første elementet utelater /Prev og det siste utelater /Next. En forelder navngir sine første og siste barn gjennom /First og /Last, og barna i mellom er bare tilgjengelige ved å gå gjennom søskenkjeden. Få én feil, og feilen er stille: en foreldet /Next kutter av et kapittel, en forelder hvis /Last ikke avslutter kjeden etterlater elementer foreldreløse, og visningsprogrammet gjengir hva det enn kan nå

/Count-feltet bærer et stykke tilstand som overrasker folk. På roten og på ethvert utvidet element inneholder det antall etterkommere som for øyeblikket er synlige; på et skjult element er det et negativt tall hvis størrelse er hvor mange etterkommere som ville vises ved utvidelse. Så /Count er ikke et fast strukturelt faktum om treet, det er den lagrede åpne eller lukkede tilstanden til panelet, og en generator som hardkoder det som en positiv total gjenåpner hver gren forfatteren mente å la være lukket

Hvert element fortjener sin plass ved å peke et sted. /Title er det panelet viser; /Dest er dit et klikk lander. En destinasjon kan være inline i elementet, som ovenfor, eller et navn som løser seg gjennom dokumentets navneordbok, noe som er det bedre valget når mange bokmerker og lenker er rettet mot de samme stedene, fordi du fikser et flyttet mål på ett sted. Et bibliotek skjuler vanligvis dette treet bak et outline-root-håndtak og metoder som legger til barneoppføringer; i HotPDF eksponerer dokumentet en OutlineRoot av typen THPDFDocOutlineObject og tråder /Prev, /Next, /Parent og /Count-lenkene for deg mens du legger til elementer. Det er verdt å utnytte, for å vedlikeholde disse invariantene for hånd på tvers av redigeringer er der disposisjoner går i stykker

Destinasjoner: grammatikken for hvor et klikk går

Både bokmerker og lenkekommentarer peker på destinasjoner, og en destinasjon er mer enn et sidetall. Det er en matrise som navngir et sideobjekt og deretter spesifiserer, gjennom et verb i det andre sporet, hvordan visningsprogrammet skal ramme det inn. Det vanligste og mest misbrukte er /XYZ, i formen [page /XYZ left top zoom]. Dens tre operander er uavhengige, og enhver kan være null for å bety "la dette være som leseren hadde det." Så [page /XYZ null null null] hopper til siden uten å røre rulleposisjon eller zoom, vanligvis det du vil ha fra en "gå til side"-lenke. Tallene er i standard brukerrom, målt fra bunn-venstre med y økende oppover, det samme koordinatsystemet sideinnholdet bruker. Forfattere som kommer fra skjermlayout måler refleksivt fra toppen og sender leseren til feil ende av siden

/Fit-familien bytter presis posisjonering mot robusthet. [page /Fit] skalerer hele siden inn i vinduet, [page /FitH top] tilpasser sidebredden med en gitt toppkant, og [page /FitR l b r t] zoomer et rektangel for å fylle visningen. Fordi disse beregner skala fra sidegeometri i stedet for faste koordinater, gjør en /Fit-destinasjon fortsatt det fornuftige etter at siden har endret størrelse, mens en /XYZ-destinasjon med en innebygd zoom kan etterlate leseren stirrende på margen. For en innholdsfortegnelse eldes /FitH med seksjonens toppkoordinat bedre enn /XYZ med en gjettet zoom

Kommentarer: alt interaktivt som ikke er sideinnhold

En kommentar (annotation) er et objekt som legger seg over siden uten å være en del av innholdsstrømmen. Lenker, klistrelapper, fremhevinger, skjemawidgeter, ikoner for filvedlegg, stempler: alle er kommentarer, oppført i /Annots-matrisen til siden de sitter på. Å fjerne en kommentar fra den matrisen fjerner den fra siden selv om det underliggende innholdet er urørt. Det er hele poenget: kommentarer er et redigeringslag, atskilt fra merkene de sitter over

Hver kommentar deler en liten ryggrad. /Subtype navngir typen, /Rect gir dens avgrensningsboks i sidekoordinater, og /Contents inneholder tekst som fungerer som den tilgjengelige beskrivelsen. Lenkekommentaren er tilfellet verdt å studere, fordi den kommer i to former: en bar destinasjon, og en handling

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 aktivt område (hotspot); å klikke inni det sender leseren til destinasjonen, og gjenbruker den samme grammatikken som disposisjonen bruker. /Border [0 0 0] gjør ekte arbeid, og undertrykker det stygge standardrektangelet visningsprogrammer tegner rundt lenker. Den andre formen bytter den bare /Dest mot en /A-handling, hvis /S-undertype velger oppførselen: /GoTo innenfor denne filen, /GoToR for en annen fil, /URI for en nettadresse, /Launch for å kjøre et eksternt program. Sistnevnte fortjener mistenksomhet. En /Launch som starter en kjørbar fil er oppførselen som gjør PDF-er til en skadevarevektor, så samsvarende visningsprogrammer blokkerer det eller spør høylytt og lenken feiler for de fleste lesere. Grip etter /URI og /GoTo og la /Launch være i fred

Markeringskommentarer som fremhevinger og klistrelapper, og formkommentarer som /Square, legger til en rynke: utseendet på skjermen er ikke antydet av typen. Et visningsprogram gjengir sin egen versjon med mindre du fester utseendet med en utseendestrøm, /AP-oppføringen, som refererer til et skjema-XObject som inneholder tegneoperatørene. Hopp over den og den samme fremhevingen kan se annerledes ut i to lesere, eller før og etter en tur innom et redigeringsprogram. For alt hvis nøyaktige utseende er en del av dokumentet, lever /AP. Filvedlegg gjenbruker forresten dette samme maskineriet: en innebygd filstrøm og en ordbok for filspesifikasjon, overflatebehandlet enten som en /FileAttachment-kommentar eller gjennom /EmbeddedFiles-navnetreet under katalogens /Names

Hvor dette laget bryter sammen, og hvordan du fanger det opp

Den tilbakevendende feilen på tvers av alt dette er den dinglende referansen. Bokmerker slutter å vises når katalogen ikke har noen /Outlines-oppføring eller en søskenkjede brytes midt i treet; metadata ignoreres når XMP-strømmen mangler dens /Type /Metadata /Subtype /XML-markering eller xpacket-omslaget er feilformet. I alle tilfeller er sideinnholdet greit, så en tilfeldig åpning ser riktig ut, og feilen dukker bare opp i panelet ingen sjekket

To billige vaner fanger opp det meste av dette. Åpne den ferdige filen i et ekte visningsprogram og klikk gjennom bokmerkepanelet og et utvalg lenker, som utøver referansegrafen på den måten en leser vil gjøre det. Les deretter metadataene tilbake med et separat verktøy og bekreft at Info-ordboken og XMP er enige, den ene uenigheten ingen mengde klikk avslører. Generer dette laget gjennom et bibliotek som eier lenkebokføringen og de fleste av disse fellene åpner seg aldri. HotPDF-komponenten for Delphi og C++Builder eksponerer disposisjons-, kommentar- og metastrukturene gjennom APIer på dokumentnivå, slik at du beskriver bokmerkehierarkiet og lenkene og lar den tråde referansene. For objektmodellen disse strukturene festes til, dekker teknisk oversikt over PDF-filstruktur katalogen og kryssreferansetabellen de avhenger av