>>212349
>>212378
Недавно тоже разбирался с этим вопросом.
Ядро юникода не меняется в зависимости от спецификаций. Юникод это просто большой список пронумерованных символов. Для нумерации используются 32-битные числа (Code Points):
/*
* Code Points 1st 2s 3s 4s
* 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
*/
В кодировке UTF-8, номера U+0000..U+007F соответствуют коду из одного байта. U+0080..U+07FF из двух. U+0800..U+0FFF, U+1000..U+CFFF, U+D000..U+D7FF, U+E000..U+FFFF из трёх. U+10000..U+3FFFF, U+40000..U+FFFFF, U+40000..U+FFFFF, U+100000..U+10FFFF из четырёх. То есть чтобы получить код нужно узнать из скольки байт состоит результат и закодировать по известному алгоритму.
if (code_point_len == 1)
{
utf8_out[0] = code_point;
}
else if (code_point_len == 2)
{
utf8_out[0] = 0xC0 + (code_point >> 6);
utf8_out[1] = 0x80 + (code_point & 0x3F);
}
else if (code_point_len == 3)
{
utf8_out[0] = 0xE0 + (code_point >> 12);
utf8_out[1] = 0x80 + ((code_point >> 6) & 0x3F);
utf8_out[2] = 0x80 + (code_point & 0x3F);
}
else if (code_point_len == 4)
{
utf8_out[0] = 0xF0 + (code_point >> 18);
utf8_out[1] = 0x80 + ((code_point >> 12) & 0x3F);
utf8_out[2] = 0x80 + ((code_point >> 6) & 0x3F);
utf8_out[3] = 0x80 + (code_point & 0x3F);
}
Обратный процесс немного сложнее. Предполагаемая длинна последовательности определяется по первому байту.
static const uint8_t UTF8_SEQUENCE_LEN[256] =
{
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x00-0x0F */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x10-0x1F */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x20-0x2F */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x30-0x3F */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40-0x4F */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x50-0x5F */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60-0x6F */
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x70-0x7F */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80-0x8F */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90-0x9F */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xA0-0xAF */
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xB0-0xBF */
0, 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xC0-0xCF */
2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xD0-0xDF */
3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xE0-0xEF */
4, 4, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xF0-0xFF */
};
Дальше нужно проверить, что каждый байт после первого является Continuation Byte (ch & 0xc0) == 0x80 и отбросить результаты не входящие в корретные периоды из первой таблицы Code Points.
Примерно так, но не ручаюсь за полную корректность. Так же здесь нет обработки ошибок.
if (sequence_len == 1)
{
return b0;
}
else if (sequence_len == 2)
{
if (utf_cont(b1))
{
return ((b0 & 0x1F) << 6) | (b1 & 0x3F);
}
}
else if (sequence_len == 3)
{
if (utf_cont(b1) && utf_cont(b2))
{
// Overlong encodings
// U+0800..U+0FFF E0 A0..BF 80..BF
if ((b0 != 0xE0) || (b1 >= 0xA0))
{
// Invalid UTF-16 surrogate halves
// U+D000..U+D7FF ED 80..9F 80..BF
if ((b0 != 0xED) || (b1 <= 0x9F))
{
return ((b0 & 0xF) << 12) | ((b1 & 0x3F) << 6) | (b2 & 0x3F);
}
}
}
}
else if (sequence_len == 4)
{
if (utf_cont(b1) && utf_cont(b2) && utf_cont(b3))
{
// Overlong encodings
// U+10000..U+3FFFF F0 90..BF 80..BF 80..BF
if ((b0 != 0xF0) || (b1 >= 0x90))
{
// Code points greater than U+10FFFF
// U+100000..U+10FFFF F4 80..8F 80..BF 80..BF
if ((b0 != 0xF4) || (b1 <= 0x8F))
{
return ((b0 & 7) << 18) | ((b1 & 0x3F) << 12)
| ((b2 & 0x3F) << 6) | (b3 & 0x3F);
}
}
}
}