- français
Unicode et UTF-8
Unicode (http://www.unicode.org/) définit un jeu de caractères le plus exhaustif possible, dans le but de régler les problèmes d'encodage de textes dans un contexte international. Par exemple, un document ou une interface qui supportent l'Unicode acceptent un texte en allemand comme en arabe, avec les contraintes du sens de la lecture que ça comporte.
Encodage et encapsulation
Actuellement, deux Unicode Character Set (UCS) sont définis :
- UCS-4
- chaque caractère étant encodé sur 4 octets, tout ceux définis dans Unicode sont pris en compte par ce charset.
- UCS-2
- ce charset est un sous-ensemble du précédent : seuls 2 octets sont utilisés ce qui permet d'écrire les 65 536 premiers caractères d'Unicode.
Pour faciliter l'intégration d'Unicode dans les systèmes actuels, on a recours aux Unicode Transformation Format (UTF) qui sont en quelque sorte des mécanismes d'encapsulation des caractères UCS. Étant donné le nombre de caractères, tout ne rentre pas dans un seul octet, comme ISO-8859-x, c'est pourquoi, on va devoir les encoder sur plusieurs octets, et chaque variante d'UTF apporte ses avantages et ses inconvénients. Sans rentrer dans les détails, en voici une courte liste :
- UTF-8
- Il permet d'encapsuler l'UCS-4 et chaque caractère fait de 1 à 4 octets. On le retrouve sur les plateformes Unix.
- UTF-16
- Il permet d'encapsuler l'UCS-2 et chaque caractère fait 2 octets. On distingue l'UTF-16LE (pour little endian) et l'UTF-16BE (pour big endian). On le retrouve sur les plateformes Microsoft Windows.
- UTF-32
- Il fonctionne sur le même principe que l'UTF-16 mais permet l'encapsulation des caractères UCS-4, donc chaque caractère fait 4 octets. Il est aussi affecté par les problématiques d'endianness.
Le fonctionnement de l'UTF-8
Au départ, UTF-8 devait remplir les conditions suivantes :
- Les caractères faisant parti de l'US-ASCII (donc ASCII
sur 7 bits) doivent rester inchangés. L'objectif est de
permettre un maximum de compatibilité entre les documents
UTF-8 et les applications qui ne connaissent que l'ASCII et
supposent qu'un caractère correspond à un octet (voir la
définition du type C
char). - Comme la majorité des caractères font plusieurs octets, il doit être possible d'en retrouver le début ; ceci afin de pouvoir l'utiliser dans un flux de données.
En tenant compte de tout ceci, le caractère UTF-8 a été défini comme ça :
- Pour l'US-ASCII (sur 7 bits, donc les caractères de 0x00
à 0x7F), le caractère est laissé tel quel, et donc prend un
octet (le bit de poids fort est à 0, l'octet sera donc de la
forme
0xxxxxxx). - Pour les autres, le caractère est stocké sur 2 à
4 octets. Le premier octet donne la longueur du caractère au
travers du nombre de 1 suivis d'un 0 (
110xxxxxpour 2 octets,1110xxxxpour 3 octets, etc.) ; les autres commencent par10xxxxxx, qui sert de marqueur pour retrouver le début d'un caractère (on ne le trouve pas sur le premier octet). - Le caractère UCS-2 ou UCS-4 sera ensuite stocké
dans les
x, en commençant avec son bit de poids faible dans le dernier bit du dernier octet, et en remontant vers le premier octet (le reste est rempli avec des 0).
Pour avoir un résumé plus graphique
, ça donne
ceci quand on regarde un caractère entier :
- 1 octet :
0xxxxxxx - 2 octets :
110xxxxx 10xxxxxx - 3 octets :
1110xxxx 10xxxxxx 10xxxxxx - 4 octets :
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
Conversion des caractères ISO-8859-1(5)
Unicode classe tous les caractères en plusieurs ensembles,
selon la langue ou la région d'où ils viennent, leur
utilisation, etc. Jusqu'à maintenant, pour la France, on
utilisait l'encodage ISO-8859-1 qui apporte à l'US-ASCII les
caractères accentués, la cédille ou encore les ligatures.
Depuis l'euro, il a été remplacé par l'ISO-8859-15 dans
lequel 8 caractères sont remplacés par d'autres, dont
€
. Dans le classement Unicode, tous ces caractères se
trouvent dans les catégories suivantes :
- Latin Base (les caractères US-ASCII)
- Latin-1 Supplement (les caractères ajoutés par ISO-8859-1 après 0x80)
- Latin Extended-A (pour
½
et¼
notamment) - Currency Symbols (pour
€
par exemple)
Pour la conversion des 2 premiers groupes, c'est tout simple.
En prenant l'exemple de é
, c'est plus clair. En UTF-8,
il s'écrit 0xC3 0xA9, soit en binaire :
11000011 10101001
L'enveloppe UTF-8 est donc celle-ci :
110xxxxx 10xxxxxx
Elle nous indique que le caractère fait 2 octets
(110xxxxx) ; les données restantes
sont donc :
xxx00011 xx101001 -> 11101001
soit 0xE9 en hexadécimal qui est le caractère é
en
ISO-8859-1.
En C, le code suivant permet une conversion d'un caractère UCS-2 (précédemment extrait de sa capsule UTF-8) en un caractère ISO-8859-1 :
/*
* Conversion d'un caractère UCS-2 en ISO-8859-1
* ---------------------------------------------
*
* Les blocs Unicode qui nous intéressent sont :
* Basic Latin (US-ASCII)
* Latin-1 Supplement (Les caractères ISO-8859-1 après 0x80)
* Parmis ces blocs, on ne converti que les caractères imprimables, les
* caractères de contrôles (même s'ils produisent normalement un effet
* comme l'espace insécable) sont ignorés.
*/
char
ucs2_to_iso8859_1(uint16_t uc)
{
if ((uc >= 0x0020 && uc <= 0x007E) || /* Basic Latin */
(uc >= 0x00A1 && uc <= 0x00FF)) /* Latin-1 Supplement */
return (char)(uc & 0xFF);
/* Les autres ne sont pas des caractères imprimables ou sont hors de
* ISO-8859-1 */
return '\0';
}
Ce code peut être utile avec SDL qui retourne un caractère UCS-2 après un évènement clavier.
Cette conversion simple fonctionne pour l'ISO-8859-1 mais pas pour l'ISO-8859-15, car ses nouveaux caractères sont distribués un peu partout dans les ensembles Unicode. Il faut donc fonctionner avec une table de correspondance.
Pour approfondir
- RFC 3629 qui défini l'UTF-8
- Ensemble des caractères Unicode
- ISO-8859-1 dans Unicode
- ISO-8859-15 dans Unicode