2005-01-27

line-height

Ces derniers temps, je me suis intéressé au rendu de texte. Tout a commencé par ceci :

Il y a quelques années, j'ai développé une bibliothèque de classes C++ me permettant de construire des interfaces graphiques, et je continue de la maintenir pour des projets divers.

Le but de cette bibliothèque est de pouvoir réaliser facilement des interfaces graphiques pour des logiciels dont l'apparence sort de l'ordinaire. Faisant des développements sous Windows, je me suis cassé les dents à deux reprises en voulant utiliser les fonctions que ce système propose afin de réaliser des interfaces à l'apparence originale. Les contraintes sont trop fortes, et si on souhaite utiliser les éléments d'interface Windows, il vaut mieux ne pas trop vouloir changer le look&feel de ces éléments. On se retrouve sinon avec un code assez bancal, bardé de verrues ou d'une complexité démesurée vis-à-vis de la tâche à accomplir.

La réalisation de cette bibliothèque fut intéressante et continue de l'être. J'ai acquis pleins de connaissances en matière d'interface 2D. Ce fut un bon terrain d'expérimentation pour apprendre à concevoir un design robuste d'API graphique, expérience que je pourrai mettre en œuvre dans le but de concevoir une nouvelle API pour un autre projet qui m'intéresse.

J'ai programmé l'ensemble des routines nécessaires à un tel projet :

  • de quoi gérer une logique d'éléments d'interface : boutons, fenêtres, événements, etc. ;
  • créer des fonctions de dessin bas niveaux : copie de pixels, resampling, flou, transparence, dégradé, affichage de texte... ; c'est ce dernier point qui nous intéresse dans ce billet.

Le rendu de texte est un problème difficile à résoudre. Il y a pleins de formats, des fichiers avec des bugs, des façons différentes d'interpréter le contenu de ces fichiers, des tracés vectoriels à effectuer... Quand j'ai ébauché ma bibliothèque, je souhaitais pouvoir offrir des fonctionnalités de rendu de texte de haute qualité. De plus, pour des raisons techniques, et comme je souhaitais garder une indépendance vis-à-vis du système d'exploitation, je ne pouvais pas utiliser les fonctions proposées par le dit système (Windows, souvent en ce qui me concerne). La seule chose que doit fournir le système à ma bibliothèque, c'est un buffer de pixels, ensuite elle se débrouille.

Pour faire du rendu de texte, je me suis penché sur la bibliothèque Freetype. Le développement m'a semblé sérieux, surtout aux vues des références. C'est en effet une bibliothèque utilisée dans de nombreux projets OpenSource.

Je n'ai effectivement pas été déçu lors de mes premiers tests, et la Freetype est donc devenue le compagnon de ma bibliothèque graphique.

Entre temps, je me suis penché sur les technologies Web, les notions de tables de caractères, un peu de typographie, l'Unicode, etc. Pleins de questions sur les caractères. Ne me demandez pas pourquoi, j'en sais rien, ça m'a bien plu :) C'est alors que j'ai souhaité revoir le code de rendu de texte de ma bibliothèque graphique.

Ayant dans la tête de faire un jour un navigateur XHTML+CSS, ou au moins un embryon de navigateur, je me suis dit qu'il serait bon de commencer par la base : avoir un rendu de texte excellent. Je suis parti de l'hypothèse suivante : mon rendu de texte actuel est suffisant mais pas parfait, la perfection étant atteinte par des logiciels reconnus. Cette perfection se traduisant aussi par une cohérence (dans le sens de « homogénéité ») dans le rendu, d'une application à une autre.

Pour améliorer mes algortihmes de rendu de texte, j'ai donc créé un projet qui créait une multitude de lignes de texte via mon algorithme, et pour comparer, j'ai créé une page XHTML qui reproduisait le même genre d'affichage.

Mon souhait dans cette manipulation était double :

  • déterminer avec précision où placer chaque glyph, c'est à dire le dessin d'un caractère, par rapport à des coordonnées d'origine ;
  • déterminer avec précision la hauteur de ligne habituelle quand un texte est composé de plusieurs lignes.

