Motifでの日本語の扱い

はじめに

一応ここではMotifと断っていますが、Xt,Xlib的にはAthenaでも共通の概念です。 実際、ウィジェットのリソースの設定の規則が異なる以外は、完全に共通です。

初期化

Motifで日本語を扱う場合、Xlib,Xtによる国際化の機能を利用することになります。

簡単に扱えるかどうかかは、あなたが利用している環境が その機能を含んだライブラリを提供しているかどうかにかかってきます。 外国産のOSに附属のものや、アマチュアが作成したライブラリで(最近のPC-UNIX関係) はまることが多いような気がします。 ダメな場合は、経験豊かな友人に「バイナリ ライブラリ欲しい」と ねだってみるのも一つの手です。 オープンソースな時代ですから、クライアントのライブラリなら技量で何とかなるかもしれません。

一般に ライブラリがうまく調整されているならば、 -- Xtレベルでは -- コードの初期化部分(XtVaOpenApplicationよりも前)に 次の記述を含めるだけです。 逆に言えば、この記述を取り除くとXtでは日本語は扱えなくなります。


XtSetLanguageProc(NULL,NULL,NULL);

もちろん具体的な意味は附属のマニュアルを参照してもらうのが一番ですが、 簡単に説明すると、この記述でsetlocale(3)が呼び出されることが重要です。 Xの実装は標準Cライブラリのロケール(locale)の機構と連動する形で 扱う文字集合(コード エンコーディング)の切替が行なわれます。

文字集合の選択

次にプログラマーが決めないといけないことは、 とりあえず「APIでどの文字集合を利用するか」です。

わざわざ とりあえずと断ったのは、もしあなたがロケールを機構をうまく 利用したコーディングの仕方を知っているなら、 エンドユーザがあとから選択できるようにアプリケーションを作成できるので、 いつでも、変更することができるからです。

各人の技量にもよるのでしょうが、 当面は ある特定の文字集合に依存したコーディングのほうが 作成時間が短縮できると思います。

驚くべきことに Xの規格的には(現時点ではたぶんそのような実装はないので、理想の話ですが) コンパイルをし直すこと無く、文字集合を実行時に選択できるだけの仕組みを提供してくれます。 もう昔の話になりますが、 Xの世界で「国際化プログラミング」として騒がれていた内容に関連があります。 (ちなみに当時の私は、その騒ぎをまるで知らない一学生でした。)

まあ実行時とまではいかなくても -- Cコンパイラさえサポートしていれば -- ソース コード的に 大抵の文字集合が扱えるはずです。

いうまでもないことかも知れませんが、ここでいう文字集合は プログラムの内部コードではなく、 外部コードとして捕えたほうがよいものです。 内部コード表現はあなたがプログラミングの都合で自由に決めてかまいません。 もちろん クイック プログラミングでは 内部コード = 外部コードとするのは よくあることです。

私が調べた範囲では、多くのプログラムで いわゆる日本語EUC(Japanese Extended Unix Code)が利用されています。

その理由はおそらく … 日本工業規格(JIS;Japan Industrial Standard)で決められている 文字コード(JIS-X0201、JIS-X0208等)からの変換演算が、 プログラマの記憶の範囲で簡単に実現できるからでしょう。 なにせ、上位ビットの操作だけでエンコード、デコードできますから。

本稿でも同様に日本語EUCを想定した内容で記述していきます。 テキスト エディタでソースやリソースを編集する際には 保存時の文字集合に注意を払って下さい。

Xtリソースの準備

Xtでは、プログラムに埋め込んでしまうものもありますが、 通常ウィジェットの見出しの文字やテキストの初期値は Xtリソース ファイルを使って設定します。 XtVaOpenApplicationに与える、アプリケーションクラスと同名のテキストファイルです。

