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;
日本語のテキストの場合には、次のルールでマッピングする。
あとは、違う属性の文字が現れたときにフォントを切り替えればいい。文字の幅は、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がある。