2020-10-23
md
Polices GFXfonts avec encodage 8 bits
Police GFX avec jeu de caractères arbitraire->

Par défaut, l'utilitaire fontconvert d'Adafruit extrait les glyphes des 95 caractères ASCII imprimables (points de code 32 (0x20) à 126 (0x7E)) à partir de fichiers de police TTF et crée un fichier d'en-tête GFXfont utilisé avec la bibliothèque Adafruit-GFX. Il manque les lettres diacritiques et autres symboles nécessaires dans les langues européennes non anglaises. Cette lacune est le sujet de discussions dans la section Issues du référentiel de la bibliothèque. On y retrouve certaines propositions, mais il n'est pas clair que cela ait abouti à une solution qui a été largement acceptée. Cet article ne prétend pas combler ce vide, mais il montre comment générer des fontes the type GFXfont avec tous les glyphes imprimables ISO 8859-1 (Latin-1) ou ISO 8859-15 (Latin-9) à partir d'un fichier de police TTF (à condition que le ce dernier contient les glyphes bien sûr) et comment accéder facilement à ces caractères dans les systèmes embarqués.

Table des matières

  1. La norme ISO 8859
  2. Polices proportionnelles de la bibliothèque Adafruit-GFX
  3. Polices GFXFont avec jeux de caractères ISO 8859
  4. Conversion de polices TrueType en fontes Adafruit-GFX
  5. Conversion de l'encodage UTF-8 vers l'encodage GFXFont 8859
  6. Autres approches
  7. Téléchargements

La norme ISO 8859 toc

ISO 8859 (ISO/CEI 8859 plus précisément) est un ensemble de normes pour le codage de caractères qui étendent sur 8 bits le codage de caractères ASCII 7 bits ce qui permet de doubler le nombre de caractères dans une police. La norme comprend 15 parties, désignées 8859-1 à 8859-16 (8859-12 a été abandonné) qui couvrent de nombreuses langues européennes. Les parties ont des noms plus ou moins confus. La partie 1 est nommée Latin-1 Europe occidentale, la partie 2 est Latin-2 Europe centrale, mais la partie 16 s'appelle Latin-10 Europe du Sud-Est. En outre, elles ont été révisées et couvrent des normes très similaires d'autres autorités. S'ajoutent à ce mélange, les variations et les ajouts des fournisseurs et le résultat devenait déroutant. On se souvient des nombreuses pages de codes telles que CP437 (l'encodage matériel du PC original d'IBM), CP850 (encore Latin-1!), CP863 (qui était utilisé au Canada français) du temps de MS-DOS. La page de code 1252, également appelée Windows-1252 utilisée dans les premières versions de Microsoft Windows et qui reste le « codage de caractères à un octet le plus utilisé au monde », est parfois étiquetée ISO-8859-1 bien qu'il s'agisse d'un surensemble de l'ISO 8859-1 (apparemment, on est censé repérer le tiret supplémentaire qui différencie les deux jeux de caractères). Ce désordre relatif a été plus ou moins résolu avec l'avènement Unicode. Ce dernier n'a pas complètement supprimé les anciennes normes. Les deux premiers blocs de la base de données de caractères Unicode,

    0000..007F; Latin de base
    0080..00FF; Supplément Latin-1 

(les 255 premiers points de code) correspondent à la partie 1 de 8859. Voici un tableau montrant tous les glyphes de 8859-1 ainsi que leurs points de code (positions, encodages (8 bits), etc. Il y a beaucoup de termes équivalents dans ce domaine!).

