Article technique

Importer et exporter les annotations PDF en XFDF en Delphi

HotPDF exporte et importe les annotations PDF au format XFDF grâce à deux fonctions qui agissent sur le document actuellement chargé, ExportLoadedAnnotationsToXFDF et ImportLoadedAnnotationsFromXFDF. XFDF est le format XML d'échange d'annotations normalisé sous la référence ISO 19444-1, et ce duo permet à un programme Delphi ou C++Builder de confier ses commentaires à Acrobat ou à un outil de relecture tiers, puis de récupérer les résultats annotés, le tout sans réécrire le contenu des pages sur lesquelles les annotations reposent

Imaginez les deux directions que cela résout. Un relecteur ouvre votre rapport généré dans Acrobat, pose une flèche rouge sur une figure mal alignée, entoure un total erroné et saisit une note dans la marge, puis exporte les commentaires vers un petit fichier XFDF. Ou l'inverse : votre programme produit lui-même les annotations, et vous devez les livrer à quelqu'un dont l'outil n'est pas HotPDF. Dans les deux cas les annotations voyagent sous forme de XML que les deux parties comprennent, et les pages du PDF restent octet pour octet ce qu'elles étaient

Schéma HotPDF de l'aller-retour XFDF : un PDF chargé exporte ses annotations vers un fichier XFDF et les réimporte pendant que les pages restent intactes
Un seul fichier XFDF transporte les commentaires dans les deux sens pendant que les pages du PDF restent identiques octet pour octet

Quelle est la différence entre FDF et XFDF ?

FDF et XFDF transportent la même charge utile dans deux syntaxes différentes, et la distinction compte dès l'instant où vous décidez quel fichier remettre à un autre outil. FDF est l'ancien Forms Data Format défini dans la spécification PDF elle-même : il utilise la syntaxe des objets PDF, si bien qu'un fichier FDF ressemble à un PDF allégé et exige un analyseur qui connaît le PDF. XFDF est l'expression XML de ces mêmes données, normalisée séparément sous la référence ISO 19444-1, ce qui signifie que n'importe quelle bibliothèque XML sur n'importe quelle plateforme peut l'ouvrir, le comparer ou le générer. Les deux formats peuvent porter les valeurs des champs de formulaire dans un arbre <fields>, que régit la clause 6.3 de la norme ISO 19444-1, et les annotations dans un arbre <annots> ; HotPDF sépare ces responsabilités, aiguillant les données de formulaire vers ExportLoadedFormToXFDF et réservant ExportLoadedAnnotationsToXFDF au versant <annots>. Quand vous échangez des commentaires avec un service web, un serveur de relecture Java ou un script, XFDF est le format qui n'oblige pas la partie adverse à embarquer un analyseur PDF

Schéma HotPDF comparant FDF et XFDF, la même charge utile d'annotations écrite en syntaxe objet PDF ou en XML ISO 19444-1
FDF parle la syntaxe des objets PDF tandis que XFDF parle XML, si bien que la même charge utile atteint bien plus de lecteurs

Comment exporter les annotations PDF en XFDF avec Delphi ?

HotPDF exporte les annotations en parcourant chaque page du document chargé, en émettant un élément XFDF pour chaque annotation prise en charge, et en renvoyant le nombre d'annotations écrites. Chargez d'abord le PDF, puis appelez ExportLoadedAnnotationsToXFDF avec un chemin cible. Le résultat entier est le nombre d'annotations sérialisées ; un résultat nul ou négatif signifie que rien n'a été exporté et qu'aucun fichier n'a été écrit, ce qui vous signale que le document ne portait aucune annotation d'un sous-type pris en charge

var
  Pdf: THotPDF;
  Written: Integer;
begin
  Pdf := THotPDF.Create(nil);
  try
    if Pdf.LoadFromFile('report-reviewed.pdf', '') > 0 then
    begin
      // Écrit un élément XFDF par annotation prise en charge sur chaque page
      Written := Pdf.ExportLoadedAnnotationsToXFDF('comments.xfdf');
      if Written <= 0 then
        ShowMessage('No supported annotations were found');
    end;
  finally
    Pdf.Free;
  end;
end;

Le XFDF produit est du XML simple et lisible. HotPDF écrit une racine <xfdf> dans l'espace de noms ISO 19444-1, un conteneur <annots>, et un enfant par annotation avec son index de page en base zéro, sa couleur et sa géométrie sous forme d'attributs ou d'éléments enfants. Une ligne à remplissage jaune et pointe de flèche ouverte, posée à côté d'un polygone plein, se sérialise ainsi

<?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>

Comment les sous-types d'annotation se traduisent en éléments XFDF

Chaque sous-type d'annotation correspond à un élément ISO 19444-1 précis avec sa propre convention de géométrie, et HotPDF suit ces structures plutôt que d'en inventer une à lui. Les annotations de type ligne portent un attribut start et un attribut end contenant les deux paires de coordonnées des extrémités, reprises directement du tableau L de l'annotation, tandis que les styles de terminaison LE deviennent les attributs head et tail. Les annotations polygone et polyligne déplacent leur liste de points dans un élément enfant <vertices> sous forme de paires x,y séparées par des points-virgules, et non dans un attribut, car un lecteur qui attend l'élément enfant abandonnera silencieusement les points cachés ailleurs. Les annotations à l'encre, qui peuvent contenir plusieurs traits distincts, imbriquent un élément <inklist> avec un enfant <gesture> par trait, de sorte qu'une signature en plusieurs traits survit au voyage sous forme de gestes distincts plutôt que de masse fusionnée

