Article technique

Mise en forme du texte arabe et RTL dans les PDF Delphi avec HotPDF

Prenez la phrase arabe يوضح ملف PDF, passez-la à TextOut, puis ouvrez la sortie : les lettres partent dans le mauvais sens et chacune apparaît sous sa forme isolée, déconnectée de ses voisines. Un lecteur arabophone voit quelque chose qui ressemble à de l'anglais tapé à l'envers avec une espace après chaque lettre. Rien n'a échoué, pas d'exception, pas d'avertissement, parce que deux transformations de texte distinctes n'ont tout simplement jamais été exécutées. Comprendre ces transformations et l'API qui les applique est tout l'enjeu de la sortie PDF pour écritures complexes.

Cet article parcourt le texte droite-gauche et les écritures complexes avec HotPDF, composant PDF VCL natif pour Delphi et C++Builder, y compris l'endroit où son support de shaping s'arrête réellement, point tout aussi important quand vous décidez s'il couvre vos locales.

Deux transformations séparent une chaîne d'une ligne imprimée

Unicode stocke le texte en ordre logique : l'ordre dans lequel il est tapé, stocké et lu à voix haute. Un moteur de rendu dessine en ordre visuel. Pour les écritures de droite à gauche, les deux diffèrent ; pour le contenu mixte, par exemple une phrase arabe contenant le jeton latin « PDF » ou un prix en chiffres, l'algorithme bidirectionnel Unicode (UAX #9) définit comment les segments gauche-droite s'imbriquent dans une ligne droite-gauche. C'est la première transformation : le réordonnancement.

La seconde transformation est le shaping contextuel. Une lettre arabe prend un glyphe différent selon qu'elle se trouve au début, au milieu ou à la fin d'un mot, ou seule ; le point de code ne change jamais, seule la forme rendue change. Un pipeline de texte qui mappe directement les points de code vers les glyphes par défaut produit la sortie déconnectée décrite ci-dessus. L'hébreu n'a pas besoin de jonction mais a tout de même besoin du réordonnancement ; l'arabe a besoin des deux, ce qui en fait le cas de test canonique.

Le développement desktop masque cette mécanique. Quand une application VCL dessine de l'arabe à l'écran, la pile de texte du système d'exploitation réordonne et façonne le texte invisiblement ; c'est pourquoi la même chaîne qui se rend parfaitement dans un TEdit sort mal dans un PDF naïf. Un flux de contenu PDF stocke des glyphes positionnés, pas des séquences de texte éditables : celui qui écrit le flux possède la responsabilité du shaping, et c'est l'écart que RtLTextOut existe pour combler.

RtLTextOut : réordonner et joindre en un seul appel

HotPDF sépare le chemin latin du chemin écritures complexes au niveau de l'API. TextOut dessine ce qu'on lui donne, dans l'ordre où on le lui donne ; RtLTextOut exécute d'abord le réordonnancement et l'analyse contextuelle. Le paramètre charset de SetFont indique au moteur quelles règles d'écriture appliquer : 178 sélectionne le traitement arabe, 177 le traitement hébreu.

// Arabic: pass logical order; RtLTextOut reorders and joins
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF');

// Hebrew: reordering only, no contextual joining
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');

Le piège qui coûte le plus de temps de débogage : RtLTextOut effectue lui-même l'inversion. Lui fournir un texte déjà inversé, souvent un reste de « correctif » d'une tentative antérieure avec TextOut, inverse la ligne deux fois. Cela peut même sembler correct sur une chaîne arabe pure, puis casser à la première ligne contenant des lettres latines ou des chiffres, car les segments mixtes ne suivent plus l'ordre UAX #9. Passez toujours l'ordre logique et laissez l'API faire le travail.

Le contenu à direction mixte est aussi l'endroit où les attentes manuelles se trompent en revue : dans une ligne droite-gauche, les nombres et les mots latins intégrés se lisent toujours de gauche à droite. Des relecteurs peu familiers de la mise en page bidirectionnelle le signalent régulièrement comme un bug ; c'est le comportement correct selon la spécification, et il vaut la peine de l'indiquer dans votre documentation d'acceptation avant la première revue par un locuteur natif.

La couverture de glyphes décide du résultat avant le shaping

Le shaping choisit des glyphes ; la police doit réellement les contenir. L'échec classique de déploiement est un rapport qui se rend parfaitement sur le poste du développeur, où Arial Unicode MS est installé par hasard, et produit des carrés vides sur le serveur du client, où Windows a silencieusement substitué une police sans couverture arabe. La correction consiste à ne plus dépendre des polices système installées et à enregistrer un fichier de police que vous livrez :

// Ship a known font instead of relying on installed system fonts
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12);

// Audit coverage for the codepoints your data actually uses
GID := Pdf.GetUnicodeGlyphForCodepoint($0628);  // U+0628 ARABIC LETTER BEH
LogGlyphAudit($0628, GID);

