Article technique

HotPDF TextOut dans Delphi : taille, style, rotation et espacement

Chaque chaîne visible dans la documentation de HotPDF passe par un appel : TextOut(X, Y, angle, Text). L'exemple Hello World l'utilise dans sa forme la plus simple, la police étant configurée une fois et les quatre paramètres conservant des valeurs par défaut raisonnables. Après la première page, ces mêmes quatre paramètres portent tout le poids de la mise en page. Le troisième paramètre fait pivoter la ligne de texte. La configuration de la police qui le précède détermine la taille et le style. Les coordonnées X, Y, mesurées en points depuis le coin de la page, sont le seul rempart contre les chevauchements de texte, les troncatures ou le décalage d'une ligne vers le bas sur l'imprimante de quelqu'un d'autre. C'est là que TextOut entre en jeu, et où les valeurs par défaut ne suffisent plus

La première chose d'importance à retenir concerne la signature : X et Y sont des Singles mesurés en points, angle est un Extended mesuré en degrés, et Text est un WideString, ce qui permet de transmettre de l'Unicode sans appel supplémentaire. La seconde surcharge accepte un PWORD avec une longueur, utile si vous détenez déjà les codes de glyphes, mais pour les chaînes ordinaires, la forme WideString est celle que vous choisirez

La taille et le style proviennent de SetFont et non de TextOut

TextOut n'a aucun paramètre de taille. La taille, la graisse, l'inclinaison, tout cela réside dans l'appel SetFont qui précède la chaîne, et reste en vigueur jusqu'au prochain SetFont qui le remplace. C'est ce seul fait qui explique la plupart des confusions du premier jour : une ligne sort en gras parce que trois appels plus tôt quelque chose a posé [fsBold] et rien ne l'a effacé

Pdf.CurrentPage.SetFont('Times New Roman', [], 24);
Pdf.CurrentPage.TextOut(72, 72, 0, 'Quarterly Report');         // 24pt regular

Pdf.CurrentPage.SetFont('Times New Roman', [fsBold], 12);
Pdf.CurrentPage.TextOut(72, 100, 0, 'Revenue');                 // 12pt bold

Pdf.CurrentPage.SetFont('Times New Roman', [fsItalic], 11);
Pdf.CurrentPage.TextOut(72, 118, 0, 'figures in thousands');    // 11pt italic

Pdf.CurrentPage.SetFont('Courier New', [fsBold, fsItalic], 10);
Pdf.CurrentPage.TextOut(72, 136, 0, '  +18.4% YoY');            // styles combine

Le deuxième paramètre est un ensemble de TFontStyles, ainsi [fsBold, fsItalic] correspond à du gras italique, et [] à du style normal. La taille est exprimée en points, la même unité que les coordonnées, ce qui rend l'espacement vertical facile à raisonner : une ligne de 12 points nécessite un pas vertical d'environ 14 à 16 points pour respirer, ainsi avancer Y de 14 à chaque ligne est un point de départ raisonnable. Il n'y a pas de retour automatique à la ligne. Vous devez calculer vous-même la position de chaque ligne, ce qui est fastidieux pour des paragraphes mais très précis pour des formulaires où chaque champ est positionné à des coordonnées fixes

Il y a deux remarques pratiques concernant le nom de la police. Il est résolu en fonction des polices installées sur la machine de construction, et ce que le système d'exploitation renvoie est intégré, de sorte que le nom résolu sur votre bureau et celui résolu sur le serveur de construction ne garantissent pas la même police. De plus, la police doit couvrir les scripts de la chaîne de caractères. Le rendu de texte cyrillique ou CJK sous une police uniquement latine produira des carrés de glyphes manquants sans aucune erreur, c'est pourquoi la page Hello World utilise une police Unicode à large couverture pour les langues mixtes

Page TextOut de HotPDF montrant Arial, Times New Roman et Courier New rendus avec les styles normal, gras et italique sur plusieurs jeux de caractères

Le paramètre d'angle pivote autour du point d'ancrage

Le troisième paramètre est celui que la plupart des codes laissent indéfiniment à zéro. Passez une valeur non nulle pour faire pivoter la ligne de texte de ce nombre de degrés dans le sens inverse des aiguilles d'une montre autour de son point d'ancrage (X, Y), l'ancre étant le coin supérieur gauche du texte. L'ancre elle-même ne se déplace pas, de sorte que les mêmes coordonnées qui positionnent une étiquette horizontale placent également son jumeau incliné ; seule la direction de progression des glyphes change

Diagramme de la géométrie de rotation de TextOut dans HotPDF : séquences à 0, 45 et 90 degrés pivotant dans le sens antihoraire autour d'une ancre fixe, avec des exemples d'appels Delphi TextOut pour étiquettes de tranche, filigranes et en-têtes de colonnes inclinés
Un angle non nul fait pivoter la séquence dans le sens antihoraire autour de son ancre immobile, le coin supérieur gauche du texte, et des lignes de base partagées avancent en X exactement comme des lignes horizontales avancent en Y
Pdf.CurrentPage.SetFont('Arial', [fsBold], 11);