Le texte enrichi, la couleur et le style de bordure survivent aux côtés de la géométrie. Le corps en texte enrichi d'une note est écrit comme un enfant <contents-richtext> ; le remplissage intérieur que le PDF stocke dans le tableau IC, la peinture à l'intérieur d'un cercle, d'un carré, d'un polygone ou d'une pointe de flèche et le fond d'un cadre de caviardage, arrive sous forme d'attribut interior-color au format #RRGGBB ; et la largeur de bordure, le motif de tirets et l'effet de bordure nuageuse se traduisent par les attributs width, dashes, style et intensity, de sorte qu'une bulle au contour nuageux se lit toujours comme telle à l'autre bout. HotPDF conserve aussi la fenêtre contextuelle attachée à une annotation de relecture, important la géométrie de l'enfant popup et son état ouvert ou fermé dans le dictionnaire Popup de l'annotation, et il transporte les états ouvert et de relecture des annotations texte, si bien qu'un document relu conserve non seulement ses formes mais aussi les métadonnées de flux de travail sur lesquelles comptent les relecteurs

Schéma HotPDF associant les propriétés des annotations PDF ligne, polygone, encre et texte enrichi à leurs attributs et éléments enfants XFDF
Chaque sous-type d'annotation suit la convention de géométrie ISO 19444-1 au lieu d'un dialecte privé

Réimporter du XFDF dans un document chargé

HotPDF importe le XFDF en analysant le XML, en créant une annotation neuve pour chaque élément via NewLoadedAnnotation, en la rattachant à la page que l'élément désigne, et en renvoyant le nombre d'annotations ajoutées. Le déroulement est symétrique de l'export : chargez le PDF de base, appelez ImportLoadedAnnotationsFromXFDF avec le fichier du relecteur, puis enregistrez le document chargé pour rendre les nouvelles annotations permanentes. Si le fichier est absent ou si le XML refuse de s'analyser, la fonction renvoie zéro et le document chargé reste intact

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;

Comme chaque élément XFDF désigne son propre index de page, les annotations atterrissent sur les pages pour lesquelles elles ont été rédigées même lorsque vous importez plusieurs fichiers à la suite, ce qui rend sûr le regroupement des commentaires de plusieurs relecteurs sur le même document chargé avant un unique enregistrement. L'exemple ci-dessous fond deux relecteurs dans une copie fusionnée. Pour construire et modifier les objets d'annotation eux-mêmes dans le code plutôt que de les échanger sous forme de fichiers, voyez comment HotPDF crée et modifie les objets d'annotation PDF directement depuis Delphi

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;

Ce qui fait proprement l'aller-retour, et ce qui ne le fait pas

HotPDF fait faire l'aller-retour aux sous-types d'annotation auxquels la norme ISO 19444-1 donne une place, et il ignore délibérément les autres plutôt que d'émettre quelque chose qu'un lecteur interpréterait de travers. L'ensemble pris en charge couvre les types de relecture qui dominent le travail réel : notes texte, texte libre, ligne, carré, cercle, polygone, polyligne, les quatre types de balisage de texte (surlignage, soulignement, texte barré et ondulé), tampon, encre et accent circonflexe, plus pièce jointe, son, caviardage et lien, soit dix-huit sous-types en tout. Une annotation dont le sous-type sort de cette liste est laissée de côté à l'export, et comme elle est ignorée plutôt qu'écrite vide, elle ne gonfle pas le compte que renvoie la fonction

Le texte enrichi est la réserve honnête. HotPDF conserve le corps <contents-richtext> pour que le texte mis en forme et le contenu brut fassent le voyage, mais XFDF transporte le texte et le balisage de style d'un commentaire, pas un flux d'apparence rendu, si bien que l'application réceptrice redessine la fenêtre contextuelle avec ses propres polices et sa propre mise en page au lieu de reproduire les pixels exacts de HotPDF. Considérez l'aller-retour comme fidèle au contenu et à l'intention, pas au rendu à l'écran au pixel près. Si votre contenu mis en forme réside dans des données de formulaire XFA plutôt que dans des flux d'annotation, les règles diffèrent, et la façon dont HotPDF traite exData, le texte enrichi et les hyperliens XFA couvre ce chemin distinct

Le traitement au niveau des caractères est plus strict qu'il n'y paraît, ce qui est exactement ce que vous voulez. HotPDF applique les règles d'échappement de la clause 5.8.2 de la norme ISO 19444-1 quand il écrit du texte, encodant les caractères significatifs pour XML et les octets de contrôle afin qu'un commentaire contenant une esperluette, un chevron ou un saut de ligne produise un XML bien formé accepté par tout analyseur conforme, et il inverse les mêmes règles à l'import. C'est pour cela qu'une note collée depuis un tableur, ponctuation comprise, revient intacte au lieu de corrompre le fichier

L'échange d'annotations n'est qu'une part de ce que fait l'API du document chargé, et il se combine au reste : importez le XFDF d'un relecteur, ajustez les pages ou modifiez les métadonnées du document, aplatissez le fichier ou redéfinissez ses permissions, puis exportez un XFDF neuf pour le tour suivant. Tout cela est livré dans le composant Delphi HotPDF standard pour Delphi et C++Builder, dont la référence documente la couverture complète des sous-types d'annotation et les fonctions XFDF associées aux données de formulaire