Cours PLURITAL 2005-2006

Cours n° 5 (6 décembre 2005)

Jean-François Perrot

Unicode

Trois axes
Organisation du catalogue
Promenade à travers le BMP
Généralités sur la représentation en machine
UTF-8 : Principe et mise en œuvre

Résumé : Qu'est-ce qu'Unicode ? (par lui-même) et Que dit Wikipédia ? (meilleur en anglais)

Références livresques :
Bernard Desgraupes, Passeport pour Unicode
et les chapitres 2 à 5 de Fontes & codages, de Yannis Haralambous
Références en ligne :
La maison-mère : http://www.unicode.org/
Le site Web d'Alan Wood, qui offre une aide efficace : http://www.alanwood.net/unicode/
  1. Trois axes principaux

    Le standard Unicode met en exergue 10 principes, dont l'exégèse ne manque pas d'intérêt
    (Desgraupes, chap. 1,  Haralambous, chap. 2).
    De notre point de vue d'utilisateurs, je retiens les trois points suivants :

    1. Universalité

      • Tous les systèmes d'écriture textuelle du monde (pas tous les répertoires de signes).
      • Convergence avec la norme ISO/IEC 10646.
      • Négociation permanente avec les instances réglementaires,
        mais le consortium reste fermement entre des mains américaines...
    2. Abandon de l'équation un caractère = un octet

      • Un total de 1 114 112 caractères potentiels (= 17 ×  65.536, 17 plans de 216 places chacun)
        En hexadécimal, de 0x000000 à 0x10FFFF.
        (Donc en principe 3 octets, mais les  ordinateurs ne raisonnent que par puissances de 2)
      • Pour la pratique courante le premier plan suffit ! BMP = Basic Multilingual Plane
      • Plusieurs formats de représentation en machine employant de 1 à 4 octets.
    3. Une véritable élaboration théorique du concept de caractère

      • Un catalogue de caractères qui est une base de données accessible en ligne.
      • Chaque caractère y est identifié par son numéro (en décimal ou en hexa -
        Desgraupes préfère employer le terme anglais code-point),
        et aussi par son nom (en anglais et en petites capitales) :
        p. ex. n° 304 = x0130 LATIN CAPITAL LETTER I WITH DOT ABOVE
      • Ces données sont exploitées par des algorithmes complètement spécifiés dans la norme.

  2. Organisation du catalogue Unicode


  3. Promenade dans le BMP

    Nous proposons ci-après une sélection de sujets qui nous intéressent particulièrement.
    Les questions relatives aux écritures CJKV n'y figurent pas, pour cause d'incompétence.
    Pour chacun d'etre eux, nous renvoyons aux pages d'Alan Wood qui traitent les différents blocs concernés.
    Si vous constatez que votre machine ne possède pas de police pouvant afficher les caractères en question,
    empressez-vous d'y remédier, en suivant les conseils d'Alan Wood !
    1. L'alphabet latin et ses extensions

      1. Latin de base = ASCII

      2. Supplément Latin-1 = ISO-8859-1

      3. Latin étendu A = la plupart des caractères des langues européennes

      4. Latin étendu B (nécessaire pour le roumain, par exemple)

      5. Extensions supplémentaires (en général, symboles utilisés dans les notations phonétiques
        et dans les transcriptions savantes, p. ex. ḫ pour le خ arabe)

      6. On peut lui rattacher l'alphabet phonétique international (IPA)

    2. L'alphabet grec, ancien et moderne, et ses dérivés

      1. Le grec moderne ayant adopté le système d'accentuation monotonique
        est plus simple à écrire que le grec ancien dont les trois accents, les deux esprits
        et le iota souscrit posent de redoutables problèmes typographiques.
        [La documentation en ligne relative à l'alphabet grec est surabondante.
          Sur la différence entre les notations monotonique et polytonique (sujet sensible qui
          se rattache historiquement à la
        querelle de la langue), voir l'article de Wikipédia en anglais.
          Pour un exemple d'écriture monotonique, voyez Wikipédia en grec : Ελληνική γλώσσα
          On trouve sur le réseau plusieurs sites qui proposent des collections plus ou moins complètes
          de textes grecs anciens en Unicode polytonique. Mon préféré est la
          Bibliotheca Augustana.
        ]

      2. L'alphabet cyrillique vient juste après le grec moderne dans le catalogue Unicode.

      3. L'alphabet arménien et l'alphabet géorgien sont tous deux dérivés du grec.
        Ils sont géographiquement voisins mais ils notent des langues profondément différentes...
        Je peux vous en dire plus sur le système graphique de l'arménien, avec quelques exemples
        et liens utiles. Je compte bientôt faire de même pour le géorgien.
    3. Les alphabets notant des langues sémitiques : arabe, hébreu et éthiopien

      1. L'alphabet arabe s'écrit de droite à gauche, bien que les caractères apparaissent de droite à gauche
        dans le fichier, grâce à l'algorithme bidirectionnel dont nous reparlerons.
        Il maintient dans sa forme imprimée des formes cursives (exemple), qui ne sont pas enregistrées
        comme caractères, au prix d'un travail demandé au logiciel de visualisation,
        dont nous reparlerons également.
        Il sera aussi à écrire le persan et l'ourdou, qui sont des langues indo-eurpoéennes.

      2. L'alphabet hébreu s'écrit aussi de droite à gauche, mais ses lettres sont bien séparées.
        Quatre lettres prennent une forme différente en position finale.
        D'une manière diamétralement opposée au traitement de l'arabe,
        ces formes finales sont enregistrées comme des caractères Unicode à part entière.
        Exemple.

      3. Le syllabaire éthiopien s'écrit de gauche à droite !
        Outre l'amharique, langue nationale de l'Éthiopie (exemple),
        il sert à noter le guèze, langue religieuse, et le tigrigna, langue nationale de l'Érythrée (exemple).
        [On peut être surpris de ne trouver pas moins de cinq (5) pages en écriture éthiopienne
        parmi les quelque cinquante traductions publiées pour
        What is Unicode ? (état du 10/12/05):
        en dehors de l'amharique et du tigrigna déjà cités, on voit en effet
        le blin, le sebatbeit et le xamtanga, ces deux derniers demandant
        des caractères supplémentaires ajoutés avec la version Unicode 4.1. 
        Le blin est une langue couchitique parlée en Érythrée, encore peu écrite, mais activement défendue
        par des groupes d'émigrés vivant en Scandinavie.
        Le xamtanga est une autre langue couchitique parlée en Éthiopie par 150 000 personnes.
        Quant au sebatbeit, Google ne m'a pas livré à son sujet d'information digne d'être ici transmise.
        ]

    4. Les écritures indiennes

      Elles dérivent toutes d'une source commune, l'écriture brahmi,
      y compris  les écritures qui notent les langues dravidiennes.
      Comme cette dernière, ce sont des écritures syllabiques avec une voyelle implicite a,
      et elles notent l'absence de voyelle par des procédés de ligature
      qui compliquent singulièrement leur emploi (nous en reparlerons).

      1. La plus célèbre est la devanâgari (divine et urbaine), qui est employée
        par le hindi et le marathi.
        Le sanscrit, qui s'est autrefois écrit dans toutes les écritures indiennes,
        est aujourd'hui imprimé exclusivement en nâgari.

      2. Voici, à titre d'échantillon, deux écritures qui sont très proches de la devanâgari,
        la gujarati (qui est en somme une nâgari dont on a supprimé le trait horizontal),
        et la gurmukhi (une des écritures du punjabi, celle des Sikhs).

  4. Représentation en machine : généralités

    1. UTF-32, UTF-16, UTF-8

      Une fois abandonnée l'équation un caractère = un octet, la question se pose du format de représentation.
      Pour des raisons historiques, les différents formats en usage portent le nom générique de Unicode Transfer Format,
      alias UTF.

      • Si on veut un format de taille fixe (nombre fixe d'octets pour chaque caractère),
        le seul choix compatible à la fois avec le nombre de caractères à représenter (1 114 112)
        et avec la technologie informatique est celui de 4 octets, ou 32 bits, appelé UTF-32.
        Il suffit d'écrire le numéro du caractère en tant que nombre entier sur 32 bits, donc (vu la taille de ce nombre)
        avec un premier octet nul dans tous les cas, et un second octet également nul pour tous les caractères
        du Basic Multilingual Plane.
        Ce procédé a l'avantage de la simplicité conceptuelle, et l'inconvénient de prendre de la place
        (4 fois plus qu'un codage sur 8 bits).

      • Si on accepte un format de taille variable (nombre d'octets dépendant du caractère considéré),
        on a le choix entre un module de base de deux octets et un module de base d'un seul octet.

        1. Si on choisit un module de deux octets, soit 16 bits : UTF-16
          on utilise un seul module pour  tous les caractères du BMP,
          et deux modules (quatre octets) pour ceux des autres plans.
          Plus compliqué, mais plus économique en place, surtout si on sort peu du BMP.

        2. Si on choisit un module d'un octet, soit 8 bits : UTF-8
          on utilise un seul module pour les caractères ASCII (7 bits),
          et deux, trois ou quatre modules (octets) pour les autres.
          Nettement plus compliqué, mais encore plus économique en place,
          et surtout compatible avec l'ASCII !
          Tout fichier ne contenant que des caractères ASCII 7 bits est ipso facto un fichier UTF-8.
          Avantage considérable en pratique...
    2. Quel est le but poursuivi ? Travail local ou communication à distance ?

      Il faut distinguer deux orientations dans la représentation des caractères Unicode en machine, selon le but poursuivi :
      • travail local (édition, au sens anglais du terme,
        ou plus généralement toute espèce de calcul sur le texte)
      • communication : réalisation d'un fichier qui sera envoyé à travers le réseau à d'autres machines.

      Ces deux ordres d'activité ne sont pas soumis aux mêmes contraintes techniques !
      • le travail local ne connaît à peu près aucune entrave, vu la puissance de calcul des machines modernes ;
      • la communication en revanche doit se préoccuper
        1. des différences de comportement entre machines (hétérogénéité des processeurs)
        2. des conventions en vigueur sur le réseau.

        1. L'hétérogénéité des processeurs se manifeste dans l'ordre de traitement des octets
          (processeurs gros-boutiens et petit-boutiens) ce qui oblige à des contorsions pour indiquer
          l'option choisie dans un fichier codé en UTF-16 ou en UTF-32 (à l'aide d'un BOM : Bit Order Mark).
        2. Les conventions en vigueur visent les jeux de caractères exigés ou attendus par les
          différents protocoles de transmission (par exemple l'ASCII 7 bits pour le protocole SMTP).
          En ce qui nous concerne, le point important est le choix d'UTF-8 comme jeu de caractères
          par défaut dans le format XML.

      Nous laisserons de côté les préoccupations relatives au travail local, en nous bornant à constater que
      les logiciels traitant des textes utilisent souvent UTF-16 ou UTF-32 pour représenter les caractères
      en mémoire centrale (buffer). Pour en savoir plus sur ces codages, voyez Wikipedia en anglais (UTF-16, UTF-32).
      Nous nous concentrons ici sur les questions de communication, pour lesquelles UTF-8 est le codage
      de choix, en raison de sa diffusion croissante et de sa compatibilité avec l'ASCII "pur".

  5. UTF-8 : principe et mise en œuvre

    1. Principe

      1. On part de l'idée de les 128 premiers caractères (ASCII) sur un seul octet, comme par le passé.
        Ces octets ont comme caractéristique d'avoir un premier bit nul.

      2. Tous les autres caractères seront représentés par au moins deux octets.
        Il faut alors distinguer le premier octet du (ou des) octet(s) suivant(s).
        Ils ne peuvent pas commencer par "0" car c'est le privilège des octets ASCII.
        On décide que :
        • les octets suivants commenceront par "10"
        • le premier octet commencera par "110" ou par "1110" ou par "11110", etc,
          le nombre de "1" initiaux donnant le nombre total d'octets dans la représentation du caractère
          (en pratique, 2, 3 ou 4) .
        • Exemples : trois octets 11100000 10100100 10001011
          deux octets 11001110 10110001
          mais un seul octet 01111000 (ASCII : on reconnaît la lettre minuscule "x")

      3. Ainsi, chacun des octets suivants peut porter 6 bits utiles (les 2 premiers étant fixés)
        tandis que le premier octet peut en porter 5, 4 ou 3, suivant le nombre de ses suivants.
        Avec ce système, un code UTF-8 de deux octets porte 11 bits utiles (5+6),
        (suivant le "squelette" 110xxxxx 10yyyyyy)
        un code UTF-8 de trois octets porte 16 bits utiles (4+6+6),
        (suivant le "squelette" 1110xxxx 10yyyyyy 10zzzzzz)
        et un code UTF-8 de quatre octets porte 21 bits utiles (3+6+6+6),
        (suivant le "squelette" 11110xxx 10yyyyyy 10zzzzzz 10tttttt)
        ce qui suffit largement pour les 17 plans Unicode.

      4. Pour déterminer le code UTF-8 d'un numéro Unicode donné, il suffit de :
        1. écrire ce numéro en binaire, et supprimer les zéros initiaux ;
          en fonction du nombre de bits utiles, déterminer le nombre d'octets UTF-8 ;
        2. décomposer le binaire de droite à gauche en tranches de 6 bits,
          plus une tranche de 5, 4 ou 3 (en complétant au besoin par des zéros à gauche) ;
        3. et "remplir" le squelette décrit en 3.

      5. Exemples :
        • DEVANAGARI LETTER VOCALIC R, n° 2315 = x090B
          en binaire 00001001 00001011, 12 bits utiles, il faut donc 3 octets UTF-8
          squelette  1110xxxx 10yyyyyy 10zzzzzz
          décomposition 4+6+6 : 0000 + 1001 00 + 00 1011
          résultat : 11100000 10100100 10001011
        • GREEK SMALL LETTER ALPHA, n° 945 = x03B1
          en binaire 00000011 10110001, 10 bits utiles, donc 2 octets UTF-8,
          squelette  110xxxxx 10yyyyyy
          décomposition 5+6 : 011 10 + 11 0001
          résultat : 11001110 10110001

      6. Propriétés du codage UTF-8 :

        1. Le dernier chiffre hexadécimal du numéro Unicode d'un caractère se retrouve identique
          comme dernier chiffre de sa représentation en UTF-8.

        2. Plus le numéro Unicode est grand, plus le code UTF-8 correspondant, vu comme un nombre entier,
          est grand.
          En termes mathématiques, le codage UTF-8 est une application injective des entiers dans les entiers
          qui est croissante par rapport à l'ordre naturel.

        3. Conséquences pratiques
          • Les caractères de numéros inférieurs à 128 sont représentés en UTF-8 sur un octet (ASCII).
          • Ceux dont les numéros sont compris entre 128 et 2047 (= x07FF = 0000 0111 1111 1111),
            par deux octets :
            c'est le cas de toutes les versions de l'alphabet latin énumérées ci-dessus
            (donc de toutes nos "lettres accentuées"), du grec monotonique (mais pas polytonique),
            du cyrillique, de l'arménien (mais pas du géorgien),
            de l'hébreu et de l'arabe (mais pas de l'éthiopien).
          • Tout le reste du BMP (entre 2048 et  65535) est codé sur trois octets.
            Voyez par exemple le cas de l'écriture devanâgari.
          • Les caractères appartenant aux plans supérieurs sont codés sur quatre octets.

    2. Notation et expérimentation :

      • Répétons qu'il faut bien distinguer

        1. la désignation du caractère (nommer le caractère)
          • par son n° (en décimal ou en hexa) suivnt une syntaxe qui dépend du contexte :
            • en HTML/XML : &#lenumérodec; ou &#xlenumérohex;
            • en Java : '\ulenumérohex'
            • en JavaScript (qui ne connaît pas les constantes de type caractère) :
              par la fonction chr appliquée au numéro vu comme une valeur entière,
              fonction qui renvoie une chaîne monocaractère
              donc chr(lenumérodec) ou chr(0xlenumérohex)
          • par son nom officiel (en Perl)
          • Exemple : le caractère Unicode n° 304 = x0130 LATIN CAPITAL LETTER I WITH DOT ABOVE
            peut être désigné en HTML/XML par İ ou par İ, en Java par '\u0130'
            et en JavaScript par chr(304) ou par chr(0x130)

        2. sa réalisation matérielle
          laquelle prend deux aspects (dissymétriques) :
          • codage en octets (ici, UTF-8)
            Exemple : LATIN CAPITAL LETTER I WITH DOT ABOVE ==> sur 2 octets C4B0
          • affichage via une police (à condition qu'elle soit présente sur la machine !)
            Exemple : LATIN CAPITAL LETTER I WITH DOT ABOVE ==> İ

        Pour marquer cette distinction, nous écrirons désormais les octets des réalisations en UTF-8
        entre chevrons (comme ils sont produits par certains logiciels) :
        Exemple : LATIN CAPITAL LETTER I WITH DOT ABOVE ==> en UTF-8 sur 2 octets <C4><B0>

      • Exemple à bien comprendre : Voici le même texte
        Ça, c'est mon frère René et ça c'est ma sœur Iñès.
        d'abord en deux codages à 8 bits, Mac Roman et iso-8859-1 (Windows) :
        On peut constater que, dans les deux cas, les caractères non-ASCII sont représentés
        par un seul octet, mais qu'à chaque fois ce sont des octets différents !
        • <82>a, c'est mon fr<8F>re Ren<8E> et <8D>a c'est ma s<CF>ur I<96><8F>s.
        • <C7>a, c'est mon fr<E8>re Ren<E9> et <E7>a c'est ma s<9C>ur I<F1><E8>s.

        Et le voici en UTF-8, montrant clairement les deux octets nécessaires pour tout caractère non-ASCII :

        <C3><87>a, c'est mon fr<C3><A8>re Ren<C3><A9> et <C3><A7>a c'est ma s<C5><93>ur I<C3><B1><C3><A8>s.

      • Pour faciliter l'expérimentation, voici un outil qui vous permettra de confronter les différents aspects
        d'un caractère Unicode :
        1. son numéro que vous pouvez donner en décimal ou en hexa, et voir en binaire
        2. sa représentation UTF-8, en hexa (notation à chevrons) et en binaire
        3. son nom officiel (en lecture seule).
        Notez que les informations que vous tapez dans un des champs ouverts en écriture
        ne sont prises en compte que lorsque vous cliquez une première fois en dehors de ce champ.
    3. Mise en œuvre

      Entrer & sortir :
      les machines modernes avec écran et clavier nous laissent croire que lire et écrire ne font qu'un
      c'est faux !
      L'écriture (fabrication d'un fichier) et la lecture (interprétation) sont deux processus dissymétriques.
      Cette dissymétrie est masquée par la puissance des machines et par la bonne adéquation des outils (clavier - écran - logiciels).
      Mais la puissance est relative et l'adéquation dépend du but poursuivi !

      • Côté entrée : deux étapes successives

        1. Écriture dans un outil d'édition par tout moyen à votre disposition
          (ils sont nombreux : menu d'insertion, donnée explicite du nom du caractère, clavier, palette, etc).
          • Note technique : cette écriture fait entrer les caractères dans le buffer de l'outil,
            qui loge en mémoire centrale en le recodant.
            Notez que l'utilisateur n'est en général pas informé de ce codage interne,
            (l'éditeur JEdit faisant exception : il déclare ouvertement
            que sa représentation interne est UTF-16).

        2. Sauvegarde dans un fichier sur disque en spécifiant le codage UTF-8.

          Notez que les modalités de cette spécification varient beaucoup avec le logiciel employé.
          Par exemple, sur Mac OS X :

          • L'éditeur de textes TextEdit répond à la commande Fichier>Enregistrer sous...
            par une fenêtre de dialogue où la question du codage est posée clairement.

          • L'éditeur HTML Netscape Composer en revanche distingue Fichier>Enregistrer sous...
            de Enregistrer en changeant de codage
            et il adopte un comportement diversifié suivant le choix de l'utilisateur.
            L'analyse de ce comportement n'est pas sans intérêt...

          • La version 1 de Microsoft Word pour Mac OS X répond à la commande Fichier>Enregistrer sous...
            par une fenêtre de dialogue où la question du codage est posée comme cas particulier de format
            le choix de Texte unicode conduisant à une sauvegarde en format texte UTF-16.

          • L'éditeur de textes JEdit distingue explicitement
            • le buffer en mémoire centrale (où les caractères sont rerpésentés en UTF-16)
            • du fichier sur disque, dont le codage courant est affiché au bord inférieur de la fenêtre
              et peut être modifié par une commande adéquate (voir la documentation)

          Il convient donc de se renseigner soigneusement sur l'usage du logiciel employé,
          de préférence avant d'avoir à sauvegarder son travail en urgence,
          car un mauvais choix de codage peut conduire à la perte irrémédiable de l'information acquise...

      • Côté sortie : avertir le logiciel de traitement qu'il doit "lire" en UTF-8

        1. pour un éditeur prenant un fichier sur le disque,
          à l'ouverture du fichier (en général, codage par défaut donné dans les préférences de l'outil).
          Là aussi les mœurs des logiciels sont diverses, et il convient de chercher où l'information pertinente doit être fournie...
          Tous n'ont pas la limpidité de TextEdit...

        2. pour un logiciel recevant le fichier par le réseau,
          par un message spécifique avec un type MIME comme text/plain ou text/xml
          assorti de la mention charset="UTF-8".

          • Pour un courrier envoyé par SMTP, on dira dans un en-tête (header) :
            Content-Type: text/xml; charset="UTF-8"

          • Dans un fichier HTML, on va ajouter comme premier élément de la partie <head> un élément ainsi conçu:
            <meta http-equiv="Content-Type" content="text/html; charset="UTF-8" />
            en observant que tous les caractères précédents, à savoir  <DOCTYPE....> <html xmlns:...><head>
            sont de l'ASCII 7 bits, donc déchiffrables sans ambiguïté.

          • Toujours en HTML, dansun formulaire (balise <form>) pour s'assurer que les chaînes tapées
            par l'utilisateur sont transmises au serveur en UTF-8 (et non pas selon la fantaisie du navigateur),
            il convient d'ajouter dans la balise l'attribut accept-charset="UTF-8".