Technischer Artikel

XFDF-Import und -Export von PDF-Annotationen in Delphi

HotPDF exportiert und importiert PDF-Annotationen als XFDF über zwei Funktionen, die auf dem aktuell geladenen Dokument arbeiten, ExportLoadedAnnotationsToXFDF und ImportLoadedAnnotationsFromXFDF. XFDF ist das als ISO 19444-1 standardisierte XML-Austauschformat für Annotationen, und dieses Paar erlaubt es einem Delphi- oder C++Builder-Programm, seine Kommentare an Acrobat oder ein Review-Werkzeug eines Drittanbieters zu übergeben und die markierten Ergebnisse zurückzunehmen, alles ohne den Seiteninhalt neu zu schreiben, auf dem die Annotationen sitzen

Stellen Sie sich die beiden Richtungen vor, die das löst. Ein Reviewer öffnet Ihren generierten Bericht in Acrobat, setzt einen roten Pfeil auf eine verrutschte Abbildung, kreist eine falsche Summe ein und tippt eine Notiz an den Rand, dann exportiert er die Kommentare in eine kleine XFDF-Datei. Oder umgekehrt: Ihr Programm erzeugt die Markierungen selbst, und Sie müssen sie an jemanden liefern, dessen Werkzeug nicht HotPDF ist. In beiden Fällen reisen die Annotationen als XML, das beide Seiten verstehen, und die PDF-Seiten bleiben Byte für Byte, was sie waren

HotPDF-Diagramm des XFDF-Roundtrips: Ein geladenes PDF exportiert Annotationen in eine XFDF-Datei und importiert sie zurück, während die Seiten unangetastet bleiben
Eine einzige XFDF-Datei trägt die Kommentare in beide Richtungen, während die PDF-Seiten byteidentisch bleiben

Was ist der Unterschied zwischen FDF und XFDF?

FDF und XFDF transportieren dieselbe Nutzlast in zwei verschiedenen Syntaxen, und der Unterschied zählt in dem Moment, in dem Sie entscheiden, welche Datei Sie einem anderen Werkzeug übergeben. FDF ist das ältere Forms Data Format, das innerhalb der PDF-Spezifikation selbst definiert ist: Es verwendet PDF-Objektsyntax, sodass eine FDF-Datei wie ein abgespecktes PDF aussieht und einen PDF-fähigen Parser zum Lesen braucht. XFDF ist der XML-Ausdruck derselben Daten, eigenständig als ISO 19444-1 standardisiert, was bedeutet, dass jede XML-Bibliothek auf jeder Plattform sie öffnen, vergleichen oder erzeugen kann. Beide Formate können Formularfeldwerte in einem <fields>-Baum transportieren, den ISO 19444-1 Abschnitt 6.3 regelt, und Annotationen in einem <annots>-Baum; HotPDF trennt diese Zuständigkeiten, leitet Formulardaten über ExportLoadedFormToXFDF und reserviert ExportLoadedAnnotationsToXFDF für die <annots>-Seite. Wenn Sie Kommentare mit einem Webservice, einem Java-Review-Server oder einem Skript austauschen, ist XFDF das Format, das die Gegenseite nicht zwingt, einen PDF-Parser einzubetten

HotPDF-Diagramm zum Vergleich von FDF und XFDF, dieselbe Annotations-Nutzlast als PDF-Objektsyntax oder als ISO-19444-1-XML geschrieben
FDF spricht PDF-Objektsyntax, XFDF spricht XML, sodass dieselbe Nutzlast weit mehr Leser erreicht

Wie exportieren Sie PDF-Annotationen in Delphi als XFDF?

HotPDF exportiert Annotationen, indem es jede Seite des geladenen Dokuments durchläuft, für jede unterstützte Annotation ein XFDF-Element ausgibt und die Anzahl der geschriebenen Annotationen zurückgibt. Laden Sie zuerst das PDF, dann rufen Sie ExportLoadedAnnotationsToXFDF mit einem Zielpfad auf. Das Integer-Ergebnis ist die Zahl der serialisierten Annotationen; ein Ergebnis von null oder darunter bedeutet, dass nichts exportiert und keine Datei geschrieben wurde, was Ihr Signal ist, dass das Dokument keine Annotationen eines unterstützten Subtyps enthielt

var
  Pdf: THotPDF;
  Written: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('report-reviewed.pdf', '') > 0 then
    begin
      // Ein XFDF-Element je unterstützter Annotation auf jeder Seite schreiben
      Written := Pdf.ExportLoadedAnnotationsToXFDF('comments.xfdf');
      if Written <= 0 then
        ShowMessage('No supported annotations were found');
    end;
  finally
    Pdf.Free;
  end;
