c - 如何获取ncurses以输出星体平面unicode字符

标签 c unicode utf-8 ncurses

我有以下一段非常简单的代码,应该输出(其中包括)三个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;
}

最终的printf按照我期望的那样输出所有字符“<☃⛄😸>”(因为我使用的是正确配置的语言环境,终端仿真器和适当的字体组合)-但是第一部分应该输出文本使用ncurses函数无法正常工作。您只能看到第一个字符(雪人),而其他两个则仅显示为空格。 “<☃>”。

我读过许多Google帖子,说我还需要包括
#define _XOPEN_SOURCE_EXTENDED 1

在源代码中-但这完全没有改变我的输出。

所以-我在这里做的事太愚蠢了,还是在使用unicode空间的某些部分时ncurses被破坏了?

最佳答案

并不是ncurses损坏了。更像是glibc已损坏。或您正在使用的libc的任何实现;我只是假设它是glibc

与简单的控制台输出(即printf)不同,ncurses需要知道每个字符在打印时的宽度,因为它需要维护自己的屏幕外观和光标位置模型。并不是所有的Unicode代码点都具有1个单位宽,即使使用比例字体也是如此:许多代码点的宽度为0个单位(例如,结合重音符号),并且有很多代码点的宽度为2个单位(汉式表意文字)[注1]。

事实证明,如果字符是“可打印的”,则存在一个标准的C库函数wcwidth,该函数采用wchar_t并返回0、1或2(或理论上为整数,但afaik是唯一实现的宽度),并且如果字符无效或控制字符,则为-1。启用了宽字符的ncurses版本使用wcwidth来预测打印字符后光标将移动多远。如果wcwidth返回错误指示,则ncurses替换一个空格。
wcwidth从语言环境的WIDTHcharmap部分读取宽度,但是该定义仅提供异常(exception);任何没有定义宽度的可打印字符都假定宽度为1。因此wcwidth还需要检查该字符是否可打印,这是在LC_CTYPE语言环境规范中定义的。这是驱动iswprint库功能的相同数据。

不幸的是,不能保证终端仿真器与C库函数共享相同的Unicode字符数据 View 。对于实际显示宽度与语言环境配置的宽度不同的字符,ncurses将产生意外的行为。

在这种情况下,宽度没有问题(字符均为1单位宽,因此默认设置是正确的);问题在于这些字符实际上已经存在于您的控制台字体中,并且您想使用它们,但是它们不存在于glibc的字符数据库中,因为该数据库是still based on Unicode 5.0。 (实际上,该错误本身应该更新,因为Unicode现在是6.3,而不是6.1。)

为了帮助您了解这一点,这里有一个微型程序,它为unicode代码点转储已配置的ctype信息[注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;
}

编译它可以查看您的字符数据。它可能看起来像这样:
$ 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与我的安装相同(并且一段时间没有更改语言环境文件,所以您可能这样做了),那么您将在/usr/share/i18n/locales和实际的语言环境文件中找到您的语言环境文件,LC_CTYPE部分将包含指令copy "i18n",这意味着实际的ctype配置位于/usr/share/i18n/locales/i18n文件中。然后,您可以编辑该文件以进行适当的更改。 (当然,在更改文件之前,请先制作备份副本。由于文件只能由root用户写入,因此您需要sudo编辑器。)

首先找到以graph开头的行,[注3],然后向前搜索U26(在我的配置中的第716行,fwiw。)您将找到一行,其中包含类似于<U26A0>..<U26C3>;的条目,这意味着26A026C3的代码指向图形(可见打印)字符。根据需要扩大该范围。 (为了进行最小限度的测试,我将26C3更改为26C4,但您可能希望包含更多字符。)再往后几行,您将看到第二个平面graph范围;添加适当的条目。 (再次,为了简化起见,我添加了一行:
   <U0001F638>;/

但您可能要包括一个范围。 (顺便说一下,结尾的/是继续标记。)

接下来,再走几行,您将找到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;
}

注释
  • 有关更多信息,请参见Wikitia上的halfwidth and fullwidth forms
  • 这是一个快速而又没有错误的检查程序,但是对于我们这里需要的东西来说已经足够了。出于生产目的,可能需要更多代码行:)
  • 您可能不需要修复graph wctype。 print可能就足够了。我没有检查。我这样做是因为ncurses有时还需要知道字符是否透明,而且将字符标记为可见似乎更安全。
  • 关于c - 如何获取ncurses以输出星体平面unicode字符,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23526353/

    相关文章:

    c - 如何在 FreeDOS 中编写内联汇编

    javascript - 如何使 toLowerCase() 和 toUpperCase() 在浏览器之间保持一致

    c# - PHP utf8 变量编码(HMAC Key -> C# Server)

    windows - R:即使指定编码也无法读取unicode文本文件

    perl - 在 Perl 中将 UTF8 字符串转换为数值

    mysql - UTF-8 在 MySQL DB 中以问号形式存储

    ruby-on-rails - 如何在 Ruby on Rails 中实现

    c - 使用 C 中最少的赋值语句构建列表 {1,2,3}

    c - 识别 RTP 流中的帧边界

    c - lldb: `print` 和 `display` 的一行输出