Technischer Artikel

Erklärt: PDF-Metadaten, Outlines und Anmerkungen

Entfernt man die Seitenbeschreibungen, bleibt eine dünne Strukturschicht übrig, die niemand ausdruckt, auf die aber jeder Reader, Indexer und jedes Archivsystem angewiesen ist. Ein Seitenobjekt weiß nichts über das Kapitel, zu dem es gehört, den Autor, der es geschrieben hat, oder die Fußnote, die woandershin verlinkt. Dieses Wissen lebt eine Ebene höher in drei Strukturen, die an den Dokumentkatalog angehängt sind: den Metadaten-Streams, dem Outline-Baum und den seitenbezogenen Annotations-Arrays. Sie teilen eine Eigenschaft, die es leicht macht, sie falsch zu implementieren. Keine von ihnen hinterlässt sichtbare Spuren auf der Seite, sodass eine Datei perfekt gerendert werden kann und dennoch ihre Lesezeichen fehlen, ihrem eigenen Autorenfeld widersprechen oder ein Link auf ein Seitenobjekt verweist, das nicht mehr existiert

Dies ist die Schicht, die eine PDF-Bibliothek als Dokumenteigenschaften, Lesezeichen-APIs und Link- oder Annotationsaufrufe bereitstellt, und die Schicht, die ein Such-Crawler liest, um zu entscheiden, wovon Ihr Dokument handelt. Das zugrunde liegende Objektmodell wird im Leitfaden zur PDF-Dokumentenstruktur behandelt. Hier liegt der Fokus ausschließlich auf dem, was am Katalog hängt

Alle drei Strukturen hängen am Katalog. Ein vollständiger Katalog, der sie miteinander verdrahtet, sieht so aus:

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

Vier Einträge, vier unabhängige Subsysteme. /Pages ist das sichtbare Dokument; /Outlines ist der Lesezeichenbaum; /Metadata verweist auf den XMP-Stream; /Names erreicht das dokumentweite Namens-Wörterbuch, das unter anderem eingebettete Dateianhänge enthält. Jeder ist optional, und ein Reader, der keinen davon findet, zeigt die Seiten trotzdem an. Diese Optionalität ist genau der Grund, warum die Navigationsschicht das Erste ist, was verrottet, wenn eine Datei von Tools bearbeitet wird, die nur Seiten verstehen

Zwei Metadatenspeicher, die sich widersprechen

PDF trägt Dokument-Metadaten an zwei Orten gleichzeitig, und der Ärger beginnt, wenn sie unterschiedliche Dinge aussagen. Der ursprüngliche Mechanismus ist das Dokumentinformations-Wörterbuch (Document Information Dictionary), auf das durch /Info im Trailer verwiesen wird: eine flache Menge von Schlüssel-Wert-Paaren für /Title, /Author, /Subject, /Keywords, /Creator, /Producer und die beiden Daten (Erstellungs- und Änderungsdatum). Es ist einfach und jeder Viewer liest es. PDF 2.0 missbilligt das meiste davon zugunsten des zweiten Mechanismus, dem XMP-Metadaten-Stream

XMP ist ein eigenständiges XML-Dokument, geschrieben in RDF, gespeichert als Stream, den der Katalog über /Metadata erreicht und der mit /Type /Metadata /Subtype /XML markiert ist. Im Gegensatz zum Info-Wörterbuch, das in der PDF-Objektstruktur vergraben ist, ist ein XMP-Paket so konzipiert, dass es von Tools, die nichts über PDF wissen, selbstständig extrahiert und geparst werden kann. Hier ist ein repräsentatives Paket:

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

