markdown

はじめに (対象読者・この記事でわかること)

この記事は、Xlibを使ったX11アプリケーション開発に興味があるC言語プログラマーを対象にしています。特に「ウィンドウに日本語を表示したいけど、文字化けしてうまくいかない」という方に向けて書いています。

この記事を読むことで、Xlibで日本語を表示するために必要な「フォントセットの作成」「文字コードの変換」「描画関数の選び方」について、実装例とともに理解できます。また、日本語表示でよくある落とし穴である「XDrawString16の文字化け」「フォントが見つからないエラー」への対処法も紹介します。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。 - C言語の基本的な文法とポインタの理解 - Xlibを使った簡単なウィンドウプログラミング経験 - 文字コード(UTF-8、EUC-JP、Shift_JIS)の基礎知識

Xlibで日本語表示が面倒な理由

Xlibは1980年代に設計された古いAPIであり、当初はASCII中心の設計でした。そのため、日本語のようなマルチバイト文字を扱うには、いくつかの追加作業が必要です。

まず、Xlibは「フォントセット(XFontSet)」という機構を使って、複数のフォントを束ねて1つの論理フォントとして扱います。これにより、日本語のように複数の文字セット(ASCII、ひらがな、カタカナ、漢字)を混在させた表示が可能になります。

また、XlibにはXDrawString(8ビット用文字列描画)とXDrawString16(16ビット用文字列描画)の2種類の描画関数があります。日本語を直接扱いたい場合、XDrawString16を使う必要がありますが、ここで文字コードの変換が必要になります。

日本語表示の実装手順と落とし穴

ここからは、実際にXlibで日本語を表示する手順を、サンプルコードを交えて解説します。環境はLinux(X11)を前提とし、コンパイルはgccで行います。

ステップ1:フォントセットの作成

まず、日本語を含む文字セットに対応したフォントセットを作成します。Xlibでは、XCreateFontSet()を使います。

C
#include <X11/Xlib.h> #include <X11/Xutil.h> #include <locale.h> #include <stdio.h> #include <stdlib.h> int main() { Display *dpy; Window win; XFontSet fontset; char *missing_charset_list; int missing_charset_count; char *default_string; // ロケールを設定(日本語環境) setlocale(LC_ALL, ""); // ディスプレイとウィンドウを開く dpy = XOpenDisplay(NULL); if (!dpy) { fprintf(stderr, "Cannot open display\n"); return 1; } win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 0, 0, 400, 300, 0, 0, WhitePixel(dpy, 0)); XStoreName(dpy, win, "日本語表示テスト"); XMapWindow(dpy, win); XFlush(dpy); // フォントセットを作成 fontset = XCreateFontSet(dpy, "-*-*-medium-r-normal--14-*-*-*-*-*-*-*", &missing_charset_list, &missing_charset_count, &default_string); if (missing_charset_count > 0) { fprintf(stderr, "Missing charsets: %d\n", missing_charset_count); XFreeStringList(missing_charset_list); } // 後で使うので保持 // ... }

ポイントは、setlocale()でロケールを日本語環境に設定しておくことです。これを忘れると、フォントセットが正しく作成されません。

ステップ2:XDrawString16で日本語を描画

次に、日本語文字列をXDrawString16で描画します。ここで注意が必要なのは、XDrawString16はXChar2b構造体の配列を受け取る点です。

C
#include <X11/Xlib.h> #include <wchar.h> #include <string.h> // UTF-8文字列をXChar2b配列に変換 int utf8_to_xchar2b(const char *utf8, XChar2b **out) { size_t len = mbstowcs(NULL, utf8, 0); if (len == (size_t)-1) return -1; wchar_t *wcs = malloc(sizeof(wchar_t) * (len + 1)); mbstowcs(wcs, utf8, len + 1); *out = malloc(sizeof(XChar2b) * len); for (size_t i = 0; i < len; i++) { (*out)[i].byte1 = (wcs[i] >> 8) & 0xFF; (*out)[i].byte2 = wcs[i] & 0xFF; } free(wcs); return len; } void draw_japanese(Display *dpy, Window win, GC gc, XFontSet fontset, int x, int y, const char *text) { XChar2b *xchar; int len = utf8_to_xchar2b(text, &xchar); if (len < 0) return; XSetFontSet(dpy, gc, fontset); XDrawString16(dpy, win, gc, x, y, xchar, len); free(xchar); }

