При написании простого рендеринга текста я обнаружил отсутствие декодеров utf-8. Большинство декодеров, которые я обнаружил, требовали выделения достаточного места для декодированной строки. В худшем случае это будет означать, что декодированная строка будет в четыре раза больше исходной.
Мне просто нужно было перебрать символы в декодированном формате, чтобы я мог отобразить их на экране, поэтому я написал простую функцию, которая позволила бы мне это сделать:
#include <cstdint>
// unsigned integer types
typedef uint64_t U64;
typedef uint32_t U32;
typedef uint16_t U16;
typedef uint8_t U8;
// signed integer types
typedef int64_t I64;
typedef int32_t I32;
typedef int16_t I16;
typedef int8_t I8;
U32 NextUTF8Char(const char* str, U32& idx)
{
// https://en.wikipedia.org/wiki/UTF-8
U8 c1 = (U8) str[idx];
++idx;
U32 utf8c;
if (((c1 >> 6) & 0b11) == 0b11)
{
// at least 2 bytes
U8 c2 = (U8) str[idx];
++idx;
if ((c1 >> 5) & 1)
{
// at least 3 bytes
U8 c3 = (U8) str[idx];
++idx;
if ((c1 >> 4) & 1)
{
// 4 bytes
U8 c4 = (U8) str[idx];
++idx;
utf8c = ((c4 & 0b00000111) << 18) | ((c3 & 0b00111111) << 12) |
((c2 & 0b00111111) << 6) | (c1 & 0b00111111);
} else
{
utf8c = ((c3 & 0b00001111) << 12) | ((c2 & 0b00111111) << 6) |
(c1 & 0b00111111);
}
} else
{
utf8c = ((c1 & 0b00011111) << 6) | (c2 & 0b00111111);
}
} else
{
utf8c = c1 & 0b01111111;
}
return utf8c;
}
Использование:
const char* text = u8"ta suhi škafec pušča";
U32 idx = 0;
U32 c;
while ((c = NextUTF8Char(text, idx)) != 0)
{
// c is our utf-8 character in unsigned int format
}
В настоящее время меня больше всего беспокоит следующее:
- Читаемость: Цель каждого фрагмента кода понятна читателю.
- Правильность: Все работает как надо (думаю понятно, что должно происходить).
- Представление: Можно ли что-нибудь сделать для повышения производительности этого кода?
1 ответ
// unsigned integer types typedef uint64_t U64; typedef uint32_t U32; typedef uint16_t U16; typedef uint8_t U8; // signed integer types typedef int64_t I64; typedef int32_t I32; typedef int16_t I16; typedef int8_t I8;
Это мгновенно сделало код более трудным для чтения (а также неправильным, поскольку <cstdint>
объявляет эти имена в std
пространство имен). Я не уверен, почему мы объявляем так много типов, если в любом случае мы используем только два из них.
U32 NextUTF8Char(const char* str, U32& idx)
Почему бы не вернуть стандарт std::wchar_t
? Или, возможно, char32_t
? Так же, str
должен быть const char8_t*
(чтобы код примера компилировался).
Я бы использовал std::size_t
для индекса (или, что более вероятно, избавьтесь от idx
вместе и вместо этого передайте ссылку на указатель).
Все это похоже на переосмысление той большой работы, которая уже была сделана за вас:
#include <cwchar>
char32_t NextUTF8Char(const char8_t*& str)
{
static const int max_utf8_len = 5;
auto s = reinterpret_cast<const char*>(str);
wchar_t c;
std::mbstate_t state;
auto len = std::mbrtowc(&c, s, max_utf8_len, &state);
if (len > max_utf8_len) { return 0; }
str += len;
return c;
}
#include <iostream>
int main()
{
std::locale::global(std::locale{"en_US.utf8"});
const auto* text = u8"ta suhi škafec pušča";
char32_t c;
std::size_t i = 0;
while ((c = NextUTF8Char(text)) != 0) {
std::cout << '[' << i++ << "] = " << (std::uint_fast32_t)c << 'n';
// c is our utf-8 character in unsigned int format
}
}
я так думаю std::codecvt<char32_t, char8_t, std::mbstate_t>
мог бы легко сделать то же самое:
#include <locale>
char32_t NextUTF8Char(const char8_t*& str)
{
if (!*str) {
return 0;
}
auto &cvt = std::use_facet<std::codecvt<char32_t, char8_t, std::mbstate_t>>(std::locale());
std::mbstate_t state;
char32_t c;
char32_t* p = &c+1;
auto result = cvt.in(state, str, str+6, str, &c, p, p);
switch (result) {
case std::codecvt_base::ok: return c;
case std::codecvt_base::partial: return c;
case std::codecvt_base::error: return 0;
case std::codecvt_base::noconv: return 0;
}
return c;
}
Либо лучше, чем писать собственный декодер UTF-8.