У меня есть программа, которая выводит текстовую таблицу, используя строки UTF-8, и мне нужно измерить количество моноширинных ячеек символов, используемых строкой, чтобы я мог правильно ее выровнять. Если возможно, я хотел бы сделать это со стандартными функциями.
Количество символов, используемых строкой
Ответ 1
Из UTF-8 и Unicode FAQ для Unix/Linux:
Количество символов можно пересчитать на C портативным способом, используя
mbstowcs(NULL,s,0)
. Это работает для UTF-8, как и для любой другой поддерживаемой кодировки, если выбрана соответствующая локаль. Сложная методика подсчета количества символов в строке UTF-8 состоит в том, чтобы считать все байты, кроме тех, которые находятся в диапазоне 0x80 - 0xBF, потому что это просто байты продолжения, а не собственные символы. Однако необходимость подсчета символов возникает неожиданно редко в приложениях.
Ответ 2
Вы можете иметь или не иметь совместимую с UTF-8 функцию strlen (3). Тем не менее, есть s ome простые функции C, которые легко доступны, которые быстро выполняют работу.
Эффективные C-решения проверяют начало символа, чтобы пропустить байты продолжения. Простой код (ссылка на ссылку выше) -
int my_strlen_utf8_c(char *s) {
int i = 0, j = 0;
while (s[i]) {
if ((s[i] & 0xc0) != 0x80) j++;
i++;
}
return j;
}
Более быстрая версия использует ту же технику, но предваряет данные и делает многобайтные сравнения, что приводит к значительному ускорению. Однако код более длинный и сложный.
Ответ 3
Я в шоке, что никто не упомянул об этом, так что здесь идет запись:
Если вы хотите выровнять текст в терминале, вам нужно использовать функции POSIX wcwidth
и wcswidth
. Здесь правильная программа, чтобы найти экранную длину строки.
#define _XOPEN_SOURCE
#include <wchar.h>
#include <stdio.h>
#include <locale.h>
#include <stdlib.h>
int measure(char *string) {
// allocate enough memory to hold the wide string
size_t needed = mbstowcs(NULL, string, 0) + 1;
wchar_t *wcstring = malloc(needed * sizeof *wcstring);
if (!wcstring) return -1;
// change encodings
if (mbstowcs(wcstring, string, needed) == (size_t)-1) return -2;
// measure width
int width = wcswidth(wcstring, needed);
free(wcstring);
return width;
}
int main(int argc, char **argv) {
setlocale(LC_ALL, "");
for (int i = 1; i < argc; i++) {
printf("%s: %d\n", argv[i], measure(argv[i]));
}
}
Вот пример его запуска:
$ ./measure hello 莊子 cAb
hello: 5
莊子: 4
cAb: 4
Обратите внимание, как два символа "莊子" и три символа "cAb" (обратите внимание на ширину ширины A) имеют ширину в четыре столбца.
Как utf8everywhere.org ставит его,
Размер строки, отображаемой на экране, не связан с количество кодовых точек в строке. Нужно общаться с рендеринга для этого. Кодовые точки не занимают ровно один столбец в моноширинных шрифтах и терминалах. POSIX учитывает это.
В Windows нет встроенной функции wcwidth
для вывода консоли; если вы хотите поддерживать многоколоночные символы в консоли Windows , вам нужно найти переносимую реализацию , потому что консоль Windows не поддерживает Unicode без сумасшедших хаков.wcwidth
Ответ 4
Если вы можете использовать сторонние библиотеки, посмотрите библиотеку ICU от IBM:
Ответ 5
В следующем коде используются некорректные последовательности байтов. пример строковых данных поступает из "Таблица 3-8. Использование U + FFFD в конвертации UTF-8" " в Unicode Standard 6.3.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#define is_trail(c) (c > 0x7F && c < 0xC0)
#define SUCCESS 1
#define FAILURE -1
int utf8_get_next_char(const unsigned char*, size_t, size_t*, int*, unsigned int*);
int utf8_length(unsigned char*, size_t);
void utf8_print_each_char(unsigned char*, size_t);
int main(void)
{
unsigned char *str;
str = (unsigned char *) "\x61\xF1\x80\x80\xE1\x80\xC2\x62\x80\x63\x80\xBF\x64";
size_t str_size = strlen((const char*) str);
puts(10 == utf8_length(str, str_size) ? "true" : "false");
utf8_print_each_char(str, str_size);
return EXIT_SUCCESS;
}
int utf8_length(unsigned char *str, size_t str_size)
{
int length = 0;
size_t pos = 0;
size_t next_pos = 0;
int is_valid = 0;
unsigned int code_point = 0;
while (
utf8_get_next_char(str, str_size, &next_pos, &is_valid, &code_point) == SUCCESS
) {
++length;
}
return length;
}
void utf8_print_each_char(unsigned char *str, size_t str_size)
{
int length = 0;
size_t pos = 0;
size_t next_pos = 0;
int is_valid = 0;
unsigned int code_point = 0;
while (
utf8_get_next_char(str, str_size, &next_pos, &is_valid, &code_point) == SUCCESS
) {
if (is_valid == true) {
printf("%.*s\n", (int) next_pos - (int) pos, str + pos);
} else {
puts("\xEF\xBF\xBD");
}
pos = next_pos;
}
}
int utf8_get_next_char(const unsigned char *str, size_t str_size, size_t *cursor, int *is_valid, unsigned int *code_point)
{
size_t pos = *cursor;
size_t rest_size = str_size - pos;
unsigned char c;
unsigned char min;
unsigned char max;
*code_point = 0;
*is_valid = SUCCESS;
if (*cursor >= str_size) {
return FAILURE;
}
c = str[pos];
if (rest_size < 1) {
*is_valid = false;
pos += 1;
} else if (c < 0x80) {
*code_point = str[pos];
*is_valid = true;
pos += 1;
} else if (c < 0xC2) {
*is_valid = false;
pos += 1;
} else if (c < 0xE0) {
if (rest_size < 2 || !is_trail(str[pos + 1])) {
*is_valid = false;
pos += 1;
} else {
*code_point = ((str[pos] & 0x1F) << 6) | (str[pos + 1] & 0x3F);
*is_valid = true;
pos += 2;
}
} else if (c < 0xF0) {
min = (c == 0xE0) ? 0xA0 : 0x80;
max = (c == 0xED) ? 0x9F : 0xBF;
if (rest_size < 2 || str[pos + 1] < min || max < str[pos + 1]) {
*is_valid = false;
pos += 1;
} else if (rest_size < 3 || !is_trail(str[pos + 2])) {
*is_valid = false;
pos += 2;
} else {
*code_point = ((str[pos] & 0x1F) << 12)
| ((str[pos + 1] & 0x3F) << 6)
| (str[pos + 2] & 0x3F);
*is_valid = true;
pos += 3;
}
} else if (c < 0xF5) {
min = (c == 0xF0) ? 0x90 : 0x80;
max = (c == 0xF4) ? 0x8F : 0xBF;
if (rest_size < 2 || str[pos + 1] < min || max < str[pos + 1]) {
*is_valid = false;
pos += 1;
} else if (rest_size < 3 || !is_trail(str[pos + 2])) {
*is_valid = false;
pos += 2;
} else if (rest_size < 4 || !is_trail(str[pos + 3])) {
*is_valid = false;
pos += 3;
} else {
*code_point = ((str[pos] & 0x7) << 18)
| ((str[pos + 1] & 0x3F) << 12)
| ((str[pos + 2] & 0x3F) << 6)
| (str[pos + 3] & 0x3F);
*is_valid = true;
pos += 4;
}
} else {
*is_valid = false;
pos += 1;
}
*cursor = pos;
return SUCCESS;
}
Когда я пишу код для UTF-8, я вижу "Таблица 3-7. Хорошо сформированные последовательности байтов UTF-8" в стандарте Unicode 6.3.
Code Points First Byte Second Byte Third Byte Fourth Byte
U+0000 - U+007F 00 - 7F
U+0080 - U+07FF C2 - DF 80 - BF
U+0800 - U+0FFF E0 A0 - BF 80 - BF
U+1000 - U+CFFF E1 - EC 80 - BF 80 - BF
U+D000 - U+D7FF ED 80 - 9F 80 - BF
U+E000 - U+FFFF EE - EF 80 - BF 80 - BF
U+10000 - U+3FFFF F0 90 - BF 80 - BF 80 - BF
U+40000 - U+FFFFF F1 - F3 80 - BF 80 - BF 80 - BF
U+100000 - U+10FFFF F4 80 - 8F 80 - BF 80 - BF
Ответ 6
Вы также можете использовать glib, который значительно упрощает вашу жизнь при работе с UTF-8. glib reference docs