// Un libellé d'axe vertical le long de la marge gauche : à 90 degrés, ça se lit de bas en haut
Pdf.CurrentPage.TextOut(40, 300, 90, 'Units sold');

// Un filigrane DRAFT en diagonale en travers du corps de page
Pdf.CurrentPage.SetFont('Arial', [fsBold], 60);
Pdf.CurrentPage.TextOut(150, 250, 45, 'DRAFT');

// En-têtes de colonnes inclinés à 60 degrés pour faire tenir des libellés longs dans un tableau étroit
Pdf.CurrentPage.SetFont('Arial', [], 9);
Pdf.CurrentPage.TextOut(120, 600, 60, 'Q1 actual');
Pdf.CurrentPage.TextOut(160, 600, 60, 'Q2 actual');

90 degrés est un cas courant, par exemple une étiquette qui s'étend vers le haut le long du côté d'un graphique ou le titre d'une reliure. Une inclinaison plus marquée comme les 60 degrés ci-dessus convient aux en-têtes de colonnes, une technique qui permet de placer de longues étiquettes au-dessus de colonnes étroites sans déborder sur les colonnes voisines, tandis que 45 degrés se prête à un filigrane en diagonale. La rotation ne modifie pas la façon dont le point d'ancrage est interprété, ce qui peut être déroutant : une ligne de texte à 90 degrés commence toujours à partir de (X, Y) et croît vers le haut, donc pour centrer une étiquette pivotée, vous devez ajuster l'ancrage plutôt que l'angle. Lorsque plusieurs lignes de texte pivotées partagent une ligne de base, attribuez-leur le même Y et incrémentez X, tout comme vous incrémenteriez Y pour des lignes horizontales empilées

Positionnement précis des coordonnées plutôt que des suppositions

Les coordonnées sont le passage qui survit à la relecture — ou qui la fait échouer discrètement. L'API de dessin HotPDF mesure depuis le coin supérieur gauche de la page, Y croissant vers le bas, en points à 72 par pouce avec la valeur par défaut Resolution de 72 ; THPDFPage.YProjection convertit chaque Y en espace utilisateur PDF, qui va du bas vers le haut, en le soustrayant de la hauteur de la page. Par défaut, le Y que vous passez à TextOut est le bord supérieur du texte, la ligne de base se situant une ascendante plus bas ; appelez TextFromBaseLine(True) sur la page si vous préférez positionner les lignes de base. Une page US Letter mesure 612 par 792 points ; l'A4 fait 595 par 842. Une marge haute d'un pouce sur Letter place donc votre première ligne à Y = 72, et non à un grand nombre proche de 792. Quiconque arrive de coordonnées PDF brutes, où Y croît vers le haut depuis le bord inférieur, écrit la première ligne près du pied de la page et passe dix minutes à se demander pourquoi le rapport commence en bas

HotPDF : diagramme d'une page US Letter montrant l'arithmétique des positions de ligne avec une marge gauche, une ligne de départ en haut et un Leading fixe progressant vers une limite de marge basse, à côté de la boucle de mise en page à constantes nommées avec sa garde AddPage manuelle et son reset SetFont
Les ancres nommées transforment une colonne de nombres magiques en arithmétique : avancez le Y courant de Leading à chaque ligne et gérez vous-même la marge basse avec AddPage

Considérez la mise en page comme une arithmétique par rapport à des ancres nommées, plutôt que comme une série de nombres magiques. Une marge gauche, un Y courant avancé à chaque ligne et un espacement de ligne fixe peuvent transformer un bloc de valeurs littérales en une courte boucle :

const
  LeftMargin = 72;        // 1 inch in
  TopLine = 72;            // première ligne, 1 pouce vers le bas (Y croît vers le bas)
  BottomLimit = 720;       // 792 - 72 : 1 pouce au-dessus du pied de Letter
  Leading = 16;            // pas vertical entre les lignes
var
  Y: Single;
  Line: string;
begin
  Pdf.CurrentPage.SetFont('Arial', [], 11);
  Y := TopLine;
  for Line in ReportLines do
  begin
    Pdf.CurrentPage.TextOut(LeftMargin, Y, 0, Line);
    Y := Y + Leading;
    if Y > BottomLimit then    // marge basse atteinte
    begin
      Pdf.AddPage;
      Pdf.CurrentPage.SetFont('Arial', [], 11);  // la police est réinitialisée sur une nouvelle page
      Y := TopLine;
    end;
  end;
end;

La protection de la pagination est la ligne que tout le monde oublie en premier et qui frappe le plus fort en pratique. Il n'y a pas de mise en page fluide sous TextOut. Si vous continuez à avancer au-delà de la marge inférieure, le texte continuera à s'écrire dans la gouttière, hors de la page, dans le vide, sans aucun avertissement. Surveillez donc vous-même le Y, appelez AddPage lorsque la valeur franchit la limite, et réinitialisez le Y courant. Le SetFont qui suit AddPage n'est pas une option : la police actuelle n'est pas conservée après un changement de page ; si vous l'omettez, la première ligne de texte de la nouvelle page apparaîtra avec la police par défaut du visualiseur

