Comment obtenir ncurses pour sortir des caractères unicode du plan astral

J’ai le code suivant extrêmement simple, qui est censé générer (entre autres choses) trois caractères unicode:

/* * To build: * gcc -o curses curses.c -lncursesw * * Expected result: display these chars: * http://www.fileformat.info/info/unicode/char/2603/index.htm (snowman) * http://www.fileformat.info/info/unicode/char/26c4/index.htm (snowman without snow) * http://www.fileformat.info/info/unicode/char/1f638/index.htm (grinning cat face with smiling eyes) * * Looks like ncurses is NOT able to display second and third char * (only the first one is OK...) */ #include  #include  #include  int main (int argc, char *argv[]) { WINDOW *stdscr; char buffer[] = { '', '\0' }; setlocale (LC_ALL, ""); stdscr = initscr (); mvwprintw (stdscr, 0, 0, buffer); getch (); endwin (); /* output the buffer outside of ncurses */ printf("%s\n",buffer); return 0; } 

La printf finale affiche tous les caractères comme je l’attendais “” (étant donné que j’utilise des paramètres régionaux correctement configurés, un émulateur de terminal et des combinaisons de polices appropriées). Cependant, la première partie, qui est supposée afficher le texte L’utilisation des fonctions ncurses ne fonctionne pas correctement. Vous ne pouvez voir que le premier caractère (le bonhomme de neige) et les deux autres ne sont que des espaces. “”.

J’ai lu de nombreux posts sur Google disant que je devais aussi inclure

 #define _XOPEN_SOURCE_EXTENDED 1 

dans la source – mais cela n’a pas du tout changé la sortie pour moi.

Alors, est-ce que je fais quelque chose d’extrêmement stupide ici, ou est-ce que des taxes sont cassées lors de l’utilisation de certaines parties de l’espace unicode?

Ce n’est pas exactement que ncurses est cassé. Plus comme, la glibc est cassée. Ou quelle que soit l’implémentation de libc vous utilisez; Je suppose juste que c’est glibc .

Contrairement à la sortie de console simple ( printf , par exemple), ncurses besoin de connaître la largeur de chaque caractère lors de son impression, car il doit conserver son propre modèle d’affichage et de position du curseur. Tous les points de code Unicode ne font pas 1 unité de large, même avec une police proportionnelle: de nombreux points de code ont 1 unité de large (combinant des accents, par exemple), et quelques-uns ont une largeur de deux unités (idéogrammes han) [Note 1].

Il s’avère qu’il existe une fonction de bibliothèque C standard, wcwidth , qui prend un wchar_t et renvoie 0, 1 ou 2 (ou théoriquement tout entier, mais autant que ce soit la seule largeur implémentée) si le caractère est “imprimable”, et -1 si le caractère n’est pas valide ou s’il s’agit d’un caractère de contrôle. La version de ncurses wcwidth pour les caractères wcwidth utilise wcwidth pour prédire le déplacement du curseur après l’impression du caractère. Si wcwidth renvoie l’indication d’erreur, ncurses remplace un espace.

wcwidth lit la largeur à partir de la section WIDTH du tableau de charmap des parameters régionaux, mais cette définition ne fournit que les exceptions; tout caractère imprimable sans largeur définie est supposé avoir une largeur de 1. Donc, wcwidth doit également vérifier si le caractère est imprimable, ce qui est défini dans la LC_CTYPE l’ LC_CTYPE local LC_CTYPE . Ce sont les mêmes données qui iswprint fonction de bibliothèque iswprint .

Malheureusement, rien ne garantit que l’émulateur de terminal partage la même vue des données de caractères Unicode que les fonctions de la bibliothèque C. De plus, ncurses produira un comportement inattendu pour les caractères dont la largeur d’affichage réelle est différente de la largeur configurée selon les parameters régionaux.

Dans ce cas, la largeur ne pose aucun problème (les caractères ont une largeur de 1 unité, la valeur par défaut est donc correcte); le problème est que les caractères existent réellement dans la police de votre console et que vous souhaitez les utiliser, mais ils n’existent pas dans la firebase database de caractères de glibc , car cette firebase database est toujours basée sur Unicode 5.0 . (En fait, ce bogue lui-même devrait être mis à jour, car Unicode est maintenant à 6.3, pas à 6.1.)