Deux frontières de version s'appliquent. Les polices enregistrées ainsi doivent être intégrées, et la gestion des polices Unicode intégrées par HotPDF exige une version PDF 1.5 ou ultérieure, détail pertinent seulement si un système aval vous impose encore PDF 1.4. La licence de la police doit également autoriser l'intégration : les fichiers TrueType portent des bits de permission d'intégration, et une police qui s'affiche bien à l'écran peut être juridiquement impropre à la distribution dans des documents client.

GetUnicodeGlyphForCodepoint est le crochet d'audit : parcourez au démarrage du service les plages de points de code utilisées par vos données et journalisez les identifiants de glyphes résolus ; un trou de couverture apparaît alors dans une ligne de log au déploiement plutôt qu'en caractères manquants dans une facture client.

Pour le texte Unicode qui n'est pas de droite à gauche, chaînes CJK, diacritiques vietnamiens, écritures européennes mixtes, le pipeline ordinaire s'applique : TextOut accepte un WideString et le dessine avec la police enregistrée sans analyse bidirectionnelle. Garder deux chemins d'appel distincts dans le code de rapport, une routine pour les segments RTL et une pour tout le reste, rend le comportement locale explicite au lieu de l'enterrer dans un flag que personne ne se rappelle régler.

L'ordre de lecture est aussi une propriété du document

La justesse au niveau glyphe ne termine pas le travail. ISO 32000-1 §12.2 définit une préférence de lecteur, /Direction, qui déclare l'ordre de lecture prédominant du document. Elle ne change aucun glyphe ; elle indique aux lecteurs comment ordonner les doubles pages, où ancrer la progression dans les mises en page en vis-à-vis et quelle direction l'interface doit présumer, détails importants pour les livrets et tout document que l'utilisateur feuillette.

// Declare right-to-left reading order at the document level
Pdf.Direction := RightToLeft;  // adds vpDirection to ViewerPreferences

Affecter Direction suffit en soi : le setter ajoute automatiquement vpDirection à ViewerPreferences, de sorte que la préférence atteint le fichier avec une seule ligne. L'omission à surveiller est l'absence complète de déclaration, facile précisément parce que rien ne change visiblement sur une page unique ; elle n'apparaît que lorsqu'un utilisateur imprime un livret recto verso et que les doubles pages sortent en miroir.

Où s'arrête le shaping de HotPDF

Une carte honnête des capacités évite une semaine d'évaluation. RtLTextOut gère automatiquement le réordonnancement bidirectionnel et la jonction contextuelle arabe. Les ligatures typographiques optionnelles et l'application plus large des fonctions OpenType ne sont pas automatiques : GetSingleSubstituteGlyph(GID, 'liga') résout une substitution à la fois, identifiant de glyphe en entrée et tag de fonction à côté, ce qui fonctionne pour une liste finie de ligatures connues que vous appliquez vous-même, mais ce n'est pas un moteur GSUB général. Pour les écritures dont les exigences de shaping vont plus loin, les écritures indiennes avec signes vocaliques réordonnés étant l'exemple habituel, exécutez un pilote avec de vraies chaînes client avant de vous engager sur la locale, plutôt que d'extrapoler depuis les résultats arabes.

La vérification doit être bout en bout, car une page peut sembler correcte et échouer dans tous les usages aval. Trois contrôles capturent l'essentiel : recopier le texte depuis Acrobat et comparer les points de code avec la chaîne source ; rechercher dans le document un mot visible sur la page ; examiner la sortie sur une machine qui ne possède pas vos polices de développement. Un collègue lecteur natif regardant un vrai document vaut mieux que n'importe quelle quantité de données de test synthétiques ; planifiez cette revue avant la livraison du format, pas après la première plainte.

Choisissez délibérément les chaînes de test au lieu de réutiliser ce qu'un traducteur a envoyé l'an dernier. Un minimum utile par locale : une phrase dans l'écriture pure, une phrase avec marques latines intégrées, une ligne avec chiffres et devise, et des noms portant diacritiques ou marques combinantes. Les vrais noms de clients cassent des hypothèses de shaping que le faux texte n'approche jamais ; le corpus de régression doit gagner une entrée chaque fois qu'un cas de support expose un nouveau motif.

L'enregistrement des polices, le subsetting et l'API générale de dessin de texte sont couverts dans l'article sur la sortie de rapports, les polices et les images avec HotPDF ; si les mêmes documents doivent aussi respecter des profils d'accessibilité, les exigences de balisage de langue et de structure décrites dans l'article de validation PDF/A et PDF/UA s'ajoutent au travail de shaping décrit ici.

Les API droite-gauche et polices Unicode de cet article sont livrées avec le HotPDF Component pour Delphi et C++Builder ; la page produit renvoie à la référence complète de sortie de texte.