Il y a un peu plus d'un mois, j'ai publié un billet portant sur l'utilisation des polices de caractères Latin 1 et Latin 9 avec la bibliothèque Adafruit GFX. Le billet laissait entendre que l'approche pouvait être généralisée sans être plus explicite. Voici une façon de procéder dans cette direction. Auparavant, les polices ASCII étendues étaient le sujet d'un autre billet axé sur les lettres diacritiques du français. La solution proposée ci-dessous prend en charge ces besoins. En fait, l'approche présentée ci-dessous devrait fonctionner avec un choix relativement arbitraire de caractères. Évidemment, il n'est pas question d'implémenter Unicode sur des microcontrôleurs, alors il y a des limites qui sont décrites à la section suivante et qui découlent en partie du respect de la PREMIÈRE DIRECTIVE de la bibliothèque Adafruit-GFX-Library
: ... maintenir la comptabilité avec les croquis Arduino existants dont plusieurs sont hébergés ailleurs [que dans le référentiel de la bibliothèque] et n'en suivent pas les changements.
Je ne sais pas si j'aurais la patience de lire ce long billet. Alors en voici un résumé. Adafruit a élaboré une bibliothèque graphique qui fonctionne très bien si l'on peut se limiter au jeu de caractères ASCII ou à un sous-ensemble de caractères ASCII consécutifs. Ces limites peuvent être trop contraignantes. Voici une approche qui demeure compatible avec la bibliothèque Adafruit-GFX
tout en permettant d'avoir un jeu de caractère choisi arbitrairement parmi les glyphes de la plage 0 d'Unicode, c'est-à-dire les glyphes dont le point de code est inférieur ou égal à 0xFFFF. On peut sauter par dessus les explications techniques des sections suivante et passer directement 7. Source et exemples pour voir comment utiliser l'utilitaire fontconvert8x
pour créer une fonte avec un choix arbitraire de glyphes à partir d'une police TTF. Puis on peut voir comment convertir des chaînes Unicode avec codage UTF-8 lors de l'exécution d'un programme avec des fonctions comme utf8tocp
ou latin9tocp
. Enfin, on peut éviter cette complication si les chaînes à afficher sont connues avant la compilation, ce qui est souvent le cas pour les microcontrôleurs, en codant toutes les chaines litéralles avec l'utilitaire convert8x
.
Table des matières
- Points de code ASCII and GFX
- Jeu de caractères arbitraire
- Conversion de polices TrueType en fontes Adafruit-GFX
- Trois autres jeux de caractères : FR, Latin1 et Latin9
- Conversion de l'encodage UTF-8 vers l'encodage GFX
- Éviter la conversion lors de l'exécution
- Source et exemples
- Suite
Points de code ASCII and GFX
ASCII American Standard Code for Information Interchange est une norme de codage de caractères établie dans les années 1960 qui compte 128 points de code puisque basée sur 7 bits. La norme contient 95 caractères imprimables (quand on admet que l'espace est un caractère visible) dont les points de code s'échelonnent de 32 (0x20 en hexadécimal) à 126 (07E en hexadécimal). Les 32 premiers codes (de 0x00 à 0x1F) et le tout dernier (code 127 ou 0x7F en hexadécimal) sont des codes de contrôle. Sur un terminal VT102, par exemple, le point de code 7 engendre un son, le code 10 provoque le passage du curseur à la ligne suivante, le code 13 le retour du curseur au début de la ligne et ainsi de suite. Pour ce qui est de la bibliothèque Adafruit-GFX
, le seul véritable code de contrôle est 10 (0x0a en hexadécimal, séquence d'échappement "\n") dont l'effet est de placer le curseur au début de la ligne suivante. Pour le reste de la discussion, les codes de contrôle n'ont presque pas de signification, l'enjeu étant l'affichage des caractères imprimables sur un écran par un microcontrôleur qui n'imite nullement un terminal. Donc voici les caractères ASCII imprimables avec leur point de code en hexadécimal. On peut voir que le code de la lettre A est 0x41 en hexadécimal (soit 4x16 + 1 = 65 en décimal).
Codes ASCII | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | |
0_ | codes de contrôle | |||||||||||||||
1_ | ||||||||||||||||
2_ | SP | ! | " | # | $ | % | & | ' | ( | ) | * | + | , | - | . | / |
3_ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | : | ; | < | = | > | ? |
4_ | @ | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O |
5_ | P | Q | R | S | T | U | V | W | X | Y | Z | [ | \ | ] | ^ | _ |
6_ | ` | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o |
7_ | p | q | r | s | t | u | v | w | x | y | z | { | | | } | ~ | DEL |
Une fonte proportionnelle de la bibliothèque GFX d'Adafruit est une structure contenant cinq champs dont le premier un pointeur (uint8_t *bitmap
) vers le tableau des images des caractères et le second est un pointeur vers le tableau des glyphes.
Puisqu'un glyphe est l'image d'un caractère on peut se demander quelle est la différence entre les deux pointeurs. Le premier, *bitmap
est le pointeur vers les images des caractères, les glyphes de la fonte, stockées en mémoire. Lady Ada et des collobarateurs ont élaboré un utilitaire, fontconvert
pour extraire ces images d'une police Free Type. Les détails du format des images est sans importance ici. Le second pointeur, *glyph
est en fait un pointeur non pas vers un tableau d'image, mais vers un tableau de structures GFXglyph
contenant des données relatives à l'affichage de chaque caractère de la fonte, dont l'index de son image, sa taille et ainsi de suite.
Voici la démarche (simplifiée) pour afficher l'image de la lettre A sur un écran.
- Repérer la structure pour la lettre A dans le tableau
*glyph
. Ci-devant, l'enregistrement sera dénoté*glyph{'A'}
. - Utiliser l'index du glyphe de la lettre
glyph{'A'}->bitmapOffset
pour calculer l'adresse de l'image du caractère :GFXfont->bitmap + glyph{'A'}->bitmapOffset
. - Utiliser les distances
glyph{'A'}->xOffset
etglyph{'A'}->yOffset
pour trouver les coordonnées à l'écran du coin supérieur gauche du glyphe. Le curseur, c'est-à-dire les coordonnées (x; y) du point d'insertion du prochain glyphe sur l'écran, se trouve sur la ligne de pied de la ligne courante. Encore une fois, c'est un détail de l'affichage sans lien direct au propos de ce billet. - Le glyphe est copier à l'écran, une ligne horizontale à la fois.
- Le curseur est avancé horizontalement selon la valeur du champ
glyph{'A'}->xAdvance
.
C'est la première étape qui est la plus importante dans ce qui suit. Pour bien saisir le mécanisme, examinons le tableau des enregistrements GFXglyph
pour la fonte FreeMono12pt7b
fournie avec la bibliothèque Adafruit-GFX
Comme d'habitude en C/C++, le tableau est une séquence indexée d'enregistrements dont l'index du premier enregistrement est 0. L'index de chaque caractère dans ce tableau peut donc être vu comme le point de code du caractère dans la fonte GFX. En voici une représentation.
Codes GFX par défaut (tableau *glyph) | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | |
0_ | SP | ! | " | # | $ | % | & | ' | ( | ) | * | + | , | - | . | / |
1_ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | : | ; | < | = | > | ? |
2_ | @ | A | B | C | D | E | F | G | H | I | J | K | L | M | N | O |
3_ | P | Q | R | S | T | U | V | W | X | Y | Z | [ | \ | ] | ^ | _ |
4_ | ` | a | b | c | d | e | f | g | h | i | j | k | l | m | n | o |
5_ | p | q | r | s | t | u | v | w | x | y | z | { | | | } | ~ |
Le code GFX d'un caractère ASCII est facile à calculer puisque l'index dans le tableau *glyph
d'un caractère est son point de code ASCII moins 32. L'index de 'A' dont le point de code ASCII est 0x41 (décimal 65) est 0x21 (décimal 33).
Supposons maintenant que la fonte ne contient que 11 caractères utilisés pour afficher l'heure dans un format 24 heures tel 18:33. Heureusement, les 11 lettres sont consécutives dans le jeu ASCII ayant les points de code 0x30 à 0x3A inclusivement. L'utilitaire fontconvert
peut extraire un tel sous-ensemble de glyphes séquentiels d'une police True Type sans difficuté en spécifiant les points de code du premier et dernier caractère à extraire sur la ligne de commande.
Dans la définition de la fonte engendrée par l'utilitaire, qui se trouve ci-dessous, on peut voir que le champs first
est égal à 48 (ou 0x30 en hexadécimal) et que la valeur du champ last
est 48 (ou 0x3A en hexadécimal).
Voici la représentation schématique du tableau *glyph
de ce qu'on appellera la fonte horloge.
Codes GFX pour la fonte horloge (tableau *glyph) | |||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | |
0_ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | : |
Si on devait soustraire 32 du point de code ASCII du chiffre 5 comme auparavant on obtiendrait 21 ce qui n'est pas du tout l'index du chiffre 5 dans le tableau *glyph
. Or 32 n'est pas une constante dans le calcul des points de code GFX. Astucieusement, le champ first
de la structure GFXfont
contient le point de code ASCII du premier caractère dans le tableau qui est 0x30 (le code ASCII de '0') dans la fonte horloge. Alors la soustraction 0x35 - 0x30 = 0x05 = 5 donne le bon indice pour le chiffre 5. Le champ last
n'est pas directement utilisé dans le calcul du point de code GFX d'un caractère à afficher, mais il constitue un garde-fou, car aucune lettre avec un point de code ASCII supérieur à last
ne peut être affichée.
Comme on peut voir la conversion d'un point de code ASCII en un point de code GFX est simple et très rapide. C'est une solution élégante pour autant que la fonte ne contient que des caractères consécutifs. Si l'on voulait ajouter la possibilité d'afficher l'heure dans le format 12 heures préféré aux Ètats-Unis, tel 6:33 pm pour reprendre l'exemple précédent, il faudrait inclure 81 caractères imprimables de l'espace ' ' à 'p', alors qu'en réalité on a besoin que de seulement 4 glyphes supplémentaires ('a', 'p' et 'm' et l'espace ' ') pour un total de seulement 15 caractères. Voila une justification pour la prise en charge des fontes avec des jeux de caractères dont les points de code ne sont pas nécessairement consécutifs. En même temps, il faut aussi que le jeu puisse contenir des caractères qui ne sont pas compris dans la norme ASCII.
Le reste de ce billet propose une solution générale qui permet de créer des fontes de type GFXFont
avec des jeux de caractères arbitraire. Cependant, respecter la directive première implique qu'il ne faut pas modifier la structure GFXFont
. La fonte ne peut pas contenir plus de 224 caractères en pratique (1). La deuxième contrainte est que les caractères inclus dans la fonte proviennent de la première zone de 65 536 points de code Unicode. En d'autres mots, le point de code des caractères Unicode à inclure doit être compris entre U+0000 et U+FFFF, soit une valeur de 16 bits.
first
et last
sont des mots de 16 bits. Cependant la taille du tableau bitmaps
est limitée à 65 536 octets et à moins que les images des caractères ne fassent qu'un 1 pixel de haut et moins de 9 pixels de large, il sera impossible d'enregistrer les glyphes. De plus, le paramètre c
quand drawChar
dessine le caractère c
est un octet de 8 bits ( @param c The 8-bit font-indexed caracter (likely ascii)
) ce qui impose une limite de 256 glyphes. Quand on soustrait les 32 codes de controle qu'il est préférable évité on arrive au nombre 224.
Jeu de caractères arbitraire
Pour fixer les idées, continuons avec la fonte horloge à laquelle sont rajoutés les trois lettres a, m et p et l'espace.
Codes GFX pour la fonte horloge (tableau *glyph) | |||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | |
0_ | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | : | a | m | p | SP |
Les lettres 'a', 'm', 'p' et ' ' occupent les positions des caractères ';', '<', '=' et '>' respectivement du jeu ASCII. Pour afficher la chaîne "6:33 pm"
avec la méthode print
de la bibliothèque GFX, on utilise la commande
Ce n'est pas très pratique, mais la fonte horloge exige beaucoup moins de mémoire ce qui n'est pas négligeable surtout lorqu'on utilise un microcontrôleur de 8 bits. La fonte FreeMono12pt7b.h
qui contient 95 glyphes occupe environ 2 130 octets alors que FreeMono12ptClock.h
, avec ses 15 glyphes n'a besoin que d'approximativement 330 octets. C'est 15% de la mémoire pour 15% des caractères.
Voici une représentation graphique des structures de la bibliothèque GFX pour gérer les fontes proportionnelles.
Les points de code des caractères qu'on peut voir en gris pale sous les enregistrements de type GFXglyph
dans le tableau FreeMono12ptClockGlyphs
ne sont pas inclus dans les données, ils sont implicites étant donnés la valeur du champs first
qui est ici 0x30. Donc le point de code du troisième caractère de la fonte, soit le chiffre 2, est 0x32 (= first
+ 2 = 0x30 + 2) ce qui est le code ASCII et Unicode du caractère. Manifestement, ce calcul est erroné pour les quatre derniers caractères d'où les substitutions faites ci-dessus pour afficher ces lettres. Ce n'est pas trop difficile de gérer la situation avec seulement quatre lettres supplémentaires, mais que fait-on quand les caractères inclus dans une fonte GFX ont des codes ASCII très dispersés ou ne proviennent même pas de la plage ASCII ? On peut ajouter un troisième tableau, nommé ClockCharset
qui contient les points de code Unicode des caractères de la fonte et le point de code GFX correspondant. Voici à quoi ressemble cette approche générale appliquée à la fonte horloge.
Avec un caractère comme 'm' à titre d'exemple, on cherche son code Unicode, 0x006d,dans le tableau ClockCharset
et l'on utile le point code GFX correspondant, soit 0x3c, pour l'afficher avec les fonctions de la bibliothèque GFX.
Bien sur, on sait que 0x3c est le code ASCII du symbole '<', donc "<" et "\x3c" sont exactement la même chose. Clairement, le tableau additionnel exige de l'espace de stockage additionel, jusqu'à 672 octets pour une fonte avec 224 glyphes ce qui peut être excéssif selon le microcontrôleur. Si le jeu de caractère contient 224 glyphes, il commence probablement avec les 95 caractères imprimables ASCII, telles la plupart des pages de code comme CP850. Puisqu'il n'est pas nécessaire de stocker les points de code des caractères ASCII, le tableau ne ferait que 387 octets. Si le transcodage n'a pas besoin d'être le plus rapide possible, on peut s'en tenir à une recherche linéaire et ainsi éliminer le code GFX de chaque caractère, car cette valeur est facilement calculée à partir de la position du caractère dans le tableau. La taille du tableau est donc réduite à 258 octets. Enfin, si toutes les chaînes affichées par le croquis sont connues d'avance, on peut éliminer le troisième tableau entièrement quitte à faire le transcodage avant la compilation.
Conversion de polices TrueType en fontes Adafruit-GFX
Heureusement, il est assez facile de convertir une police TTF en une fonte GFXFont
en apportant quelques modifications au programme fontconvert.c
fourni avec la bibliothèque Adafruit-GFX
. Le principal changement apporté à l'utilitaire est qu'il crée la fonte à partir de points de code Unicode stockés dans un tableau, appelé chartable
. Le contenu du tableau est arbitraire et peut contenir des glyphes dont les points de code Unicode sont en désordre. Pour simplifier, chartable
est défini dans un fichier séparé qui sera inclus dans fontconvert8x.c
, la version modifiée de l'utilitaire d'Adafruit. À titre d'exemple, voici le contenu de clock.h
.
Tous les fichiers définissant un tableau chartable
doivent contenir une macro nommée CHARSET_NAME
qui définit la chaîne qui sera utilisée pour identifier de façon unique la fonte engendrée avec l'utilitaire et le tableau xxxxCharset
. Plus explicitement, le contenu de la macro remplace le "7b" ou "8b" utilisé par l'utilitaire original. En plus de spécifier le nom du fichier contenant la définition du jeu de caractère, deux autres macros peuvent être définies. La macro MAKE_CHARSET
doit être définie pour que le tableau xxxxCharset[]
soit engendré par l'utilitaire en plus des définitions de la fonte. Enfin la macro INCLUDE_ASCII
peut être définie si les caractères ASCII imprimables doivent être inclus dans la fonte. Ce n'est pas le cas pour la fonte horloge.
En plus de créer le fichier contenant la définition du tableau chartable
, il faut modifier le code source fontconvert8x.c
avant de le compiler. Le fichier "clock.h", ou éventuellement un autre fichier contenant une définition d'un tableau chartable
, doit être inclus dans le code source de fontconvert8x.c
, ce qu'on fait à la ligne 89. Si nécessaire, on peut modifier la résolution de l'écran à la ligne 87 du fichier. Une fois ces changements apportés on doit compiler le code source pour obtenir l'exécutable. La compilation est simple dans Linux.
Le paramètre -B
, équivalent de --always-make
est nécessaire si l'on a modifié le fichier définissant le tableau chartable
, car autrement make
ne fait rien si des changements n'ont pas été fait à fontconvert8x.c
. Les utilisateurs de Windows peuvent consulter le guide préparé par Adafruit A short guide to use fontconvert.c to create your own fonts using MinGW pour compiler l'application.
L'exécutable modifié est plus simple à utiliser que l'original. Il ne prend que deux paramètres obligatoires, le nom du fichier contenant la police TTF à utiliser comme source et la taille en points de la fonte à créer.
Le nom du fichier vers lequel la production de l'utilitaire est consignée est la concaténation
- du nom de la police TTF dont proviennent les glyphes,
- de la taille en points (pt) des glyphes,
- du nom assigné à la macro
MAKE_CHARSET
et - de l'extension
.h
un peu comme ce qui est fait par les utilitaires fontconvert.c
et makefonts.sh
de la bibliothèque GFX.
Notons qu'il n'est pas nécessaire de spécifier les paramètres first
et last
comme dans l'ancien utilitaire. Le script makefonts8.sh
est une adaptation du script makefonts.sh
qui génère toutes les fontes présentes dans le dossier Fonts
de la bibliothèque Adafruit-GFX
. Vous devrez peut-être ajuster la variable inpath
si les polices FreeFont se trouvent dans un autre répertoire.
Le commentaire à droite de chaque enregistrement GFXglyph
, contient le point de code GFX 8 bits du caractère, la description du glyphe fournie dans le tableau chartable
et son point de code Unicode comme référence.
Si la macro MAKE_CHARSET
est définie, alors l'utilitaire engendre aussi le tableau des codes Unicode des caractères de la fonte ainsi que sa taille.
Trois autres jeux de caractères : FR, Latin1 et Latin9
Cette section montre comment engendrer les jeux de caractères dont il a été question dans les deux billets précédents. En fait, engendrer un jeu de caractères se résume à fournir la bonne définition du tableau chartable
; l'utilitaire fontconvert8x
se charge du reste.
Je voudrais que le jeu FR contienne tous les caractères de la norme ASCII, les lettres diacritiques et d'autres glyphes typographiques couramment utilisés en français. Il y a cinq signes diacritiques, soient les accents et le tréma qui ne ne portent que sur les voyelles, mais pas de façon systématique et la cédille dont seule la consonne, c, peut être affublée.
diacritique | A a | E e | I i | O o | U u | Y y | C c | |
---|---|---|---|---|---|---|---|---|
accent aigu | ´ | É é | ||||||
accent grave | ` | À à | È è | Ù ù | ||||
accent circonflexe | ^ | Â â | Ê ê | Î î | Ô ô | Û û | ||
tréma | ¨ | Ä ä | Ë ë | Ï ï | Ö ö | Ü ü | Ÿ ÿ | |
cédille | ¸ | Ç ç |
À noter que les lettres a, o et y avec tréma ne sont retrouvées que dans des noms propres ou des mots de langues étrangères. Faut-il les inclure ou non? Faut-il rajouter à cette liste les ligatures et d'autres signes typographiques couramment utilisés en français? On retrouve de nombreuses normes dans les entités francophones à ce sujet qui de surcroît ne sont pas nécessairement cohérentes. Pour les fins de cet exemple, j'ai choisi de créer un jeu de caractères contenant la page de code ASCII et 40 glyphes supplémentaires.ASCII
À Â Ä É È Ê Ë Î Ï Ô Ö Ù Û Ü Ÿ Ç Æ Œ « ° à â ä é è ê ë î ï ô ö ù û ü ÿ ç æ œ » €
Les glyphes sont affichés dans un ordre plus ou moins logique ci-dessus, mais quand dans le tableau chartable
il sont en ordre croissant du point de code. On peut alors obtenir un petit gain de performance lors de la conversion des chaînes UTF-8 en temps d'exécution. Voici le fichier de définition fr.h
.
Les trois macros qui peuvent se retrouver dans un fichier de définition du tableau chartable
sont présentes,
CHARSET_NAME
qui doit être présente et qui doit contenir une chaîne identifiant le jeu de caractères de façon unique,MAKE_CHARSET
dont la présence fait que l'utilitairefontconvert8x
engendrera le tableauFRCharset
,INCLUDE_ASCII
dont la présence fait que l'utilitairefontconvert8x
ajoutera les caractères ASCII 0x20 à 0x7e.
Voici une partie de la définition de fonte FreeMono8ptFR
qui est engendrée avec fontconvert8x
.
Comme on peut voir les trois premiers éléments du fichier, le tableau des images des glyphes FreeMono8ptFRBitmaps[]
, le tableau de définition FreeMono8ptFRGlyphs[]
avec ses 135 enregistrements de type GFXglyph
et la structure de la fonte FreeMono8ptFR
sont exactement les mêmes qu'on retrouverait dans un fichier engendré par l'utilitaire original d'Adafruit fontconvert
. Et puis il y l'élément supplémentaire, le tableau FRCharset
avec les points de code Unicode des 40 caractères supplémentaires. On peut voir que ce tableau est entouré par un ensemble typique de macros pour empêcher qu'il soit inclus plus d'une fois si d'autres fontes avec le même jeu de caractère étaient utilisées dans le même programme.
La démarche pour obtenir une fonte qui correspond à la page de code Latin 1 est pratiquement la même comme on peut voir en examinant la définition du tableau chartable
.
Le fichier latin9.h
est sensiblement le même sauf qu'il contient les glyphes de la page de code ISO 8859-15 dont certain ont un point de code d'une valeur supérieur à 255. Ces fichiers sont presque identiques à ceux utilisés dans le billet précédent (Polices GFXfonts avec encodage 8 bits) à deux exceptions près.
- Les macros à définir sont différentes. Parce qu'il y a une façon efficace de convertir les codes supplémentaires des pages Latin 1 et Latin 9, il n'est pas utile de faire engendrer le tableau
charset
d'où l'absence de la macroMAKE_CHARSET
. - Il faut spécifier explicitement le premier caractère additionnel qui occupera la position 0x7f normalement occupée par le code de contrôle DEL. Dans le fichier
latin1.h
c'est le caractère de remplacement Unicode � (U+fffd) qui est spécifié. Danslatin9.h
c'est la valeur 0x7f elle-même qui est spécifiée et ce qui sera affiché dépend du contenu de la police TTF d'où seront extraits les glyphes. Si l'espace ou l'espace insécable est choisi, le tableau des images occupera quelques octets de moins. Je reviendrai plus tard sur la présence de ce caractère.
Conversion de l'encodage UTF-8 vers l'encodage GFX
La chaîne "6:33 pm" ne s'affichera pas correctement même si la police FreeMono12ptClock est utilisée. Ce qu'on verra à l'écran est 6:33 pour la simple raison que le code ASCII de 'p' est 0x70 et quand on soustrait 0x30 de ce nombre on obtient 0X40 soit 60 alors qu'il n'y a que 15 caractères dans la fonte. Autrement dit, 0x70 est de loin supérieur à last
= 0x3E.
Pour contourner ce problème, une chaîne littérale peut être définie en évitant le codage UTF-8.
Les constantes hexadécimales à incorporer dans la chaîne se trouvent dans le tableau GFXglyph
généré avec fontconvert8
. Le codage manuel des chaînes littérales n'est pas très pratique, il serait préférable de décoder les chaînes encodées en UTF-8. Contrairement à Bodmer qui a ajouté un "attribut utf-8" à la bibliothèque Adafruit-GFX
, j'ai décidé de créer des convertisseurs de chaînes UTF-8 vers le jeu de de la fonte. L'avantage de cette approche est qu'ils peuvent servir avec d'autres bibliothèques prenant en charge les polices proportionnelles en utilisant les structures GFXfont. Voici le fichier d'en-tête gfx8.h
qui déclare deux fonctions qui effectuent les conversions d'un objet String
ou d'une chaîne C.
Les fonctions utf8tocp()
peuvent être utilisées pour créer des fonctions de délégation (des wrappers en anglais) vers des fonctions d'impression de texte de la bibliothèque Adafruit-GFX
.
La bibliothèque TFT_22_ILI9225 de Johan Cronje (Nkawu) que j'utilise avec les écrans TFT couleur ILI9225 de 2" prend en charge les fontes de type GFXFont
. La fonction d'impression des chaînes sur l'écran de cette bibliothèque est différente, mais elle est tout aussi simple à « envelopper ».
Il n'est pas essentiel de créer des fonctions de délégation, mais on ne peut pas se passer de l'initialisation d'une structure de type gfx8charset_t
. Cette fonction conserve les éléments nécessaires pour que utf8tocp
puisse convertir les caractères Unicode : l'adresse du tableau charset
, son nombre d'éléments, le code ASCII du premier caractère et enfin lastascii
, le point de code du dernier des caractères ASCII consécutifs commençant avec le premier caractère. Quand lastascii
est égal à 0, sa valeur par défaut, le tableau Charset
contient les points de code de tous les caractères de la fonte. Cette initialisation est typiquement faite dans la fonction setup()
avant que l'exécution en boucle de la fonction loop
ne débute. La structure par défaut, defaultCharset
qui est déclarée dans gfx8.h
et définie dans gfx8.cpp
est initialisée dans setup
dans l'exemple précédent.
Il faut définir une structure de type gfx8charset_t
pour chaque jeu de caractères différents implicitement importé lorsqu'une fonte avec un jeu de caractère non standard est ajoutée au croquis. Dans l'exemple qui suit, en plus de la structure par défaut, une seconde structure est déclarée et initialisée pour ainsi accommoder les deux fontes avec jeux de caractères différents utilisés.
Dans la seconde invocation de la routine d'initialisation on peut voir que lastascii
est égal à 0x7e. Cela veut dire que le tableau des GFXglyph
commence avec le caractère ASCII first
= 0x20 et puis contient tous les glyphes ASCII jusqu'à lastaciii
= 0x7e. Attention, lastascii
n'est pas égal au champ last
de la définition de la fonte, car la valeur de last
est 0xa6. Ce sont les 40 caractères additionnels dans la fonte qui font la différence. Quand lastascii
n'est pas 0 comme ici, cela veut dire que le tableau Charset
ne contient pas les points de code des caractères entre first
et lastascii
.
On trouvera une discussion dans Polices GFXfonts avec encodage 8 bits sur l'implémentation des fonctions utf8tocp()
. Il faudra être vigilent, car il y eu des changements depuis cette vieille version, mais la fonction de conversion uint16_t decodeUTF8(uint8_t c)
demeure la même bien que la version utilisée maintenant est un peu simplifiée. Le traitement des codes de contrôle 0x00 à 0x1F est peut-être meilleur dans la nouvelle version; les commentaires dans le code source gfx8.cpp
sont plus clair à cet égard.
Il y a un autre changement par rapport à l'ancienne version. Les fonctions utf8tocp()
étaient définies dans les fichiers d'en-tête gfxlatin1.h
et gfxlatin9.h
parce que j'avais supposé qu'on utiliserait qu'une page de code dans un croquis. C'était une erreur comme on peut voir avec le dernier exemple. Il est tout à fait raisonnable de supposer qu'il peut y avoir plus d'un encodage dans un même croquis. Conséquemment on a maintenant trois fonctions de conversion qui peuvent toutes être utilisées dans un même programme.
fonction | fichier d'en-tête | page de code |
---|---|---|
latin1tocp() | gfxlatin1.h | Latin 1 (8859-1) |
latin9tocp() | gfxlatin9.h | Latin 9 (8859-15) |
gfx8tocp() | gfx8.h | arbitraire mais définie dans une structure gfx8charset_t |
Éviter la conversion lors de l'exécution
La plupart des programmes exécutés sur des microcontrôleurs ne sont pas interactifs. Conséquemment, les textes éventuellement affichés sur un écran sont statiques et connus d'avance. Les fonctions utf8tocp
ou latinXtocp
ne sont pas nécessaire si les chaînes sont converties vers le bon codage GFX dans le code source, exactement comme proposé ci-dessus dans la section 2. Jeu de caractères arbitraire.
Il est vrai que la conversion manuelle des chaînes devient vite une besogne fastidieuse avec l'augmentation du nombre de points de code non standard dans la fonte et du nombre de chaînes à convertir. Conséquemmemnt, j'ai bricolé un utilitaire vite fait (un véritable hack dans le jargon des programmeurs anglais) nommé convert8x.cpp
pour simplifier la tâche. J'ai un peu honte du code monté à l'aide de bribes receuillies sur le Web à l'aide d'un moteur de recherche. Au minimum, j'aurais dû conserver les coordonnées des sources pour donner le crédit aux véritables auteurs auxquels j'offre mes excuses.
L'utilisation d'un exécutable, convert8x_clock
, pour créer un fichier de chaînes litéralles converties est très simple.
Le fichier stringconsts_clock.txt
contient les chaînes qui seront affichées avec la fonte FreeMono12ptClock
et qui doivent donc être converties de Unicode à l'encodage de la fonte
Seuls 3 caractères sont modifiés comme on peut voir en examinant la production de l'utilitaires
L'utilitaire ressemble à fontconvert
et fontconvert8x
, car il faudra compiler une version pour chaque jeu de caractère distinct. En effet, il faut ajouter au code source le tableau xxxxCharset
des codes qui devront être convertis. Le plus simple est d'ajouter la macro MAKE_CHARSET
dans la définition de la fonte dpour que le tableau ClockCharset
soit ajouter dans le fichier FreeMono12ptClock
par fontconvert8x
. On élimine le tableau ClockCharset
et la variable ClockCharcount
de la définition de la fonte, où ils ne seront plus utiles, et on ajoute le tableau à convert8x
tout en le renommant encoding[]
. A noter qu'il est préférable de ne pas inclure les caractères ASCII consécutifs au début du tableau pour que la traduction soit la plus facile à lire possible. C'est quand même préférable de copier la chaîne originale dans un commentaire pour facilement l'identifier dans le fichier converti.
Source et exemples
Actuellement, l'archive gfx8-v007.zip contient le projet platformIO que j'utilise pour créer la bibliothèque GFX-8
. Le matériel pour tester est constitué de deux cartes de développement avec microcontrôleur ESP8266 : une de type D1 mini et l'une autre de type nodeMCU. L'écran connecté au D1 mini est afficheur de type OLED monochrome de 0,96" avec connexion I²C alorqu que celui du nodeMCU est de type TFT couleurs de 2" avec connexion SPI. L'écran OLED est pris en charge par la bibliothèque Adafruit-GFX
alors que l'écran TFT est piloté avec une autre bibliothèque distincte qui néanmoins prend en charge les fontes GFXFont.
Voici le contenu de l'archive avec en premier, la bibliothèque elle-même qui est dans le répertoire src
.
gfx8/src/ | Répertoire contenant les fichiers source de la bibliothèque GFX-8 |
---|---|
decodeuf8.h | fichier d'en-tête de la fonction de décodage des chaînes en UTF-8 vers UCS-2 |
decodeuf8.cpp | implémentation de la fonction de décodage des chaînes en UTF-8 vers UCS-2 utilisée par les trois fonctions suivantes |
gfx8.h | fichier d'en-tête de la fonction utf8tocp pour convertir une chaîne Unicode en chaîne GFX |
gfx8.cpp | implementation de la fonction de conversion utf8tocp |
gfxlatin1.h | fichier d'en-tête de la fonction latin1tocp pour convertir une chaîne de la page Latin 1 d'Unicode en chaîne GFX |
gfxlatin1.cpp | implementation de la fonction de conversion latin1tocp |
gfxlatin9.h | fichier d'en-tête de la fonction latin9tocp pour convertir une chaîne de la page Latin 9 d'Unicode en chaîne GFX |
gfxlatin9.cpp | implementation de la fonction de conversion latin9tocp |
Si l'on veut utiliser la bibliothèque dans un projet platformIO nommé testgfx8
alors il suffit d'extraire le dossier gfx/src
vers le dossier testgfx8/lib/gfx8/src. En d'autres mots on deverait avoir l'arborescense suivante:
.../testgfx8/ .pio/ .vscode/ include/ lib/ gfx8/ src/ decodeutf8.cpp decodeutf8.h gfx8.cpp gfx8.h gfxlatin1.cpp gfxlatin1.h gfxlatin9.cpp gfxlatin9.h src/ main.cpp test/ .gitignore platformio.ini
La source des deux utilitaires dont l'objectif est de rendre plus facile l'utilisation de fontes proportionnelles de type GFXfont
avec des jeux de caractères quelconques.
gfx8/fontconvert8x/ | L'utilitaire fontconvert8x pour extraire une liste arbitraire de glyphes d'une police TTF |
---|---|
fontconvert8x.c | source de l'utilitaire à modifier pour chaque jeu de glyphes désiré |
Makefile8x | fichier de configuration pour l'utilitaire make (ex. ~ $ make Makefile8x |
makefonts8x.sh | script pour extraire de nombreuses fontes de tailles et styles différents d'une police TTF |
gfxfont.h | définition d'une fonte GFXfont inclue dans fontconvert8x |
ascii.h | définition des caractères ASCII inclue dans fontconvert8x |
Exemples de jeux de caractères pouvant être inclus dans fontconvert8x | |
latin1.h | définition du jeu de caractères latin 1 ou ISO 8859-1 |
latin9.h | définition du jeu de caractères latin 9 ou ISO 8859-15 |
clock.h | définition d'un jeu de caractères pour affichage de l'heure qu'on retrouve dans les exemples |
fr.h | définition d'un jeu de caractères avec lettres diacritiques et symboles du français qu'on retrouve dans les exemples |
gfx8/convert8x/ | L'utilitaire convert8x pour convertir des chaînes litéralles |
---|---|
convert8x.cpp | source de l'utilitaire modifier pour convertir les caractères d'affichage de l'heure |
convert8x_fr.cpp | source de l'utilitaire modifier pour convertir les caractères français |
Makefile | fichier de configuration pour l'utilitaire make (ex. ~ $ make |
Source des chaînes litéralles à convertir pour l'exemple 2-true_clock_no_tocp.cpp | |
stringconsts_clock.txt | ~ ./convert8x < stringconsts_clock.txt > stringconsts_clock.h |
stringconsts_fr.txt | ~ ./convert8x_fr < stringconsts_fr.txt > stringconsts_fr.h |
Il y a quelques petits test des fonctions de conversions. Clairement il me faut apprendre à utiliser les tests unitaires de platformIO.
gfx8/exemples/test/ | Tests des fonctions de conversion d'Unicode à GFX |
---|---|
test_decodeutf8.cpp | Test illustrant les erreurs de decodeutf8 qui converti UTF-8 en UCS-2 |
test_latin1tocp.cpp | Vérification de latin1tocp |
test_latin9tocp.cpp | Vérification de latin9tocp |
test_utf8tocp_clock.cpp | Vérification de utf8tocp avec le jeu de caractères pour affichage de l'heure |
Enfin, il y a les exemples en commençant avec l'utilisation de gfx8
avec la bibliothèque Adafruit-GFX
.
gfx8/exemples/sdd1306/ | Exemple avec un microcontrôleur ESP8266 (D1 mini) et un affichage I²C (SDD 1306) |
---|---|
1-check_hardware.cpp | Vérification que le matériel fonctionne avec les bibliothèques d'Adafruit |
2-gfx_control_codes.cpp | Traitement des codes de contrôles par la bibliothèque Adafruit-GFX |
3-check_gfx.cpp | Utilisation typique des fontes GFXfont |
4-check_gfx8.cpp | Utilisation de la fonte FreeMono12ptClock de type GFXfont avec un jeu de caractères ASCII non consécutifs |
5-gfx8_clock.cpp | Conversion des chaînes Unicode vers l'encodage de la fonte FreeMono12ptClock lors de l'exécution avec utf8tocp |
6-gfx8_clock_alt.cpp | Conversion lors de l'exécution avec utf8tocp utilisant un tableau ClockCharset de taille minimale |
7-two_gfx8_fonts.cpp | Conversion lors de l'exécution avec utf8tocp avec deux fontes utilisant des jeux de caractères différents |
J'ai eu quelques difficultés à faire fonctionner l'affichage OLED avec un contrôleur I²C SDD1306. Il s'agit d'un modèle « générique » acheté sur eBay ou Aliexpress, je ne souviens plus et dont les adresses I²C imprimées sur la carte sont incorrectes. Inutile de continuer avec le projet si le matériel ne fonctionne pas. Tous les exemples affichent l'heure mais celle-ci est incorrect sauf à un moment précis chaque jour. Leur seule raison d'être est de voir si l'affichage des jeux de caractères est correct.
gfx8/exemples/ili9225/ | Exemple avec un microcontrôleur ESP8266 (nodeMCU) et un affichage SPI (ILI9225) |
---|---|
1-gfx8_true_clock.cpp | Affichage de l'heure exacte (via SNTP) avec deux fontes ayant des jeux de caractères différent. La conversion des chaînes est faite lors de l'exécution avec utf8cp |
2-true_clock_no_tocp.cpp | Affichage de l'heure exacte (via SNTP) avec deux fontes ayant des jeux de caractères différents avec des chaînes litéralles converties préalablement à l'aide de convert8x |
secrets.h | Identité et mot de passe du réseau Wi-Fi (doit être modifier évidemment) |
Chaînes litéralles converties avec convert8x et convert8x_fr . | |
stringconsts_clock.txt | |
stringconsts_fr.txt | |
Source des chaînes litéralles | |
stringconsts_clock.txt | |
stringconsts_fr.txt |
Cet exemple qui n'utilise pas la bibliothèque d'Adafruit affiche l'heure correcte à l'aide du protocole SNTP. Malgré cette amélioration, il s'agit toujours d'un exemple et le choix des fontes et des couleurs est loin d'être optimal.
Pour faire rouler un des test ou un des exemples, il suffit de copier le fichier source dans le répertoire gfx8/src
qui contient déjà quelques fichiers.
gfx8/src/ | Répertoire de la source du projet platformIO |
---|---|
FreeMono12ptClock.h | Fonte pour affichage de l'heure - utilisée par les exemples |
FreeMono12ptClockAlt.h | Fonte pour affichage de l'heure avec codage plus léger - utilisée par les exemples |
FreeMono8pt.h | Fonte pour affichage du français - utilisée par les exemples |
Même si la convention dans l'environnement platformIO est de nommer le fichier source d'un projet main.cpp
ce n'est pas obligatoire. Cependant, il y a deux consignes à respecter.
- Il ne peut y avoir qu'un seul test ou un seul exemple dans le dossier
gfx8/src
. - Le bon environnement doit être spécifié dans le fichier
platformio.ini
qui se trouve dans le répertoire du projet.
gfx8 | Répertoire du projet platformIO |
---|---|
platformio.ini | Fichier de configuration du projet |
Dans platformio.ini
que voici,
il faut s'assurer que default_envs
dans la première section [platformio]
correspond au fichier source du projet à compiler:
- d1_mini_test s'il s'agit d'un test proveant de
gfx8/exemples/test/
. - d1_mini s'il s'agit d'un exemple proveant de
gfx8/exemples/sdd1306/
. - nodemcuv2 s'il s'agit d'un test proveant de
gfx8/exemples/ili9225/
.
Je ne suis pas programmeur, surtout quand il s'agit de C et C++. Alors toute amélioration qu'on voudra me proposer sera bienvenue. Utilisez le lien vers mon courriel au bas de la page à cette fin.
Suite
J'ai dépensé tellement de temps sur cette question que j'ai hâte de passer à autre chose. C'est d'autant plus vrai qu'il y a longtemps que j'ai une solution spécifique pour les besoins immédiats qui ont motivé cette excursion dans les méandres d'Unicode, des pages de codes et des bibliothèques graphiques. Je pourrai revenir sur ce sujet avec d'autres microcontrôleurs, car plusieurs se sont ajoutés à ma collection dernièrement. Si tout se passe bien et s'il semble y avoir un peu d'intérêt, pourquoi ne pas créer une véritable bibliothèque Arduino?
S'il n'est pas certain que je créerais la bibliothèque à l'avenir, je me promets d'acheter au moins un écran (probablement le ILI9341 avec écran tactile) d'Adafruit en guise de remerciement pour l'immense contribution de cette entreprise. En effet, elle offre gratuitement plus de 1 400 bibliothèques sur GithHub dont seulement trois sont utilisées ci-dessus.