2 minute read

Unicode 문자 개수 구하기.

UTF-16 문자 개수 구하기.

int GetUtf16GlyphCount(const wstring& utf16) {
    static const wchar_t    HIGH_SURROGATE_MIN = L'\xD800';
    static const wchar_t    HIGH_SURROGATE_MAX = L'\xDBFF';
    static const wchar_t    LOW_SURROGATE_MIN = L'\xDC00';
    static const wchar_t    LOW_SURROGATE_MAX = L'\xDFFF';

    bool    bHighSurrogate = false;
    int     count = 0;
    for(wstring::const_iterator iter = utf16.begin();
        iter != utf16.end();
        ++iter) {
        const wchar_t& ch = *iter;

        if( HIGH_SURROGATE_MIN <= ch && ch <= HIGH_SURROGATE_MAX ) {
            assert( !bHighSurrogate );
            bHighSurrogate = true;
        } else if( LOW_SURROGATE_MIN <= ch && ch <= LOW_SURROGATE_MAX ) {
            assert( bHighSurrogate );
            bHighSurrogate = false;

            ++count;
            assert( count < numeric_limits<int>::max() );
        } else {
            assert( !bHighSurrogate );
            ++count;
        }
    }

    return count;
}

UTF-8 문자수 구하기.

#define assert_if(a, b) assert( !a || b )

int GetUtf8GlyphCount(const string& utf8) {
    static const unsigned char ONE_BYTE_HEADER_MAX = '\x7F';    // 0xxxxxxx
    static const unsigned char TWO_BYTE_HEADER_MAX = '\xDF';    // 110xxxxx
    static const unsigned char THREE_BYTE_HEADER_MAX = '\xEF';  // 1110xxxx
    static const unsigned char FOUR_BYTE_HEADER_MAX = '\xF7';   // 11110xxx
    static const unsigned char TAIL_BYTE_MAX = '\xBF';          // 10xxxxxx

    int     tailByteCount = 0;
    int     count = 0;
    for( string::const_iterator iter = utf8.begin();
         iter != utf8.end();
         ++iter ) {
        unsigned char ch = *iter;
        assert_if( (ch <= ONE_BYTE_HEADER_MAX || ch > TAIL_BYTE_MAX), tailByteCount == 0 );

        // 크기 오름차로 정렬해 비교한다.
        // ONE_BYTE_HEADER_MAX 다음에 TAIL_BYTE_MAX를 끼어넣음
        if( ch <= ONE_BYTE_HEADER_MAX )         ++count;
        else if( ch <= TAIL_BYTE_MAX ) {
            assert( tailByteCount != 0 );
            --tailByteCount;
            if( tailByteCount == 0 )    ++count;
        }
        else if( ch <= TWO_BYTE_HEADER_MAX )    tailByteCount = 1;
        else if( ch <= THREE_BYTE_HEADER_MAX )  tailByteCount = 2;
        else if( ch <= FOUR_BYTE_HEADER_MAX )   tailByteCount = 3;
        else                                    assert( ch <= FOUR_BYTE_HEADER_MAX );
    }

    assert( tailByteCount == 0 );
    return count;
}

변환(convert)

MBCS -> UTF-16

wstring MbcsToUtf16(const string& mbcs) {
    // 한글은 ANSI code page나 OEM code page 아무거나 써도 된다.
    // Code Pages Supported by Windows - OEM Code Pages 참고
    // http://www.microsoft.com/globaldev/reference/oem.mspx
    // default로 설정된 코드 페이지를 사용하자.
    int length = MultiByteToWideChar(
        CP_ACP, 0,
        mbcs.c_str(), static_cast<int>(mbcs.length()), 0, 0);
    vector<wchar_t> buffer;
    buffer.resize(length+1); // '\0'

    MultiByteToWideChar(
        CP_ACP,
        0,
        mbcs.c_str(),
        static_cast<int>(mbcs.length()),
        &buffer[0],
        static_cast<int>(buffer.size()));

    return &buffer[0];
}

UTF-16 -> UTF-8

string  Utf16ToUtf8(const wstring& utf16) {
    int length = WideCharToMultiByte(
        CP_UTF8, 0,
        utf16.c_str(), static_cast<int>(utf16.length()), NULL, 0, NULL, NULL);
    vector<char> buffer;
    buffer.resize(length+1); // '\0'

    WideCharToMultiByte(
        CP_UTF8, 0,
        utf16.c_str(), static_cast<int>(utf16.length()),
        &buffer[0], static_cast<int>(buffer.size()), NULL, NULL);

    return &buffer[0];
}

UTF-8 <-> MBCS 변환 API는 제공하지 않는다. UTF-16을 거쳐야지 가능하다.

PS : character라는 단어를 사용하기가 애매했다. UTF-16, UTF-8 문자(character) 여러 개가 하나의 유니코드 문자(character)를 나타내는 경우가 있기 때문이다. 그래서 unicode character라고 써도 되나 glyph라는 단어를 선택했다. 네이밍은 끝까지 날 괴롭힐 것 같다.

참고