URL: https://linuxfr.org/users/sygne/journaux/jouons-avec-unicode-tchars-un-dchars-pour-troff Title: Jouons avec Unicode: Tchars, un Dchars pour Troff Authors: Sygne Date: 2013年10月31日T15:02:22+01:00 License: CC By-SA Tags: troff, utroff, dchars, unicode, utf8 et programmation Score: 22 Vous n'avez pas manqué de suivre ici les aventures de [Dchars](https://linuxfr.org/news/dchars-pour-lire-ecrire-et-modifier-des-caracteres-unicodes-complexes), de notre ami [Xavier Faure](https://linuxfr.org/users/suizokukan). Dchars permet entre autres de transformer une suite codifiée de caractères ascii en caractères utf8 complexes. Par exemple, il transforme le beta-code en grec polytonique. Dans ce journal, vous découvrirez l'histoire de Tchars, le sosie de Dchars. # Tchars, un Dchars pour Troff # Ayant justement besoin d'écrire quelques mots en grec polytonique dans ma thèse, je me suis penché dans les sources de Dchars, pour voir si je ne pouvais pas le modifier pour en faire un pré-processeur pour troff, mon logiciel de formatage de texte, et l'ajouter aux logiciels du projet [Utroff](http://utroff.org). Malheureusement, Dchars est écrit en python, et je rechignais à ajouter python aux dépendances d'Utroff. Plutôt que de modifier Dchars, je me suis donc lancé dans une ré-écriture en C des fonctionnalités de Dchars qui m'intéressaient. Et cela m'a beaucoup amusé, puisque ça a été l'occasion pour moi de comprendre un peu Unicode, l'utf8, l'hexadécimal et les opérations binaires. Il me semble que cela vous amusera beaucoup aussi, alors puisqu'il me faut publier quelque part ce savoir encore frais pour le jour prochain ou je l'oublierai, entrons dans les entrailles de Tchars. Tchars, qui signifie *Troff|Translate characters*, a beaucoup moins de fonctionnalités que Dchars. Mais il fait bien son travail qui consiste à transformer une forme simplifiée de beta-code en grec polytonique. C'est un programme finalement assez simple, écrit en 600 lignes de C, dont 300 ne font que définir les correspondances entre le beta-code et le code unicode. # Du Beta-code à l'unicode # Cette liste de correspondances est d'ailleurs générique car générée automatiquement. La fondation unicode fournit en effet un fichier [UnicodeData.txt](http://www.unicode.org/Public/5.0.0/ucd/UnicodeData.txt) qui liste l'ensemble des caractères unicode, et indique entre autres leur nom ainsi que leur code hexadécimal. J'ai donc commencé par écrire un script shell appuyé sur sed qui filtre ce fichier pour en extraire les caractères grecs (repérés par le nom GREEK), effacer les lignes inutiles, substituer les noms complets par leur correspondance en beta code (s/SMALL LETTER ALPHA/a/g;), substituer le nom des tons par leur correspondance en beta code (s/DIALYTIKA/+/g;), mettre le tout en forme, et trier la liste pour l'utiliser dans une structure en C. Au final, j'ai une longue liste de 300 lignes de correspondances entre code ascii et valeur hexadécimale, de la forme : ``` C struct runelist { char *tag; unsigned hexa; }; runelist alphabeta[] = { /* This list must be sorted */ {"A", 0x0391,}, {"A&", 0x1FB9,}, {"A'", 0x1FB8,}, {"A(", 0x1F09,}, {"A(/", 0x1F0D,}, {"A(=", 0x1F0F,}, {"A(\\", 0x1F0B,}, {"A)", 0x1F08,}, {"A)/", 0x1F0C,}, {"A)=", 0x1F0E,}, {"A)\\", 0x1F0A,}, {"A/", 0x0386,}, {"A/", 0x1FBB,}, {"A\\", 0x1FBA,}, } ``` # De l'unicode à l'utf8 : généralités # Sur cette base, il a été possible d'écrire le code C. L'ensemble est assez banal : une fonction lit le texte en entrée, repère les lignes où il y a du code à translittérer, et pour chaque suite de caractères en beta-code, un binary search recherche le code hexadécimal correspondant. On pourrait en rester là, car Troff peut transformer lui-même ce code hexadécimal en un caractère utf8. Mais lorsque j'ai compris qu'écrire une fonction qui fasse cette transformation n'était pas hors de ma portée, je me suis jeté à l'eau. Pour comprendre la dite fonction, il faut savoir qu'un caractère utf8 est encodé sur 1, 2, 3, 4, 5 ou 6 bytes, selon la valeur de son code hexadécimal : ``` table First Last Bytes U+0000 U+007F 1 U+0080 U+07FF 2 U+0800 U+FFFF 3 U+10000 U+1FFFFF 4 U+200000 U+3FFFFFF 5 U+4000000 U+7FFFFFFF 6 ``` Le premier byte d'un caractère utf8 indique combien de bytes composent le caractère. Chacun des bytes suivant commence par les bits 10, suivant cette table : ``` table Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 0xxxxxxx 110xxxxx 10xxxxxx 1110xxxx 10xxxxxx 10xxxxxx 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx ``` La table hexadécimale nous explique comment écrire chacun de ces bytes sous forme hexadécimale : ``` table 0 0000 8 1000 1 0001 9 1001 2 0010 A 1010 3 0011 B 1011 4 0100 C 1100 5 0101 D 1101 6 0110 E 1110 7 0111 F 1111 1100 0000 = C0 1110 0000 = E0 1111 0000 = F0 1111 1000 = F8 1111 1100 = FC 1000 0000 = 80 ``` # De l'unicode à l'utf8 : implémentation # Ainsi, pour chaque code unicode indiqué en hexadécimal, il faut commencer par repérer de combien de bytes est composé le caractère. Pour cela, il suffit de quelques tests : ``` C void hexatochars(unsigned hexa) { if (hexa < 0x0080) { /* 1 byte */ } else if (hexa < 0x800) { /* 2 bytes */ } else if (hexa < 0x10000) { /* 3 bytes */ } else if (hexa < 0x200000) { /* 4 bytes */ } else if (hexa < 0x4000000) { /* 5 bytes */ } else if (hexa < 0x7FFFFFFF) { /* 6 bytes */ } else { /* hors unicode */ } } ``` Sitot le nombre de bytes à construire connu, il n'y a plus qu'à construire ces bytes un par un, par des opérations binaires. L'opération générale consiste à décaler, mettre à zero, combler ces zeros. Prenons l'exemple d'un caractère unicode à deux bytes. Il aura la forme : 110xxxxx 10xxxxxx. On commence par compter le nombre de petits x : il y en a 11, ce qui signifie que le code hexadécimal du caractère unicode a donc 11 bits : xxxxxxxxxxx. Pour composer le premier byte, on a besoin des 5 premiers bits. On décale donc tout vers la droite, pour en mettre 6 dehors et obtenir 000xxxxx : ``` C hexa>> 6 ``` Il faut en outre combler les trois premiers zeros par les trois premiers bits du premier byte, pour obtenir 110xxxxx. On utilise l'opérateur binaire | et le byte 11000000 (soit 0xc0 en hexadecimal) : ``` C ((hexa>> 6) | 0xc0) ``` Pour composer le second byte, on a besoin des 6 derniers bits du code hexadecimal. On ne conserve donc que les les 6 derniers avec l'opérateur binaire & et le byte 0011 1111 (soit 0x3F) : ``` C hexa & 0x3F ``` Puis on impose les deux premiers bits au second byte, avec l'opérateur binaire | et le byte 10000000 (soit 0x80) : ``` C ((hexa & 0x3F) | 0x80) ``` Petit à petit, on obtient la fonction suivante : ``` C void hexatochars(unsigned hexa) { char a=0, b=0, c=0, d=0, e=0, f=0; /* ** From U+000 to U+007F ** Utf8 is coded on 1 byte of the form : ** 0xxxxxxx */ if (hexa<0x0080) { a = hexa; // 0xxxxxxx } /* ** From U+0080 to U+07FF ** Utf8 is coded on 2 bytes of the form : ** 110xxxxx 10xxxxxx */ else if (hexa < 0x800) { a = ((hexa>> 6) | 0xC0); // 110xxxxx b = ((hexa & 0x3F) | 0x80); // 10xxxxxx } /* ** From U+0800 to U+FFFF ** Utf8 is coded on 3 bytes of the form : ** 1110xxxx 10xxxxxx 10xxxxxx */ else if (hexa < 0x10000) { a = ((hexa>> 12) | 0xE0); // 1110xxxx b = (( (hexa>> 6) & 0x3F) | 0x80); c = ((hexa & 0x3F) | 0x80); } /* ** From U+10000 to U+1FFFFF ** Utf8 is coded on 4 bytes of the form : ** 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx */ else if (hexa < 0x200000) { a = ((hexa>> 18) | 0xF0); // 11110xxx b = (((hexa>> 12) & 0x3F) | 0x80); c = (((hexa>> 6) & 0x3F) | 0x80); d = ((hexa & 0x3F) | 0x80); } /* ** From U+200000 to U+3FFFFFF ** Utf8 is coded on 5 bytes of the form : ** 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */ else if (hexa < 0x4000000) { a = ((hexa>> 24) | 0xF8); // 111110xx b = (((hexa>> 18) & 0x3F) | 0x80); c = (((hexa>> 12) & 0x3F) | 0x80); d = (((hexa>> 6) & 0x3F) | 0x80); e = ((hexa & 0x3F) | 0x80); } /* ** From U+4000000 to U+7FFFFFFF ** Utf8 is coded on 6 bytes of the form : ** 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx */ else if (hexa < 0x7FFFFFFF) { a = ((hexa>> 30) | 0xFC); // 1111110x b = (((hexa>> 24) & 0x3F) | 0x80); c = (((hexa>> 18) & 0x3F) | 0x80); d = (((hexa>> 12) & 0x3F) | 0x80); e = (((hexa>> 6) & 0x3F) | 0x80); f = ((hexa & 0x3F) | 0x80); } /* ** Out of unicode range */ else { fprintf(stderr, "tchars : [%x] is not in unicode range, file xxx, line %d\n", hexa, l); } printf("%c%c%c%c%c%c", a, b, c, d, e, f); } ``` # Conclusion # Il existe probablement une bibliothèque standard du C qui fait cette transformation: j'ai cherché un peu, mais ne l'ai pas trouvée... Si vous la connaissez, ou si, simplement, vous avez des choses à redire sur le code ci-dessus, utilisez les commentaires, je serais enchanté d'en apprendre un peu plus. Quoi qu'il en soit, [Dchars](http://94.23.197.37/dchars/doc/fr/index.html) a maintenant un compagnon, et [Utroff](http://utroff.org) intègre dorénavant un interpréteur de beta-code facilement extensible à d'autres langages. Pour le reste, vous pouvez consulter, modifier, publier tchars ([licence BSD à deux clauses](http://utroff.org/man/bsd2.html)) ou tout simplement le tester en téléchargeant [l'archive d'Utroff](http://download.tuxfamily.org/utroff/pub/) - Dchars : http://94.23.197.37/dchars/doc/fr/index.html - La liste des caractères unicodes : http://www.unicode.org/Public/5.0.0/ucd/UnicodeData.txt - Sur l'UTF-8 : https://en.wikipedia.org/wiki/UTF-8 - Sur les opérateurs binaires du C : http://www.bien-programmer.fr/bits.htm - Readme de Tchars : http://utroff.org/man/tchars-readme.html - Manuel de Tchars : http://utroff.org/man/tchars.html - L'archive d'Utroff contenant Tchars : http://download.tuxfamily.org/utroff/pub/

AltStyle によって変換されたページ (->オリジナル) /