Drei Details in diesem Block entscheiden, ob die Metadaten den Kontakt mit echten Tools überleben. Die xpacket-Verarbeitungsanweisungen (Processing Instructions) sind keine Dekoration: Sie rahmen das Paket ein, damit ein Extraktor es innerhalb eines größeren Bytestreams finden kann, und ein Writer, der das schließende <?xpacket end="w"?> weglässt, erzeugt eine Datei, die sich zwar problemlos öffnen lässt, aber strenge Validatoren stolpern lässt. Die Eigenschaftendatentypen (Property Datatypes) sind ebenfalls wichtig. dc:title ist eine Sprachalternative (Language Alternative), eingewickelt in rdf:Alt, während dc:creator eine geordnete Liste ist und rdf:Seq benötigt; beides als einfachen Textknoten auszugeben, ist der häufigste XMP-Fehler, der von den meisten Viewern toleriert wird, bis auf den einen, der es nicht tut. Die Namensraum-Präfixe sind konventionell, aber die URIs, an die sie gebunden sind, sind normativ: Ein Parser orientiert sich an der URI, nicht am Präfix

Die eiserne Regel bei zwei Speichern lautet, dass sie übereinstimmen müssen. Wenn /Info besagt, dass der Autor eine Person ist, und dc:creator eine andere benennt, haben Sie ein Dokument ausgeliefert, das dieselbe Frage auf zwei Arten beantwortet, und welche Antwort gewinnt, hängt davon ab, welches Feld das konsumierende Tool liest. Eine Bibliothek schreibt in der Regel beide für Sie, aber in dem Moment, in dem Sie eines von Hand bearbeiten oder Dateien von verschiedenen Generatoren zusammenführen, driften die beiden auseinander. Behandeln Sie das Info-Wörterbuch als Legacy-Kompatibilität und XMP als die einzige Quelle der Wahrheit (Source of Truth) und generieren Sie beide aus einem Satz von Werten, anstatt sie unabhängig voneinander zu flicken. Für PDF/A wird dies zu einer Konformitätsanforderung: ISO 19005 schreibt XMP vor und verbietet jede Info-Eigenschaft, die ihrem XMP-Gegenstück widerspricht

Der Outline-Baum hinter dem Lesezeichen-Panel

Was ein Viewer als Lesezeichen-Panel anzeigt, ist in der Datei ein doppelt verketteter Baum von Wörterbüchern, der Dokument-Outline genannt wird. Der Katalog verweist über /Outlines auf ein Root-Outline-Wörterbuch; der Root verweist auf sein erstes und letztes Element auf der obersten Ebene; und jedes Element ist mit seinen Nachbarn und seinem übergeordneten Element (Parent) verknüpft. Es gibt nirgendwo ein Array von Lesezeichen. Die gesamte Struktur wird durch Verfolgen von Referenzen rekonstruiert, was genau der Grund ist, warum ein einziger defekter Link einen ganzen Zweig aus dem Panel verschwinden lassen kann, ohne dass ein Fehler auftritt

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

Liest man die Links, werden die Invarianten offensichtlich. Jedes Element zeigt zurück auf sein /Parent. Geschwister bilden eine Kette durch /Prev und /Next, wobei das erste Element /Prev und das letzte /Next weglässt. Ein Elternteil benennt sein erstes und letztes Kind durch /First und /Last, und die Kinder dazwischen sind nur durch Durchlaufen der Geschwisterkette erreichbar. Machen Sie hier einen Fehler, so ist das Scheitern stumm: Ein veraltetes /Next schneidet ein Kapitel ab, ein Parent, dessen /Last die Kette nicht abschließt, hinterlässt verwaiste Elemente, und der Viewer rendert, was auch immer er erreichen kann

Das /Count-Feld trägt ein Stück Zustand, das viele überrascht. Auf dem Root und auf jedem erweiterten Element enthält es die Anzahl der aktuell sichtbaren Nachkommen; auf einem zusammengeklappten Element ist es eine negative Zahl, deren Betrag angibt, wie viele Nachkommen bei der Erweiterung erscheinen würden. Also ist /Count kein festgelegter struktureller Fakt über den Baum, sondern der gespeicherte offene oder geschlossene Zustand des Panels, und ein Generator, der ihn hart auf eine positive Gesamtzahl codiert, öffnet jeden Zweig wieder, den der Autor eigentlich geschlossen lassen wollte

