Envoyez la phrase arabe يوضح ملف PDF هذا à un simple TextOut et la page qui revient est erronée de deux manières à la fois. Les mots vont de gauche à droite au lieu de droite à gauche, et les lettres se trouvent séparées dans leurs formes isolées au lieu de se joindre en mots connectés. Aucune erreur n'est signalée. Le code Delphi se compile, le fichier s'ouvre, et un réviseur qui lit l'arabe vous dit que la sortie est inutilisable. Le correctif consiste en un seul appel, et non en un changement de bibliothèque : HotPDF achemine le texte de droite à gauche via une méthode distincte, RtLTextOut, qui gère la réorganisation qu'un simple TextOut ne fera pas. Quatre éléments de cette méthode déterminent si la sortie est utilisable : ce qu'elle fait à la chaîne, comment son argument de jeu de caractères sélectionne le script, la modification au niveau du document qu'elle apporte comme effet secondaire, et le travail sur la police qui doit précéder
Pourquoi le de-droite-à-gauche nécessite son propre appel
Un flux de contenu PDF ne stocke pas de texte modifiable. Il stocke des glyphes à des positions fixes, ce qui signifie que ce qui émet le flux a la responsabilité de décider dans quel ordre ces glyphes vont. À l'écran, le système d'exploitation l'a fait pour vous : déposez de l'arabe dans un TEdit et la pile de texte de l'OS le réorganise et le joint avant même que vous ne voyiez un pixel. C'est exactement pourquoi la chaîne est parfaite dans votre formulaire et se brise dans le PDF. Le bureau a fait le travail en silence, et au moment où vous écrivez votre propre flux de contenu, le travail est de nouveau de votre côté
TextOut vous prend au mot. Il dessine les points de code dans l'ordre où vous les passez, de gauche à droite, ce qui est correct pour le latin, le cyrillique et le CJK et faux pour l'arabe et l'hébreu. RtLTextOut est l'appel qui réorganise d'abord la ligne dans l'ordre visuel de droite à gauche, puis dessine. HotPDF maintient délibérément les deux méthodes séparées plutôt que de deviner la direction à partir des caractères, de sorte que le choix de celle à appeler est le choix du comportement de script que vous obtenez. Les mécanismes plus profonds de la réorganisation bidirectionnelle et de la jointure contextuelle arabe constituent leur propre sujet, couvert dans l'article sur la mise en forme de textes arabes et RTL avec HotPDF ; ici, le point pratique est plus étroit. Utilisez RtLTextOut pour les exécutions de droite à gauche, utilisez TextOut pour tout le reste, et n'acheminez jamais l'un par l'autre

