2016-05-23 53 views
5

Sto lavorando a un programma basato su terminale con supporto Unicode. Ci sono alcuni casi in cui ho bisogno di determinare quante colonne terminali consumerà una stringa prima di stamparla. Sfortunatamente alcuni caratteri sono larghi 2 colonne (cinese, ecc.), Ma ho trovato this answer che indica che un buon modo per rilevare caratteri a larghezza intera è chiamando u_getIntPropertyValue() dalla libreria ICU.Come rilevare la larghezza della stringa unicode nel terminale?

Ora sto cercando di analizzare i caratteri della mia stringa UTF8 e passarli a questa funzione. Il problema che sto avendo ora è che u_getIntPropertyValue() si aspetta un punto di codice UTF-32.

Qual è il modo migliore per ottenere questo da una stringa utf8? Attualmente sto provando a farlo con boost :: locale (usato altrove nel mio programma), ma ho problemi a ottenere una conversione pulita. Le mie stringhe UTF32 che provengono da boost :: locale sono pre-pese con uno zero-width character per indicare l'ordine dei byte. Ovviamente posso saltare solo i primi quattro byte della stringa, ma c'è un modo più pulito per farlo?

Ecco il mio attuale soluzione di brutto:

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

Se si utilizza già in terapia intensiva, perché non usarlo per utf8-to-UTF-32 di conversione troppo? –

+0

Non ho familiarità con l'ICU. Stavo cercando di usare boost :: locale per isolarmi dalla maggior parte della complessità. C'è un modo semplice per ottenere direttamente questo punto di codice utf32 dall'ICU? – KyleL

+0

Non ho familiarità con questo, ma so che ha tutto ciò che chiunque ha mai voluto da una libreria Unicode. Passa un po 'di tempo con google e lo troverai. –

risposta

1

UTF-32 è la rappresentazione diretta dei "punti di codice" dei singoli caratteri. Quindi tutto ciò che devi fare è estrarre quelli dai caratteri UTF-8 e inviarli a u_getIntPropertyValue.

ho preso il codice e modificato in modo da utilizzare u8_to_u32_iterator, che sembra fatta proprio per questo:

#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

Grazie per l'implementazione boost. Interessante che questo è parte della libreria regex e non locale. – KyleL

2

@ N.m era corretta: c'è un modo semplice per fare questo con ICS direttamente. Il codice aggiornato è sotto. Sospetto che probabilmente potrò semplicemente usare UnicodeString e bypassare l'intero utilizzo delle impostazioni locali di boost in questo scenario.

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

Non dimenticare di gestire anche caratteri di larghezza zero! – o11c

+0

@ o11c sai come controllare questo? Sto presentando dei vuoti con la mia ricerca google probabilmente sbagliata. – KyleL

+0

Qualcosa come 'General_Category in {" Mn "," Me "} o Default_Ignorable_Code_Point' - quest'ultimo include caratteri di formattazione, trattino morbido, ecc. Ma poi, devi anche fare cose più complesse per la combinazione di Hangul, che dipende da cosa il personaggio precedente era. – o11c