2020-11-29
md
Police GFX avec jeu de caractères arbitraire
<-Polices GFXfonts avec encodage 8 bits

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

  1. Points de code ASCII and GFX
  2. Jeu de caractères arbitraire
  3. Conversion de polices TrueType en fontes Adafruit-GFX
  4. Trois autres jeux de caractères : FR, Latin1 et Latin9
  5. Conversion de l'encodage UTF-8 vers l'encodage GFX
  6. Éviter la conversion lors de l'exécution
  7. Source et exemples
  8. Suite

Points de code ASCII and GFX toc

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
0123456789ABCDEF
0_codes de contrôle
1_
2_SP!"#$%&'()*+,-./
3_0123456789:;<=>?
4_@ABCDEFGHIJKLMNO
5_PQRSTUVWXYZ[\]^_
6_`abcdefghijklmno
7_pqrstuvwxyz{|}~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.

/// Données globales pour la fonte typedef struct { uint8_t *bitmap; /// tableau des images concatenées des caractères GFXglyph *glyph; /// tableau des glyphs uint16_t first; /// premier caractère ASCII dans la fonte uint16_t last; /// dernier caractère ASCII dans la fonte uint8_t yAdvance; /// distance verticale entre lignes (axe y) } GFXfont;

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.

/// Données pour chaque caractère de la fonte typedef struct { uint16_t bitmapOffset; /// index du glyphe du caractère dans le tableau GFXfont->bitmap uint8_t width; /// largeur (pixels) du glyphe uint8_t height; /// hauteur (pixels) du glyphe uint8_t xAdvance; /// distance horizontale vers glyphe suivant (axe x) int8_t xOffset; /// distance X du curseur vers le coin supérieur gauche du glyphe int8_t yOffset; /// distance Y du curseur vers le coin supérieur gauche du glyphe } GFXglyph;

Voici la démarche (simplifiée) pour afficher l'image de la lettre A sur un écran.

  1. Repérer la structure pour la lettre A dans le tableau *glyph. Ci-devant, l'enregistrement sera dénoté *glyph{'A'}.
  2. 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.
  3. Utiliser les distances glyph{'A'}->xOffset et glyph{'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.
  4. Le glyphe est copier à l'écran, une ligne horizontale à la fois.
  5. 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

const GFXglyph FreeMono12pt7bGlyphs[] PROGMEM = { {0, 0, 0, 14, 0, 1}, // 0x20 ' ' {0, 3, 15, 14, 6, -14}, // 0x21 '!' {6, 8, 7, 14, 3, -14}, // 0x22 '"' {13, 10, 16, 14, 2, -14}, // 0x23 '#' {33, 10, 17, 14, 2, -14}, // 0x24 '$' {55, 10, 15, 14, 2, -14}, // 0x25 '%' {74, 9, 12, 14, 3, -11}, // 0x26 '&' {88, 3, 7, 14, 5, -14}, // 0x27 ''' {91, 3, 18, 14, 7, -14}, // 0x28 '(' {98, 3, 18, 14, 4, -14}, // 0x29 ')' {105, 9, 9, 14, 3, -14}, // 0x2A '*' {116, 9, 11, 14, 3, -11}, // 0x2B '+' {129, 5, 7, 14, 3, -3}, // 0x2C ',' {134, 11, 1, 14, 2, -6}, // 0x2D '-' {136, 3, 3, 14, 5, -2}, // 0x2E '.' {138, 9, 18, 14, 3, -15}, // 0x2F '/' {159, 9, 15, 14, 3, -14}, // 0x30 '0' {176, 7, 14, 14, 4, -13}, // 0x31 '1' {189, 9, 15, 14, 2, -14}, // 0x32 '2' {206, 10, 15, 14, 2, -14}, // 0x33 '3' {225, 8, 15, 14, 3, -14}, // 0x34 '4' {240, 9, 15, 14, 3, -14}, // 0x35 '5' {257, 9, 15, 14, 3, -14}, // 0x36 '6' {274, 8, 15, 14, 3, -14}, // 0x37 '7' {289, 9, 15, 14, 3, -14}, // 0x38 '8' {306, 9, 15, 14, 3, -14}, // 0x39 '9' {323, 3, 10, 14, 5, -9}, // 0x3A ':' {327, 5, 13, 14, 3, -9}, // 0x3B ';' {336, 11, 11, 14, 2, -11}, // 0x3C '&lt;' {352, 12, 4, 14, 1, -8}, // 0x3D '=' {358, 11, 11, 14, 2, -11}, // 0x3E '>' {374, 9, 14, 14, 3, -13}, // 0x3F '?' {390, 9, 16, 14, 3, -14}, // 0x40 '@' {408, 14, 14, 14, 0, -13}, // 0x41 'A' {433, 11, 14, 14, 2, -13}, // 0x42 'B' {453, 10, 14, 14, 2, -13}, // 0x43 'C' {471, 10, 14, 14, 2, -13}, // 0x44 'D' {489, 11, 14, 14, 2, -13}, // 0x45 'E' {509, 11, 14, 14, 2, -13}, // 0x46 'F' {529, 11, 14, 14, 2, -13}, // 0x47 'G' {549, 10, 14, 14, 2, -13}, // 0x48 'H' {567, 7, 14, 14, 4, -13}, // 0x49 'I' {580, 11, 14, 14, 2, -13}, // 0x4A 'J' {600, 12, 14, 14, 2, -13}, // 0x4B 'K' {621, 11, 14, 14, 2, -13}, // 0x4C 'L' {641, 13, 14, 14, 1, -13}, // 0x4D 'M' {664, 12, 14, 14, 1, -13}, // 0x4E 'N' {685, 12, 14, 14, 1, -13}, // 0x4F 'O' {706, 10, 14, 14, 2, -13}, // 0x50 'P' {724, 12, 17, 14, 1, -13}, // 0x51 'Q' {750, 12, 14, 14, 2, -13}, // 0x52 'R' {771, 10, 14, 14, 2, -13}, // 0x53 'S' {789, 11, 14, 14, 2, -13}, // 0x54 'T' {809, 12, 14, 14, 1, -13}, // 0x55 'U' {830, 14, 14, 14, 0, -13}, // 0x56 'V' {855, 14, 14, 14, 0, -13}, // 0x57 'W' {880, 12, 14, 14, 1, -13}, // 0x58 'X' {901, 12, 14, 14, 1, -13}, // 0x59 'Y' {922, 9, 14, 14, 3, -13}, // 0x5A 'Z' {938, 3, 18, 14, 7, -14}, // 0x5B '[' {945, 9, 18, 14, 3, -15}, // 0x5C '\' {966, 3, 18, 14, 5, -14}, // 0x5D ']' {973, 9, 6, 14, 3, -14}, // 0x5E '^' {980, 14, 1, 14, 0, 3}, // 0x5F '_' {982, 4, 4, 14, 4, -15}, // 0x60 '`' {984, 10, 10, 14, 2, -9}, // 0x61 'a' {997, 13, 15, 14, 0, -14}, // 0x62 'b' {1022, 11, 10, 14, 2, -9}, // 0x63 'c' {1036, 11, 15, 14, 2, -14}, // 0x64 'd' {1057, 10, 10, 14, 2, -9}, // 0x65 'e' {1070, 9, 15, 14, 4, -14}, // 0x66 'f' {1087, 11, 14, 14, 2, -9}, // 0x67 'g' {1107, 10, 15, 14, 2, -14}, // 0x68 'h' {1126, 9, 15, 14, 3, -14}, // 0x69 'i' {1143, 7, 19, 14, 3, -14}, // 0x6A 'j' {1160, 12, 15, 14, 1, -14}, // 0x6B 'k' {1183, 9, 15, 14, 3, -14}, // 0x6C 'l' {1200, 13, 10, 14, 1, -9}, // 0x6D 'm' {1217, 12, 10, 14, 1, -9}, // 0x6E 'n' {1232, 11, 10, 14, 2, -9}, // 0x6F 'o' {1246, 12, 14, 14, 1, -9}, // 0x70 'p' {1267, 11, 14, 14, 2, -9}, // 0x71 'q' {1287, 10, 10, 14, 3, -9}, // 0x72 'r' {1300, 10, 10, 14, 2, -9}, // 0x73 's' {1313, 11, 14, 14, 1, -13}, // 0x74 't' {1333, 11, 10, 14, 2, -9}, // 0x75 'u' {1347, 13, 10, 14, 1, -9}, // 0x76 'v' {1364, 13, 10, 14, 1, -9}, // 0x77 'w' {1381, 12, 10, 14, 1, -9}, // 0x78 'x' {1396, 12, 14, 14, 1, -9}, // 0x79 'y' {1417, 9, 10, 14, 3, -9}, // 0x7A 'z' {1429, 5, 18, 14, 5, -14}, // 0x7B '{' {1441, 1, 18, 14, 7, -14}, // 0x7C '|' {1444, 5, 18, 14, 5, -14}, // 0x7D '}' {1456, 10, 3, 14, 2, -7}}; // 0x7E '~'

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)
0123456789ABCDEF
0_SP!"#$%&'()*+,-./
1_0123456789:;<=>?
2_@ABCDEFGHIJKLMNO
3_PQRSTUVWXYZ[\]^_
4_`abcdefghijklmno
5_pqrstuvwxyz{|}~

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.

michel@hp: ~/...$ ./fontconvert /usr/share/fonts/truetype/freefont/FreeMono.ttf 12 48 58 > 24hrfont.h

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).

const GFXfont FreeMono12pt7b PROGMEM = { (uint8_t *)FreeMono12pt7bBitmaps, (GFXglyph *)FreeMono12pt7bGlyphs, 0x30, 0x3A, 24 };

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)
0123456789A
0_0123456789:

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.

(1)
S'il y a assez d'espace de stockage, une fonte GFX peut théoriquement contenir jusqu'à 65 536 caractères puisque les champs 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 toc

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)
0123456789ABCDE
0_0123456789:ampSP

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

display.print("6:33>=<");

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.

display.print("\x3c");

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 toc

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.

#define CHARSET_NAME "Clock" #define MAKE_CHARSET //#define INCLUDE_ASCII struct chardef chartable[] = { {0x0030, "0"}, {0x0031, "1"}, {0x0032, "2"}, {0x0033, "3"}, {0x0034, "4"}, {0x0035, "5"}, {0x0036, "6"}, {0x0037, "7"}, {0x0038, "8"}, {0x0039, "9"}, {0x003a, ":"}, {0x0061, "a"}, {0x006d, "m"}, {0x0070, "p"}, {0x0020, " "} };

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.

... $ make -B -f Makefile8x

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.

... $ ./fontconvert8x /usr/share/fonts/truetype/freefont/FreeMono.ttf 12 > FreeMono12ptClock.h

Le nom du fichier vers lequel la production de l'utilitaire est consignée est la concaténation

  1. du nom de la police TTF dont proviennent les glyphes,
  2. de la taille en points (pt) des glyphes,
  3. du nom assigné à la macro MAKE_CHARSET et
  4. 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.

const GFXglyph FreeMono12ptClockGlyphs[] PROGMEM = { { 0, 9, 15, 14, 3, -14 }, // 0x30 '0' U+0030 { 17, 7, 14, 14, 4, -13 }, // 0x31 '1' U+0031 { 30, 9, 15, 14, 2, -14 }, // 0x32 '2' U+0032 { 47, 10, 15, 14, 2, -14 }, // 0x33 '3' U+0033 { 66, 8, 15, 14, 3, -14 }, // 0x34 '4' U+0034 { 81, 9, 15, 14, 3, -14 }, // 0x35 '5' U+0035 { 98, 9, 15, 14, 3, -14 }, // 0x36 '6' U+0036 { 115, 8, 15, 14, 3, -14 }, // 0x37 '7' U+0037 { 130, 9, 15, 14, 3, -14 }, // 0x38 '8' U+0038 { 147, 9, 15, 14, 3, -14 }, // 0x39 '9' U+0039 { 164, 3, 10, 14, 5, -9 }, // 0x3a ':' U+003A { 168, 10, 10, 14, 2, -9 }, // 0x3b 'a' U+0061 { 181, 13, 10, 14, 1, -9 }, // 0x3c 'm' U+006D { 198, 12, 14, 14, 1, -9 }, // 0x3d 'p' U+0070 { 219, 0, 0, 14, 0, 1 } }; // 0x3e ' ' U+0020

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.

// Only one charset table is needed when more // than one font with the same encoding is used #ifndef CHARSET_Clock #define CHARSET_Clock const uint8_t ClockCharcount = 15; const uint16_t ClockCharset[15] PROGMEM = { 0x0030, // [0x30] '0' 0x0031, // [0x31] '1' 0x0032, // [0x32] '2' 0x0033, // [0x33] '3' 0x0034, // [0x34] '4' 0x0035, // [0x35] '5' 0x0036, // [0x36] '6' 0x0037, // [0x37] '7' 0x0038, // [0x38] '8' 0x0039, // [0x39] '9' 0x003a, // [0x3a] ':' 0x0061, // [0x3b] 'a' 0x006d, // [0x3c] 'm' 0x0070, // [0x3d] 'p' 0x0020}; // [0x3e] ' ' #endif

Trois autres jeux de caractères : FR, Latin1 et Latin9 toc

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 aE eI iO oU uY yC 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.

/* * fr printable caracters + degree and Euro symbols + 0x32 to 0x7e ASCII * ********************************************************************** */ #define CHARSET_NAME "FR" #define MAKE_CHARSET #define INCLUDE_ASCII struct chardef chartable[] = { { 0x00ab, "LEFT-POINTING DOUBLE ANGLE QUOTATION MARK"}, { 0x00b0, "DEGREE SIGN"}, { 0x00bb, "RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK"}, { 0x00c0, "LATIN CAPITAL LETTER A WITH GRAVE"}, { 0x00c2, "LATIN CAPITAL LETTER A WITH CIRCUMFLEX"}, { 0x00c4, "LATIN CAPITAL LETTER A WITH DIAERESIS"}, { 0x00c6, "LATIN CAPITAL LETTER AE"}, { 0x00c7, "LATIN CAPITAL LETTER C WITH CEDILLA"}, { 0x00c8, "LATIN CAPITAL LETTER E WITH GRAVE"}, { 0x00c9, "LATIN CAPITAL LETTER E WITH ACUTE"}, { 0x00ca, "LATIN CAPITAL LETTER E WITH CIRCUMFLEX"}, { 0x00cb, "LATIN CAPITAL LETTER E WITH DIAERESIS"}, { 0x00ce, "LATIN CAPITAL LETTER I WITH CIRCUMFLEX"}, { 0x00cf, "LATIN CAPITAL LETTER I WITH DIAERESIS"}, { 0x00d4, "LATIN CAPITAL LETTER O WITH CIRCUMFLEX"}, { 0x00d6, "LATIN CAPITAL LETTER O WITH DIAERESIS"}, { 0x00d9, "LATIN CAPITAL LETTER U WITH GRAVE"}, { 0x00db, "LATIN CAPITAL LETTER U WITH CIRCUMFLEX"}, { 0x00dc, "LATIN CAPITAL LETTER U WITH DIAERESIS"}, { 0x00e0, "LATIN SMALL LETTER A WITH GRAVE"}, { 0x00e2, "LATIN SMALL LETTER A WITH CIRCUMFLEX"}, { 0x00e4, "LATIN SMALL LETTER A WITH DIAERESIS"}, { 0x00e6, "LATIN SMALL LETTER AE"}, { 0x00e7, "LATIN SMALL LETTER C WITH CEDILLA"}, { 0x00e8, "LATIN SMALL LETTER E WITH GRAVE"}, { 0x00e9, "LATIN SMALL LETTER E WITH ACUTE"}, { 0x00ea, "LATIN SMALL LETTER E WITH CIRCUMFLEX"}, { 0x00eb, "LATIN SMALL LETTER E WITH DIAERESIS"}, { 0x00ee, "LATIN SMALL LETTER I WITH CIRCUMFLEX"}, { 0x00ef, "LATIN SMALL LETTER I WITH DIAERESIS"}, { 0x00f4, "LATIN SMALL LETTER O WITH CIRCUMFLEX"}, { 0x00f6, "LATIN SMALL LETTER O WITH DIAERESIS"}, { 0x00f9, "LATIN SMALL LETTER U WITH GRAVE"}, { 0x00fb, "LATIN SMALL LETTER U WITH CIRCUMFLEX"}, { 0x00fc, "LATIN SMALL LETTER U WITH DIAERESIS"}, { 0x00ff, "LATIN SMALL LETTER Y WITH DIAERESIS"}, { 0x0152, "LATIN CAPITAL LIGATURE OE"}, { 0x0153, "LATIN SMALL LIGATURE OE"}, { 0x0178, "LATIN CAPITAL LETTER Y WITH DIAERESIS"}, { 0x20ac, "EURO SIGN"} };

Les trois macros qui peuvent se retrouver dans un fichier de définition du tableau chartable sont présentes,

Voici une partie de la définition de fonte FreeMono8ptFR qui est engendrée avec fontconvert8x.

const uint8_t FreeMono8ptFRBitmaps[] PROGMEM = { 0xFE, 0x40, 0x49, 0x24, 0x92, 0x48, 0x14, 0x28, 0x53, 0xF2, 0x44, 0xBF, 0x94, 0x28, 0x50, 0x00, 0x10, 0x79, 0x12, 0x03, 0x80, 0xC0, 0xC3, 0xFC, ... 0x07, 0x00, 0x1F, 0x10, 0x50, 0x1F, 0x84, 0x07, 0xE1, 0x00, 0x60, 0x1F, 0x80 }; const GFXglyph FreeMono8ptFRGlyphs[] PROGMEM = { { 0, 0, 0, 10, 0, 1 }, // 0x20 'SPACE' U+0020 { 0, 1, 10, 10, 4, -9 }, // 0x21 'EXCLAMATION MARK' U+0021 { 2, 6, 5, 10, 2, -9 }, // 0x22 'QUOTATION MARK' U+0022 ... { 1045, 9, 11, 10, 1, -10 }, // 0xa5 'LATIN CAPITAL LETTER Y WITH DIAERESIS' U+0178 { 1058, 9, 9, 10, 0, -8 } }; // 0xa6 'EURO SIGN' U+20AC const GFXfont FreeMono8ptFR PROGMEM = { (uint8_t *)FreeMono8ptFRBitmaps, (GFXglyph *)FreeMono8ptFRGlyphs, 0x20, 0xA6, 16 }; #ifndef CHARSET_FR #define CHARSET_FR const uint8_t FRCharcount = 40; const uint16_t FRCharset[40] PROGMEM = { 0x00ab, // [0x7f] 'LEFT-POINTING DOUBLE ANGLE QUOTATION MARK' 0x00b0, // [0x80] 'DEGREE SIGN' ... 0x0178, // [0xa5] 'LATIN CAPITAL LETTER Y WITH DIAERESIS' 0x20ac}; // [0xa6] 'EURO SIGN' #endif

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.

/* * ISO 8859-1 (Latin-1) printable caracters * ***************************************** */ #define CHARSET_NAME "Latin1" #define INCLUDE_ASCII struct chardef chartable[] = { {0xfffd, "REPLACEMENT caractER"}, {0x00a0, "NO-BREAK SPACE"}, {0x00a1, "INVERTED EXCLAMATION MARK"}, {0x00a2, "CENT SIGN"}, {0x00a3, "POUND SIGN"}, {0x00a4, "CURRENCY SIGN"}, {0x00a5, "YEN SIGN"}, {0x00a6, "BROKEN BAR"}, {0x00a7, "SECTION SIGN"}, {0x00a8, "DIAERESIS"}, {0x00a9, "COPYRIGHT SIGN"}, {0x00aa, "FEMININE ORDINAL INDICATOR"}, {0x00ab, "LEFT-POINTING DOUBLE ANGLE QUOTATION MARK"}, {0x00ac, "NOT SIGN"}, {0x00ad, "SOFT HYPHEN"}, {0x00ae, "REGISTERED SIGN"}, {0x00af, "MACRON"}, {0x00b0, "DEGREE SIGN"}, {0x00b1, "PLUS-MINUS SIGN"}, {0x00b2, "SUPERSCRIPT TWO"}, {0x00b3, "SUPERSCRIPT THREE"}, {0x00b4, "ACUTE ACCENT"}, {0x00b5, "MICRO SIGN"}, {0x00b6, "PILCROW SIGN"}, {0x00b7, "MIDDLE DOT"}, {0x00b8, "CEDILLA"}, {0x00b9, "SUPERSCRIPT ONE"}, {0x00ba, "MASCULINE ORDINAL INDICATOR"}, {0x00bb, "RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK"}, {0x00bc, "VULGAR FRACTION ONE QUARTER"}, {0x00bd, "VULGAR FRACTION ONE HALF"}, {0x00be, "VULGAR FRACTION THREE QUARTERS"}, {0x00bf, "INVERTED QUESTION MARK"}, {0x00c0, "LATIN CAPITAL LETTER A WITH GRAVE"}, {0x00c1, "LATIN CAPITAL LETTER A WITH ACUTE"}, {0x00c2, "LATIN CAPITAL LETTER A WITH CIRCUMFLEX"}, {0x00c3, "LATIN CAPITAL LETTER A WITH TILDE"}, {0x00c4, "LATIN CAPITAL LETTER A WITH DIAERESIS"}, {0x00c5, "LATIN CAPITAL LETTER A WITH RING ABOVE"}, {0x00c6, "LATIN CAPITAL LETTER AE"}, {0x00c7, "LATIN CAPITAL LETTER C WITH CEDILLA"}, {0x00c8, "LATIN CAPITAL LETTER E WITH GRAVE"}, {0x00c9, "LATIN CAPITAL LETTER E WITH ACUTE"}, {0x00ca, "LATIN CAPITAL LETTER E WITH CIRCUMFLEX"}, {0x00cb, "LATIN CAPITAL LETTER E WITH DIAERESIS"}, {0x00cc, "LATIN CAPITAL LETTER I WITH GRAVE"}, {0x00cd, "LATIN CAPITAL LETTER I WITH ACUTE"}, {0x00ce, "LATIN CAPITAL LETTER I WITH CIRCUMFLEX"}, {0x00cf, "LATIN CAPITAL LETTER I WITH DIAERESIS"}, {0x00d0, "LATIN CAPITAL LETTER ETH"}, {0x00d1, "LATIN CAPITAL LETTER N WITH TILDE"}, {0x00d2, "LATIN CAPITAL LETTER O WITH GRAVE"}, {0x00d3, "LATIN CAPITAL LETTER O WITH ACUTE"}, {0x00d4, "LATIN CAPITAL LETTER O WITH CIRCUMFLEX"}, {0x00d5, "LATIN CAPITAL LETTER O WITH TILDE"}, {0x00d6, "LATIN CAPITAL LETTER O WITH DIAERESIS"}, {0x00d7, "MULTIPLICATION SIGN"}, {0x00d8, "LATIN CAPITAL LETTER O WITH STROKE"}, {0x00d9, "LATIN CAPITAL LETTER U WITH GRAVE"}, {0x00da, "LATIN CAPITAL LETTER U WITH ACUTE"}, {0x00db, "LATIN CAPITAL LETTER U WITH CIRCUMFLEX"}, {0x00dc, "LATIN CAPITAL LETTER U WITH DIAERESIS"}, {0x00dd, "LATIN CAPITAL LETTER Y WITH ACUTE"}, {0x00de, "LATIN CAPITAL LETTER THORN"}, {0x00df, "LATIN SMALL LETTER SHARP S"}, {0x00e0, "LATIN SMALL LETTER A WITH GRAVE"}, {0x00e1, "LATIN SMALL LETTER A WITH ACUTE"}, {0x00e2, "LATIN SMALL LETTER A WITH CIRCUMFLEX"}, {0x00e3, "LATIN SMALL LETTER A WITH TILDE"}, {0x00e4, "LATIN SMALL LETTER A WITH DIAERESIS"}, {0x00e5, "LATIN SMALL LETTER A WITH RING ABOVE"}, {0x00e6, "LATIN SMALL LETTER AE"}, {0x00e7, "LATIN SMALL LETTER C WITH CEDILLA"}, {0x00e8, "LATIN SMALL LETTER E WITH GRAVE"}, {0x00e9, "LATIN SMALL LETTER E WITH ACUTE"}, {0x00ea, "LATIN SMALL LETTER E WITH CIRCUMFLEX"}, {0x00eb, "LATIN SMALL LETTER E WITH DIAERESIS"}, {0x00ec, "LATIN SMALL LETTER I WITH GRAVE"}, {0x00ed, "LATIN SMALL LETTER I WITH ACUTE"}, {0x00ee, "LATIN SMALL LETTER I WITH CIRCUMFLEX"}, {0x00ef, "LATIN SMALL LETTER I WITH DIAERESIS"}, {0x00f0, "LATIN SMALL LETTER ETH"}, {0x00f1, "LATIN SMALL LETTER N WITH TILDE"}, {0x00f2, "LATIN SMALL LETTER O WITH GRAVE"}, {0x00f3, "LATIN SMALL LETTER O WITH ACUTE"}, {0x00f4, "LATIN SMALL LETTER O WITH CIRCUMFLEX"}, {0x00f5, "LATIN SMALL LETTER O WITH TILDE"}, {0x00f6, "LATIN SMALL LETTER O WITH DIAERESIS"}, {0x00f7, "DIVISION SIGN"}, {0x00f8, "LATIN SMALL LETTER O WITH STROKE"}, {0x00f9, "LATIN SMALL LETTER U WITH GRAVE"}, {0x00fa, "LATIN SMALL LETTER U WITH ACUTE"}, {0x00fb, "LATIN SMALL LETTER U WITH CIRCUMFLEX"}, {0x00fc, "LATIN SMALL LETTER U WITH DIAERESIS"}, {0x00fd, "LATIN SMALL LETTER Y WITH ACUTE"}, {0x00fe, "LATIN SMALL LETTER THORN"}, {0x00ff, "LATIN SMALL LETTER Y WITH DIAERESIS"} };

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.

Conversion de l'encodage UTF-8 vers l'encodage GFX toc

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.

char msg[] = "6:33\x3E\x3D\x3C"

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.

typedef struct gfx8charset_struct { uint16_t* charset; // array of Unicodes of glyphs in the font uint16_t charcount; // number of Unicodes uint16_t first; // Unicode of the first glyph uint8_t asciicount; // number of consecutive ASCII codes starting at first } gfx8charset_t ; extern gfx8charset_t* defaultCharset; // Initialize a gfx8charset_t structure with data needed to convert UTF-8 encoded strings // to GFX encoded strings using one of the utf8tocp() functions. // The first version intializes the defaut structure, the second void initGfx8charset(uint16_t* charset, uint16_t charcount, uint16_t first = 0x20, uint8_t lastascii = 0); void initGfx8charset(gfx8charset_t* gfxcharset, uint16_t* charset, uint16_t charcount, uint16_t first = 0x20, uint8_t asciicount = 0); // Convert a UTF-8 encoded String object to a GFX encoded String where the target // encoding is described by gfx8charset. String utf8tocp(String s, gfx8charset_t* gfx8charset = defaultCharset); // Convert a UTF-8 encoded string to a GFX encoded string where the target encoding // is described by gfx8charset. // Be careful, the in-situ conversion will "destroy" the UTF-8 string s. void utf8tocp(char* s, gfx8charset_t* gfx8charset = defaultCharset);

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.

#include <Arduino.h> #include <Wire.h> // using I²C SSD1306 display #include <Adafruit_GFX.h> #include <Adafruit_SSD1306.h> #include "gfx8.h" #include "FreeMono12ptClock.h" // GFX Latin 1 font generated with fontconvert8 void drawUtf8Text(String str) { String xtdcp(utf8tocp(str)); display.print(xtdcp); } void setup(void) { ... initGfx8charset((uint16_t*) ClockCharset, ClockCharcount, 0x30, 0); // intialize the default gfx8charset_t structure ... }

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 ».

#include <Arduino.h> #include "SPI.h" // using SPI ILI9225 display #include "TFT_22_ILI9225.h" #include "gfx8.h" #include "FreeMono12ptClock.h" // GFX Latin 1 font generated with fontconvert8 void drawUtf8Text(uint16_t x, uint16_t y, String str, uint16_t color) { String xtdcp(utf8tocp(str)); display.drawGFXText(x, y, xtdcp, color); } void setup(void) { ... initGfx8charset((uint16_t*) ClockCharset, ClockCharcount, FreeMono12ptClock.first); // intialize the default gfx8charset_t structure ... }

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.

#include <Arduino.h> #include <SPI.h> #include "Adafruit_I2CDevice.h" #include "Adafruit_GFX.h" #include "Adafruit_SSD1306.h" #include "FreeMono12ptClock.h" #include "FreeMono8ptFR.h" #include "gfx8.h" ... // Setup the proportional font for time GFXfont gfxTimefont = FreeMono12ptClock; // Setup the proportion font for the date GFXfont gfxDatefont = FreeMono8ptFR; gfx8charset_t datecharset = {}; // for FreeMono8ptFR caracter set, default gfx8charset_t will be used for FreeMono12ptClock void setup() { ... // intialize defautCharset with the charset definition in FreeMono12ptClock initGfx8charset((uint16_t*) ClockCharset, ClockCharcount, 0x30, 0); // initialize datecharset with the charset definition in FreeMono8ptFr // all the ASCII caracters 0x20 to 0x7e are included in that font, hence // asciicount = 0x7e initGfx8charset(&datecharset, (uint16_t*) FRCharset, FRCharcount, 0x20, 0x7f); ... } char timebuf[32]; void loop() { ... String s = "août"; display.setCursor(0, gfxDatefont.yAdvance); // cursor y position to the top line of the screen = baseline of date font r = utf8tocp(s, &datecharset); // convert the UTF-8 encoded string to (FreeMono8ptFR) FRCharset code points display.setFont(&gfxDatefont); // Use the FRMono8ptFR font display.print(r); sprintf(timebuf, "%02d:%02d%s", 1, 19, " am"); display.setCursor(0, gfxDatefont.yAdvance + gfxTimefont.yAdvance ); // cursor y at the baseline of 2nd line with utf8tocp(timebuf); // convert the UTF-8 encoded string to the default (FreeMono12ptClock) ClockCharset display.setFont(&gfxTimefont); // Use the FreeMono12ptClock font display.print(timebuf); display.display(); ... }

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.

fonctionfichier d'en-têtepage de code
latin1tocp()gfxlatin1.hLatin 1 (8859-1)
latin9tocp()gfxlatin9.hLatin 9 (8859-15)
gfx8tocp()gfx8.harbitraire mais définie dans une structure gfx8charset_t

Éviter la conversion lors de l'exécution toc

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.

~ $ ./convert8x_clock < stringconsts_clock.txt > stringconsts_clock.h

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

// Literal strings to be displayed with FreeMono12pt and // included in 9-ili9225_gfx8_clock.cpp with the following // directive // // #include "../lib/gfx8/examples/ili9225/stringconsts_clock.h" // #define AM_STR "am" // "am" #define PM_STR "pm" // "pm"

Seuls 3 caractères sont modifiés comme on peut voir en examinant la production de l'utilitaires

// Literal strings to be displayed with FreeMono12pt and // included in 9-ili9225_gfx8_clock.cpp with the following // directive // // #include "../lib/gfx8/examples/ili9225/stringconsts_clock.h" // #define AM_STR "\x3b\x3c" // "am" #define PM_STR "\x3d\x3c" // "pm"

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 toc

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.hfichier d'en-tête de la fonction de décodage des chaînes en UTF-8 vers UCS-2
decodeuf8.cppimplémentation de la fonction de décodage des chaînes en UTF-8 vers UCS-2 utilisée par les trois fonctions suivantes
gfx8.hfichier d'en-tête de la fonction utf8tocp pour convertir une chaîne Unicode en chaîne GFX
gfx8.cppimplementation de la fonction de conversion utf8tocp
gfxlatin1.hfichier 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.cppimplementation de la fonction de conversion latin1tocp
gfxlatin9.hfichier 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.cppimplementation 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.csource de l'utilitaire à modifier pour chaque jeu de glyphes désiré
Makefile8xfichier de configuration pour l'utilitaire make (ex. ~ $ make Makefile8x
makefonts8x.shscript pour extraire de nombreuses fontes de tailles et styles différents d'une police TTF
gfxfont.hdéfinition d'une fonte GFXfont inclue dans fontconvert8x
ascii.hdéfinition des caractères ASCII inclue dans fontconvert8x
Exemples de jeux de caractères pouvant être inclus dans fontconvert8x
latin1.hdéfinition du jeu de caractères latin 1 ou ISO 8859-1
latin9.hdéfinition du jeu de caractères latin 9 ou ISO 8859-15
clock.hdéfinition d'un jeu de caractères pour affichage de l'heure qu'on retrouve dans les exemples
fr.hdé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.cppsource de l'utilitaire modifier pour convertir les caractères d'affichage de l'heure
convert8x_fr.cppsource de l'utilitaire modifier pour convertir les caractères français
Makefilefichier 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.cppTest illustrant les erreurs de decodeutf8 qui converti UTF-8 en UCS-2
test_latin1tocp.cppVérification de latin1tocp
test_latin9tocp.cppVérification de latin9tocp
test_utf8tocp_clock.cppVé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.cppVérification que le matériel fonctionne avec les bibliothèques d'Adafruit
2-gfx_control_codes.cppTraitement des codes de contrôles par la bibliothèque Adafruit-GFX
3-check_gfx.cppUtilisation typique des fontes GFXfont
4-check_gfx8.cppUtilisation de la fonte FreeMono12ptClock de type GFXfont avec un jeu de caractères ASCII non consécutifs
5-gfx8_clock.cppConversion des chaînes Unicode vers l'encodage de la fonte FreeMono12ptClock lors de l'exécution avec utf8tocp
6-gfx8_clock_alt.cppConversion lors de l'exécution avec utf8tocp utilisant un tableau ClockCharset de taille minimale
7-two_gfx8_fonts.cppConversion 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.cppAffichage 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.cppAffichage 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.hIdentité 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.hFonte pour affichage de l'heure - utilisée par les exemples
FreeMono12ptClockAlt.hFonte pour affichage de l'heure avec codage plus léger - utilisée par les exemples
FreeMono8pt.hFonte 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.

  1. Il ne peut y avoir qu'un seul test ou un seul exemple dans le dossier gfx8/src.
  2. Le bon environnement doit être spécifié dans le fichier platformio.ini qui se trouve dans le répertoire du projet.
gfx8Répertoire du projet platformIO
platformio.iniFichier de configuration du projet

Dans platformio.ini que voici,

[platformio] default_envs = d1_mini [extra] baud = 115200 show_unmapped_codes = ;show_unmapped_codes = SHOW_UNMAPPED_CODES charset_sorted = ;charset_sorted = CHARSET_SORTED [env] platform = espressif8266 framework = arduino monitor_speed = ${extra.baud} build_flags = -DBAUD=${extra.baud} ${extra.show_unmapped_codes} ${extra.charset_sorted} monitor_flags = --eol LF --echo [env:d1_mini] board = d1_mini lib_deps = adafruit/Adafruit GFX Library@^1.10.3 adafruit/Adafruit SSD1306@^2.4.1 [env:nodemcuv2] board = nodemcuv2 lib_deps = nkawu/TFT 22 ILI9225@^1.4.4 paulstoffregen/Time@^1.6 akajes/AsyncPing(esp8266)@^1.1.0 [env:d1_mini_test] board = d1_mini

il faut s'assurer que default_envs dans la première section [platformio] correspond au fichier source du projet à compiler:

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 toc

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.

<-Polices GFXfonts avec encodage 8 bits