Comment obtenir ncurses pour sortir des caractères Unicode plan astral

J'ai le morceau de code extrêmement simple suivant, qui est censé produire (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 <ncurses.h>
#include <stdio.h>
#include <locale.h>

int
main (int argc, char *argv[])
{
    WINDOW *stdscr;
    char buffer[] = {
        '<',
        0xE2, 0x98, 0x83,       // U+2603 : snowman: OK
        0xE2, 0x9B, 0x84,       // U+26C4 : snowman without snow: ERROR (space displayed)
        0xF0, 0x9F, 0x98, 0xB8, // U+1F638: grinning cat face: ERROR (space displayed)
        '>',
        '' };

    setlocale (LC_ALL, "");

    stdscr = initscr ();
    mvwprintw (stdscr, 0, 0, buffer);
    getch ();
    endwin ();

    /* output the buffer outside of ncurses */
    printf("%sn",buffer);
    return 0;
}

Le printf final affiche tous les caractères comme je m'y attendais "

25
demandé sur GodEater 2014-05-07 23:14:47

1 réponses

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

Contrairement à la sortie simple de la console (c'est-à-dire, printf), ncurses a besoin de savoir à quelle largeur chaque caractère est quand il est imprimé, car il a besoin de maintenir son propre modèle de ce que l'écran ressemble, et où le curseur est. Tous les points de code Unicode ne mesurent pas 1 unité de largeur, même avec une police proportionnelle: de nombreux points de code ont zéro Unité de largeur (combinant les accents, par exemple), et un bon nombre sont deux unités de large (idéographes 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 n'importe quel entier, mais afaik ce sont les seules largeurs implémentées) si le caractère est "imprimable", et -1 si le caractère est invalide ou un caractère de contrôle. La version à caractères larges de ncurses utilise wcwidth pour prédire jusqu'où le curseur se déplacera après la caractère est imprimé. Si wcwidth renvoie l'indication d'erreur, ncurses remplace un espace.

wcwidth lit la largeur de la section WIDTH des paramètres régionaux charmap, mais cette définition ne fournit que les exceptions; tout caractère imprimable sans largeur définie est supposé avoir une largeur de 1. Alors wcwidth doit également vérifier si le caractère est imprimable, ce qui est défini dans la spécification locale LC_CTYPE. Ce sont les mêmes données qui conduisent la bibliothèque iswprint fonction.

Malheureusement, il n'y a aucune garantie 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. Et pour les caractères dont les largeurs d'affichage réelles sont différentes de la largeur configurée en fonction des paramètres régionaux, ncurses produira un comportement inattendu.

Dans ce cas, il n'y a pas de problème avec la largeur (les caractères sont tous de 1 unité de large, donc la valeur par défaut est correcte); le problème est que les caractères existent réellement dans votre police de console et vous voulez les utiliser, mais ils n'existent pas dans la base de données de caractères de glibc, car cette base de données est toujours basée sur Unicode 5.0. (En fait, ce bug lui-même devrait être mis à jour, car Unicode est maintenant à 6.3, pas 6.1.)

Pour vous aider à voir cela, Voici un tout petit programme qui vide les informations ctype configurées pour les points de code unicode [Note 2]:

#define _XOPEN_SOURCE 600
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <wctype.h>
#include <wchar.h>

#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;
}

Compilez-le, vous pouvez regarder vos données de caractère. Il 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, quoi faire faire? Vous pouvez attendre que la base de données glibc soit mise à jour, mais je soupçonne que cela ne se produira pas de sitôt. Donc, si vous voulez vraiment utiliser ces caractères, vous devrez modifier vos propres définitions de paramètres régionaux.

Si vous avez la même installation glibc que moi (et que les fichiers de paramètres régionaux n'ont pas changé depuis un moment, donc vous le faites probablement), alors vous trouverez vos fichiers de paramètres régionaux dans /usr/share/i18n/locales et dans le fichier de paramètres régionaux réel, la section LC_CTYPE inclura la directive copy "i18n", ce qui signifie que le la configuration ctype réelle est dans le fichier /usr/share/i18n/locales/i18n. Vous pouvez ensuite modifier ce fichier pour apporter les modifications appropriées. (Faire une copie de sauvegarde avant de modifier le fichier, bien sûr. Et vous aurez besoin de {[32] } votre éditeur car le fichier est uniquement accessible en écriture par root.)

Trouvez D'abord la ligne qui commence graph, [Note 3], puis recherchez U26 (ligne 716 dans ma configuration, fwiw.) Vous trouverez une ligne avec une entrée qui ressemble à <U26A0>..<U26C3>;, ce qui signifie que les points de code 26A0 à 26C3 sont caractères graphiques (impression visible). Élargissez cette plage si nécessaire. (J'ai changé le 26C3 en 26C4 pour un test minimal, mais vous voudrez peut-être inclure plus de caractères.) Quelques lignes plus bas, vous verrez le deuxième plan graph gammes; ajoutez une entrée appropriée. (Encore une fois, étant minimaliste, j'ai ajouté une nouvelle ligne:

   <U0001F638>;/

Mais vous voudrez probablement inclure une plage. (La fin / est le marqueur de continuation, en passant.)

Ensuite, descendez quelques lignes de plus, et vous trouverez la section print. Faire exactement les mêmes modifications.

Ensuite, vous pouvez régénérer vos informations de paramètres régionaux en exécutant:

$ sudo locale-gen

Et puis 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 faites cela, votre programme ncurses d'origine devrait produire la sortie attendue.

En passant, vous pouvez utiliser des chaînes de caractères larges avec ncurses; vous n'avez pas besoin de produire manuellement des encodages 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;
}

Notes

  1. Pour pour plus d'informations, voir Wikipedia sur les formulaires halfwidth et fullwidth.

  2. C'est un programme de vérification sans erreur rapide et sale, mais c'est assez bon pour ce dont nous avons besoin ici. À des fins de production, on voudrait quelques lignes de code supplémentaires :)

  3. Vous ne pourriez pas besoin de fixer le graph wctype; print, peut être suffisant. Je n'ai pas vérifier. J'ai fait les deux parce que ncurses a parfois besoin de savoir si les caractères sont transparents, et il semblait plus sûr de marquer le caractère comme visible, puisqu'il est.

50
répondu rici 2014-05-08 06:14:06