Как определить ширину строки Юникода в терминале?

Я работаю над терминальной программой, которая поддерживает Unicode. Есть определенные случаи, когда мне нужно определить, сколько терминальных столбцов будет использовать строка, прежде чем я ее напечатаю. К сожалению, некоторые символы имеют ширину 2 столбца (китайский и т. Д.), Но я нашел этот ответ, который указывает на хороший способ обнаружения символов полной ширины - вызовом u_getIntPropertyValue() из библиотеки ICU.

Теперь я пытаюсь разобрать символы моей строки UTF8 и передать их этой функции. Теперь у меня проблема в том, что u_getIntPropertyValue() ожидает кодовую точку UTF-32.

Каков наилучший способ получить это из строки utf8? В настоящее время я пытаюсь сделать это с boost::locale (используется в другом месте в моей программе), но у меня возникают проблемы с получением чистого преобразования. Мои строки UTF32, которые происходят из boost:: locale, предварительно снабжены символом нулевой ширины для указания порядка байтов. Очевидно, я могу просто пропустить первые четыре байта строки, но есть ли более чистый способ сделать это?

Вот мое текущее уродливое решение:

inline size_t utf8PrintableSize(const std::string &str, std::locale loc)
{
    namespace ba = boost::locale::boundary;
    ba::ssegment_index map(ba::character, str.begin(), str.end(), loc);
    size_t widthCount = 0;
    for (ba::ssegment_index::iterator it = map.begin(); it != map.end(); ++it)
    {
        ++widthCount;
        std::string utf32Char = boost::locale::conv::from_utf(it->str(), std::string("utf-32"));

        UChar32 utf32Codepoint = 0;
        memcpy(&utf32Codepoint, utf32Char.c_str()+4, sizeof(UChar32));

        int width = u_getIntPropertyValue(utf32Codepoint, UCHAR_EAST_ASIAN_WIDTH);
        if ((width == U_EA_FULLWIDTH) || (width == U_EA_WIDE))
        {
            ++widthCount;
        }

    }
    return widthCount;
}

2 ответа

Решение

UTF-32 является прямым представлением "кодовых точек" отдельных символов. Так что все, что вам нужно сделать, это извлечь их из символов UTF-8 и передать это u_getIntPropertyValue,

Я взял ваш код и изменил его, чтобы использовать u8_to_u32_iterator, который, кажется, сделан только для этого:

#include <boost/regex/pending/unicode_iterator.hpp>

inline size_t utf8PrintableSize(const std::string &str, std::locale loc)
{
    size_t widthCount = 0;
    for(boost::u8_to_u32_iterator<std::string::iterator> it(input.begin()), end(input.end()); it!=end; ++it)
    {
        ++widthCount;

        int width = u_getIntPropertyValue(*it, UCHAR_EAST_ASIAN_WIDTH);
        if ((width == U_EA_FULLWIDTH) || (width == U_EA_WIDE))
        {
            ++widthCount;
        }

    }
    return widthCount;
}

@nm был верен: есть простой способ сделать это с помощью ICS напрямую. Обновленный код ниже. Я подозреваю, что в этом сценарии я могу просто использовать UnicodeString и обойти все использование локали Boost.

inline size_t utf8PrintableSize(const std::string &str, std::locale loc)
{
    namespace ba = boost::locale::boundary;
    ba::ssegment_index map(ba::character, str.begin(), str.end(), loc);
    size_t widthCount = 0;
    for (ba::ssegment_index::iterator it = map.begin(); it != map.end(); ++it)
    {
        ++widthCount;

        //Note: Some unicode characters are 'full width' and consume more than one
        // column on output.  We will increment widthCount one extra time for
        // these characters to ensure that space is properly allocated
        UnicodeString ucs = UnicodeString::fromUTF8(StringPiece(it->str()));
        UChar32 codePoint = ucs.char32At(0);

        int width = u_getIntPropertyValue(codePoint, UCHAR_EAST_ASIAN_WIDTH);
        if ((width == U_EA_FULLWIDTH) || (width == U_EA_WIDE))
        {
            ++widthCount;
        }

    }
    return widthCount;
}
Другие вопросы по тегам