ICUを使う

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)。

イテレータ

文字列オブジェクトを作るだけでは面白くないので、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.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がある。

サイト内関連文書