end;

Das XFDF, das herauskommt, ist schlichtes, lesbares XML. HotPDF schreibt eine <xfdf>-Wurzel im ISO-19444-1-Namensraum, einen <annots>-Container und ein Kind je Annotation mit ihrem nullbasierten Seitenindex, ihrer Farbe und Geometrie als Attribute oder Kindelemente. Eine Linie mit gelber Füllung und offener Pfeilspitze, neben einem gefüllten Polygon, wird so serialisiert

<?xml version="1.0" encoding="UTF-8"?>
<xfdf xmlns="http://ns.adobe.com/xfdf/">
  <annots>
    <line page="0" start="72,700" end="220,700"
          color="#FF0000" interior-color="#FFFF00"
          head="OpenArrow" tail="None">
      <contents-richtext>Baseline looks off</contents-richtext>
    </line>
    <polygon page="0" color="#0000FF" interior-color="#CCE5FF">
      <vertices>72,120;180,120;180,200;72,200</vertices>
    </polygon>
  </annots>
</xfdf>

Wie Annotations-Subtypen auf XFDF-Elemente abgebildet werden

Jeder Annotations-Subtyp wird auf ein bestimmtes ISO-19444-1-Element mit eigener Geometriekonvention abgebildet, und HotPDF folgt diesen Strukturen, statt eigene zu erfinden. Linienannotationen tragen ein start- und ein end-Attribut mit den beiden Endpunkt-Koordinatenpaaren, direkt aus dem L-Array der Annotation übernommen, während die LE-Linienendstile zu head- und tail-Attributen werden. Polygon- und Polylinienannotationen verschieben ihre Punktliste in ein <vertices>-Kindelement als durch Semikolon getrennte x,y-Paare, nicht in ein Attribut, weil ein Leser, der das Kindelement erwartet, an anderer Stelle versteckte Punkte stillschweigend verwirft. Ink-Annotationen, die mehrere getrennte Striche enthalten können, verschachteln ein <inklist>-Element mit einem <gesture>-Kind je Strich, sodass eine mehrstrichige Unterschrift die Reise als getrennte Gesten übersteht statt als ein verschmolzener Klumpen

Rich Text, Farbe und Rahmenstil überleben neben der Geometrie. Der Rich-Text-Körper einer Notiz wird als <contents-richtext>-Kind geschrieben; die Innenfüllung, die PDF im IC-Array speichert, die Farbe innerhalb eines Kreises, Quadrats, Polygons oder einer Linienpfeilspitze sowie die Füllung eines Schwärzungskastens, kommt als interior-color-Attribut in der Form #RRGGBB an; und Rahmenbreite, Strichmuster und ein wolkiger Rahmeneffekt werden auf die Attribute width, dashes, style und intensity abgebildet, sodass ein wolkenumrandeter Callout am anderen Ende noch als wolkig gelesen wird. HotPDF bewahrt auch das an eine Markup-Annotation angehängte Popup-Fenster, importiert die Geometrie des Popup-Kindes und seinen geöffneten oder geschlossenen Zustand in das Popup-Dictionary der Annotation, und es überträgt die Öffnungs- und Review-Zustände von Textannotationen, sodass ein geprüftes Dokument nicht nur seine Formen behält, sondern auch die Workflow-Metadaten, auf die Reviewer sich verlassen

HotPDF-Diagramm, das die Eigenschaften von PDF-Linien-, Polygon-, Ink- und Rich-Text-Annotationen auf ihre XFDF-Attribute und Kindelemente abbildet
Jeder Annotations-Subtyp folgt seiner ISO-19444-1-Geometriekonvention statt einem privaten Dialekt

XFDF zurück in ein geladenes Dokument importieren

HotPDF importiert XFDF, indem es das XML parst, für jedes Element über NewLoadedAnnotation eine neue Annotation anlegt, sie an die vom Element benannte Seite hängt und zurückgibt, wie viele Annotationen hinzugefügt wurden. Der Workflow ist symmetrisch zum Export: Laden Sie das Basis-PDF, rufen Sie ImportLoadedAnnotationsFromXFDF mit der Datei des Reviewers auf und speichern Sie dann das geladene Dokument, um die neuen Markierungen zu persistieren. Fehlt die Datei oder lässt sich das XML nicht parsen, gibt die Funktion null zurück und das geladene Dokument bleibt unangetastet

var
  Pdf: THotPDF;
  Added: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('report.pdf', '') > 0 then
    begin
      Added := Pdf.ImportLoadedAnnotationsFromXFDF('comments.xfdf');
      if Added > 0 then
        Pdf.SaveLoadedDocument('report-annotated.pdf');
    end;
  finally
    Pdf.Free;
  end;