ISO/CEI 8859-1
x0x1x2x3x4x5x6x7x8x9xAxBxCxDxExF
0xcodes de contrôle
1x
2xSP!"#$%&'()*+,-./
3x0123456789:;<=>?
4x@ABCDEFGHIJKLMNO
5xPQRSTUVWXYZ[\]^_
6x`abcdefghijklmno
7xpqrstuvwxyz{|}~
8xcodes de contrôles
9x
AxNBSP¡¢£¤¥¦§¨©ª«¬-®¯
Bx°±²³´µ·¸¹º»¼½¾¿
CxÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ
DxÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß
Exàáâãäåæçèéêëìíîï
Fxðñòóôõö÷øùúûüýþÿ

Dans cet encodage:

Source: ISO/CEI 8859-1.

Polices proportionnelles de la bibliothèque Adafruit-GFX toc

Tous les caractères d'une police à chasse fixe (à espacement fixe ou à largeur fixe) ont la même largeur. Puisque les lettres i et W occupent la même largeur, la première est entourée d'une marge vide alors que le W semble un peu étriqué. Ces polices étaient largement utilisées dans les premiers systèmes informatiques lorsque les caractères affichés à l'écran étaient arrangés dans une grille de cellules rectangulaires de taille égale. La largeur fixe de chaque caractère simplifie également la représentation numérique de chaque glyphe (l'image ou bitmap en anglais) puisque chaque glyphe aura la même taille. Supposons que la taille nominale de chaque glyphe est pareille au "A" montré ci-dessus: 6 pixels de large et 7 pixels de haut. Alors chaque bitmap de caractère sera composé de 7 octets, chaque octet représentant une ligne de points du caractère. Ls glyphes pour l'ensemble des x caractères de la fonte est un tableau de x tableaux consécutifs de 7 octets.

On peut voir les 16 octets du tableau des images des glyphes représentant les deux caractères A et B. Il est très facile de trouver l'image du nième caractère de l'ensemble; elle est à l'index n*7 à partir du début du tableau d'octets où tout est compté à partir de 0.

Comme la plupart des bibliothèques d'affichage, Adafruit-GFX-Library inclut une police à chasse fixe par défaut, mais elle prend en charge des polices proportionnelles aussi. Ces polices, dans lesquelles les caractères peuvent avoir des largeurs différentes, sont plus agréables à regarder, ont toujours été la norme pour les imprimés et sont devenues omniprésentes dans les systèmes informatiques, à l'exception des environnements de programmation et d'autres contextes de niche. La largeur des caractères n'étant pas constante, les bitmaps de glyphes sont de taille variable. Celles-ci pourraient être complétées par des bits 0 pour représenter des colonnes vides de sorte que tous les caractères occuperaient la même largeur en mémoire mais pas sur l'écran. Accéder à l'image d'un caractère serait aussi simple qu'avec une police à chasse fixe. Cependant, cela gaspille de la mémoire si certains caractères peuvent avoir une largeur supérieure à 8 colonnes; et la mémoire est une denrée rare sur les microcontrôleurs. De plus, il serait nécessaire de conserver la largeur des caractères ailleurs. La bibliothèque Adafruit stocke les bitmaps dans une séquence continue et elle stocke le décalage du début de chaque bitmap de caractère dans un autre tableau avec d'autres attributs du caractère, y compris sa largeur.

Voici la structure du glyphe de chaque caractère.

/// Font data stored PER GLYPH typedef struct { uint16_t bitmapOffset; /// Pointer into GFXfont->bitmap uint8_t width; /// Bitmap dimensions in pixels uint8_t height; /// Bitmap dimensions in pixels uint8_t xAdvance; /// Distance to advance cursor (x axis) int8_t xOffset; /// X dist from cursor pos to UL corner int8_t yOffset; /// Y dist from cursor pos to UL corner } GFXglyph;

Une police GFXfont est une structure avec des pointeurs vers le tableau bitmap et vers le tableau d'attributs de caractères et trois autres champs :

/// Data stored for FONT AS A WHOLE typedef struct { uint8_t *bitmap; /// Glyph bitmaps, concatenated GFXglyph *glyph; /// Glyph array uint8_t first; /// ASCII extents (first char) uint8_t last; /// ASCII extents (last char) uint8_t yAdvance; /// Newline distance (y axis) } GFXfont;

Notons les champs first and last. Par défaut, ce sont respectivement 32 (0x20 hexadécimal) qui est le code ASCII pour le caractère espace et 126 (0xFE) qui est le code ASCII pour le caractère '~'. Puisque les 32 premiers points de code sont des codes de contrôle, rien n'est enregistré dans le bitmap de police pour ces codes et il n'y a aucun enregistrement dans la liste de glyphes pour ces points de code. Par conséquent, le premièr GFXglyph pointe vers le premier caractère imprimable dans le bitmap de la police. Cela signifie également qu'il est tout à fait possible d'avoir une police avec seulement un sous-ensemble de caractères, tant que ceux-ci ont des points de code séquentiels. Par exemple, dans un projet d'horloge numérique, j'utilise une police GFXfont avec de gros caractères (en termes de largeur et de hauteur) qui ne prend pas beaucoup de mémoire, car il ne contient que 11 caractères: '0', '1', jusqu'à '9' et ':'. Donc first est 48 (0x30) et last est 58 (0x3A). Il s'agit d'un moyen compact de stocker un sous-ensemble de tous les points de code tant qu'ils ont des points de code séquentiels.

Polices GFXFont avec jeux de caractères ISO 8859 toc

Toutes les parties ISO 8859 contiennent 32 codes de contrôle commençant au point de code 128 (0xA0) qu'il serait inutile d'inclure dans le tableau GFXglyph. Alors, pourquoi ne pas sauter ces codes de contrôle comme cela est fait pour les 32 premiers codes de contrôle ASCII ? Le tableau suivant montre donc comment je propose enregistrer les glyphes des caractères imprimables à partir d'un jeu de caractères ISO 8859, en utilisant 8859-1 (Latin-1) comme exemple.

GFX Latin 1
x0x1x2x3x4x5x6x7x8x9xAxBxCxDxExF
0x !"#$%&'()*+,-./
1x0123456789:;<=>?
2x@ABCDEFGHIJKLMNO
3xPQRSTUVWXYZ[\]^_
4x`abcdefghijklmno
5xpqrstuvwxyz{|}~
6x ¡¢£¤¥¦§¨©ª«¬-®¯
7x°±²³´µ·¸¹º»¼½¾¿
8xÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ
9xÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß
Axàáâãäåæçèéêëìíîï
Bxðñòóôõö÷øùúûüýþÿ