Espacement des caractères et des mots pour l'ajustement et l'alignement

Parfois, la chaîne est correcte mais la largeur est erronée : un titre devant croiser des lignes fixes, un code devant s'afficher avec des chiffres plus espacés, ou des valeurs de colonnes nécessitant un alignement précis. Le PDF intègre deux opérateurs d'état de texte à cet effet : l'espacement des caractères (Tc, espace supplémentaire ajouté après chaque glyphe) et l'espacement des mots (Tw, espace supplémentaire ajouté à chaque caractère espace), tous deux exprimés en unités d'espace de texte non mises à l'échelle, ce qui correspond en pratique à des points à la taille de police actuelle. Ce sont des états et non des paramètres de TextOut, vous les configurez, dessinez, puis les réinitialisez

HotPDF : l'espacement de caractères Tc répartissant l'espace supplémentaire uniformément après chaque glyphe de SUMMARY, contre l'espacement de mots Tw l'accumulant uniquement aux caractères espace, branché dans le cycle set, draw, reset qui empêche l'état d'espacement de fuir vers les paragraphes suivants
Tc étale son ajustement sur chaque glyphe tandis que Tw n'agit que sur le caractère espace, et une réinitialisation à la ligne suivante garde l'état de dessin propre
// Interlettrage sur un titre court pour qu'il s'étire sur toute la largeur d'un filet
Pdf.CurrentPage.SetCharacterSpacing(4);
Pdf.CurrentPage.SetFont('Arial', [fsBold], 14);
Pdf.CurrentPage.TextOut(72, 72, 0, 'S U M M A R Y');
Pdf.CurrentPage.SetCharacterSpacing(0);   // remise à zéro avant le texte courant

// Élargit les espaces entre les mots sur une seule ligne large
Pdf.CurrentPage.SetWordSpacing(6);
Pdf.CurrentPage.SetFont('Arial', [], 11);
Pdf.CurrentPage.TextOut(72, 100, 0, 'Name        Department        Extension');
Pdf.CurrentPage.SetWordSpacing(0);

L'espacement des mots ne s'applique qu'au caractère d'espace (code 32), ce qui a une conséquence d'intérêt : cela ne fonctionne pas dans les textes CJK dépourvus d'espaces ASCII, et interagit curieusement avec le texte codé en index de glyphes plutôt qu'en octets. Pour les tableaux latins, c'est un moyen économique d'élargir les écarts sans ressaisir la chaîne. L'espacement des caractères est un meilleur outil pour adapter les titres à la largeur cible car il répartit l'ajustement uniformément sur chaque glyphe, au lieu de le concentrer sur l'espace

La réinitialisation est toute la discipline. L'espacement, au même titre que la police, fait partie de l'état graphique de la page, et cet état persiste jusqu'à ce que vous le modifiiez. Configurer l'espacement des lettres sur un titre puis oublier de le remettre à zéro appliquera ce décalage à tous les paragraphes suivants, ce qui créera une sensation d'étrangeté subtile et difficile à cerner, capable de passer une relecture rapide mais capturée lors d'une relecture attentive. Une habitude fiable consiste à configurer l'espacement, tracer le texte qui le requiert, puis le réinitialiser à zéro sur la ligne suivante, de sorte que le code ultérieur n'ait pas à se soucier des actions précédentes

Page TextOut de HotPDF comparant la mise à l'échelle horizontale du texte, l'espacement de caractères, l'espacement de mots, et le rendu fill contre stroke

Vérifier la sortie là où les problèmes surviennent réellement

La mise en page du texte échoue sur la deuxième machine, et non sur la première, de sorte que les vérifications réellement importantes se font en dehors de votre bureau. Ouvrez le fichier généré sur un système où l'ensemble des polices de développement n'est pas installé, confirmez que les polices intégrées s'affichent toujours, y compris les caractères latins accentués, les écritures non latines et la ponctuation, en effectuant un passage complet plutôt qu'en vérifiant uniquement les caractères faciles. Sélectionnez et copiez quelques lignes, confirmez que le texte est du texte réel et non des contours, ce qui est crucial pour la recherche ou l'extraction. Saisissez des données représentatives (les étiquettes allemandes les plus longues et les nombres les plus larges, plutôt que des espaces réservés propres) dans la mise en page, car le texte qui déborde du champ est toujours celui que vous n'avez pas saisi manuellement. Si la page doit correspondre à un formulaire pré-imprimé, imprimez ou rasterisez un échantillon et alignez-le sur l'original ; un décalage de ligne de base d'un quart de millimètre est invisible à l'écran mais évident sur le papier

Si vous n'avez pas encore écrit de page, commencez par l'exemple HotPDF Hello World, qui configure le document, la police et le système de coordonnées en haut à gauche dont tout dépend. Les appels TextOut, SetFont et d'espacement présentés ici font partie du Composant HotPDF pour Delphi et C++Builder