Pour vous aider à comprendre cela, voici un tout petit programme qui exporte les informations de type de configuration configurées pour les points de code Unicode [Note 2]:

 #define _XOPEN_SOURCE 600 #include  #include  #include  #include  #include  #define CONC_(x,y) x##y #define IS(x) (CONC_(isw,x)(c)?#x" ":"") int main(int argc, char** argv) { setlocale(LC_CTYPE,""); for (int i = 1; i < argc; ++i) { wint_t c = strtoul(argv[i], NULL, 16); printf("Code %04X: width %d %s%s%s%s%s%s%s%s%s%s%s%s\n", c, wcwidth(c), IS(alpha),IS(lower),IS(upper),IS(digit),IS(xdigit),IS(alnum), IS(punct),IS(graph),IS(blank),IS(space),IS(print),IS(cntrl)); } return 0; } 

Comstackz-le, vous pouvez consulter vos données de personnage. Cela ressemble probablement à ceci:

 $ gcc -std=c11 -Wall -o wcinfo wcinfo.c $ ./wcinfo 2603 26c4 1f638 Code 2603: width 1 punct graph print Code 26C4: width -1 Code 1F638: width -1 

Alors que faire? Vous pourriez attendre que la firebase database glibc soit mise à jour, mais je suppose que cela n'arrivera pas de si tôt. Donc, si vous voulez vraiment utiliser ces caractères, vous devrez modifier vos propres définitions de parameters régionaux.

Si vous avez la même installation glibc que moi (et que les fichiers de parameters régionaux n’ont pas changé depuis un moment, vous le ferez probablement), vous trouverez vos fichiers de parameters régionaux dans /usr/share/i18n/locales et dans le /usr/share/i18n/locales réel. fichier de parameters régionaux, la section LC_CTYPE inclura la copy "i18n" directive copy "i18n" , ce qui signifie que la configuration actuelle du type est dans le fichier /usr/share/i18n/locales/i18n . Vous pouvez ensuite éditer ce fichier pour apporter les modifications appropriées. (Faites une copie de sauvegarde avant de modifier le fichier, bien sûr. Et vous aurez besoin de votre éditeur, car le fichier ne peut être écrit que par root.)

Recherchez d’abord la ligne qui commence par le graph , [Note 3], puis recherchez U26 avance (ligne 716 dans ma configuration, fwiw.). Vous trouverez une ligne avec une entrée qui ressemble à ..; , ce qui signifie que les points de code 26A0 à 26C3 sont des caractères graphiques (impression visible). Développez cette plage si nécessaire. (J'ai remplacé le 26C3 par le 26C4 pour un test minimal, mais vous souhaiterez peut-être inclure plus de caractères.) Quelques lignes plus bas, vous verrez les plages du deuxième graph ; ajoutez une entrée appropriée. (Encore une fois, étant minimaliste, j'ai ajouté une nouvelle ligne:

  ;/ 

mais vous voudrez probablement inclure une plage. (Au fait, le marqueur de fin / est le marqueur de continuation.)

Ensuite, descendez quelques lignes et vous trouverez la section print . Faites exactement les mêmes changements .

Ensuite, vous pouvez régénérer vos informations de localisation en exécutant:

 $ sudo locale-gen 

Et ensuite, vous pouvez tester:

 $ ./wcinfo 2603 26c4 1f638 Code 2603: width 1 punct graph print Code 26C4: width 1 graph print Code 1F638: width 1 graph print 

Une fois que vous avez fait cela, votre programme ncurses original devrait produire le résultat attendu.

En passant, vous pouvez utiliser des chaînes de caractères larges avec ncurses; vous n'avez pas à produire manuellement les codages UTF-8:

 int main (int argc, char *argv[]) { WINDOW *stdscr; setlocale (LC_ALL, ""); const wchar_t* wstr = L"<\u2603\u26c4\U0001F638>"; stdscr = initscr (); mvwaddwstr(stdscr, 0, 0, wstr); getch (); endwin (); return 0; } 

Remarques

  1. Pour plus d'informations, voir Wikipedia sur les formulaires demi-largeur et pleine largeur .

  2. C'est un programme rapide et fiable de vérification sans erreur, mais il suffit pour ce dont nous avons besoin ici. Pour la production, on voudrait quelques lignes de code supplémentaires 🙂

  3. Vous n'aurez peut-être pas besoin de corriger le type de graph ; print peut être suffisante. Je n'ai pas vérifié. J'ai fait les deux parce que ncurses aussi parfois besoin de savoir si les caractères sont transparents, et il semblait plus prudent de marquer le caractère comme visible, car il l'est.