viajes - Cómo hacer que ncurses emita caracteres unicode en el plano astral
videos del viajes astrales (1)
Tengo la siguiente pieza de código extremadamente simple, que se supone que produce (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 muestra todos los caracteres como esperaría "<☃⛄😸>" (ya que estoy usando un entorno local, un emulador de terminal y combinaciones de fuentes apropiadas configuradas correctamente); sin embargo, la primera parte, que se supone que debe mostrar el texto el uso de las funciones ncurses no funciona correctamente. Solo puedes ver el primer personaje (el muñeco de nieve), y los otros dos solo se representan 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 en absoluto.
Entonces, ¿estoy haciendo algo supremamente estúpido aquí, o está ncurses roto cuando uso algunas partes del espacio Unicode?
No es exactamente que ncurses
está roto. Más bien, glibc
está roto. O cualquier implementación de libc
que estés usando; Solo estoy asumiendo que es glibc
.
A diferencia de la salida simple de la consola (es decir, printf
), ncurses
necesita saber cuán ancho es 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 algunos tienen dos unidades de ancho (ideogramas Han) [Nota 1].
Resulta que hay una función de biblioteca C estándar, wcwidth
, que toma un wchar_t
y devuelve 0, 1 o 2 (o teóricamente cualquier 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 un carácter de control. La versión habilitada para caracteres anchos de ncurses
usa wcwidth
para predecir qué tan lejos se moverá el cursor después de que se imprima el carácter. Si wcwidth
devuelve la indicación de error, ncurses
sustituye un espacio.
wcwidth
lee el ancho desde la sección WIDTH
del charmap
de la configuración 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. Por wcwidth
tanto, wcwidth
también debe verificar para ver si el carácter es imprimible, que se define en la especificación regional LC_CTYPE
. Esa es la misma información que maneja la función de la biblioteca iswprint
.
Desafortunadamente, no hay garantía de que el emulador de terminal comparta la misma vista de los datos de caracteres Unicode a medida que funciona la biblioteca C. Y para los caracteres cuyo ancho de visualización real es diferente del ancho configurado por configuración regional, ncurses
producirá un comportamiento inesperado.
En este caso, no hay problema con el ancho (los caracteres son todos de 1 unidad de ancho, por lo que el valor predeterminado es correcto); el problema es que los caracteres existen realmente en la fuente de la consola y desea usarlos, pero no existen en la base de datos de caracteres de glibc
, porque esa base de datos todavía se basa 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 pequeño que volca la información de tipo c configurada para los puntos de código Unicode [Nota 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ílalo puedes mirar los datos de tu personaje. Probablemente se ve 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ía esperar a que la base de datos glibc
se actualice, pero sospecho que eso no va a suceder pronto. Entonces, si realmente desea usar esos caracteres, deberá modificar sus propias definiciones de configuración regional.
Si tiene la misma instalación de glibc
que yo (y los archivos de configuración regional no han cambiado durante un tiempo, por lo que probablemente sí lo haga), encontrará los archivos de configuración regional en /usr/share/i18n/locales
y en el local, la sección LC_CTYPE
incluirá la copy "i18n"
la directiva copy "i18n"
, lo que significa que la configuración actual de tipo 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 se puede escribir por root).
Primero encuentra la línea que inicia el graph
, [Nota 3] y luego busca el U26
(línea 716 en mi configuración, ahora). Encontrarás una línea con una entrada que se parece a <U26A0>..<U26C3>;
, lo que significa que los puntos de código 26A0
a 26C3
son caracteres gráficos (impresión visible). Expande ese rango según sea necesario. (Cambié el 26C3
al 26C4
para una prueba mínima, pero es posible que desee incluir más caracteres.) Algunas líneas más abajo, verá los rangos del segundo graph
plano; agregue una entrada apropiada. (De nuevo, siendo minimalista, agregué una nueva línea:
<U0001F638>;/
pero probablemente quieras incluir un rango. (El final /
es el marcador de continuación, por cierto).
Luego, baja un par de líneas más y encontrarás la sección de print
. Haga exactamente los mismos cambios .
Luego puede regenerar su información de configuración regional 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 lo hagas, tu 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
Para obtener más información, consulte Wikipedia en formularios de ancho medio y ancho completo .
Es un programa rápido y sucio de 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 :)
Puede que no necesites arreglar el
graph
wctype;print
puede ser suficiente. No revisé. Hice ambas cosas porquencurses
también a veces necesita saber si los caracteres son transparentes, y parecía más seguro marcar al personaje como visible, ya que lo es.