Декодер C ++ UTF-8

При написании простого рендеринга текста я обнаружил отсутствие декодеров 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 ответ
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.

    Добавить комментарий

    Ваш адрес email не будет опубликован. Обязательные поля помечены *