end;

Weil jedes XFDF-Element seinen eigenen Seitenindex benennt, landen Annotationen auf den Seiten, gegen die sie verfasst wurden, selbst wenn Sie mehrere Dateien nacheinander importieren, was es sicher macht, Kommentare von mehr als einem Reviewer auf demselben geladenen Dokument zu sammeln, bevor ein einziges Speichern erfolgt. Das folgende Beispiel führt zwei Reviewer in einer zusammengeführten Kopie zusammen. Wie Sie die Annotationsobjekte selbst im Code aufbauen und bearbeiten, statt sie als Dateien auszutauschen, zeigt, wie HotPDF PDF-Annotationsobjekte direkt aus Delphi erstellt und bearbeitet

var
  Pdf: THotPDF;
  Total, I: Integer;
  Files: array[0..1] of string;
begin
  Files[0] := 'alice-comments.xfdf';
  Files[1] := 'bob-comments.xfdf';
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('master.pdf', '') > 0 then
    begin
      Total := 0;
      for I := Low(Files) to High(Files) do
        Inc(Total, Pdf.ImportLoadedAnnotationsFromXFDF(Files[I]));
      if Total > 0 then
        Pdf.SaveLoadedDocument('master-merged.pdf');
    end;
  finally
    Pdf.Free;
  end;
end;

Was sauber den Roundtrip übersteht und was nicht

HotPDF überträgt die Annotations-Subtypen im Roundtrip, denen ISO 19444-1 ein Zuhause gibt, und überspringt den Rest bewusst, statt etwas auszugeben, das ein Leser fehlinterpretieren würde. Die unterstützte Menge umfasst die Markup-Typen, die reale Review-Arbeit dominieren: Textnotizen, Freitext, Linie, Quadrat, Kreis, Polygon, Polylinie, die vier Text-Markup-Typen (Hervorhebung, Unterstreichung, Durchstreichung und Wellenlinie), Stempel, Ink und Caret, dazu Dateianhang, Ton, Schwärzung und Link, insgesamt achtzehn Subtypen. Eine Annotation, deren Subtyp außerhalb dieser Liste liegt, wird beim Export übergangen, und weil sie übersprungen statt leer geschrieben wird, bläht sie die von der Funktion zurückgegebene Zahl nicht auf

Rich Text ist der ehrliche Vorbehalt. HotPDF bewahrt den <contents-richtext>-Körper, sodass der formatierte Text und der reine Inhalt die Reise antreten, aber XFDF transportiert den Text und das Stil-Markup eines Kommentars, keinen gerenderten Appearance-Stream, sodass die empfangende Anwendung das Popup mit ihren eigenen Schriftarten und ihrem eigenen Layout neu zeichnet, statt die exakten Pixel von HotPDF zu reproduzieren. Behandeln Sie den Roundtrip als treu gegenüber Inhalt und Absicht, nicht gegenüber der Bildschirmdarstellung bis aufs Pixel. Wenn Ihr formatierter Inhalt in XFA-Formulardaten statt in Annotations-Streams lebt, gelten andere Regeln, und wie HotPDF XFA exData, Rich Text und Hyperlinks behandelt deckt diesen separaten Pfad ab

Die Behandlung auf Zeichenebene ist strenger, als sie aussieht, und genau das wollen Sie. HotPDF wendet beim Schreiben von Text die Escaping-Regeln aus ISO 19444-1 Abschnitt 5.8.2 an, kodiert die XML-relevanten Zeichen und Steuerbytes, sodass ein Kommentar mit einem Kaufmanns-Und, einer spitzen Klammer oder einem Zeilenumbruch wohlgeformtes XML ergibt, das jeder konforme Parser akzeptiert, und kehrt dieselben Regeln beim Import um. Deshalb kommt eine aus einer Tabellenkalkulation eingefügte Notiz samt aller Satzzeichen unversehrt zurück, statt die Datei zu beschädigen

Der Annotationsaustausch ist ein Ausschnitt dessen, was die API für geladene Dokumente leistet, und er lässt sich mit dem Rest kombinieren: Importieren Sie das XFDF eines Reviewers, passen Sie die Seiten an oder bearbeiten Sie die Dokumentmetadaten, glätten Sie die Datei oder vergeben Sie neue Berechtigungen und exportieren Sie dann ein frisches XFDF für die nächste Runde. All das wird in der Standard-HotPDF Delphi Component für Delphi und C++Builder ausgeliefert, deren Referenz die vollständige Abdeckung der Annotations-Subtypen und die begleitenden XFDF-Funktionen für Formulardaten dokumentiert