L'index d'un caractère de cette table est en fait l'ordinal de l'entrée du caractère dans le tableau de glyphes GFXglyph. Par exemple, l'index de «m» est 0x4D (hexadécimal, 77 décimal). Par conséquent, l'enregistrement dans GFXGlyph de "m" est glyph[77]. Cependant, le point de code pour "m" est 109 (77 + 32). Cette façon de compiler les bitmaps et le tableau d'attributs de glyphe signifie qu'il faudra faire preuve de prudence pour accéder au bon glyphe. Si cp représente le point de code d'un caractère de la norme ISO 8859-1 alors le tableau suivant montre comment se calcule son index.

cp (point de code Latin-1)Index de l'enregistrement en GFXglyph
0 (0x00) ≤ cp < 32 (0x20)hors bornes (*)
32 (0x20) ≤ cp < 128 (0x80) cp - 32
128 (0x80) ≤ cp < 160 (0xA0)hors bornes (*)
160 (0x80) ≤ cp ≤ 255 (0xFF) cp - 64

Peut-être que � (U+FFFD REMPLACEMENT CHARACTER) pourrait être affiché dans les cas où un point de code ne correspond pas à un glyphe, comme ceux marqués d'un astérisque dans le tableau. C'est ce que font les navigateurs Web. La bibliothèque d'Adafruit effectue déjà la translation par 32 (ou quel que soit le point de code du premier glyphe visible) lors de l'affichage d'un point de code, mais il est clair que les choses seront plus compliquées avec la partie inférieure du tableau. Plus à ce sujet plus tard, pour l'instant continuons la discussion au sujet des glyphes eux-mêmes.

Je ne savais pas à quoi m'attendre pour trois caractères. En regardant un fichier TFT GNU FreeFont, j'ai trouvé un espace au point de code 128 (0x80) comme version "imprimable" d'un espace insécable, un trait d'union au point de code 141 (0x8D) pour le trait d'union souple. Je suppose que l'on pourrait renoncer à stocker ces deux bitmaps de caractères et s'assurer que le champ bitmapOffset dans leur enregistrement glyph pointe vers le bitmap pour l'espace et «-» respectivement. Cependant, il s'avère que la partie 11 de l'ISO 8859 ne contient pas le trait d'union souple et a un caractère imprimable dans cette position: ญ. J'ai décidé qu'il était inutile de sauvegarder quelques octets de mémoire pour ne pas stocker l'image de l'espace insécable.

Le jeu de caractères Latin-9 (ISO 8859-15) est mieux adapté à mes besoins, car il contient quelques ligatures utilisées en français et non présents dans Latin-1. Il contient également le symbole de l'euro qui peut parfois être utile.

