IBMによるUnicodeライブラリ、ICUを使う。
2005.4.29新規公開。2000.6.14の日記に加筆、修正。
2005.5.15 更新。
インストール
Unicodeを扱うためのライブラリはいくつかあるが、IBMによるUnicodeライブラリICU "International Components for Unicode" を試してみる。
ICUはC++版とjava版がある。2011年1月現在の最新版はバージョン4.6。CLDR (Unicode Common Locale Date Repository) 1.9, Unicode 6.0に対応している。
次のサイトから入手できる。
あるいは、Fedora Linuxには、パッケージが含まれている (rpm名=libicu-devel)。
Unicodeへの変換
ICUでは文字列はUnicodeStringクラスのオブジェクトになる。データがシフトJIS、日本語EUCなどUnicode以外のときは、何らかの方法で変換してやる必要がある。
UNIXであればiconvが使えるので、それを使うのがいい。変換表をあれこれ使うと、思わぬところで文字化けしたりする恐れがある。iconvを使わないのなら、ICUの変換器を使ってもいい。UConverterクラスが文字コードの変換器。
次のソースコードは、日本語EUCからUnicodeへ変換したうえで、UnicodeStringオブジェクトを作る。
まずは、必要なヘッダをincludeする。
3| #undef USE_ICONV 4| 5| #include <stdio.h> 6| #include <string.h> 7| #include <assert.h> 8| #ifdef USE_ICONV 9| #include <iconv.h> 10| #else 11| #include <unicode/ucnv.h> 12| #endif 13| #include <unicode/unistr.h> 14| #include <unicode/uchar.h> 15| #include <unicode/schriter.h>
次の部分は、iconvを使った変換。iconv_open()で文字コードを指定し、iconv()で変換する。ただ、変換後の大きさが1000バイトに収まるという仮定を置いているので、実用にするときは、修正が必要。
変換したバイト列と文字コード名をUnicodeStringに与えて文字列オブジェクトを生成する。
17| #ifdef USE_ICONV 18| static const char* UTF8_CES = "UTF-8"; 19| static const char* EUCJP_CES = "eucJP-open"; 20| #endif 21| 22| int main() 23| { 24| const char* euc = "ABC日本語のテキスト\\/¥/\"; 25| #ifdef USE_ICONV 26| // エンコーディングの変換:iconvを使う場合 27| iconv_t cd = iconv_open(UTF8_CES, EUCJP_CES); 28| size_t left = strlen(euc); 29| char buf[1000]; 30| char* p = buf; 31| size_t bufleft = 1000; 32| printf("euc = %x, p = %x\n", euc, p); 33| size_t r = iconv(cd, const_cast<char**>(&euc), &left, &p, &bufleft); 34| printf("euc = %x, p = %x\n", euc, p); 35| printf("r = %d, left = %d, bufleft = %d\n", r, left, bufleft); 36| *p = '\0'; 37| iconv_close(cd); 38| UnicodeString str(buf, UTF8_CES);
今度はICUのコンバータを使ってみる。ucnv_open()でUConverterオブジェクトを生成し、それと変換元のバイト列をUnicodeStringに与えるだけでいい。
ucnv_open()に渡す文字コード名だが、日本語EUCの場合は、mappings/convrtrs.txtを見ると、次の3つがある。ibm-954でいいだろう。
名称 | ucnv_open()に渡す文字コード名 | コメント |
---|---|---|
ibm-33722_P12A-1999 | "ibm-33722_VPUA" | OK |
ibm-33722_P120-1999 | "ibm-33722" | '\' => yen sign. NG |
ibm-954_P101-2000 | "ibm-954" | OK |
その他、aliasなどは、次のページで見れる;
39| #else 49| UErrorCode error = U_ZERO_ERROR; 50| UConverter* cnv = ucnv_open("ibm-954", &error); 51| assert(U_SUCCESS(error)); 52| UnicodeString str(euc, strlen(euc), cnv, error); 53| assert(U_SUCCESS(error)); 54| #endif
イテレータ
文字列オブジェクトを作るだけでは面白くないので、1文字ごとに属性を表示してみる。ICUにはイテレータクラスがある。UnicodeStringオブジェクトのイテレータとしては、StringCharacterIteratorを使う。
次のソース片では、East_Asian_Width属性を表示する。
56| StringCharacterIterator it(str); 57| for (UChar uc = it.first(); uc != it.DONE; uc = it.next()) 58| printf("%04x(%d) ", uc, 59| (UEastAsianWidth) u_getIntPropertyValue(uc, UCHAR_EAST_ASIAN_WIDTH)); 60| printf("\n"); 61| 62| return 0; 63| }
正規化
(2005.4.30 この節を追加。)
正規化とは
Unicodeは、既存の多くの文字集合の文字を収録しているため、複数のコード列が同じ文字を表す場合がある。これらをそのまま扱うのは検索などで困難が多いので、正規化して特定のコード列にまとめたほうがいい。
Unicodeでの正規化には、NFD, NFKD, NFC, NFKCの4種類がある。NFD/NFKDはできるだけ文字を分解する。NFC/NFKCはできるだけコードを結合する。実用では、通常は NFC で正規化する。例えば、ペーストされた文字列を正規化したうえで取り込むなど。
例えば、U+0041 U+030Aというコード列をNFCまたはNFKCで正規化すると次のようになる。
A 0041 | U+030A COMBINING RING ABOVE | C/KC → | Å 00c5 |
ひらがなでも同様。NFD/NFKDは逆向きの変換を行う。
か 304b | U+3099 COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK | C/KC → | が 304c |
NFKC / NFKD は、まず互換分解 Compatibility Decomposition を行う。NFKCはその上で結合を行う。互換分解は、互換文字をより基本的な文字に置換するほかに、丸付き文字などを展開する。
テキストの検索などで利用価値があるかもしれない。しかし、外部からのデータを取り込むときにこの方法で正規化するのはやりすぎ。
① 2460 | KC/KD → | 1 0031 |
㊤ 32a4 | KC/KD → | 上 4e0a |
㌶ 3336 | KC/KD → | ヘ 30d8 | ク 30af | タ 30bf | ー 30fc | ル 30eb |
正規化するには
ICUでUnicode文字列を正規化するには、Normalizerクラスを用いる。次の例は、UnicodeStringオブジェクトであるstrを正規化するコード片。Normalizerオブジェクトは、イテレータとしても使える。
Normalizer norm(str, UNORM_NFKC); for (UChar uc = norm.first(); uc != norm.DONE; uc = norm.next()) printf("%04x ", uc); printf("\n");
Normalizerのコンストラクタに正規化方法を渡す。
UNORM_NFD | 正規分解 |
UNORM_NFKD | 互換分解 |
UNORM_NFC | 正規分解したうえで正規結合 |
UNORM_NFKC | 互換分解したうえで正規結合 |
リンク
文字の幅
(2005.5.15 この節追加。) 2000.6.28の日記を修正、加筆。
本来は、文字コードは文字の幅を規定しない。文字の幅や大きさ、色というものは表示上のスタイルであって、プレーンテキストでは表現しない、というのが建前だから。しかし、現実には、文字の幅は全角文字・半角文字として使い分けている。
Unicodeでは各コードの属性として文字の幅を持っている (East Asian Width)。試しに、その属性を利用してフォントを使い分けて表示してみる。
このサンプルは、単純に二つのフォントを用意して使い分けるだけなので、複雑な合字や右から左(BIDI)には対応していない。
まずはフォントを決めたり、表示するテキストを用意する。
4| #include <stdio.h> 5| #include <string.h> 6| #include <assert.h> 7| #include <X11/Xlib.h> 8| #include <unicode/normlzr.h> 9| #include <unicode/uchar.h> 10| 11| const char* FONT_NAME_FULL 12| = "-misc-fixed-medium-r-normal-ja-18-*-*-*-*-*-iso10646-1"; 13| const char* FONT_NAME_HALF 14| = "-misc-fixed-medium-r-normal--18-*-*-*-*-*-iso10646-1"; 15| 16| const UChar text[] = { 17| // UnicodeData-3.0.0.txt 18| 0x0041, 0x030a, // U+00C5 N 19| 0x0041, 0x0300, // U+00C0 N 20| 0x0043, 0x0327, // U+00C7 N 21| 0x0049, 0x0301, // U+00CD N 22| 0x00fc, 0x0301, // U+01D8 A or 0075 0308 0301 23| 0x0227, 0x0304, // U+01E1 N or 0061 0307 0304 24| 0x3042, 0x3099, // あ W ゛ W 25| 0x304b, 0x3099, // U+304C が W 26| }; 27| 28| GC gc = NULL; 29| XFontStruct* font_full = NULL; 30| XFontStruct* font_half = NULL;
日本語のテキストの場合には、次のルールでマッピングする。
- Wide文字は、常に全角にする
- NarrowおよびNeutralは、常に半角にする。
- Half-widthは、常に半角にする
- Ambiguousは、常に全角にする。
あとは、違う属性の文字が現れたときにフォントを切り替えればいい。文字の幅は、UCHAR_EAST_ASIAN_WIDTHで取れる。また、結合文字 (combining character) かどうかはUCHAR_CANONICAL_COMBINING_CLASSで取れるので、基底文字 (base character) の場合だけx座標を進める。
32| void onExposed(const XExposeEvent& e) 33| { 34| int x = 10; int y = 30; 35| 36| XChar2b xc; 37| memset(&xc, 0, sizeof(xc)); 38| 39| // 正規化しておく 40| Normalizer it(text, sizeof(text) / sizeof(UChar), UNORM_NFC); 41| 42| UEastAsianWidth width = (UEastAsianWidth) -1; 43| int cx = 0; 44| for (UChar uc = it.first(); uc != it.DONE; uc = it.next()) { 45| UEastAsianWidth cell_width = 46| (UEastAsianWidth) u_getIntPropertyValue(uc, UCHAR_EAST_ASIAN_WIDTH); 47| printf("%04x (%d) ", uc, cell_width); 48| xc.byte1 = (uc >> 8) & 0xff; 49| xc.byte2 = uc & 0xff; 50| 51| if (u_getIntPropertyValue(uc, UCHAR_CANONICAL_COMBINING_CLASS) == 0) 52| x += cx; 53| 54| // 幅が変わるときにフォントを切り替える 55| if (cell_width != width) { 56| // UAX #11: East Asian Width 57| // http://www.unicode.org/reports/tr11/ 58| width = cell_width; 59| switch (cell_width) { 60| case U_EA_WIDE: 61| case U_EA_FULLWIDTH: 62| case U_EA_AMBIGUOUS: // 日本語テキストでは全角にする 63| cx = XTextWidth16(font_full, &xc, 1); 64| XSetFont(e.display, gc, font_full->fid); 65| break; 66| case U_EA_NARROW: 67| case U_EA_NEUTRAL: 68| case U_EA_HALFWIDTH: 69| cx = XTextWidth16(font_half, &xc, 1); 70| XSetFont(e.display, gc, font_half->fid); 71| break; 72| default: 73| assert(0); 74| break; 75| } 76| } 77| 78| XDrawString16(e.display, e.window, gc, x, y, &xc, 1); 79| } 80| printf("\n"); 81| }
残りは、ウィンドウを準備したり、フォントを生成して、イベントループをまわすだけ。
83| int main() { 84| // ウィンドウを準備する 85| Display* disp = XOpenDisplay(NULL); 86| Window top = XCreateSimpleWindow(disp, XRootWindow(disp, 0), 87| 400, 200, 300, 50, 2, 88| BlackPixel(disp, 0), 89| WhitePixel(disp, 0)); 90| XSelectInput(disp, top, ExposureMask); 91| XMapWindow(disp, top); 92| gc = XCreateGC(disp, top, 0, NULL); 93| 94| // フォントを生成する 95| font_full = XLoadQueryFont(disp, FONT_NAME_FULL); 96| font_half = XLoadQueryFont(disp, FONT_NAME_HALF); 97| 98| while (true) { 99| XEvent e; 100| XNextEvent(disp, &e); 101| if (e.type == Expose) 102| onExposed(e.xexpose); 103| } 104| 105| XFreeGC(disp, gc); 106| XUnloadFont(disp, font_full->fid); 107| XUnloadFont(disp, font_half->fid); 108| 109| return 0; 110| }
実行結果は、次のようになる。一応、「あ」に濁点も表示できている。単純に重ねて表示しただけだが。
とはいうものの、自分でフォントを切り替えて表示するのは、手間が掛かるわりに得られるものが乏しい。レイアウトエンジンを使ったほうがいい。多言語に対応したレイアウトエンジンには、Pangoやm17n libraryがある。
サイト内関連文書
トップ > 技術メモ (ソフトウェア開発) > 文字コード・国際化 > ICUを使う
このページについてのご感想・ご提案などをお寄せください。なお、コメントに「http:」、HTML aタグが含まれると送信されません。