L'architecture XFA (XML Forms Architecture) est obsolète. La norme ISO 32000-1 la mentionne au §12.7 en précisant qu'elle est supprimée dans la version PDF 2.0, et les lecteurs modernes abandonnent les uns après les autres leurs moteurs de rendu XFA. Rien de tout cela n'a vidé les archives. Les formulaires administratifs, les demandes d'assurance et les relevés bancaires ont été conçus au format XFA pendant près de deux décennies, et ces fichiers arrivent encore aujourd'hui dans les boîtes de réception et les chaînes de traitement. Lorsque le visualiseur qui les affichait cesse de le faire, le formulaire se transforme en une page blanche avec un message invitant à l'ouvrir dans un autre lecteur. La solution durable consiste à aplatir le format XFA en un contenu PDF statique que n'importe quel lecteur peut afficher.
La partie difficile de cet aplatissement ne réside pas dans les champs. Les zones de texte et les cases à cocher correspondent assez proprement aux widgets AcroForm. La difficulté réside dans le texte enrichi que le format XFA stocke dans un élément graphique, au sein d'un bloc <exData contentType="text/html">. Ce bloc est un sous-ensemble HTML avec des styles en ligne et, souvent, ancres. L'insérer sur la page implique de reproduire à la fois le texte stylisé et les liens hypertextes actifs, et c'est sur les liens hypertextes que la plupart des implémentations renoncent discrètement.
À quoi ressemble réellement le texte enrichi XFA
Un corps exData est une petite portion de XHTML. Un paragraphe est un <p> ; une plage de caractères stylisée est un <span> avec son propre CSS en ligne pour l'épaisseur, la posture, la couleur et la taille ; et un lien hypertexte est une balise <a href="..."> enveloppant son texte visible. Une seule ligne peut contenir plusieurs balises span consécutives, chacune avec un style différent, et l'une d'elles peut être une ancre. Le style n'est pas une décoration que l'on peut ignorer. Une clause affichée en rouge et gras parce qu'elle constitue un avertissement légal doit rester en rouge et gras après aplatissement, sous peine de trahir le document d'origine.
So the flatten engine cannot treat the block as one string. Il doit parcourir la structure en ligne, résoudre le style effectif de chaque segment en superposant le CSS en ligne du span à la police de base de l'élément de dessin, et disposer les segments les uns après les autres sur la ligne. HotPDF modélise chacun de ces fragments disposés sous la forme d'un enregistrement interne TXFARichRun. L'enregistrement contient le texte du segment, son style résolu, sa boîte mesurée et, pour une ancre, la cible Href vers laquelle elle pointe.
Disposition des segments de gauche à droite
Le positionnement est le moment où le texte enrichi cesse d'être un problème d'analyse pour devenir un problème de composition. Les segments partagent une ligne, de sorte que chaque segment commence là où le précédent s'est terminé. Il n'y a pas de balise pour enregistrer ces positions ; elles doivent être mesurées. La routine interne LayoutRichText du moteur mesure chaque segment avec les métriques de police mêmes qui serviront plus tard à le peindre, puis définit le décalage horizontal du segment comme la somme courante de toutes les largeurs des segments précédents. Le premier commence à l'origine de la boîte graphique, le deuxième à la largeur du premier, le troisième à la largeur cumulée des deux premiers, et ainsi de suite sur toute la ligne.
This is why measurement font alignment matters so much. La passe de disposition mesure les avancées ; une passe de rendu distincte dessine les glyphes. Si ces deux passes ne s'accordent pas sur la police, les boîtes calculées par la disposition ne correspondront pas aux glyphes peints par le moteur de rendu. HotPDF les maintient synchronisées en mappant le style résolu de chaque segment sur une spécification de police, via l'assistant interne RunStyleToFontSpec, qui correspond aux valeurs par défaut du moteur de rendu (Arial à 10 points). L'avancée mesurée et le texte dessiné correspondent alors, et la boîte calculée du segment couvre réellement les caractères vus par le lecteur.
// Conceptual shape of one laid-out run. The engine builds an array of these
// internally; you never construct them yourself, but the fields explain how a
// link's hit box is derived from measured geometry rather than from text.
type
TRichRunInfo = record
Dx, Dy : Double; // top-left, relative to the draw-box origin
W, H : Double; // measured run box (width from the layout pass)
Text : AnsiString; // the run's visible characters
Href : AnsiString; // URI target for an <a> run, '' otherwise
end;
D'un segment d'ancre à une annotation de lien PDF
Un lien hypertexte dans un PDF finalisé ne fait pas partie du contenu de la page. Il s'agit d'un objet distinct, une annotation de lien (Link annotation), décrite dans l'ISO 32000-1 §12.5.6.5. L'annotation possède un /Rect qui définit le rectangle cliquable sur la page et une action qui s'active lorsque l'on clique sur ce rectangle. Pour un lien externe, il s'agit d'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 sensible invisible placée par-dessus.
Le traitement d'aplatissement suit exactement ce modèle. Lorsqu'un segment porte un Href, HotPDF dessine d'abord le texte stylisé, puis construit une annotation de lien par-dessus la boîte du segment. 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 du segment, convertie des coordonnées locales de l'élément graphique en coordonnées de page. Le résultat est un lien qui se positionne précisément sur le texte d'ancre et nulle part ailleurs.
// The same public API the flatten path uses for each anchor run. It produces
// an ISO 32000-1 12.5.6.5 Link annotation: /Subtype /Link with a /URI action
// over the given rectangle. The optional description fills /Contents so a
// screen reader can announce the target.
var
LinkRect: TRect;
Annot: THPDFDictionaryObject;
begin
LinkRect := Rect(72, 690, 268, 706); // page-space hit box for the run
Annot := Pdf.CurrentPage.AddURILink(LinkRect,
'https://www.example.gov/appeal', 'File an appeal online');
end;
Pourquoi la zone de clic doit dépendre des largeurs mesurées
Il est tentant de penser que l'on peut localiser le lien en recherchant son texte visible sur la page et en dessinant le rectangle autour de ce qui est de la géométrie trouvée. Cela ne fonctionne pas, pour une raison fondamentale liée au mode de stockage du texte aplati. Les segments stylisés sont dessinés avec des sous-ensembles de polices intégrés. Un sous-ensemble de police renumérote les glyphes qu'il conserve, de sorte que le flux de contenu de la page contient des codes hexadécimaux CID, et non les codes de caractères d'origine. Les octets sur la page ne sont pas les lettres lues par un humain et ne sont pas interrogeables sous forme de texte. Une recherche sur le libellé de l'ancre ne renverrait rien, car ce libellé n'existe sous forme de texte littéral nulle part dans le flux.
Le seul ancrage fiable pour le rectangle est la géométrie déjà produite par la passe de disposition. Le décalage et la largeur mesurée de chaque segment ont été calculés lors du flux de la ligne, avant toute renumérotation des glyphes, et décrivent l'emplacement physique du texte. HotPDF prend donc le rectangle du lien directement depuis la boîte tracée du segment plutôt que depuis une recherche de texte. La mesure ayant utilisé la police de rendu, la boîte reste correcte quel que soit le sous-ensemble. La géométrie survit à l'encodage, pas le texte. C'est tout l'argument en faveur du positionnement par largeur mesurée, et c'est pourquoi un outil d'aplatissement qui tente de recréer les liens par recherche textuelle produit des zones actives 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, puis enregistrez le résultat. Le paramètre Editable décide du traitement des champs de formulaire : passez True pour les conserver comme widgets AcroForm remplissables, ou False pour marquer chaque widget en lecture seule afin d'obtenir un enregistrement figé. Les blocs graphiques de texte enrichi, avec leurs segments stylisés et leurs annotations de liens, sont générés dans les deux cas. La fonction renvoie le nombre de widgets émis.
var
Pdf: THotPDF;
Emitted, i: Integer;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.LoadFromFile('xfa_appeal_form.pdf');
// True keeps fields fillable; False freezes them read-only.
Emitted := Pdf.FlattenLoadedXFA(True);
// Anything the engine could not map is reported, not raised.
for i := 0 to Pdf.XFAFlattenWarnings.Count - 1 do
Writeln('XFA warning: ', Pdf.XFAFlattenWarnings[i]);
Pdf.SaveLoadedDocument('appeal_form_flat.pdf');
Writeln('Widgets emitted: ', Emitted);
finally
Pdf.Free;
end;
end;
Lisez toujours XFAFlattenWarnings après l'appel. La liste est vidée au début de chaque aplatissement et accumule une ligne pour chaque élément que le moteur a refusé de restituer : un type de champ non pris en charge, une image graphique impossible à décoder, un bloc exData dépourvu de span utilisable. Aucun d'eux ne lève d'exception, ainsi une liste d'avertissements vide prouve que tout a été mappé, tandis qu'une liste non vide indique exactement quels éléments d'origine inspecter. Lorsque vous disposez du XFA brut sous forme d'octets XDP plutôt que d'un PDF chargé, la méthode sœur ApplyXFAAsAcroForm prend ces octets directement et partage le même chemin de code et le même comportement d'avertissements. La méthode complémentaire AddXFAPacket effectue le chemin inverse, en intégrant un paquet XFA dans un document en cours de construction.
Confirmer le résultat dans un lecteur
Ouvrez le fichier aplati dans Acrobat, ou dans tout lecteur récent, et vérifiez deux points. Premièrement, le texte enrichi doit s'afficher avec son style intact : les segments en gras sont en gras, les segments en couleur portent leur couleur et les plages se placent dans le bon ordre sur la ligne sans se chevaucher ni déborder de la boîte. Deuxièmement, les liens hypertextes doivent être actifs. Survolez une ancre et la barre d'état doit afficher l'adresse cible ; cliquez et l'action URI doit l'ouvrir. Utilisez l'inspecteur d'annotations du lecteur pour confirmer que chacune d'elles est une véritable annotation /Link dont le /Rect épouse le texte de l'ancre, se superposant à un contenu composé désormais de glyphes dessinés ordinaires et non d'un rendu XFA généré par le formulaire. Cette combinaison (texte statique stylisé plus véritables annotations Link sur les bons rectangles) est ce qui permet au document aplati de survivre aux moteurs XFA dont il n'a plus besoin.
Flattening the fields themselves, the text boxes, check boxes, and choice lists that surround this rich text, is covered in our walkthrough on flattening XFA forms into AcroForm widgets. For the wider story of building and placing Link annotations by hand, beyond the ones the flatten path generates, see working with PDF annotations in HotPDF. Both build on the same annotation and forms model that ships with the HotPDF Component for Delphi and C++Builder.