ハマった点:XDrawText16は使わない方が無難

最初、XDrawText16を使おうとして、文字化けに悩まされました。XDrawText16XTextItem16構造体を使いますが、ここで文字セットの情報が複雑に絡み合い、日本語が文字化けして表示されることがあります。

また、フォントセットを設定しても、環境によっては「JIS X 0208.1990-0」や「JIS X 0212.1990-0」という文字集合が見つからないというエラーが出ることがあります。この場合、以下のようにフォントセットを指定すると回避できます。

C
// より具体的にフォントセットを指定 fontset = XCreateFontSet(dpy, "-*-*-medium-r-normal--14-*-*-*-*-*-*-jisx0208.1983-0," "-*-*-medium-r-normal--14-*-*-*-*-*-*-iso8859-1", &missing_charset_list, &missing_charset_count, &default_string);

解決策:完全な日本語表示プログラム

以下は、これまでの知識をまとめた、日本語を表示する完全なプログラムです。

C
#include <X11/Xlib.h> #include <X11/Xutil.h> #include <locale.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <wchar.h> int utf8_to_xchar2b(const char *utf8, XChar2b **out) { size_t len = mbstowcs(NULL, utf8, 0); if (len == (size_t)-1) return -1; wchar_t *wcs = malloc(sizeof(wchar_t) * (len + 1)); mbstowcs(wcs, utf8, len + 1); *out = malloc(sizeof(XChar2b) * len); for (size_t i = 0; i < len; i++) { (*out)[i].byte1 = (wcs[i] >> 8) & 0xFF; (*out)[i].byte2 = wcs[i] & 0xFF; } free(wcs); return len; } int main() { setlocale(LC_ALL, ""); Display *dpy = XOpenDisplay(NULL); if (!dpy) { fprintf(stderr, "Cannot open display\n"); return 1; } Window win = XCreateSimpleWindow(dpy, DefaultRootWindow(dpy), 100, 100, 400, 300, 0, 0, WhitePixel(dpy, 0)); XStoreName(dpy, win, "日本語表示"); XSelectInput(dpy, win, ExposureMask); XMapWindow(dpy, win); GC gc = XCreateGC(dpy, win, 0, NULL); XFontSet fontset = XCreateFontSet(dpy, "-*-*-medium-r-normal--16-*-*-*-*-*-*-*", NULL, NULL, NULL); XEvent ev; while (1) { XNextEvent(dpy, &ev); if (ev.type == Expose) { const char *text = "こんにちは、Xlibで日本語表示!"; XChar2b *xchar; int len = utf8_to_xchar2b(text, &xchar); if (len > 0) { XSetFontSet(dpy, gc, fontset); XDrawString16(dpy, win, gc, 50, 100, xchar, len); free(xchar); } } } XFreeFontSet(dpy, fontset); XFreeGC(dpy, gc); XCloseDisplay(dpy); return 0; }

コンパイルと実行:

Bash
gcc -o xlib_jp xlib_jp.c -lX11 -lXft ./xlib_jp

まとめ

本記事では、Xlibで日本語を表示するために必要な「フォントセットの作成」「XDrawString16の使い方」「文字コードの変換」について解説しました。

  • Xlibで日本語を表示するには、XCreateFontSetで日本語対応フォントセットを作成する
  • 日本語文字列は、XChar2b配列に変換してXDrawString16で描画する
  • XDrawText16は文字化けの原因になることがあるため、XDrawString16を使うのが無難

この記事を通して、Xlibでも日本語が表示できるようになり、古いX11アプリケーションでも日本語UIが実装できるようになりました。

今後は、Xftライブラリを使ったより美しいアンチエイリアス日本語表示や、Pangoを使った複雑な文字レイアウトについても記事にする予定です。

参考資料