wxWidgets: 文字列とUnicode正規化 [wxWidgets 3.0対応]

(2018.9) 新規作成.

wxWidgets 3.0 になって, 文字列が Unicode ベースになった。しかしながら、ビルドオプションを考慮しなければならず、微妙に面倒になっている.

wxString クラス

wxWidgets 3.0 の文字列は, Unicode "Code Point" (32bit) 単位ではない。"Code Unit" 単位で扱われる。

Unicode は "文字" が Code Point と一致していない。1文字が複数の Code Point になりうるし、逆もありうる.

https://www.unicode.org/reports/tr17/

最近では Unicode 異体字シーケンス (Ideographic Variation Sequence; IVS) が導入され、漢字も1 code point とは限らない。親字の後に Variation Selector (VS; 字形選択子) を続けて、字形を選択する. この符号列を Ideographic Variation Sequence という。

wxWidgets 3.0 では, ビルドオプションにより, 文字列の内部表現が UTF-32, UTF-16 または UTF-8 のいずれかになる。Windows では UTF-16, UNIX (Linux) では UTF-32 または UTF-8.

アプリケーション開発者は、ポータブルにするためには, 3つとも対応する必要がある。

UTF-16 一つの code point が, 1つまたは 2つの wchar (code unit) に なる.
UTF-8 一つの code point (21bit) が 1-4 バイト (code unit) になる.

必要な場所では、次のようにして判定しなければならない。wchar_t の大きさは、WCHAR_MAX マクロで判定するのがもっともポータブルだろう。

wxUSE_UNICODE_WCHAR マクロは、定義される・未定義ではなく、1 or 0 なので、#if を使う。wxUSE_UNICODE_WCHAR マクロが 0 の場合, wxUSE_UNICODE_UTF8 マクロが有効になる (排他なので片方だけテストでOK)。

C++
[RAW]
  1. // #ifdef ではなく #if で判定
  2. #if wxUSE_UNICODE_WCHAR
  3. // wxWidgets 文字列の要素は wchar_t で定義される。
  4. // => 環境により, wchar_t = 2 の場合と = 4 の場合がある。地味に面倒.
  5. #if WCHAR_MAX >= 0x10000L // <wchar.h>
  6. // wchar_t = 4バイト
  7. #else
  8. // wchar_t = 2バイト (UTF-16)
  9. #endif
  10. #else
  11. // wxWidgets 文字列の要素は char で定義される。
  12. // バイト列 (UTF-8)
  13. #endif

サンプルプログラムを書いてみる。

まず, バイト列から wxString インスタンスを作るには, FromUTF8() を使う.

C++
[RAW]
  1. int main()
  2. {
  3. ////////////////////////////////
  4. // 文字列の生成
  5. // - static FromUTF8(const char* s, [optional] size_t length)
  6. // - wxString::wxString(const wchar_t* pwz, [optional] size_t length)
  7. // U+1F681 HELICOPTER
  8. wxString str = wxString::FromUTF8("ABCあいう漢字🚁");
  9. // UTF-16 におけるサロゲートペアは、1 ではなく 2 になる。
  10. printf("length = %d\n", str.length());
  11. // begin() が返すのは wxString::iterator.
  12. for(wxString::const_iterator p = str.begin(); p != str.end(); p++)
  13. printf("%x ", *p);
  14. printf("\n");
  15. ////////////////////////////////
  16. // 検索
  17. printf("%d\n", str.Find("BC")); // 見つかったとき: インデックス
  18. printf("%d\n", str.Find(wxString::FromUTF8("漢")) ); // => 6
  19. // 見つからないとき => -1
  20. printf("%d\n", str.Find(wxString::FromUTF8("愛")) );

Unicode 文字列を扱う場合, データベースに格納する直前に正規化するのがセオリー. しかし, wxString は Unicode 特有の処理がほぼできないので、ICU と組み合わせる.

正規化はnormalize() 呼び出しだけなのに, 行ったり来たりが面倒。ヘルパー関数を作ったりするんだろう。

C++
[RAW]
  1. ////////////////////////////////
  2. // Unicode正規化 => wxWidgets にはないので, ICU を利用
  3. wxString str2 = wxString::FromUTF8("㎞㍻㍿");
  4. std::string x; // DEBUG
  5. #if wxUSE_UNICODE_WCHAR
  6. #if WCHAR_MAX >= 0x10000L // <wchar.h>
  7. // wchar_t* を引数にとるコンストラクタは, sizeof(wchar_t) == 2 のときのみ
  8. // 定義される.
  9. UnicodeString us = UnicodeString::fromUTF32( (UChar32*) str2.wc_str(),
  10. str2.length() );
  11. us.toUTF8String(x); printf("%s\n", x.c_str()); // DEBUG
  12. #else
  13. UnicodeString us(str2.wc_str());
  14. #endif
  15. #else
  16. UnicodeString us = UnicodeString::fromUTF8(
  17. static_cast<const char*>(str2.utf8_str()) );
  18. #endif
  19. UErrorCode err;
  20. u_init(&err);
  21. const icu::Normalizer2* norm = icu::Normalizer2::getNFKDInstance(err);
  22. UnicodeString normalized = norm->normalize(us, err);
  23. x = ""; // toUTF8String() は append する.
  24. normalized.toUTF8String(x); printf("%s\n", x.c_str()); // DEBUG
  25. // 書き戻す.
  26. #if wxUSE_UNICODE_WCHAR
  27. #if WCHAR_MAX >= 0x10000L // <wchar.h>
  28. wxString n;
  29. normalized.toUTF32( (UChar32*) static_cast<wchar_t*>(wxStringBuffer(n, 1000)),
  30. normalized.length() + 1, err );
  31. #else
  32. wxString n = normalized.getBuffer();
  33. #endif
  34. #else
  35. wxString n;
  36. //normalized.toUTF8( wxStringBuffer(n, 1000) );
  37. std::string nn;
  38. normalized.toUTF8String(nn);
  39. n = wxString::FromUTF8(nn.c_str());
  40. #endif
  41. for(wxString::const_iterator p = n.begin(); p != n.end(); p++)
  42. printf("%x ", *p);
  43. printf("\n");
  44. return 0;
  45. }

Makefile はこんな感じ.

CXX = gcc
CXXFLAGS = -Wall `wx-config --cflags` `pkg-config --cflags icu-uc icu-io`
LDFLAGS = `wx-config --libs` `pkg-config --libs icu-uc icu-io` -lstdc++

wx3-string: wx3-string.o

wx3-string.o: wx3-string.cc