Ответ 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
также иногда должен знать, прозрачны ли символы, и было бы безопаснее отмечать символ как видимый, так как это.