Xtリソース ファイルをファイルシステム上のどこに配置するかで どの文字集合がが利用できるかが決まります。 詳細はシステム附属のオンライン マニュアル「X」の RESOURCESの章 (xjmanプロジェクトによる配布では「リソース」の章) 以後や 「XtResolvePathname」に記述されています。 具体的なやり方は呆れるほど沢山あります。

ASCIIによる「BSD magazine No.2 1999」の 特集2「X Window Systemの環境構築」が詳しいです。 興味のある人はバックナンバーを入手してみて下さい。

プログラム開発中はホームディレクトリから必要に応じてシンボリックリンクを張るのお勧めです。

例:
$ pushd ~    … $HOMEへ移動
~ ~/X/foo-1.1
$ ln -fs X/foo-1.1/Foo.ja Foo    … Xtリソース定義をリンク
$ popd
$ ./foo -xnl ja_JP.EUC    … 動作の確認
$ LC_CYPTE=ja_JP.EUC ./foo    … もう一つのやり方

文字集合の整合を手軽に確認するには、 -xnllanguageオプションか 環境変数$LC_CTYPEで指示するのが便利です。 並行して別のlocaleの機構を確認するなら$LANGでも構いませんが、 Xt,Xlib的には$LC_CTYPEだけが影響を受けます。

ソースを展開している人はライブラリのディレクトリでのgrepで確認してみるのもよいでしょう。

Motifでのリソースの規則

まずウィジェットのリソース レベルでのロケールの影響から紹介します。

影響があるのは基本的にテキストの話ですから、 リソース的には文字列とフォント関係ということです。

まず、メニューやボタンの見出しに使う コンパウンド ストリング(compound string)の指定に 日本語が使えるようになります。
(SEE: man XmString)

例: コンパウンド ストリングのでの日本語を使う
*menu_bar*save.labelString: 編集内容を保存する(S)
*menu_bar*save.accelerator: Ctrl<Key>s
*menu_bar*save.acceleratorText: C-s   … 実はここでも使える
*menu_bar*save.mnemonic: S

*menu_bar*save_as.labelString: 名前を付けて保存する(A)

フォントに日本語を表現できるものを含めることができます。 ちなみに コマンドライン オプションの -font は、基本的に意味をなさなくなります。 (意味がよくわからない人のために … Xt的にはコマンドライン オプションは 実行時のXtリソースの最優先の変更を意味します。)

例: メニュー等で日本語を使う
! 従来互換のフォント リストによる指定
! ローカラムとそれに格納される様々なウィジェットのためのフォントリストを定義
*XmRowColumn*fontList: \
-mnkaname-gothic-medium-r-normal--12-110-75-75-p-120-jisx0208.1983-0;\
-elisa-fixed-medium-r-normal--10-70-75-75-c-100-jisx0208.1983-0;\
-adobe-courier-medium-r-normal--14-140-75-75-m-90-iso8859-1;\
-adobe-courier-medium-r-normal--18-180-75-75-m-110-iso8859-1;\
-*-fixed-medium-r-normalb--14-*-*-*-c-*-jisx0208.1983-0;\
-*-fixed-medium-r-normal--16-*-*-*-*-*-*-*:

! レンダーテーブルによる指定
! 標準のレンダーテーブル構成要素の定義
*renderTable:
*renderTable.fontName: \
-mnkaname-gothic-medium-r-normal--12-110-75-75-p-120-jisx0208.1983-0,\
-elisa-fixed-medium-r-normal--10-70-75-75-c-100-jisx0208.1983-0,\
-adobe-courier-medium-r-normal--14-140-75-75-m-90-iso8859-1,\
-adobe-courier-medium-r-normal--18-180-75-75-m-110-iso8859-1,\
-*-fixed-medium-r-normalb--14-*-*-*-c-*-jisx0208.1983-0,\
-*-fixed-medium-r-normal--16-*-*-*-*-*-*-*
*renderTable.fontType: FONT_IS_FONTSET
*renderTable.renditionForeground: maroon

