Как определить ширину строки Юникода в терминале?
Я работаю над терминальной программой, которая поддерживает 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;
}