Unicode est un hydre : lorsqu'on découvre Unicode, chaque point éclairci va faire surgir deux nouvelles questions ! Cet article tente de démystifier Unicode en présentant quelques particularités de ce jeu de caractères.

Unicode 5.0

Unicode est un jeu de caractères incluant tous les autres jeux de caractères existant : c'est un surensemble qui est donc forcément plus gros. La dernière version publiée date de juillet 2006 : la norme Unicode 5.0.

Nombre de caractères :

  • Caractères graphiques : 98.884 (lettres, marqueurs, chiffres, ponctuations, symboles et espaces)
  • Code de formatage : 140 (séparateurs de ligne et de paragraphe, sens du texte, ...)
  • Code de contrôle : 65 (héritage de l'ASCII)
    • U+00–U+1F
    • U+7F–U+9F
  • Caractères à usage privé : 137.468 (l'émetteur et le récepteur peuvent définir leurs propres codes)
    • U+E000–U+F8FF
    • U+F0000–U+FFFFD
    • U+100000–U+10FFFD
  • Codes surrogates : 2.048
    • U+D800–U+DBFF : Low surrogates
    • U+DC00–U+DFFF : High surrogates
    • Pour UTF-16, les codes U+10000 à U+10FFFF sont encodés sur deux mots de 16 bits :
      • On retire 0x10000 au code : 0x10000..0x10FFFF => 0x0000..0xFFFF
      • On découpe le résultat en deux valeurs de 10 bits chacune
      • Finalement, le premier mot est : 0xD800 + (code-0x10000) & 0x003FF (10 bits de poids faible)
      • Et le deuxième mot est : 0xDC00 + ((code-0x10000) >> 10) & 0x003FF (10 bits de poids fort)
    • Les surrogates sont donc des codes Unicode interdits pour éviter de confondre un code avec un mot d'UTF-16
  • Non caractères : 66
    • Codes terminés par 0xFFFE en hexadécial : U+FFFE, U+1FFFE, ... U+10FFFE (17 codes). Codes interdits pour que le code U+FEFF puisse servir comme marqueur d'endian pour les encodages UTF-16 et UTF-32.
    • Codes terminés par 0xFFFF en hexadécial : U+FFFF, U+1FFFF, ... U+10FFFF (17 codes). Peuvent être utilisé dans une implémentation d'Unicode comme « valeur maximale ».
    • U+FDD0–U+FDEF
  • Codes réservés : 875.441 (réservés pour des usages futurs)

Unicode 5.0 contient donc 1.114.112 codes caractères : codes 0x000000 à 0x10FFFF.

Les codes 0x0000 à 0xFFFF constituent ce qu'on appelle le « Plan multilingue de base » (abrégé BMP en anglais).

Sérialisation d'Unicode

Un octet ne pouvant contenir que 256 codes différents, Unicode a besoin d'un encodage sur plusieurs octets pour chaque code. Il existe de nombreux encodages :

  • UTF-7 : mots de 7 bits (le 8e bit peut être utilisé comme bit de parité)
  • UTF-8 : mots de 8 bits
  • UTF-EBCDIC : mots de 8 bits (compatible avec le jeu de caractère EBCDIC)
  • CESU-8 : mots de 8 bits (mélange entre UTF-8 et UTF-16)
  • UTF-16 : mots de 16 bits (UTF-16LE et UTF-16BE pour little endian et big endian)
  • UTF-32 : mots de 32 bits (UTF-32LE et UTF-32E)

Il existe également les algorithmes SCSU et BOCU qui servent à compresser un texte Unicode sérialisé.

Exemple avec Python 2.5 :

>>> u'é'.encode('utf8')
'\xc3\xa9'
>>> u'é'.encode('UTF-16LE')
'\xe9\x00'
>>> u'é'.encode('UTF-16BE')
'\x00\xe9'

Caractère U+AF05 :

>>> u'\uAF05'.encode('utf8')
'\xea\xbc\x85'
>>> u'\uAF05'.encode('UTF-16LE')
'\x05\xaf'
>>> u'\uAF05'.encode('UTF-16BE')
'\xaf\x05'

Encodage Punycode

L'algorithme Punycode, normalisé par la RFC 3492, sert à convertir un nom de domaine Unicode sous forme ASCII : lettres A à Z, chiffres 0 à 9 et le caractère « - ». L'algorithme est réversible : on peut convertir un punnycode en Unicode sans perte d'information. Cet encodage est surtout utilisé pour les noms de domaine Unicode (serveurs DNS) : les Internationalized Domain Names (IDN). Lire le billet de Stéphane Bortzmeyer sur les IDNA.

Exemple avec Python 2.5 :

>>> from encodings.idna import ToASCII, ToUnicode
>>> ToASCII(u'café')
'xn--caf-dma'
>>> print ToUnicode('xn--caf-dma')
café

Normalisation d'un caractère

On peut composer un caractère. Le caractère « ä » peut s'écrire « a » + umlaut (U+308). Il existe trois caractères correspondant dans la table Unicode :

  • U+61 : « a », lettre A
  • U+E4 : « ä », lettre A avec umlaut
  • U+308 : « ¨ », umlaut

Donc U+E4 et (U+61, U+308) sont équivalents. Il existe des outils pour normaliser selon les différentes formes :

  • NFD (Forme normale D) : décomposition canonique
  • NFC (Forme normale C) : décomposition canonique suivie d'une recomposition des caractères précomposés
  • NFKC (Forme normale KC) : décomposition de compatibilité, c'est-à-dire remplace les caractères de compatibilité par leurs équivalents
  • NFKD (Forme normale KD) : décomposition de compatibilité suivie d'une décomposition canonique

Exemple avec Python 2.5 (Décompose puis Compose) :

>>> from unicodedata import normalize
>>> list(normalize('NFD', u'ä'))
[u'a', u'\u0308']
>>> list(normalize('NFC', u'a\u0308'))
[u'\xe4']

Conclusion

Unicode est un sacré bordel ! Les gens habitués à manipuler ASCII et son jeu de caractère ridicule de 128 codes sont perdus. Même ceux habitués à ISO-8859-15 flambant neuf avec son euro bling-bling (€) sont perdus car ils connaissent presque les 256 codes par cœur. Unicode contient plus d'un million de codes qu'on peut écrire d'un millier de manières différentes !

Néanmoins, c'est un pari sur l'avenir car on peut enfin mélanger sans broncher des textes dans n'importe quel langue. D'ailleurs, on peut changer l'ordre d'affichage pour les écritures de droite à gauche et inversement. Les encodages UTF-8, UTF-16 et UTF-32 simplifient la détection de l'encodage et sont plus simples que leurs ancêtres (ex: Shift JIS et ses nombreux codes de contrôle). UTF-8 est le charset ultime pour l'interopérabilité car il n'a pas de problème d'endian, est simple à détecter et peu coûteux en place pour l'encodage d'ISO-8859-1.