Подтвердить что ты не робот

Как получить ncurses для вывода символов юникода астральной плоскости

У меня есть следующий фрагмент чрезвычайно простого кода, который должен выводить (среди прочего) три символа Юникода:

/*
 * 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

в источнике, но это не изменило вывод для меня вообще.

Итак, я делаю что-то в высшей степени глупо здесь или ncurses сломался при использовании некоторых частей пространства юникода?

4b9b3361

Ответ 1

Это не совсем то, что ncurses нарушено. Более того, glibc нарушается. Или что бы вы не использовали libc; Я просто предполагаю, что это glibc.

В отличие от простого вывода на консоль (т.е. printf), ncurses должен знать, насколько широким является каждый символ, когда он печатается, потому что ему нужно поддерживать собственную модель того, как выглядит экран, и где курсор, Не все кодовые страницы Unicode имеют ширину в 1 единицу, даже с пропорциональным шрифтом: многие коды являются нулевыми единицами (например, комбинация акцентов), а некоторые из них - две единицы (идеограммы хана) [Примечание 1].

Оказывается, существует стандартная функция библиотеки C, wcwidth, которая принимает wchar_t и возвращает 0, 1 или 2 (или теоретически любое целое число, но afaik - единственные реализованные ширины), если символ "печатается" и -1, если символ недействителен или управляющий символ. Версия ncurses с широким символом использует wcwidth, чтобы предсказать, как далеко перемещается курсор после печати символа. Если wcwidth возвращает индикацию ошибки, ncurses заменяет пробел.

wcwidth читает ширину из раздела WIDTH локали charmap, но это определение предоставляет только исключения; любой печатный символ без определенной ширины считается шириной 1. Таким образом, wcwidth также необходимо проверить, можно ли печатать символ, который определен в спецификации языка LC_CTYPE. Это те же данные, которые управляют библиотечной функцией iswprint.

К сожалению, нет гарантии, что эмулятор терминала использует один и тот же вид символьных данных Unicode, поскольку функции библиотеки C. И для символов, фактическая ширина которых отличается от заданной по языку ширины, ncurses приведет к неожиданному поведению.

В этом случае нет проблем с шириной (все символы 1 единицу ширины, поэтому по умолчанию правильно); проблема в том, что символы на самом деле существуют в вашем шрифте консоли, и вы хотите их использовать, но они не существуют в базе данных символов glibc, потому что эта база данных по-прежнему на основе Unicode 5.0. (На самом деле, эта ошибка должна быть обновлена, потому что Unicode теперь имеет значение 6.3, а не 6.1.)

Чтобы помочь вам увидеть, вот небольшая небольшая программа, которая выгружает сконфигурированную информацию ctype для кодовых точек Unicode [Примечание 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 и в фактическом locale, раздел LC_CTYPE будет содержать директиву copy "i18n", что означает, что фактическая конфигурация ctype находится в файле /usr/share/i18n/locales/i18n. Затем вы можете отредактировать этот файл, чтобы внести соответствующие изменения. (Сделайте резервную копию перед изменением файла, конечно, и вам понадобится sudo ваш редактор, потому что файл доступен только для записи root.)

Сначала найдите строку, которая запустит graph, [Примечание 3], а затем выполните поиск вперед для U26 (строка 716 в моей конфигурации, fwiw.) Вы найдете строку с записью, которая выглядит как <U26A0>..<U26C3>;, что означает, что кодовые точки 26A0 через 26C3 являются графическими (видимыми печатными) символами. При необходимости расширьте этот диапазон. (Я изменил значение 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;
}

Примечания

  • Дополнительные сведения см. в Википедии полуширина и форматы полной ширины.

  • Это программа быстрой проверки ошибок без ошибок, но она достаточно хороша для того, что нам нужно здесь. Для производственных целей нужно было бы еще несколько строк кода:)

  • Возможно, вам не нужно исправлять wctype graph; print может быть достаточно. Я не проверял. Я сделал это потому, что ncurses также иногда должен знать, прозрачны ли символы, и было бы безопаснее отмечать символ как видимый, так как это.