フォントリスト(font list)のリソースファイルでの指定のこつですが、 それぞれのコードセットのフォント名をセミコロン(;)で区切り、 最後に最後にコロン(:)を付けるということでしょうか。 実際には複雑な意味があるのですが、とりあえず これで文字集合とフォントの対応の定義が完了します。
(SEE: man XmFontList)

レンダーテーブル(Render Table)はMotif 2.xで利用できるようになった仕掛けです。 Compound Stringsと組み合わせて使われる新たな仕掛けで、 タグと関連づけて表示内容を設定できるようになっていますが、ここでは詳細は説明しません。 (open Motifのソースを追いかけると雰囲気が掴みやすいかもしれません。)
(SEE: man XmRenderTable)

Motif Programmer's Guideの「Compound Strings」の章に簡単な説明が見つかります。

最後に日本語テキストの入力に関してです。

Motif的にはXIM;X Input Methodの機構を利用すれば、 附属のウィジェットを使う上では(具体的にはTextとTextFieldについて) 入力行為そのものに付いての Cレベルでのコーディングの必要はありません。 もちろん然るべきタイミングで -- 例えば入力完了のボタンを押したなど -- 入力されたテキストを取り出して利用するというコーディングは必要です。

例: InputMethod関係
! XIMの入力種別
!*inputMethod: kinput2
*preeditType: Root
*XmDialogShell.preeditType: OverTheSpot

まず inputMethodについてですが、上の例では(コメントアウトしていなければ) XSetLocaleModifiers("@im=kinput2") に相当する処理が入ります。 おそらく 環境変数$XMODIFIERS にて設定するほうが一般的なので、 リソースではあまり指定しないほうがよいかも知れません。

次にpreeditTypeですがXIM変換アプリケーションのスクリーンにおける振舞いを指定します。

OverTheSpot
入力対象のウィジェット上にかぶる形で 仮入力のためのウィンドウが現れます。
OffTheSpot
特定の領域で入力して、 確定するとウィジェットに転送されるという方式ということなのですが、 Motifではダイアログの表示が一時的に完全に消えてしまうので、 はっきりいって戸惑います。
Root
XIMアプリケーション別途入力ための 専用のトップレベル ウィンドウを用意して、 そこで確定するとウィジェットに転送されるという方式になります。
OnTheSpot
見かけ的にはウィジェット自体がその機能を持っているような形で変換、 入力できますが、これをうまくサポートしているXIMアプリケーションは少ないです。 実際 FreeBSD4.xと現在のkinput2と組み合わせではうまく動きません。

公開されているソースから確認した範囲では、 preeditType はシェルについて設定できるもののようで、 文字列やフォントの指定と違って、必須な性質のものではありません。

ちなみに上の例では、あまり意味はありませんが、 ダイアログと普通のシェルで入力の形態を変えてみています。

標準Cライブラリの復習

少なくとも マルチバイト文字列(MBCS; Multi Byte Character Scring)と ワイドキャラクタ(WC; Wide Chnaracter)に関連する 次のAPIの動作を把握していないと、実際のコーディングは大変だと思います。

標準Cライブラリ(JIS-X-3010:1993)のMBCS関連のAPI
API説明
setlocaleロケールの変更
mblenマルチバイト文字列から、1文字を構成する長さをえる
mbstowcsマルチバイト文字列をワイドキャラクタ列に変換
mbtowcマルチバイト文字列を1文字分をワイドキャラクタの1文字に変換
wcstombsワイドキャラクタ列からマルチバイト文字列に変換
wctombワイドキャラクタ1文字をマルチバイト文字列に変換

ちなみにJIS-X-3010:1993はISO/IEC 9899:1990に対応する内容でもあります。

これらのAPIを利用したサンプルを少々示しましょう。 具体的な引数の意味はそれぞれの環境附属のマニュアルを御覧になって下さい。

ロケールの変更

日本語EUCが利用できるロケールに変更しています。 Xアプリケーションでは 普通、このような記述は含まれません。