J'ai utilisé exprès des caractères qui montent haut, comme le À et d'autre qui descendent, comme le Ç. J'ai utilisé un fond de couleur pour le texte, me permettant de mesurer les paramètres associés à une taille donnée de police de caractères selon le logiciel utilisé. Voici un schéma des informations que je souhaitais reccueillir et reproduire dans mon algorithme de rendu de texte :

Le texte est dessiné à partir d'une ligne de base. La hauteur depuis la ligne de base s'appelle « ascent », et la hauteur descendante est appelée « descent ». En additionnant les deux, on obtient la hauteur de ligne. On peut aussi ajouter un espacement supplémentaire, mais de base la simple addition de l'« ascent  » et du « descent » est bien adaptée.

Il convient de noter qu'il y a parfois des désaccords sur les définitions, ainsi, selon les sources, l'« ascent » prend en compte ou pas les accents sur les capitales. J'ai considéré ici, comme Windows, que l'« ascent » est censé monter jusqu'au pixel le plus haut, incluant donc les accents sur les capitales.

Avec Firefox sous Windows, pas de problème, le fond englobe toujours bien le texte. Je teste avec Internet Explorer aussi, l'englobage n'est pas exactement le même par le bas, mais vers le haut c'est identique à Firefox. J'en viens à la conclusion que je dois atteindre ce but en terme de rendu, au moins verticalement.

Là, catastrophe, mon accent dépasse du fond dans certains cas. Lorsqu'on dessine avec la Freetype, les coordonnées sont données à partir de la ligne de base. Ainsi, si on souhaite faire rentrer ce texte dans un cadre, il faut faire descendre la ligne de base.

Mais de combien faut-il la faire descendre ? J'ai décidé d'utiliser l'information d'« ascent » que la Freetype me donne, mais visiblement, cela ne suffit pas. Je décide alors d'utiliser les informations de boite englobante, mais cette fois-ci, l'englobage devient trop important.

J'ai beau trifouiller dans tous les sens les valeurs offertes par la Freetype, je n'arrive pas à avoir un rendu identique à Firefox. Je finis par avoir la conviction que Firefox utilise les fonctions de rendu de texte du système sur lequel il se trouve, donc dans mon cas, il doit utiliser les fonction de texte Windows, en raison des similitudes d'« ascent » avec IE. Je vais voir dans le code source de Mozilla (cool l'Open Source pour ça :)) et je découvre effectivement des fonctions de texte Windows. Pour conforter ma découverte, je décide d'aller voir ce que ça donne sous un Linux avec KDE.

Après une installation laborieuse de la police Arial sur KDE (tiens, X11, un vieux camarade), je lance Firefox et Konqueror sur ce système, leur fais afficher la page, et prends quelques captures d'écran. Le résultat est, comme je le pensais, différent de celui de Windows.

Enfin, je demande à Vincent de me faire des tests sous MacOS X, avec Firefox et Safari. Là, on obtient encore un autre rendu, très beau par ailleurs : on sent que le système d'Apple est soigné graphiquement. En revanche, il y a de très légèrement dépassement de l'accent ou de la cédille, un pixel très transparent. Bon, ça ira ;)

Voici les résultats concernant Firefox sur Windows, KDE et MacOS X :

À noter que l'apparence des glyphs dans le navigateur obsolète sous Windows, dans Konqueror sous KDE et dans Safari sous MacOS X est identique à celle dans Firefox sur les mêmes plateformes.

Tadaaaaam ! KDE et moi avons le même rendu, avec les accents qui dépassent et tout. La Freetype est passée par là. Je constate aussi avec surprise que Firefox écrit le dernier texte sur deux lignes au lieu d'une seule. L'algorithme de calcul de largeur de texte doit être légèrement différent sur cette version.

