2016-05-23 16 views
5

Pracuję nad programem terminalowym obsługującym standard Unicode. Są pewne przypadki, w których muszę określić, ile kolumn terminalu zużyje, zanim wydrukuję. Niestety niektóre znaki mają szerokość 2 kolumn (chiński, itd.), Ale znalazłem this answer, który wskazuje na dobry sposób wykrywania znaków o pełnej szerokości poprzez wywołanie u_getIntPropertyValue() z biblioteki ICU.Jak wykryć szerokość napisu Unicode w terminalu?

Teraz próbuję przeanalizować znaki mojego łańcucha UTF8 i przekazać je do tej funkcji. Problem, który mam teraz, polega na tym, że u_getIntPropertyValue() oczekuje punktu kodowego UTF-32.

Jaki jest najlepszy sposób na uzyskanie tego z ciągu utf8? Obecnie próbuję zrobić to z boost :: locale (używane w innym miejscu w moim programie), ale mam problem z uzyskaniem czystej konwersji. Moje ciągi znaków UTF32 pochodzące z funkcji boost :: locale są wstępnie oznaczone numerem zero-width character, aby wskazać kolejność bajtów. Oczywiście mogę po prostu pominąć pierwsze cztery bajty ciągu, ale czy jest to czystszy sposób na zrobienie tego?

Oto mój obecny brzydki rozwiązanie:

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; 
} 
+1

Jeśli używałeś ICU, dlaczego nie użyć go do konwersji utf8-to-utf32 też? –

+0

Nie jestem zaznajomiony z ICU. Próbowałem użyć boost :: locale, aby odizolować mnie od większości złożoności. Czy istnieje prosty sposób na uzyskanie tego kodu UTF bezpośrednio z ICU? – KyleL

+0

Nie jestem również obeznany z tym, ale wiem, że ma wszystko, czego ktoś chciał, z biblioteki unikodowej. Poświęć trochę czasu na google, a znajdziesz to. –

Odpowiedz

1

UTF-32 jest bezpośrednią reprezentacją "punktów kodowych" poszczególnych znaków. Wszystko, co musisz zrobić, to wyodrębnić je ze znaków UTF-8 i przekazać do u_getIntPropertyValue.

Wziąłem swój kod i modyfikować go używać u8_to_u32_iterator, który wydaje się być po prostu za to:

#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; 
} 
+0

Dziękuję za wprowadzenie ulepszenia. Ciekawe, że jest to część biblioteki regex, a nie locale. – KyleL

2

@ n.m była prawidłowa: istnieje prosty sposób to zrobić z ICS bezpośrednio. Zaktualizowany kod znajduje się poniżej. Podejrzewam, że prawdopodobnie mogę po prostu użyć UnicodeString i obejść całe użycie locale doładowania w tym scenariuszu.

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; 
} 
+1

Nie zapomnij również obsługiwać znaków o zerowej szerokości! – o11c

+0

@ o11c Czy wiesz, jak to sprawdzić? Pojawiam się w pustkach z moją prawdopodobnie błędną wyszukiwarką Google. – KyleL

+0

Coś jak 'General_Category w {" Mn "," Me "} lub Default_Ignorable_Code_Point' - ta ostatnia obejmuje formatowanie znaków, miękki łącznik, itp. Ale wtedy, musisz także zrobić jeszcze bardziej skomplikowane rzeczy do łączenia Hangul, które zależy od tego, co poprzednia postać była. – o11c