如何让ncurses输出星体平面unicode字符
我有以下一段非常简单的代码,它应该输出(除其他外)三个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; }
最终的printf输出所有字符,因为我期望“”(因为我使用的是正确配置的区域设置,终端模拟器和适当的字体组合) – 但是第一部分,应该输出文本使用ncurses函数无法正常工作。 您只能看到第一个角色(雪人),而其他两个角色只能呈现为空格。 “”。
我已经阅读了很多谷歌post,说我还需要包含
#define _XOPEN_SOURCE_EXTENDED 1
在源头 – 但这样做并没有改变我的输出。
所以 – 我在这里做了一些极其愚蠢的事情,或者在使用unicode空间的某些部分时是不是已经破坏了?
并不完全是ncurses
被破坏了。 更像是, glibc
被打破了。 或者你正在使用的libc
任何实现; 我只是假设它是glibc
。
与简单的控制台输出(即printf
)不同, ncurses
需要知道每个字符在打印时的宽度,因为它需要维护自己的屏幕外观模型以及光标所在的模型。 并非所有Unicode代码点都是1个单位宽,即使使用比例字体:许多代码点是零单位宽(例如组合重音),而且很多是两个单位宽(汉字表意文字)[注1]。
事实certificate,有一个标准的C库函数wcwidth
,它接受一个wchar_t
并返回0,1或2(或者理论上是任何整数,但是afaik那些是唯一实现的宽度)如果字符是“可打印的”,并且如果字符无效或控制字符,则返回-1。 启用宽字符的ncurses
版本使用wcwidth
来预测在打印字符后光标移动的距离。 如果wcwidth
返回错误指示,则ncurses
替换空格。
wcwidth
从语言环境的charmap
的WIDTH
部分读取宽度,但该定义仅提供exception; 假定任何没有定义宽度的可打印字符的宽度为1.因此wcwidth
还需要检查字符是否可打印,这是在LC_CTYPE
语言环境规范中定义的。 这与驱动iswprint
库函数的数据相同。
遗憾的是,无法保证终端仿真器与C库函数共享相同的Unicode字符数据视图。 对于实际显示宽度与区域设置配置宽度不同的字符, ncurses
将产生意外行为。
在这种情况下,宽度没有问题(字符都是1个单位宽,所以默认是正确的); 问题是这些字符实际存在于您的控制台字体中并且您想要使用它们,但它们在glibc
的字符数据库中不存在,因为该数据库仍然基于Unicode 5.0 。 (事实上,应该更新该bug本身,因为Unicode现在是6.3,而不是6.1。)
为了帮助您看到这一点,这里有一个小程序,它为unicode代码点转储配置的ctype信息[注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; }
编译它你可以查看你的角色数据。 它可能看起来像这样:
$ 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
那么该怎么办? 您可以等待glibc
数据库更新,但我怀疑这不会很快发生。 因此,如果您确实想要使用这些字符,则需要修改自己的区域设置定义。
如果你有和我一样的glibc
安装(并且locale文件暂时没有改变,那么你可能会这样做),那么你将在/usr/share/i18n/locales
和实际中找到你的语言环境文件语言环境文件, LC_CTYPE
部分将包含指令copy "i18n"
,这意味着实际的ctype配置位于文件/usr/share/i18n/locales/i18n
。 然后,您可以编辑该文件以进行适当的更改。 (当然,在更改文件之前制作备份副本。然后你需要sudo
你的编辑器,因为该文件只能由root写入。)
首先找到开始graph
的线,[注3]然后向前搜索U26
(我的配置中的第716行,fwiw。)你会找到一条条目,其中的条目看起来像
这意味着代码点26A0
到26A0
是图形(可见打印)字符。 根据需要扩展该范围。 (我将26C4
更改为26C4
以进行最小测试,但您可能希望包含更多字符。)再往下几行,您将看到第二个平面图范围; 添加适当的条目。 (再次,极简主义,我添加了一个新的一行:
;/
但你可能想要包括一个范围。 (顺便说一句,尾随/
是延续标记。)
接下来,再往下走几行,你就会找到print
部分。 进行完全相同的更改 。
然后,您可以通过运行以下命令重新生成区域设置信息
$ sudo locale-gen
然后你可以测试:
$ ./wcinfo 2603 26c4 1f638 Code 2603: width 1 punct graph print Code 26C4: width 1 graph print Code 1F638: width 1 graph print
一旦你这样做,你原来的ncurses程序应该产生预期的输出。
顺便说一句,你可以使用带有ncurses的宽字符串; 您不必手动生成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; }
笔记
-
有关更多信息,请参阅有关半宽和全宽表单的 Wikipedia。
-
这是一个快速而肮脏的无错误检查程序,但它足以满足我们的需求。 出于生产目的,人们会想要更多的代码行:)
-
您可能不需要修复
graph
wctype;print
可能就足够了。 我没有检查。 我之所以这样做是因为ncurses
有时也需要知道字符是否透明,将字符标记为可见似乎更安全,因为它是。