Jedes Element verdient seinen Platz, indem es irgendwohin zeigt. Der /Title ist das, was das Panel anzeigt; die /Dest (Destination) ist, wo ein Klick landet. Ein Ziel (Destination) kann inline im Element stehen, wie oben, oder ein Name sein, der durch das Namens-Wörterbuch des Dokuments aufgelöst wird, was die bessere Wahl ist, wenn viele Lesezeichen und Links auf dieselben Stellen zielen, da Sie ein verschobenes Ziel an nur einem Ort korrigieren müssen. Eine Bibliothek versteckt diesen Baum im Allgemeinen hinter einem Outline-Root-Handle und Methoden, die untergeordnete Einträge hinzufügen; in HotPDF legt das Dokument einen OutlineRoot vom Typ THPDFDocOutlineObject offen und fädelt die /Prev-, /Next-, /Parent- und /Count-Links für Sie auf, während Sie Elemente anhängen. Es lohnt sich, dies zu nutzen, denn das manuelle Warten dieser Invarianten über Bearbeitungen hinweg ist genau der Punkt, an dem Outlines zerbrechen

Ziele (Destinations): Die Grammatik, wohin ein Klick führt

Sowohl Lesezeichen als auch Link-Anmerkungen verweisen auf Ziele, und ein Ziel ist mehr als nur eine Seitenzahl. Es ist ein Array, das ein Seitenobjekt benennt und dann über ein Verb (Aktionswort) an zweiter Stelle angibt, wie der Viewer es einrahmen (framen) soll. Am häufigsten, aber auch am missbräuchlichsten verwendet, wird /XYZ, in der Form [page /XYZ left top zoom]. Seine drei Operanden sind unabhängig voneinander, und jeder darf null sein, um auszudrücken: „Lass dies so, wie der Leser es hatte.“ [page /XYZ null null null] springt also zur Seite, ohne Scrollposition oder Zoom anzutasten, was in der Regel genau das ist, was man von einem „Gehe zu Seite“-Link erwartet. Die Zahlen sind im Standard-Benutzerbereich (Default User Space) definiert, gemessen von unten links mit Y aufwärts zunehmend – dasselbe Koordinatensystem, das auch der Seiteninhalt verwendet. Autoren, die vom Bildschirm-Layout kommen, messen reflexartig von oben und schicken den Leser ans falsche Ende der Seite

Die /Fit-Familie tauscht präzise Positionierung gegen Resilienz. [page /Fit] skaliert die gesamte Seite in das Fenster, [page /FitH top] passt die Seitenbreite an eine bestimmte obere Kante an, und [page /FitR l b r t] zoomt ein Rechteck, um die Ansicht auszufüllen. Da diese die Skalierung aus der Seitengeometrie berechnen, statt feste Koordinaten zu verwenden, verhält sich ein /Fit-Ziel auch nach einer Größenänderung der Seite noch sinnvoll, während ein /XYZ-Ziel mit fest einkompiliertem Zoom den Leser auf den Rand starren lassen kann. Für ein Inhaltsverzeichnis altert /FitH mit der oberen Koordinate des Abschnitts besser als /XYZ mit einem geschätzten Zoom

Anmerkungen (Annotations): Alles Interaktive, das kein Seiteninhalt ist

Eine Anmerkung ist ein Objekt, das die Seite überlagert, ohne Teil ihres Content-Streams zu sein. Links, Notizzettel, Hervorhebungen, Formular-Widgets, Icons für Dateianhänge, Stempel: alles sind Anmerkungen, aufgelistet im /Annots-Array der Seite, auf der sie sitzen. Das Entfernen einer Anmerkung aus diesem Array entfernt sie von der Seite, obwohl der zugrunde liegende Inhalt unberührt bleibt. Genau das ist der Punkt: Anmerkungen sind eine Bearbeitungsschicht, getrennt von den Markierungen, über denen sie sitzen