L'argument du jeu de caractères décide du script
Ce qui indique à RtLTextOut s'il dispose de l'arabe ou de l'hébreu n'est pas la méthode, c'est la police. SetFont prend un jeu de caractères (charset) Windows comme quatrième argument, et cette valeur porte les règles du script dans l'appel de droite à gauche : 178 sélectionne l'arabe, 177 sélectionne l'hébreu. Définissez le jeu de caractères, puis dessinez, et les deux lignes ci-dessous ressortent dans le bon ordre de lecture sans aucune configuration supplémentaire
// Arabe : le jeu de caractères 178 indique à RtLTextOut d'appliquer les règles arabes
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
// Hébreu : le jeu de caractères 177 fait basculer les règles sur l'hébreu
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 660, 0, 'קובץ PDF זה');
Il est facile de manquer deux détails concernant ces coordonnées. La position que vous passez est toujours le début de l'exécution dans le propre système de coordonnées de la page, mesuré à partir du coin inférieur gauche avec Y croissant vers le haut, la même origine qu'utilise tout TextOut ; RtLTextOut modifie l'ordre des glyphes, et non l'endroit à partir duquel la page effectue la mesure. Et comme pour tout appel de dessin, le SetFont doit venir en premier et doit être répété après chaque AddPage, car la police actuelle ne survit pas à un saut de page. Oubliez la répétition et la deuxième page reviendra à la police active quelle qu'elle soit, ce qui, pour l'arabe, signifie généralement des boîtes vides
Il ne retourne pas le texte que vous avez déjà retourné
La seule erreur qui engloutit le plus de temps de débogage ici est de fournir à RtLTextOut une chaîne que vous avez déjà retournée à la main. Les gens en arrivent à cette méthode après qu'une première tentative avec un simple TextOut soit sortie à l'envers, et un palliatif courant consiste à inverser les caractères dans le code avant de dessiner. RtLTextOut s'inverse lui-même en interne, de sorte qu'une chaîne pré-inversée est inversée une seconde fois et atterrit exactement là où elle a commencé. Transmettez le texte dans un ordre logique, l'ordre dans lequel vous le taperiez et le liriez à haute voix, et laissez l'appel faire la réorganisation
Le piège est plus pernicieux qu'un simple retournement car une chaîne doublement inversée peut sembler correcte pour une phrase de test entièrement en arabe, puis se briser à l'instant où une ligne contient un mot latin ou un nombre. À l'intérieur d'une ligne de droite à gauche, ces exécutions intégrées sont censées se lire de gauche à droite, et l'inversion manuelle détruit cette imbrication, tandis que le cas purement arabe y survit par hasard. Ainsi, le bogue passe à travers votre premier test de fumée (smoke test) et fait surface plus tard sur une vraie facture contenant un numéro de compte. Supprimez toute inversion manuelle dès l'instant où vous passez à RtLTextOut
L'effet secondaire Direction bon à connaître
L'appel à RtLTextOut modifie plus que la ligne que vous dessinez. Il inverse également la préférence de direction de lecture du document vers la droite, la même chose que vous définiriez autrement vous-même via la propriété Direction. Ce setter (accesseur en écriture) ajoute vpDirection aux ViewerPreferences du document, ce qui indique à un lecteur comment disposer les doubles pages (spreads) et de quel côté commence une mise en page en vis-à-vis. Lorsque l'ensemble du document est en arabe ou en hébreu, c'est exactement ce que vous voulez, et vous l'obtenez gratuitement
Il vaut la peine de le savoir précisément parce qu'il est invisible sur une seule page. Si le document est principalement de gauche à droite avec un seul bloc de droite à gauche, le premier appel RtLTextOut fera tout de même basculer la préférence de tout le fichier, et rien dans votre épreuve d'une page ne le montrera. Le symptôme apparaît des semaines plus tard lorsque quelqu'un imprime un livret recto verso et que les doubles pages sortent en miroir. Si ce n'est pas ce que vous voulez, réinitialisez explicitement Direction après l'exécution de droite à gauche :
// RtLTextOut a déjà défini la direction du document sur RightToLeft ;
// restaurer de gauche à droite si le document est principalement LTR
Pdf.Direction := LeftToRight;
Pour un document qui se lit véritablement de droite à gauche, n'y touchez pas. Le but est de savoir que l'appel a un effet à l'échelle du document afin que la surprise du livret n'arrive jamais
Enregistrez la police que vous expédiez, pas celle qui est, espérez-vous, installée
Rien dans la réorganisation n'a d'importance si la police n'a pas de glyphes à dessiner. L'échec classique est un rapport qui s'affiche parfaitement sur la machine du développeur, où Arial Unicode MS se trouve être présent, et qui sort sous forme de rangées de boîtes vides sur le serveur d'un client où Windows a discrètement substitué une police n'ayant aucune couverture arabe. Le remède consiste à cesser de faire confiance aux polices système installées et à en enregistrer une que vous livrez avec l'application
// Fournissez une police arabe connue et enregistrez-la avant de dessiner
Pdf.RegisterUnicodeTTF('C:\Fonts\NotoSansArabic.ttf');
Pdf.CurrentPage.SetFont('NotoSansArabic', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 700, 0, 'يوضح ملف PDF هذا');
Deux limites accompagnent l'enregistrement. Une police importée via RegisterUnicodeTTF est intégrée, et la gestion Unicode intégrée de HotPDF nécessite le document au format PDF 1.5 ou ultérieur ; cela ne pose problème que si quelque chose en aval insiste sur le PDF 1.4, mais lorsque c'est le cas, l'échec est silencieux. L'autre est juridique plutôt que technique : les fichiers TrueType comportent des bits d'autorisation d'intégration, et une police qui semble correcte à l'écran peut faire l'objet d'une licence qui interdit son expédition à l'intérieur de documents clients. Confirmez la licence avant l'intégration, pas après une plainte
Un exemple de console complet
En rassemblant les pièces, voici un programme autonome qui écrit une page avec une ligne en arabe, une ligne en hébreu et une ligne mixte portant un nom de produit latin. Chaque bloc définit son jeu de caractères (charset), puis dessine dans l'ordre logique
program RtLTextOutDemo;
{$APPTYPE CONSOLE}
uses
HPDFDoc; // Unité principale HotPDF
var
Pdf: THotPDF;
begin
Pdf := THotPDF.Create(nil);
try
Pdf.FileName := 'RtLTextOut.pdf';
Pdf.BeginDoc;
// Un titre latin passe par le chemin TextOut ordinaire
Pdf.CurrentPage.SetFont('Arial', [fsBold], 16);
Pdf.CurrentPage.TextOut(40, 780, 0, 'Texte de droite à gauche avec HotPDF');
// Arabe : jeu de caractères 178, ordre logique, RtLTextOut effectue la réorganisation
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 720, 0,
'يوضح ملف PDF هذا كيفية التعامل مع النص العربي.');
// Hébreu : jeu de caractères 177
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 177);
Pdf.CurrentPage.RtLTextOut(400, 680, 0,
'קובץ PDF זה מדגים טקסט עברי הזורם מימין לשמאל.');
// Ligne mixte : le mot latin intégré se lit toujours de gauche à droite
Pdf.CurrentPage.SetFont('Arial Unicode MS', [], 12, 178);
Pdf.CurrentPage.RtLTextOut(400, 640, 0,
'مرحبا بالعالم! تم إنشاؤه بواسطة HotPDF');
Pdf.EndDoc;
Writeln('RtLTextOut.pdf écrit');
finally
Pdf.Free;
end;
end.
Exécutez-le et ouvrez le résultat. Les lignes arabes et hébraïques se lisent de droite à gauche, les lettres se joignent là où le script les joint, et dans la dernière ligne, le jeton HotPDF se trouve de gauche à droite à l'intérieur de l'exécution arabe, ce qui est le résultat correct selon les spécifications, même s'il surprend quiconque voit une mise en page bidirectionnelle pour la première fois. Il vaut la peine d'écrire ce dernier point dans vos critères d'acceptation avant qu'un lecteur natif ne passe en revue la sortie, car l'exécution intégrée lue dans le "mauvais" sens par rapport au script environnant est la seule chose le plus souvent signalée comme un bogue alors que ce n'en est pas un
Vérification de la sortie
Une page qui semble correcte n'est pas la même chose qu'une page qui est correcte, alors vérifiez-la de la manière dont le fera un système en aval. Copiez le texte hors de la visionneuse et comparez les points de code à votre chaîne source ; un ordre visuel correct avec un ordre logique brouillé est un véritable mode de défaillance. Exécutez la recherche dans le document du lecteur pour trouver un mot que vous pouvez voir sur la page. Ensuite, ouvrez le fichier sur une machine qui ne possède pas vos polices de développement, celle qui est la plus susceptible d'exposer une substitution silencieuse. Rien de tout cela ne remplace la lecture d'un document authentique par un locuteur natif, ce qui permet de détecter des problèmes qu'aucune chaîne de test synthétique ne pourra détecter, alors inscrivez cette révision à l'ordre du jour avant l'expédition du format
RtLTextOut gère la réorganisation bidirectionnelle et la jointure contextuelle arabe, ce qui couvre la grande majorité des travaux de rapports et de documents de droite à gauche. Là où elle s'arrête — les scripts qui nécessitent plus que la réorganisation et la jointure tels que les familles indiennes, et les fonctionnalités OpenType facultatives qui passent par la substitution d'un seul glyphe — est cartographié à côté de la couverture des glyphes et des détails de mise en forme dans l'article connexe sur la mise en forme de texte arabe et RTL avec HotPDF
Les appels RtLTextOut, SetFont et RegisterUnicodeTTF présentés ici font partie du composant HotPDF pour Delphi et C++Builder