Je me retrouve donc avec un rendu identique aux logiciels sous KDE, mais ça ne me satisfait pas. J'ai donc cherché à amélioré mon algorithme, et j'ai choisi celui-ci : pour corriger le tir de l'englobage, je calcule désormais la hauteur de la lettre « À ». Si cette dernière est plus haute que la hauteur d'ascent donnée par la Freetype, je décide d'utiliser cette hauteur. Au final, l'englobage est complet et bien serré autour de la lettre, avec l'accent à l'intérieur. Pour le descent, les valeurs sont toujours différentes d'un logiciel à un autre, mais l'englobage est toujours au rendez-vous. Je décide donc de ne pas intervenir à ce niveau.

J'en arrive aux conclusions suivantes :

  • il n'existe pas de perfection ou de cohérence en terme d'algorithme de rendu de texte ;
  • chaque logiciel a son propre algorithme de rendu de texte ;
  • quand vous faites un design Web, ne vous attendez pas à un rendu au pixel près quelle que soit la plateforme.

Concernant ce dernier point, mes recherches confortent les réflexions du moment vis-à-vis du refus des exigences d'un design complètement figé, s'affichant de manière exactement identique d'un logiciel à l'autre. Il est possible de faire du rendu au pixel près pour tout ce qui est modèle de boite CSS, mais pas en ce qui concerne le rendu de texte. Il convient donc de laisser un peu de « mou » dans un design. Avoir des marges de sécurité si jamais l'affichage n'était pas rendu de la même façon.

Mon rendu actuel, de la même façon que les autres, est donc différent, et ça ne me pose plus de problème. Je constate que mon hypothèse de départ, sur la cohérence de rendu entre applications, n'est pas valide. Au final, j'ai travaillé sur mon code afin que le rendu me plaise et qu'il soit assez proche dans les mesures des autres logiciels et des autres plateformes, ce qui est pleinement satisfaisant ^^

Épilogue :

Depuis trois jours, j'ai obtenu une nouvelle amélioration d'affichage du texte.

J'utilisais une ancienne version de la Freetype, qui avait l'inconvénient de faire un rendu moche pour les polices de caractères de petite taille. C'est en effet compliqué de faire du rendu de police de petite taille tout en gardant une bonne lisibilité.

Pour atteindre ce but, les polices TrueType, qui sont à la base vectorielles, embarquent carrément du code à exécuter pour dessiner les glyphs quand on a besoin de texte de petite taille. Ce code est injecté dans un interpréteur de byte-code TrueType. On sent une certaine différence en effet quand on commence à descendre la taille du texte.

Pour contourner ce rendu moche de la Freetype, j'avais donc activé l'interpréteur de byte-code TrueType. J'avais ainsi le même rendu que sous Windows, et que sous la version de KDE que j'ai à disposition (3.2.2).

Le rendu devient en effet plus lisible, et dans un premier temps, on est conforté à l'idée d'avoir des glyphs qui ont la même apparence que sur des systèmes graphiques bien établis. Mais dans un second temps, force est de constater que la forme de ces glyphs manquent un peu de cohérence par rapport aux mêmes versions mais dans une taille plus élevée. D'ailleurs sous MacOS X, le rendu de police reste joli et cohérent, quelle que soit la taille utilisée.

J'ai décidé alors d'utiliser la dernière version de la Freetype, la 2.19 à l'heure actuelle. Et cette fois, j'ai désactivé l'interpréteur de byte-code. Quelle ne fut pas ma surprise de voir un rendu vraiment très joli, proche de ce qu'on a sous MacOS X. Du beau boulot. Eh c'est aussi tant mieux, car la technique d'interprétation de byte-code des polices TrueType est brevetée, il est donc délicat d'avoir un produit qui embarque un tel interpréteur (l'interpréteur est désactivé par défaut dans le code de la Freetype).

Voici le rendu que j'ai à présent, qui me plait d'un point de vue esthétique, qui conserve une hauteur de ligne bien englobante, et dont la disposition reste proche des autres systèmes de rendu de texte :

À droite, se trouve un rendu Windows. Notez le saut en terme d'apparence entre la taille 17 et 18 : l'interpréteur de byte-code TrueType a été activé.

J'ai désormais une bonne base de travail pour créer des logiciels affichant du texte, et comme disait Édika, en ture pour de nouvelles avenroutes !