char *lc_ctype, *japanese_EUC_locale = "ja_JP.EUC";

if (lc_ctype = setlocale(LC_CTYPE,japanese_EUC_locale))
    fprintf(stderr,"ロケールの変更: %s ← %s\n",lc_ctype,japanese_EUC_locale);
else
    fprintf(stderr,"Warrning:ロケール「%s」は本システムではサポートされていません。\n"
        "とりあえずこのエラーは無視して動作します。",japanese_EUC_locale);

fprintf(stderr,"このロケールの MB_CUR_MAX:%d\n",MB_CUR_MAX);

なお“ja_JP.EUC”の記述の部分は、似たような傾向はありますが、厳密には環境依存です。

文字単位の変換

余り深い意味はありませんが、mblen、mbtowc、wctombの利用例です。

int reset_state(int category) 
{
    char *last_locale = setlocale(category,NULL);

    return strcmp(last_locale,"C") && setlocale(category,"C") && 
        setlocale(category,last_locale);
}

int out_by_char(char *t,size_t len,char *buf)
{
    wchar_t wc;
    size_t n, m;
	
    while (*t && len && (n = mblen(t,len))) {
        /* mbcs → wc → mbcsの変換に成功したら出力 */
        if (n == -1 || mbtowc(&wc,t,n) == -1 || (m = wctomb(buf,wc)) == -1)
            return mblen(NULL,0) && !reset_state(LC_CTYPE) ? -2 : -1;

        printf("%.*s",m,buf);

        t += n, len -= n;
    }

    return 0;
}

文字単位の変換では、変換API内部の状態保持を少し意識しなくてはなりません。

インターネットの日本語のメッセージ交換で良く利用されている ISO-2022-JP を想像してもらえばよいかと思いますが、 変換の途中で失敗したら、どこかのタイミングでリセットが必要です。 ISO-2022-JPでは行末でASCIIかJIS-X-0201に戻すというアレですね。

リセットの具体的方法が処理系のマニュアルで触れられていないのが普通なので、 どうするのが良いのか迷いますが、 上の例では強引に一時的にロケール“C”に戻してみることで対処しています。 (“C”についてはどの処理系でも存在することになっていますからです)

文字列単位の変換

こちらは、mbstowcsとwcstombsの利用例です。

int out_by_line(char *t,size_t len,wchar_t *wbuf)
{
    char buf[len];
    size_t n;

    /* mbcs → wcs → mbcsの変換に成功したら出力 */
    mbstowcs(wbuf,t,len) != -1 &&
        (n = wcstombs(buf,wbuf,len)) != -1 ? printf("%.*s",n,buf) : -1;
}

例えば、次のようなコードと組み合わせて動作確認してみるとよいでしょう。

char buf[BUFSIZ], mb_buf[MB_CUR_MAX];
size_t len;

wchar_t wbuf[BUFSIZ];

while (fgets(buf,sizeof buf,stdin))
    if (out_by_line(buf,len = strlen(buf),wbuf) < 0 &&
        out_by_char(buf,len,mb_buf) < 0) break;

ソースコードに埋め込まれるワイドキャラクタ

あなたの利用しているコンパイラが対応しているかどうかにもよりますが、 標準C対応であるならば 文字列リテラルの前に L を添えるとバイナリコードに ワイドキャラクタを埋め込むことができます。

MBCSを普通に埋め込む人は多いですが、WCSはどうでしょうね。

CライブラリがJIS-X-3010:1993に対応しているなら、 printf等で 簡単に出力できるはずですが、PC-UNIXの世界ではまだまだのようです。

Xlib文字集合変換API

Xの世界でも同様な処理関数があります。 バイナリ ライブラリの構築の仕方によりますが、 内部で標準Cライブラリを呼び出すものもあります。 これらは単にMBCS⇔WCSというものではなく、コンパウンド テキスト(Compound Text)との 相互変換が目的で存在するものなので、扱いが少々複雑です。