Jede Anmerkung teilt sich ein kleines Rückgrat (Spine). /Subtype benennt die Art, /Rect gibt ihr Begrenzungsrechteck in Seitenkoordinaten an und /Contents enthält Text, der gleichzeitig als barrierefreie Beschreibung (Accessible Description) dient. Die Link-Anmerkung ist der Fall, der es wert ist, studiert zu werden, denn es gibt sie in zwei Formen: als bloßes Ziel (Destination) und als Aktion (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

Das /Rect ist ein Hotspot; ein Klick hinein schickt den Leser an das Ziel und nutzt dabei dieselbe Grammatik wie die Outline. Das /Border [0 0 0] leistet echte Arbeit, indem es das hässliche Standardrechteck unterdrückt, das Viewer um Links ziehen. Die zweite Form tauscht die bloße /Dest gegen eine /A-Aktion, deren /S-Untertyp (Subtype) das Verhalten wählt: /GoTo innerhalb dieser Datei, /GoToR für eine andere Datei, /URI für eine Webadresse, /Launch zum Ausführen eines externen Programms. Letzteres verdient Misstrauen. Ein /Launch, das eine ausführbare Datei startet, ist das Verhalten, das PDFs zu einem Malware-Vektor macht, daher blockieren konforme Viewer es oder fragen laut nach, und der Link schlägt für die meisten Leser fehl. Greifen Sie zu /URI und /GoTo und lassen Sie die Finger von /Launch

Markup-Anmerkungen wie Hervorhebungen und Notizzettel sowie Form-Anmerkungen wie /Square fügen eine Komplikation hinzu: Ihr Aussehen auf dem Bildschirm wird nicht durch ihren Typ impliziert. Ein Viewer rendert seine eigene Version, es sei denn, Sie pinnen das Erscheinungsbild mit einem Appearance Stream fest, dem /AP-Eintrag, der auf ein Formular-XObject mit den Zeichenoperatoren verweist. Lassen Sie es weg, und dieselbe Hervorhebung kann in zwei Readern oder vor und nach einem Editor-Roundtrip unterschiedlich aussehen. Für alles, dessen exaktes Aussehen Teil des Dokuments ist, müssen Sie /AP liefern. Dateianhänge (File Attachments) verwenden übrigens denselben Mechanismus: einen eingebetteten Dateistream und ein Dateispezifikations-Wörterbuch, das entweder als /FileAttachment-Anmerkung oder über den Namensbaum (Name Tree) /EmbeddedFiles unter /Names im Katalog auftaucht

Wo diese Schicht bricht und wie man es abfängt

Der wiederkehrende Fehler bei all dem ist die ins Leere laufende Referenz (Dangling Reference). Lesezeichen tauchen nicht mehr auf, wenn der Katalog keinen /Outlines-Eintrag hat oder eine Geschwisterkette mitten im Baum bricht; Metadaten werden ignoriert, wenn dem XMP-Stream die Markierung /Type /Metadata /Subtype /XML fehlt oder der xpacket-Wrapper fehlerhaft ist. In jedem Fall ist der Seiteninhalt in Ordnung, sodass ein beiläufiges Öffnen korrekt aussieht und der Defekt erst in dem Panel auftaucht, das niemand überprüft hat

Zwei einfache Gewohnheiten fangen das meiste davon ab. Öffnen Sie die fertige Datei in einem echten Viewer und klicken Sie sich durch das Lesezeichen-Panel und eine Auswahl an Links, was den Referenz-Graphen so auslastet, wie es ein Leser tun wird. Lesen Sie dann die Metadaten mit einem separaten Tool zurück und vergewissern Sie sich, dass das Info-Wörterbuch und das XMP übereinstimmen – die einzige Unstimmigkeit, die durch noch so viel Klicken nicht offenbart wird. Generieren Sie diese Schicht über eine Bibliothek, der die Link-Buchführung obliegt, und die meisten dieser Fallen öffnen sich gar nicht erst. Die HotPDF-Komponente für Delphi und C++Builder legt die Outline-, Annotations- und Metadaten-Strukturen über APIs auf Dokumentebene frei, sodass Sie die Lesezeichenhierarchie und die Links beschreiben und sie die Referenzen fädeln lassen können. Für das Objektmodell, an das diese Strukturen angebunden sind, behandelt der technische Überblick zur PDF-Dateistruktur den Katalog und die Querverweistabelle, von denen sie abhängen