2016-05-23 5 views
5

Ich arbeite an einem Terminal-basierten Programm, das Unicode-Unterstützung hat. Es gibt bestimmte Fälle, in denen ich feststellen muss, wie viele Terminalspalten eine Zeichenfolge verbraucht, bevor ich sie drucke. Leider sind einige Zeichen 2 Spalten breit (chinesisch, etc.), aber ich fand this answer, die eine gute Möglichkeit zur Erkennung von Zeichen mit voller Breite anzeigt, indem Sie u_getIntPropertyValue() von der ICU-Bibliothek aufrufen.Wie erkennt man Unicode-String-Breite im Terminal?

Jetzt versuche ich, die Zeichen meiner UTF8 Zeichenfolge zu analysieren und sie an diese Funktion zu übergeben. Das Problem, das ich jetzt habe, ist, dass u_getIntPropertyValue() einen UTF-32-Codepunkt erwartet.

Was ist der beste Weg, um dies aus einer utf8-Saite zu erhalten? Ich versuche das gerade mit boost :: locale (anderswo in meinem Programm), aber ich habe Probleme, eine saubere Konvertierung zu bekommen. Meine UTF32-Strings, die von boost :: locale stammen, sind mit einem zero-width character versehen, um die Byte-Reihenfolge anzuzeigen. Natürlich kann ich die ersten vier Bytes der Saite einfach überspringen, aber gibt es einen saubereren Weg, dies zu tun?

Hier ist meine aktuelle hässliche Lösung:

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

Wenn Sie bereits ICU verwenden, warum es nicht zu UTF-8-to-UTF32 Umwandlung verwenden für? –

+0

Ich bin nicht mit der Intensivstation vertraut. Ich habe versucht, boost :: locale zu verwenden, um mich vor dem größten Teil der Komplexität zu isolieren. Gibt es eine einfache Möglichkeit, diesen utf32-Code direkt von der ICU zu bekommen? – KyleL

+0

Ich kenne mich auch nicht aus, aber ich weiß, dass es alles hat, was jemand von einer Unicode-Bibliothek wollte. Verbringen Sie etwas Zeit mit Google und Sie werden es finden. –

Antwort

1

UTF-32 ist die direkte Darstellung der "Codepunkte" der einzelnen Zeichen. Alles, was Sie tun müssen, ist, diese aus den UTF-8-Zeichen zu extrahieren und diese an u_getIntPropertyValue zu senden.

nahm ich Ihren Code und modifiziert es u8_to_u32_iterator, zu verwenden, die dies gemacht zu sein scheint gerade für:

#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

Vielen Dank für die Boost-Implementierung. Interessant, dass dies Teil der Regex-Bibliothek und nicht Gebietsschema ist. – KyleL

2

@ n.m richtig war: es eine einfache Möglichkeit ist direkt das mit ICS zu tun. Aktualisierter Code ist unten. Ich vermute, dass ich wahrscheinlich nur UnicodeString verwenden und die gesamte Boost-Gebietsschema-Verwendung in diesem Szenario umgehen kann.

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

Vergessen Sie nicht, auch Null-Breite Zeichen zu behandeln! – o11c

+0

@ o11c wissen Sie, wie man das überprüft? Ich mache mit meiner wahrscheinlich fehlgeleiteten Google-Suche nach Leerzeichen. – KyleL

+0

Etwas wie 'General_Category in {" Mn "," Me "} oder Default_Ignorable_Code_Point' - Letzteres beinhaltet Formatierungszeichen, weichen Bindestrich, etc. Aber dann müssen Sie auch noch komplexere Sachen für Hangul kombinieren, was davon abhängt das vorhergehende Zeichen war. – o11c