ちなみにコンパウンド テキストは ISO-2022 の限定したエンコーディングの一つです。 Xのクライアント間でのテキスト通信で利用されます。

Motifのコンパウンド ストリングとX標準のコンパウンド テキストは 複数の文字集合を表現しようとする意味では似ていますが、 仕掛け的にはまったく別物ですから混同しないように注意して下さい。

Xlib文字集合変換API
API説明
XDefaultString変換できなかった時に出力される文字列
XmbTextListToTextPropertyMBCSの配列をテキスト プロパティに変換
XmbTextPropertyToTextListテキスト プロパティをMBCSの配列に変換
XwcTextPropertyToTextListMBCSWCS配列をテキスト プロパティに変換
XwcTextListToTextPropertyテキスト プロパティをWCS配列に変換
XwcFreeStringListWCS配列の解放

上記表を参照するとわかると思いますが、 これらのAPIは、 MBCS ⇔ テキスト プロパティ ⇔ WCS の仕掛けを提供するものです。

テキスト プロパティはそのメンバーが公開されている構造体で、 その内部表現(スタイル)として STRING(ISO-Latin1)か TEXT(現ロケールに依存したエンコーディング) か コンパウンド テキストになるというものです。
(SEE: man XmbTextListToTextProperty)

XTextPropertyのスタイル
スタイル内部表現
XStringStyleSTRING (ISO-Latin1)
XCompoundTextStyleCOMPOUND_TEXT
XTextStyleTEXT (MBCS)
XStdICCTextStyle可能ならSTRING、あるいはCOMPOUND_TEXT

Xlibを使ったWCS→MBCS変換

簡単なサンプルを示します。説明はコメントで十分だと思います。

/* dからtとoptを順次格納し,最終的にコピーした文字の最後を指すポインタを返す */

char *xstrcat(char *d,const char *t,const char *opt)
{
    while (*d = *t++)
        d++;

    if (opt)
        while (*d = *opt++)
            d++;

    return d;
}

/* 指示するスタイルでもってWCSをMBCSに変換する
  wcsは配列の形で与えることができ、連結した形で受けとることができる。
  変換後のそれぞれのMBCSの間にpを挟むことができる。pはNULLでもよい。 */

char *xmbs(Display *display,wchar_t **wcs,size_t n,char *p,XICCEncodingStyle encoding_style)
{
    char **list;
    int list_count;

    assert(display);
    assert(wcs);
    assert(n);


    /* テキストプロパティ経由の変換 */
    {
        XTextProperty text_property;

        if (XwcTextListToTextProperty(display,wcs,n,encoding_style,&text_property) != Success)
            return 0;

        if (XmbTextPropertyToTextList(display,&text_property,&list,&list_count) != Success) {
            XFree(text_property.value);
            return 0;
        }

        XFree(text_property.value);
    }

    /* MBCS配列の合成 */
    {
         /* リスト間に詰める文字列の大きさの計算 */
        size_t len = p ? strlen(p) * n: 0;
        char **t, *u, *r;

        for (t = list; t < list + list_count; t++)
            len += strlen(*t);

        r = u = XtMalloc(len + 1);

        for (t = list; t < list + list_count; t++)
            u = xstrcat(u, *t, p);

        XFreeStringList(list);

        return r;
    }
}

ここで定義するxmbsは、次のように利用できます。

wchar_t sample[] = {
    L"WCSを変換してみましょうか。",
    L"うまくいくかな?",
    L"", 
    L"OK連結されてるよ。",
};

char *t;

fputs((t = xmbs(display,sample,XtNumber(sample),"\n",XStdICCTextStyle)) ? t : "",stderr);
XtFree(t);

実装によるのかも知れませんが、日本語を扱う場合は なるべくスタイルに XStdICCTextStyleを指定したほうがよいみたいです。 少なくともFreeBSD 4.0 付属のXFree86によるバイナリライブラリを使うと XTextStyle だと変換が失敗して何も出力されません。


その2へ 続きます