GFX Latin 15
x0x1x2x3x4x5x6x7x8x9xAxBxCxDxExF
0x !"#$%&'()*+,-./
1x0123456789:;<=>?
2x@ABCDEFGHIJKLMNO
3xPQRSTUVWXYZ[\]^_
4x`abcdefghijklmno
5xpqrstuvwxyz{|}~
6x ¡¢£¥Š§š©ª«¬-®¯
7x°±²³Žµ·ž¹º»ŒœŸ¿
8xÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏ
9xÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß
Axàáâãäåæçèéêëìíîï
Bxðñòóôõö÷øùúûüýþÿ

L'alphabet de Latin 15 est presque le même que celui de Latin 1 à l'exception de 8 caractères: ¤, ¦, ¨, ´, ¸, ¼, ½ et ¾ qui sont remplacés par €, Š, š, Ž, ž, Œ, œ et Ÿ. Les positions Unicode de ces 8 caractères sont toutes supérieures à 255 ce qui posera des problèmes comme on le verra plus tard.

Conversion de polices TrueType en fontes Adafruit-GFX toc

Obtenir les glyphes et configurer les structures GFXfont peut sembler être une tâche complexe et difficile. En fait, il est assez facile de convertir une police TTF en une fonte GFXFont grâce au travail des autres. Quelques modifications au programme fontconvert.c fourni avec la bibliothèque Adafruit-GFX étaient tout ce qui était nécessaire. 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, selon un ordre arbitraire au lieu de tirer tous les glyphes d'un premier point de code donné à un dernier point de code donné. La source de l'utilitaire modifié peut être téléchargée ici: fontconvert8.zip. Le contenu de cette archive peut être extrait vers répertoire fontconvertde la bibliothèque GFX, il n'y a pas de conflit de nom. La source doit être compilée pour obtenir un fichier exécutable. Cela se fait tout simplement avec la commande suivante sur la plupart des systèmes Linux .

... $ make -f Makefile8

qu'on exécute à partir du dossier contenant les fichiers fontconvert8.c et Makefile8. L'exécutable, fontconvert8 sera créé dans le même répertoire. 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.

Avant de compiler l'utilitaire, il peut être nécessaire de modifier fontconvert8.c. Jusqu'à trois ajustements peuvent être nécessaires.

  1. Ligne 59. Choisir le glyphe à charger au point de code 0x7F au lieu du caractère DEL. Il y a quatre choix.
    define DEL_GLYPHCharacter Glyph at 0x7F
    SPACE_AS_DEL" " U++0020 the space character
    REPLACEMENT_CHARACTER_AS_DEL"�"U+FFFD the Unicode replacement character
    APL_QUAD_QUESTION_AS_DEL"⍰"U+2370 the APL functional symbol quad question

    Si la macro DEL_GLYPH n'est pas définie, le glyphe de U+007F tel que défini dans le fichier TTF sera utilisé, quel qu'il soit.

  2. Ligne 75. Choisir un fichier d'en-tête contenant le tableau chartable. Rappelons que ce tableau contient les point de code Unicode des glyphes à inclure dans la fonte GFX. Deux parties ISO 8859 sont incluses dans l'archive, 8859-1.h et 8859-15.h. Il devrait être assez simple d'ajouter d'autres glyphes, qui ne doivent pas nécessairement correspondre à une définition ISO 8859, d'ailleurs,
  3. Ligne 77. Définir la résolution de l'affichage. Il s'avère que 141 points par pouce est la résolution correcte pour l'écran ILI9225 de 2 pouces que j'utilise. Il s'agit d'un écran de 176 x 220 pixels avec une zone d'affichage de 31,68 x 39,6 mm (ou 1,25 x 1,56"), ce qui donne 140,8 x 141 dpi.

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.

... $ ./fontconvert_xtd /usr/share/fonts/truetype/freefont/FreeMono.ttf 9 > FreeMono9pt8b.h

Il n'est pas nécessaire de spécifier les paramètres first et last comme dans l'ancien utilitaire car ces valeurs sont implicitement définies par le tableau chartable. Le script makefonts8.sh est une adaptation du script 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 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 FreeMono9pt8bGlyphs[] PROGMEM = { { 0, 0, 0, 11, 0, 1 }, // 0x20 ' ' U+0020 { 0, 2, 11, 11, 4, -10 }, // 0x21 '!' U+0021 { 3, 6, 5, 11, 2, -10 }, // 0x22 '"' U+0022 { 7, 7, 12, 11, 2, -10 }, // 0x23 '#' U+0023 ... { 1698, 8, 12, 11, 1, -11 }, // 0xc8 'LATIN SMALL LETTER E WITH GRAVE' U+00E8 { 1710, 8, 12, 11, 1, -11 }, // 0xc9 'LATIN SMALL LETTER E WITH ACUTE' U+00E9 ...

On pourrait choisir uniquement les chiffres comme je l'ai mentionné ci-dessus avec fontconvert8 en créant un fichier d'en-tête avec un tableau chartable approprié. Cependant, il est beaucoup plus rapide d'utiliser l'utilitaire fontconvert d' origine tant que les points de code Unicode du jeu de caractères sont séquentiels et inférieurs à 256. Si des caractères Unicode consécutifs avec des points de code supérieurs à 255 sont requis, le fork de fontconvert.c de Bodmer peut être utilisé. Je crois qu'il a changé le type de first, last et d'autres variables liées au point de code à int pour gérer la gamme complète de caractères Unicode. Attention, il faudra dès lors utiliser la version Bodmer de la bibliothèque GFX.

Conversion de l'encodage UTF-8 vers l'encodate GFXFont 8859 toc

La chaîne "L'été à ..." ne s'affichera pas correctement même si une police GFX Latin 1 ou GFX Latin 9 est installée.

char msg[] = "L'été à ..." display.setCursor(5, 32); display.println(msg); display.display();

Ce qui précède s'affichera comme  L'ãÉtãÉ ãÀ ...  sur l'écran pour une raison bien simple. Unicode est utilisé dans les environnements Arduino et PlatformIO (au moins sous Linux ) et le tableau d'octets msg est encodé en UTF-8.

   L    '    é        t    é        SP   à        .    .    .
  \x4C \x27 \xC3\xA9 \x74 \xC3\xA9 \x20 \xC3\xA0 \x2E \x2E \x2E

N'oubliez pas que la fonction drawChar soustrait 32 (0x20) de chaque caractère pour obtenir son glyphe. Ainsi, lors du décodage des encodages UTF-8 de 2 octets, ce qui suit se produit.

  0xC3 - 0x20 = 0xA3 (163) qui est la position de ã dans GFX Latin-1
  0xA9 - 0x20 = 0x89 (137) qui est la position de É dans GFX Latin-1
  0xA0 - 0x20 = 0x80 (128) qui est la position de À dans GFX Latin-1

Pour contourner ce problème, une chaîne littérale peut être définie en évitant le codage UTF-8.

char msg[] = "L'\xC9t\xC9 \xC0 ..."

Les constantes hexadécimales à incorporer dans la chaîne peuvent être trouvées en regardant 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 GFX-8859-x. 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 gfxlatin1.h qui déclare deux fonctions qui effectuent les conversions d'un objet String ou d'une chaîne C du codage UTF-8 au codage GFX Latin 1.

#ifndef GFXLATIN1_H #define GFXLATIN1_H // Replace code points not found in the GFX Latin 1 font with 0x7F extern bool showUnmapped; // Convert a UTF-8 encoded String object to a GFX Latin 1 encoded String String utf8tocp(String s); // Convert a UTF-8 encoded string to a GFX Latin 1 encoded string // Be careful, the in-situ conversion will "destroy" the UTF-8 string s. void utf8tocp(char* s); #endif

Le fichier d'en-tête pour l'encodage UTF-8 en GFX Latin 9, gfxlatin9.h est le même sauf pour Latin 9 qui apparaît où Latin 1 se trouve dans l'autre fichier. Ces fonctions peuvent ensuite ê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 "decodeutf8.h" #include "gfxlatin1.h" #include "FreeSans10pt8b.h" // GFX Latin 1 font generated with fontconvert8 void drawUtf8Text(String str) { String xtdcp(utf8tocp(str)); display.print(xtdcp); }

Comme indiqué précédemment, il existe d'autres bibliothèques graphiques qui utilisent les définitions GFXFont dont celle de Johan Cronje (Nkawu) TFT_22_ILI9225. Je l'utilise avec les écrans TFT couleur ILI9225 de 2". La fonction d'impression des chaînes sur l'écran 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 "decodeutf8.h" #include "gfxlatin1.h" #include "FreeSans10pt8b.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); }

Si la variable booléenne showUnmapped est définie sur true, les routines de conversion afficheront le caractère DEL (0x7F, point de code GFX 0x5F, ou plus précisément le caractère de remplacement qui a été choisi lors de la création de la fonte GFXfont) à la place des caractères invalides ou hors limites dans la chaîne source. Cela pourrait être utile lors du débogage d'un programme.

Le reste de cette section concerne les détails techniques que les curieux, ceux qui pourraient vouloir utiliser cette approche avec d'autres jeux de caractères, ou ceux qui veulent m'aider à améliorer mes faibles compétences en programmation C/C++ auront peut-être le courage de lire.

Lorsque j'ai travaillé pour la première fois avec le jeu de caractères GFX ISO 8859-1, j'ai testé quatre fonctions pour convertir UTF-8 en GFX Latin 1. Cependant, au moment de travailler avec le jeu de caractères ISO 8859-15, et en pensant à une solution générale qui pourrait fonctionner pour tous les jeux de caractères, j'ai rapidement réduit le champ à une fonction, celle de Bodmer (encore !). Elle convertit un flux UTF-8 8 bits en un flux UCS-2 16 bits, puis convertit certains de ces caractères UCS-2 en points de code corrects dans la police GFX installée. La fonction uint16_t decodeUTF8(uint8_t c) se charge de convertir UTF-8 en UCS-2. La fonction est en fait une machine à états qui prend une valeur de 8 bits en entrée et sort la valeur de 16 bits correspondante dans la plage de 0 à 0xFFFE si un caractère UTF-8 valide a été trouvé ou elle génère 0xFFFF si la machine à états a besoin de plus d'octets d'entrée pour décoder un caractère UTF-8 multioctets ou si un encodage invalide est trouvé. Voici comment la fonction est utilisée dans gfxlatin1.cpp pour coder un objet String.

String utf8tocp(String s) { String r=""; uint16_t c; resetUTF8decoder(); for (int i=0; i<s.length(); i++) { c = decodeUTF8(s.charAt(i)); if (0x20 <= c && c <= 0x7F) r += (char) c; else if (0xA0 <= c && c <= 0xFF) r += (char) (c - 32); } return r; }

La machine à états est réinitialisée avec la fonction resetUTF8decoder() au début du processus de décodage. Les caractères ASCII 7 bits imprimables sont mappés comme eux-mêmes tandis que 32 sont soustraits des caractères Latin 1 imprimables. N'oublions pas que le point de code Unicode plus grand ou égal à 160 correspondent à un index réduit de 64 dans le tableau GFXGlyph à cause des deux zones escamotées des codes de contrôle. Toutefois, leur point de code GFX Latin 1 est 32 de moins que le point de code Unicode, car la bibliothèque GFX soustrait le point de code du premier caractère de la police (qui est l'espace dont la valeur est 32). Tout le travail difficile est effectué par la fonction décodeur. À quel point est-il compliqué d'analyser un caractère UTF-8 de longueur variable? Commençons par la syntaxe de ces caractères en métalangage Backus-Naur augmenté (ABNF).

UTF8-charUTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
UTF8-1 %x00-7F
UTF8-2 %xC2-DF%x80-BF
UTF8-3%xE0%xA0-BF%x80-BF
%xE1-EC%x80-BF%x80-BF
%xED%x80-9F%x80-BF
%xEE-EF%x80-BF%x80-BF
UTF8-4%xF0%x90-BF%x80-BF%x80-BF
%xF1-F3 %x80-BF%x80-BF%x80-BF
%xF4%x80-8F%x80-BF%x80-BF

Source: Request for Comments: 3629 de F. Yergeau, novembre 2003.

Voici une version légèrement simplifiée de la fonction décodeur utilisée par String utf8tocp(String s) dans gfxlatin1.cpp et gfxlatin9.cpp.

uint8_t decoderState = 0; // UTF-8 decoder state uint16_t decoderBuffer; // Unicode code-point buffer uint16_t decodeUTF8(uint8_t c) { if ((c & 0x80) == 0x00) { // 7 bit Unicode Code Point decoderState = 0; return (uint16_t) c; } if (decoderState == 0) { if ((c & 0xE0) == 0xC0) { // 11 bit Unicode code point decoderBuffer = ((c & 0x1F)<<6); // Save first 5 bits decoderState = 1; } else if ((c & 0xF0) == 0xE0) { // 16 bit Unicode code point decoderBuffer = ((c & 0x0F)<<12); // Save first 4 bits decoderState = 2; } } else { decoderState--; if (decoderState == 1) decoderBuffer |= ((c & 0x3F)<<6); // Add next 6 bits of 16 bit code point else if (decoderState == 0) { decoderBuffer |= (c & 0x3F); // Add last 6 bits of code point (UTF8-tail) return decoderBuffer; } } return 0xFFFF; }

Il est clair que la fonction n'analyse pas correctement la grammaire. Les chaînes encodées en UTF-8 ne doivent jamais contenir les caractères \xC0 et \xC1, mais le décodeur les accepte comme premier octet d'un encodage UTF-8 de 2 octets. La RFC 3629 met en garde contre de telles erreurs:

Par exemple, une implémentation naïve peut décoder la séquence UTF-8 C0 80 en le caractère U+0000, ou la paire de substitution ED A1 8C ED BE B4 en U+233B4. Le décodage de séquences non valides peut avoir des conséquences sur la sécurité ou entraîner d'autres problèmes.
...
Un autre exemple pourrait être un analyseur qui interdit la séquence d'octets 2F 2E 2E 2F ("/../"), tout en autorisant la séquence d'octets illégale 2F C0 AE 2E 2F. Cet astuce a été utilisé dans un virus répandu qui a attaqué des serveurs Web en 2001; ainsi, la menace pour la sécurité est bien réelle.
Citation originale

En effet, le décodeur renvoie NUL pour la séquence "0xC0 0x80" (la fonction retourne 0xFFFFpour 0XC0, qui est ignoré et 0x00 qui est le point de code pour NUL). Quant à la paire de substitution, elle renvoie deux valeurs, 0xD84Ce t 0xDFB4, ce qui n'est pas U+233B4 mais qui est néanmoins incorrect. Le décodeur présente le dernier problème identifié, la chaîne "\x2F\xC0\xAE\x2E\x2F" est convertie en chaîne ASCII "\..\". De plus, les codages sur 4 octets tels que "\xF0\x90\x8C\x94" pour U+10314 ANCIENNE LETTRE ITALIQUE ES '𐌔' ne renvoient rien sauf 0xFFFF.

Au départ, j'ai été un peu décontenancé par ces erreurs et j'ai essayé de créer un décodeur UTF-8 «validant». Après réflexion, j'ai décidé que le décodeur plus simple et plus petit basé sur le code de Bodmer était tout à fait suffisant pour une utilisation dans un microcontrôleur. Je ne vois pas vraiment qu'il y aurait des problèmes de sécurité dans ce contexte.

Il y a une complication en ce qui concerne l'encodage GFX Latin 9. Les caractères Latin 9 non trouvés dans Latin 1 ont tous des points de code Unicode supérieurs à 0xFF. Ainsi, une fonction appelée recode mappe ces caractères vers points de code GFX Latin 9 le cas échéant.

uint16_t recode(uint8_t b) { uint16_t ucs2 = decodeUTF8(b); if (ucs2 > 0x7F) { switch (ucs2) { case 0x0152: return 0xbc; break; case 0x0153: return 0xbd; break; case 0x0160: return 0xa6; break; case 0x0161: return 0xa8; break; case 0x0178: return 0xbe; break; case 0x017D: return 0xb4; break; case 0x017E: return 0xb8; break; case 0x20AC: return 0xa4; break; } } return ucs2; }

Au lieu d'appeler decodeUTF8(), utf8tocp utilise recode() pour décoder les chaînes UTF-8. Il y a un léger problème avec cette fonction. La chaîne "¤€" sera affichée sous la forme "€€". C'est parce que la séquence "\xC2\A4", le codage UTF-8 pour U+00A4 CURRENCY SIGN, soit le caractère '¤', sera convertie en 0xA4 par decodeUTF8 et transmise comme telle par recode et la séquence "\xE2\x82\xAC", le codage UTF-8 pour U+20AC EURO SIGN sera mappé à 0x20AC par decodeUTF8 puis converti en 0xA4 par recode. Je ne pense pas que ce soit vraiment un problème, mais ceux qui préfèrent utiliser le décodeur de validation UTF-8 voudront peut-être définir la macro INVALIDATE_OVERWRITTEN_LATIN_1_CHARS pour marquer le signe de la devise et les autres caractères Latin 1 écrasés comme non mappés.

Autres approches toc

Il n'y a pas si longtemps, j'ai affiché un billet (Lettres diacritiques françaises avec les polices GFXfont) qui a été conçu pour ajouter juste quelques caractères du bloc Unicode Latin-1. Cette approche n'a plus d'intérêt et j'espère montrer bientôt comment adapter l'approche actuelle aux besoins de la langue française uniquement.

Chris Young a écrit un article intitulé Creating Custom Symbol Fonts for Adafruit GFX Library qui pourrait être une base pour faire quelque chose de similaire à ce qui est fait ici. L'utilitaire fontconvert d'origine peut être utilisé pour créer une deuxième fonte à partir du bloc Latin-1. Puis la fonction drawSymbol peut être modifiée pour sélectionner la police ASCII ou la police Latin 1 pour imprimer un caractère en fonction de son point de code. Il serait probablement toujours utile d'ajouter les routines de conversion UTF-8 pour écrire facilement des chaînes UTF-8 sur l'écran. En termes d'utilisation de la mémoire, 2 blocs de 96 glyphes devraient être à peu près identiques à un bloc de 192 glyphes. En revanche le passage d'un GFXfont à un autre me semble un peu gênant. Pour ces raisons, je ne pense pas que cette approche ait un avantage par rapport à celle proposée ici, mais cela peut être un bon point de départ lors de la première utilisation la bibliothèque GFX d'Adafruit.

J'ai déjà évoqué la « fourche » de Adafruit-GFX-Library de Bodmer. Non seulement il a ajouté le support UTF-8 à la bibliothèque, mais il a également modifié l'utilitaire fontconvert pour qu'il fonctionne avec n'importe quel ensemble de glyphes contigus dans le répertoire Unicode. Pour mes besoins, ce n'est pas aussi utile car certains des écrans que j'utilise ne sont pas pris en charge par la bibliothèque d'Adafruit et les caractères pour Latin 9 ne sont pas contigus ni même séquentiels. Cependant, sa routine de conversion UTF-8, qui n'est pas totalement différente de celle disponible sur Playground d'Arduino, s'est avérée être un excellent point de départ.

En lisant une entrée intitulée international character sets #64 dans la page Issues du référentiel Adafruit-GFX-Library, j'ai vu que Peter Jakobs (pljakobs) s'était attaqué aux jeux de caractères ASCII étendus. J'ai besoin de regarder de plus près comment il a géré le codage Latin 2 parce qu'il semble avoir fait le recodage d'une manière qui est généralement applicable, mais au prix d'utiliser plus de mémoire et peut-être avec des conversions plus lentes. Je dois également lui attribuer l'idée de créer une fonction de délégation qui prend en charge le décodage UTF-8.

Téléchargements toc

Cet article se terminera par une liste de téléchargements.

  1. L'utilitaire fontconvert modifié : fontconvert8.zip.
  2. Conversions UTF-8 vers GFX Latin1 : gfxlatin1.zip. Ce sont les 4 routines de décodage UTF-8 initialement examinés avant de faire un choix définitif.

  3. Environnement de test (platformIO) : gfxfont_8bit_v01.zip (comprend le contenu de fontconvert8.zip, les bibliothèques gfxlatin1 et gfxlatin9, le décodeur imparfait decodeUTF8 et le décodeur validant validatingdecodeUTF8). Les programmes d'exemple inclus sont en fait des programmes de test; j'ai évidemment besoin d'apprendre à utiliser les capacités de test unitaire de PlatformIO.

Comme d'habitude, la très peu contraignante licence BSD à deux clauses s'applique à mon code source disponible dans l' archive gfxfont_8bit_v01.zip. Cependant, les licences des contributeurs originaux doivent être respectées. Des liens vers ce que je crois être les sources originales se trouvent dans gfxlatin1.cpp de l' archive gfxlatin1.zip.

Police GFX avec jeu de caractères arbitraire->