XFA, la XML Forms Architecture, est obsolète. La norme ISO 32000-1 l'inclut au §12.7 avec la note qu'elle est supprimée du PDF 2.0, et les lecteurs modernes abandonnent leurs moteurs XFA un par un. Rien de tout cela n'a vidé les archives. Les formulaires d'admission gouvernementaux, les demandes d'assurance et les relevés bancaires ont été créés en XFA pendant la majeure partie de deux décennies, et ces fichiers arrivent encore aujourd'hui dans les boîtes de réception et les pipelines de documents. Lorsque le lecteur qui les rendait cesse de le faire, le formulaire se transforme en une page blanche avec un espace réservé "veuillez ouvrir dans un lecteur différent". La solution durable consiste à aplatir (flatten) le XFA en contenu PDF statique que n'importe quel lecteur peut peindre
La partie difficile de cet aplatissement n'est pas les champs. Les zones de texte et les cases à cocher se mappent assez proprement sur les widgets AcroForm. La partie difficile est le texte riche (rich text) que XFA stocke à l'intérieur d'un élément de dessin (draw element), dans un bloc <exData contentType="text/html">. Ce bloc est un sous-ensemble HTML avec un style en ligne et, souvent, des ancres. L'obtenir sur la page signifie reproduire à la fois le texte stylisé et les liens hypertextes actifs, et c'est au niveau des liens hypertextes que la plupart des implémentations abandonnent discrètement
À quoi ressemble réellement le texte riche XFA
Un corps exData est une petite tranche de XHTML. Un paragraphe est un <p> ; une étendue (span) de caractères stylisée est un <span> avec son propre CSS en ligne pour la graisse, la posture, la couleur et la taille ; et un lien hypertexte est un <a href="..."> enveloppant son texte visible. Une seule ligne peut contenir plusieurs étendues d'affilée, chacune avec un style différent, et l'une d'entre elles peut être une ancre. Le style n'est pas une décoration qui peut être supprimée. Une clause rendue en rouge gras parce qu'il s'agit d'un avertissement légal doit rester en rouge gras après l'aplatissement, sinon le document aplati déforme l'original
Ainsi, le moteur d'aplatissement ne peut pas traiter le bloc comme une seule chaîne. Il doit parcourir la structure en ligne, résoudre le style effectif de chaque exécution (run) en superposant le CSS en ligne de l'étendue sur la police de base de l'élément de dessin, et disposer les exécutions l'une après l'autre sur la ligne. HotPDF modélise chacun de ces fragments disposés comme un enregistrement TXFARichRun interne. L'enregistrement porte le texte de l'exécution, son style résolu, sa boîte mesurée et, pour une ancre, le Href vers lequel elle pointe
Disposition des exécutions de gauche à droite
Le positionnement est le moment où le texte riche cesse d'être un problème d'analyse (parsing) et devient un problème de typographie. Les exécutions partagent une ligne, de sorte que chaque exécution commence là où la précédente s'est terminée. Aucun balisage n'enregistre ces positions ; elles doivent être mesurées. La routine interne LayoutRichText du moteur mesure chaque exécution avec les mêmes métriques de police qui la peindront plus tard, puis définit le décalage horizontal de l'exécution à la somme cumulée de toutes les largeurs d'exécution précédentes. L'exécution une commence à l'origine de la boîte de dessin, l'exécution deux commence à la largeur de l'exécution une, l'exécution trois à la largeur combinée des deux premières, et ainsi de suite sur toute la ligne
C'est pourquoi l'alignement des polices de mesure est si important. La passe de disposition mesure les avances ; une passe de rendu séparée dessine les glyphes. Si ces deux passes sont en désaccord sur la police, les boîtes calculées par la disposition ne se trouveront pas sous les glyphes que le moteur de rendu peint. HotPDF les maintient synchronisées en mappant le style résolu de chaque exécution sur une spécification de police, via l'assistant interne RunStyleToFontSpec, qui correspond aux propres valeurs par défaut du moteur de rendu, Arial à 10 points. L'avance mesurée et le texte dessiné concordent alors, et la boîte calculée d'une exécution couvre véritablement les caractères qu'un lecteur voit
// Forme conceptuelle d'une exécution disposée. Le moteur construit un tableau de ces éléments
// en interne ; vous ne les construisez jamais vous-même, mais les champs expliquent comment une
// boîte de frappe de lien (hit box) est dérivée d'une géométrie mesurée plutôt que de texte.
type
TRichRunInfo = record
Dx, Dy : Double; // en haut à gauche, relatif à l'origine de la boîte de dessin
W, H : Double; // boîte d'exécution mesurée (largeur issue de la passe de disposition)
Text : AnsiString; // les caractères visibles de l'exécution
Href : AnsiString; // cible URI pour une exécution <a>, '' sinon
end;
D'une exécution d'ancre à une annotation de lien PDF
Un lien hypertexte dans un PDF fini ne fait pas partie du contenu de la page. C'est un objet séparé, une annotation de lien (Link annotation), décrit dans la norme ISO 32000-1 §12.5.6.5. L'annotation a un /Rect qui définit le rectangle cliquable sur la page et une action qui se déclenche lorsque l'on clique sur le rectangle. Pour un lien externe, l'action est une action URI : /S /URI avec l'adresse cible comme chaîne /URI. Le texte visible en dessous est un contenu de page ordinaire ; l'annotation est la zone réactive invisible posée par-dessus
Le chemin d'aplatissement suit exactement ce modèle. Lorsqu'une exécution porte un Href, HotPDF dessine d'abord le texte stylisé, puis construit une annotation de lien au-dessus de la boîte de l'exécution. Le point d'entrée public pour cette annotation est la méthode de page AddURILink, qui crée l'objet /Type /Annot /Subtype /Link avec une action /URI et renvoie le dictionnaire d'annotations. Son rectangle est la boîte mesurée de l'exécution, traduite des coordonnées locales de l'élément de dessin en coordonnées de page. Le résultat est un lien qui atterrit précisément sur le texte d'ancrage et nulle part ailleurs
// La même API publique que le chemin d'aplatissement utilise pour chaque exécution d'ancre. Elle produit
// une annotation de lien ISO 32000-1 12.5.6.5 : /Subtype /Link avec une action /URI
// sur le rectangle donné. La description facultative remplit /Contents afin qu'un
// lecteur d'écran puisse annoncer la cible.
var
LinkRect: TRect;
Annot: THPDFDictionaryObject;
begin
LinkRect := Rect(72, 690, 268, 706); // boîte de frappe dans l'espace de la page pour l'exécution
Annot := Pdf.CurrentPage.AddURILink(LinkRect,
'https://www.example.gov/appeal', 'File an appeal online');
end;
Pourquoi la boîte de frappe doit provenir de largeurs mesurées
Il est tentant d'imaginer localiser le lien en recherchant son texte visible sur la page et en dessinant le rectangle autour de ce qui est trouvé. Cela ne fonctionne pas, et la raison est fondamentale dans la façon dont le texte aplati est stocké. Les exécutions stylisées sont peintes avec des polices de sous-ensemble intégrées. Une police de sous-ensemble renumérote les glyphes qu'elle conserve, de sorte que le flux de contenu de la page contient des codes CID hexadécimaux, et non les codes de caractères d'origine. Les octets sur la page ne sont pas les lettres qu'un humain lit, et ils ne peuvent pas être recherchés en tant que texte. Une recherche de la légende de l'ancre ne trouve rien, car cette légende n'existe nulle part dans le flux en tant que texte littéral
Le seul point d'ancrage fiable pour le rectangle est la géométrie que la passe de disposition a déjà produite. Le décalage et la largeur mesurée de chaque exécution ont été calculés lors du flux de la ligne, avant que tout glyphe ne soit renuméroté, et ils décrivent où le texte apparaîtra physiquement. HotPDF prend donc le rectangle du lien directement de la boîte disposée de l'exécution plutôt que de toute recherche de texte. Étant donné que la mesure a utilisé la police de rendu, la boîte est correcte quel que soit le sous-ensemble. La géométrie survit à l'encodage ; le texte non. C'est tout l'argument en faveur d'un positionnement sur des largeurs mesurées, et c'est pourquoi un aplatisseur qui tente d'installer des liens a posteriori par recherche de texte produit des zones de frappe qui dérivent ou disparaissent
Piloter l'aplatissement depuis votre code
Pour un PDF qui contient déjà un paquet XFA, le point d'entrée est FlattenLoadedXFA. Chargez le document, appelez la méthode et enregistrez le résultat. Le paramètre Editable décide de ce qu'il advient des champs de formulaire : transmettez True pour les conserver en tant que widgets AcroForm remplissables, ou False pour marquer chaque widget en lecture seule afin que la sortie soit un enregistrement figé. Les blocs de dessin en texte riche, avec leurs exécutions stylisées et leurs annotations de lien, sont produits de toute façon. La fonction renvoie le nombre de widgets qu'elle a émis
var
Pdf: THotPDF;
Emitted, i: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('xfa_appeal_form.pdf');
// True maintient les champs remplissables ; False les fige en lecture seule.
Emitted := Pdf.FlattenLoadedXFA(True);
// Tout ce que le moteur n'a pas pu mapper est signalé, mais ne déclenche pas d'exception.
for i := 0 to Pdf.XFAFlattenWarnings.Count - 1 do
Writeln('Avertissement XFA : ', Pdf.XFAFlattenWarnings[i]);
Pdf.SaveLoadedDocument('appeal_form_flat.pdf');
Writeln('Widgets émis : ', Emitted);
finally
Pdf.Free;
end;
end;
Lisez toujours XFAFlattenWarnings après l'appel. La liste est effacée au début de chaque aplatissement et accumule une ligne pour chaque élément que le moteur a refusé de rendre : un type de champ non pris en charge, une image de dessin qui ne se décode pas, un bloc exData sans étendues utilisables. Aucun de ces cas ne déclenche d'exception, donc une liste d'avertissements vide est votre preuve que tout a été mappé, et une liste non vide vous indique exactement quels originaux inspecter. Lorsque vous détenez le XFA brut sous forme d'octets XDP plutôt qu'un PDF chargé, la méthode sœur ApplyXFAAsAcroForm prend directement ces octets et partage le même chemin de code et le même comportement d'avertissements. La méthode complémentaire AddXFAPacket va dans l'autre sens, en intégrant un paquet XFA dans un document que vous construisez
Confirmer le résultat dans un lecteur
Ouvrez le fichier aplati dans Acrobat, ou dans tout lecteur actuel, et vérifiez deux choses. Tout d'abord, le texte riche est rendu avec son style intact : les exécutions en gras sont en gras, les exécutions colorées portent leur couleur et les étendues sont placées dans le bon ordre sur la ligne plutôt que de se chevaucher ou de déborder de la boîte. Deuxièmement, les liens hypertextes sont actifs. Survolez une ancre et la barre d'état devrait afficher l'adresse cible ; cliquez dessus et l'action URI devrait l'ouvrir. Utilisez l'inspecteur d'annotations du lecteur pour confirmer que chacun est une véritable annotation /Link dont le /Rect épouse le texte d'ancrage, reposant sur un contenu qui est désormais de simples glyphes peints plutôt qu'un XFA rendu par formulaire. Cette combinaison, texte statique stylisé plus véritables annotations de lien sur les bons rectangles, est ce qui permet au document aplati de survivre aux moteurs XFA dont il n'a plus besoin
L'aplatissement des champs eux-mêmes, des zones de texte, des cases à cocher et des listes de choix qui entourent ce texte riche, est couvert dans notre tutoriel sur l'aplatissement des formulaires XFA en widgets AcroForm. Pour l'histoire plus large de la construction et du placement manuel d'annotations de lien, au-delà de celles générées par le chemin d'aplatissement, consultez l'utilisation des annotations PDF dans HotPDF. Les deux s'appuient sur le même modèle d'annotations et de formulaires qui est livré avec le composant HotPDF pour Delphi et C++Builder