Cómo hacer que ncurses emita caracteres Unicode del plano astral

8 minutos de lectura

Tengo la siguiente pieza de código extremadamente simple, que se supone que genera (entre otras cosas), tres caracteres 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)
        '>',
        '\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;
}

El printf final genera todos los caracteres como esperaría “<☃⛄😸>” (ya que estoy usando una configuración regional correctamente configurada, un emulador de terminal y combinaciones de fuentes apropiadas); sin embargo, la primera parte, que se supone que genera el texto el uso de funciones ncurses no funciona correctamente. Solo puede ver el primer carácter (el muñeco de nieve) y los otros dos se muestran como espacios. “<☃ >“.

He leído numerosas publicaciones de Google que dicen que también necesito incluir

#define _XOPEN_SOURCE_EXTENDED 1

en la fuente, pero hacerlo no ha cambiado la salida para mí en absoluto.

Entonces, ¿estoy haciendo algo sumamente estúpido aquí, o se rompen las ncurses cuando se usan algunas partes del espacio Unicode?

avatar de usuario
rico

no es exactamente eso ncurses está roto. Más como, glibc está roto. O cualquier implementación de libc Tu estas usando; Solo estoy asumiendo que es glibc.

A diferencia de la salida de consola simple (es decir, printf), ncurses necesita saber qué ancho tiene cada carácter cuando se imprime porque necesita mantener su propio modelo de cómo se ve la pantalla y dónde está el cursor. No todos los puntos de código Unicode tienen 1 unidad de ancho, incluso con una fuente proporcional: muchos puntos de código tienen cero unidades de ancho (combinando acentos, por ejemplo), y bastantes tienen dos unidades de ancho (ideografías Han) [Note 1].

Resulta que hay una función de biblioteca C estándar, wcwidthque toma un wchar_t y devuelve 0, 1 o 2 (o teóricamente cualquier número entero, pero afaik esos son los únicos anchos implementados) si el carácter es “imprimible”, y -1 si el carácter no es válido o es un carácter de control. La versión habilitada para caracteres anchos de ncurses usos wcwidth para predecir cuánto se moverá el cursor después de imprimir el carácter. Si wcwidth devuelve la indicación de error, ncurses sustituye un espacio.

wcwidth lee el ancho desde el WIDTH sección del local charmap, pero esa definición solo proporciona las excepciones; se supone que cualquier carácter imprimible sin un ancho definido tiene un ancho de 1. Entonces wcwidth además necesita verificar si el carácter es imprimible, lo cual se define en el LC_CTYPE especificación local. Esos son los mismos datos que impulsan el iswprint función de biblioteca.

Desafortunadamente, no hay garantía de que el emulador de terminal comparta la misma vista de los datos de caracteres Unicode que las funciones de la biblioteca C. Y para los caracteres cuyos anchos de visualización reales son diferentes del ancho configurado en la configuración regional, ncurses producirá un comportamiento inesperado.

En este caso, no hay problema con el ancho (todos los caracteres tienen 1 unidad de ancho, por lo que el valor predeterminado es correcto); el problema es que los caracteres realmente existen en la fuente de su consola y desea usarlos, pero no existen en glibcla base de datos de personajes de , porque esa base de datos es todavía basado en Unicode 5.0. (De hecho, ese error en sí debería actualizarse, porque Unicode ahora está en 6.3, no en 6.1).

Para ayudarlo a ver eso, aquí hay un pequeño programa que descarga la información de ctype configurada para los puntos de código 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;
}

Compílelo, puede ver los datos de su personaje. Probablemente se vea así:

$ 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 

¿Entonces lo que hay que hacer? Podrías esperar a que glibc base de datos para que se actualice, pero sospecho que eso no sucederá pronto. Entonces, si realmente desea usar esos caracteres, deberá modificar sus propias definiciones de configuración regional.

si tienes lo mismo glibc instalación como lo hago yo (y los archivos de configuración regional no han cambiado durante un tiempo, por lo que probablemente sí), encontrará sus archivos de configuración regional en /usr/share/i18n/locales y en el archivo de configuración regional real, el LC_CTYPE sección incluirá la directiva copy "i18n"lo que significa que la configuración real de ctype está en el archivo /usr/share/i18n/locales/i18n. Luego puede editar ese archivo para hacer los cambios apropiados. (Haga una copia de seguridad antes de cambiar el archivo, por supuesto. Y tendrá que sudo su editor porque el archivo solo puede ser escrito por root.)

Primero encuentra la línea que comienza graph, [Note 3] y luego buscar hacia adelante para U26 (línea 716 en mi configuración, fwiw). Encontrará una línea con una entrada que se parece a <U26A0>..<U26C3>;lo que significa que los puntos de código 26A0 a través de 26C3 son caracteres gráficos (impresión visible). Amplíe ese rango según sea necesario. (Cambié el 26C3 para 26C4 para una prueba mínima, pero es posible que desee incluir más caracteres). Unas pocas líneas más abajo, verá el segundo plano graph rangos; agregar una entrada apropiada. (Nuevamente, siendo minimalista, agregué una nueva línea:

   <U0001F638>;/

pero probablemente querrá incluir un rango. (El final / es el marcador de continuación, por cierto.)

A continuación, baje un par de líneas más y encontrará el print sección. Hacer exactamente los mismos cambios.

Luego puede regenerar su información local ejecutando:

$ sudo locale-gen

Y luego puedes probar:

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

Una vez que haga eso, su programa ncurses original debería producir el resultado esperado.

Por cierto, puede usar cadenas de caracteres anchas con ncurses; no tiene que producir manualmente codificaciones 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;
}

notas

  1. Para obtener más información, consulte Wikipedia en formularios de ancho medio y ancho completo.

  2. Es un programa rápido y sucio sin verificación de errores, pero es lo suficientemente bueno para lo que necesitamos aquí. Para fines de producción, uno querría algunas líneas más de código 🙂

  3. Es posible que no necesite arreglar el graph tipowc; print podría ser suficiente. No revisé. Hice las dos porque ncurses a veces también necesita saber si los caracteres son transparentes, y parecía más seguro marcar el carácter como visible, ya que lo es.

  • Esta es solo una respuesta asombrosamente completa. ¡Muchas gracias!

    – Comedor de dioses

    8 mayo 2014 a las 16:26

  • ¡Sorprendente en verdad! Algunas buenas noticias: ese error se solucionó recientemente y glibc ahora está actualizado a Unicode 7.0 🙂

    – Mestre León

    11 de marzo de 2015 a las 7:10

  • Ya era hora 😉 Sin embargo, aún desearía tener más de un voto a favor para dar rici. Esta es la mejor respuesta que he recibido a una pregunta sobre Stackoverflow. Realmente me derribó.

    – Comedor de dioses

    12 de marzo de 2015 a las 14:28

  • Buena pregunta; respuesta sobresaliente.

    – ulidtko

    5 de junio de 2019 a las 12:42

  • Esta es una de las respuestas más completas y de mayor calidad que he encontrado en SO. Gracias por tu tiempo.

    – eepp

    27 de junio de 2020 a las 11:27

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad