PDFlibPas hängt eine eingebettete Datei an eine bestimmte Seite statt ans Gesamtdokument, indem es ein /AF-Array in das Seitenverzeichnis schreibt, während die Nutzdaten selbst im EmbeddedFiles-Namensbaum des Dokuments registriert bleiben. Diese Aufteilung beschreibt ISO 32000-2 §14.13, und sie ist es, die einem Reader die Frage beantwortet, die ein Anhang auf Dokumentebene offenlässt: Zu welcher Seite gehören diese Daten
Die Anwendungsfälle sind spezieller als allgemeine Anhänge. Ein Messbericht, bei dem jede Seite die Rohmessreihe hinter ihrem Diagramm mitführt. Ein Scanstapel, bei dem jede Seite das OCR-Ergebnis behält, das ihre Textebene erzeugt hat. Ein Zeichnungssatz, bei dem jedes Blatt den CAD-Auszug mitbringt, aus dem es gerendert wurde. In jedem Fall wäre eine Anhangsliste auf Dokumentebene ein Haufen Dateien, deren Namen Seitennummern kodieren — eine Konvention, keine Struktur
Eine Nutzlast, zwei Referenzstellen
Der strukturell wichtige Punkt: Eine Assoziation auf Seitenebene erzeugt keine zweite Kopie von irgendetwas. Die Datei wird einmal eingebettet und im EmbeddedFiles-Namensbaum registriert, genau wie ein Anhang auf Dokumentebene, mit derselben Dateispezifikations-Mechanik. Der Unterschied liegt darin, wohin die Referenz und ihr Relationship-Key geschrieben werden: ins Seitenverzeichnis statt in den Dokumentkatalog
Zwei Konsequenzen. Erstens findet ein Reader, der nur Anhänge auf Dokumentebene kennt, die Nutzdaten trotzdem, weil sie genau in dem Namensbaum liegen, in dem er sucht. Zweitens entfernt das Zurücksetzen der Seitenassoziation die Bindung, nicht die Datei. ClearPageAssociatedFiles löst die Seite von ihren Associated Files und lässt die Nutzdaten über den Namensbaum erreichbar — das konservative Verhalten: Eine Operation, die sagt, hebe die Assoziation auf, darf nicht lautlos Daten zerstören, auf die andere Teile des Dokuments noch verweisen
Diese Funktion hat eine bewusst enge Erfolgsbedingung, die man kennen sollte. Erfolg meldet sie nur, wenn die Seite tatsächlich einen /AF-Key trug. Eine Seite, die nie Assoziationen hatte, liefert ein Fehlerergebnis statt einer fröhlichen Bestätigung, sodass der Aufrufer einen No-op nicht für eine vollendete Aufräumaktion halten kann
var
Lib: TPDFlib;
Idx, I: Integer;
begin
Lib := TPDFlib.Create(nil);
try
Lib.LoadFromFile('survey-report.pdf');
// Messreihe anhängen, die das Diagramm auf Seite 3 erzeugt hat
Idx := Lib.AddPageAssociatedFileFromFile(3,
'series-03.csv', // Datei auf der Festplatte
'measurements.csv', // Anzeigename im PDF
'text/csv', // MIME-Typ
'Raw measurement series for figure 3',
'Data'); // AFRelationship, ISO 32000-2 14.13
if Idx < 0 then
raise Exception.Create('page association refused');
for I := 0 to Lib.GetPageAssociatedFileCount(3) - 1 do
Writeln('page 3 associated file, embedded index ',
Lib.GetPageAssociatedFileEmbeddedIndex(3, I));
Lib.SaveToFile('survey-report-with-data.pdf');
finally
Lib.Free;
end;
end;
Der Relationship-String ist in der Praxis kein Freitext. ISO 32000-2 definiert ein Vokabular — Source, Data, Alternative, Supplement, EncryptedPayload, FormData, Schema und Unspecified — und Consumer knüpfen daran an. Data für die Zahlen hinter einem Diagramm, Source für das Dokument, aus dem eine Seite erzeugt wurde, Alternative für eine äquivalente Darstellung. Schöpfen Sie aus dem Vokabular, auch wenn noch nichts in Ihrer Pipeline es liest, denn das nächste Werkzeug in der Kette könnte es tun
Warum braucht dieselbe Suche FollowRef in beide Richtungen?
Weil das Verfolgen von Referenzen zwei verschiedene Fragen beantwortet und der Code wissen muss, welche er gerade stellt. Eine Key-Suche, die indirekten Referenzen folgt, liefert das Objekt, auf das die Referenz zeigt. Eine Suche ohne Verfolgen liefert die Referenz selbst. Beides ist korrekt, und die falsche Variante erzeugt ein stilles Fehlverhalten statt eines Fehlers
Das Lesen einer Associated File demonstriert die erste Richtung. Um die Objektnummer des eingebetteten Streams hinter den /EF- und /F-Keys der Dateispezifikation zu bekommen, darf die Suche nicht folgen, denn das Verfolgen löst die Referenz ins Stream-Objekt auf und die Objektnummer ist weg. Die Regel verallgemeinert: Jeder Codepfad, der eine Objektidentität statt Objektinhalt braucht, muss die rohe Referenz nehmen
Optional Content zeigt die Gegenrichtung, und sie war aufwendiger zu finden. Das Properties-Wörterbuch für Optional Content wird als indirektes Objekt in den Katalog geschrieben, also bekommt Code, der es ohne Verfolgen zurückliest, eine Referenz statt eines Wörterbuchs. Die Typprüfung auf diesem Wert schlägt dann fehl, und der naheliegende Fallback-Zweig — gibt es keine Konfiguration, lege eine an — läuft und überschreibt die bereits vorhandene Konfiguration. Nichts wirft eine Exception. Die Ebenen aus Optional Content Groups und Layern verlieren schlicht ihren Standard-Sichtbarkeitszustand
Die Lektion verallgemeinert über beide Fälle hinaus. Wenn eine Suche entweder eine Referenz oder das Objekt liefern kann, ist eine blanke Typprüfung keine Fehlerbehandlung: Sie ist ein Zweig, der früher oder später aus dem falschen Grund genommen wird. Entscheiden Sie explizit, was jede Aufrufstelle braucht, und greifen Sie lieber zur öffentlichen API, die die Frage direkt beantwortet, etwa einer Count-Eigenschaft für Optional Content, statt sich in einen protected Accessor für das Katalogwörterbuch zu wühlen
// Anhänge auf Dokumentebene und Seitenassoziationen koexistieren. Eine
// eingebettete Datei kann auch auf Dokumentebene als assoziiert markiert werden
if Lib.IsEmbeddedFileAssociated(0) = 0 then
Lib.SetEmbeddedFileAssociated(0, 1, 'Supplement');
Writeln('document associated files: ', Lib.GetAssociatedFileCount);
Writeln('page 3 associated files : ',
Lib.GetPageAssociatedFileCount(3));
// Das Zurücksetzen löst die Seitenbindung; die Nutzdaten bleiben im Namensbaum
if Lib.ClearPageAssociatedFiles(3) > 0 then
Writeln('page 3 associations removed, payloads still reachable');
Was Konformitätsmodi mit Anhängen machen
Archivprofile beschränken, was eingebettet werden darf, und die Beschränkung wird am Eingang durchgesetzt, nicht beim Speichern. PDF/A-1 verbietet eingebettete Dateien komplett, PDF/A-2 erlaubt nur eingebettete PDF/A-Dokumente, und PDF/A-3 ist das Profil, das das Einbetten für beliebige Dateitypen geöffnet hat — genau deshalb bauen hybride Rechnungsformate darauf auf
PDFlibPas verweigert den Anhang, wenn der aktive Konformitätsmodus ihn nicht zulässt — direkt beim Aufruf, nicht hunderte Operationen später bei der Ausgabe. Das ist eine bewusste Entscheidung darüber, wo ein Fehler am billigsten zu behandeln ist: Eine Verweigerung an der Aufrufstelle nennt die Datei, die Sie gerade hinzufügen wollten, eine Verweigerung beim Speichern nennt nur ein Dokument und lässt Sie herausfinden, welcher von vierzig Anhängen schuld war
Deshalb tauchen Associated Files auch so oft im E-Invoicing auf. Eine hybride Rechnung ist ein PDF, das ein Mensch liest, mit einer maschinenlesbaren XML-Nutzlast als Anhang, markiert mit der richtigen Relationship — und sowohl das Container-Profil als auch der Relationship-Key sind Teil der Spezifikation, keine Konvention. Dieser Aufbau ist in Factur-X- und ZUGFeRD-Hybridrechnungen bauen beschrieben, die Metadaten-Seite in dem PDF/A-3-XMP-Extension-Schema
Wann gehört die Assoziation pro Seite statt pro Dokument?
Wenn ein Consumer wissen muss, zu welcher Seite die Daten gehören — und nur dann. Anhänge auf Dokumentebene sind einfacher, in Viewern weiter verbreitet und ausreichend, sobald die Nutzlast das ganze Dokument beschreibt: eine Rechnungs-XML, ein Signaturmanifest, ein Quellarchiv. Greifen Sie zur Seitenassoziation, wenn die Nutzlast wirklich seitenbezogen ist und die Seitenidentität Teil ihrer Bedeutung ist
Unterstützung ist die praktische Grenze. Seitengebundene Associated Files sind ein PDF-2.0-Konstrukt, und die Viewer-Unterstützung ist dünner als bei Dokumentanhängen. Weil die Nutzlast so oder so im Namensbaum liegt, zeigt ein Viewer, der /AF auf Seiten ignoriert, die Datei trotzdem in seiner Anhangsliste — der Abbau ist also graceful. Aber wenn die Seitenbindung für Ihren Consumer essenziell ist statt nur nützliche Metadaten, prüfen Sie den Reader, den Sie tatsächlich anvisieren, statt es anzunehmen
Seitengebundene Associated Files, Anhänge auf Dokumentebene und die Archivprofil-Schranken, die beide regeln, liegen in der PDFlibPas Delphi PDF library vor. Wenn Sie beim Eingang zusätzlich ältere Dateien reparieren, entscheidet die Meta- und Konformitätsarbeit in Konvertierung zu PDF/A mit Metadaten-Reparatur, welche dieser Anhangsrouten Ihnen überhaupt offensteht