[ C++で開発 ]

Xlib(+Xft)で日本語文字表示

X Window Systemの相当低レベルAPIであるXlibを使って文字列を表示します。また、日本語文字を表示する方法として、XlibおよびXftの2種類を使用します。

概要

国際化対応(Internationalization: I18N)される前のX Window System(X11R4まで)のXlibでは、XLoadFont()、XDrawString()を使って文字を描画していましたが、これらはASCII文字用で、日本語を出すことができません。XDrawString16()という、パッチ的な(?)関数で日本語を出すこともできるようですが、国際化対応版のX Window System/Xlib(X11R5以降)では推奨されません。

国際化対応版Xlibで日本語文字列を表示するには、ロケール設定とXCreateFontSet()、XmbDrawString()などを使います。

Xlibが扱う文字描画は、Xコアフォントシステムを使うため、最近のスケーラブルフォントを扱うことができません。そこで、Xftフォントシステムを使ってスケーラブルフォントで日本語文字列を表示する方法も調べてみました。

サンプル

文字表示のサンプルを3種類示します。いずれも、C++言語で記述しています。

  1. ASCII文字の表示
  2. Xlibでの日本語表示
  3. Xftを使った日本語表示

XlibでASCII文字の表示

Xlibで文字列を表示する非国際化対応コードの基本形です。

textdisplay.cpp
#include <X11/Xlib.h>
#include <unistd.h>

namespace {
// 定数定義
const unsigned int WIN_WIDTH = 640;
const unsigned int WIN_HEIGHT = 480;
const unsigned int WIN_BORDER_WIDTH = 6;
const char* MESSAGE = "Welcom to Xlib Programming World!";

unsigned long getColor(Display* display, const char* colorName) {
    Colormap cmap = DefaultColormap(display, DefaultScreen(display));
    XColor color, exact;
    ::XAllocNamedColor(display, cmap, colorName, &color, &exact);
    return color.pixel;
}

}

int main(int argc, char* argv[]) {
    Display* display = ::XOpenDisplay(0);
    Window root = RootWindow(display, 0);
    unsigned int rootWidth = DisplayWidth(display, 0);
    unsigned int rootHeight = DisplayHeight(display, 0);

    // 使用するカラーを設定
    unsigned long black = BlackPixel(display, 0);
    unsigned long white = WhitePixel(display, 0);
    unsigned long green = getColor(display, "green");

    Window toplevel = ::XCreateSimpleWindow(
        display, root, (rootWidth - WIN_WIDTH)/2, (rootHeight - WIN_HEIGHT)/2,
        WIN_WIDTH, WIN_HEIGHT, WIN_BORDER_WIDTH, black, white
    );
    // フォント読み込み
    Font font = ::XLoadFont(display, "r24");

    // 文字描画用GC
    GC gc = XCreateGC(display, toplevel, 0, 0);
    ::XSetBackground(display, gc, black);
    ::XSetForeground(display, gc, green);
    ::XSetFont(display, gc, font);

    ::XMapWindow(display, toplevel);
    ::XFlush(display);

    ::XSelectInput(display, toplevel, ExposureMask | ButtonPressMask);
    while (true) {
        XEvent event;
        ::XNextEvent(display, &event);
        if (event.type == Expose) {
            ::XDrawString(
                display, toplevel, gc,
                100, 100, MESSAGE, strlen(MESSAGE)
            );
        } else if (event.type == ButtonPress) {
            break;
        }
    }

    ::XCloseDisplay(display);
}

フォントの指定に、XLoadFont()関数、文字列の表示にXDrawString()関数を使用しています。

ビルドは、以下の指定です。

$ g++ textdisplay.cpp -o textdisplay -lX11

Xlibで日本語文字の表示

国際化対応関数を使った日本語の表示です。

i18ntextdisplay.cpp
#include <X11/Xlib.h>
#include <unistd.h>
#include <iostream>

namespace {
// 定数定義
const unsigned int WIN_WIDTH = 640;
const unsigned int WIN_HEIGHT = 480;
const unsigned int WIN_BORDER_WIDTH = 6;
const char* MESSAGE = "Xlibプログラミングの世界へようこそ";

unsigned long getColor(Display* display, const char* colorName) {
    Colormap cmap = DefaultColormap(display, DefaultScreen(display));
    XColor color, exact;
    ::XAllocNamedColor(display, cmap, colorName, &color, &exact);
    return color.pixel;
}

}

int main(int argc, char* argv[]) {
    if (setlocale(LC_CTYPE, "") == 0) {
        std::cerr << "Can't set locale" << std::endl;
        return 2;
    }
    if (! ::XSupportsLocale()) {
        std::cerr << "Current locale is not supported" << std::endl;
        return 3;
    }
    Display* display = ::XOpenDisplay(0);
    Window root = RootWindow(display, 0);
    unsigned int rootWidth = DisplayWidth(display, 0);
    unsigned int rootHeight = DisplayHeight(display, 0);

    // 使用するカラーを設定
    unsigned long black = BlackPixel(display, 0);
    unsigned long white = WhitePixel(display, 0);
    unsigned long green = getColor(display, "green");

    Window toplevel = ::XCreateSimpleWindow(
        display, root, (rootWidth - WIN_WIDTH)/2, (rootHeight - WIN_HEIGHT)/2,
        WIN_WIDTH, WIN_HEIGHT, WIN_BORDER_WIDTH, black, white
    );

    // 文字描画用GC
    GC gc = XCreateGC(display, toplevel, 0, 0);
    ::XSetBackground(display, gc, black);
    ::XSetForeground(display, gc, green);
    int missingCount;
    char** missingList;
    char* defString;
    XFontSet fontSet = ::XCreateFontSet(
        display, "-*-fixed-medium-r-normal--16-*-*-*",
        &missingList, &missingCount, &defString
    );
    if (fontSet == 0) {
        std::cerr << "Failed to create fontset" << std::endl;
        return 1;
    }
    ::XFreeStringList(missingList);

    ::XMapWindow(display, toplevel);
    ::XFlush(display);

    ::XSelectInput(display, toplevel, ExposureMask | ButtonPressMask);
    while (true) {
        XEvent event;
        ::XNextEvent(display, &event);
        if (event.type == Expose) {
            ::XmbDrawImageString(
                display, toplevel, fontSet, gc,
                100, 100, MESSAGE, strlen(MESSAGE));
        } else if (event.type == ButtonPress) {
            break;
        }
    }

    ::XCloseDisplay(display);
}

main関数の最初の部分で、ロケール指定をしています。国際化(I18N)対応では、アプリケーションのロケールを指定します。setlocale()でLC_CTYPEカテゴリを、環境変数で指定したロケールに設定します。なお、決め打ちする場合、setlocaleの第2引数に直接ロケール文字を指定します。(決め打ちしてしまうとI18N対応とは言えませんが、このサンプルのようにプログラム内に特定ロケールの文字列をハードコーディングしている場合、環境変数で他のロケールを指定しても対応できません)

次にX Window Systemがsetlocaleで指定したロケールに対応できるかをXSupportsLocale()関数で検査しています。

フォントの読み込みは、XCreateFontSet()関数で行います。

文字の表示は、XmbDrawString()関数で行います。なお、XwcDrawString()関数もXlibで提供されています。こちらは表示したい文字がwchar_t型で格納されている場合に使用します。

ビルドは、以下の指定です。

$ g++ textdisplay.cpp -o i18ntextdisplay -lX11

Xftで日本語文字の表示

スケーラブルフォントを使用する場合、Xlibのみでは対応できないので、freetypeをXから利用できるようにしたXftを用います。

xfttextdisplay.cpp
#include <X11/Xlib.h>
#include <X11/Xft/Xft.h>
#include <unistd.h>
#include <iostream>

namespace {
// 定数定義
const unsigned int WIN_WIDTH = 640;
const unsigned int WIN_HEIGHT = 480;
const unsigned int WIN_BORDER_WIDTH = 6;
const char* MESSAGE = "XlibとXftプログラミングの世界へようこそ";

}

int main(int argc, char* argv[]) {

    Display* display = ::XOpenDisplay(0);
    Window root = RootWindow(display, 0);
    unsigned int rootWidth = DisplayWidth(display, 0);
    unsigned int rootHeight = DisplayHeight(display, 0);

    // 使用するカラーを設定
    unsigned long black = BlackPixel(display, 0);
    unsigned long white = WhitePixel(display, 0);

    Window toplevel = ::XCreateSimpleWindow(
        display, root, (rootWidth - WIN_WIDTH)/2, (rootHeight - WIN_HEIGHT)/2,
        WIN_WIDTH, WIN_HEIGHT, WIN_BORDER_WIDTH, black, white
    );

    XftFont* xftFont = XftFontOpen(
        display, 0,
        XFT_FAMILY, XftTypeString, "VL ゴシック",
        XFT_SIZE, XftTypeDouble, 24.0,
        0
    );
    Colormap cmap = DefaultColormap(display, 0);
    XftColor color;
    ::XftColorAllocName(display, DefaultVisual(display, 0), cmap, "green", &color);
    XftDraw* draw = ::XftDrawCreate(
        display, toplevel, DefaultVisual(display, 0), cmap
    );
    ::XMapWindow(display, toplevel);
    ::XFlush(display);

    ::XSelectInput(display, toplevel, ExposureMask | ButtonPressMask);
    while (true) {
        XEvent event;
        ::XNextEvent(display, &event);
        if (event.type == Expose) {
            ::XftDrawStringUtf8(
                draw, &color, xftFont,
                10, 100, (FcChar8*)MESSAGE, strlen(MESSAGE)
            );
        } else if (event.type == ButtonPress) {
            break;
        }
    }
    ::XftDrawDestroy(draw);
    ::XCloseDisplay(display);
}

フォントの指定は、Xftの関数XftFontOpen()を使用します。

色の指定も、XftColorAllocName()などを使用します。

文字の描画はXftDrawStringUtf8()を使用しますが、描画対象領域はXftDraw型で指定する必要があるため、XlibのWindow型をラップしてXftDraw型を生成するXftDrawCreate()関数を使用します。

ビルドは、以下の指定です。

なお、ソースファイルはUTF-8でエンコーディングされている必要があります。

$ g++ xfttextdisplay.cpp -o i18ntextdisplay -I/usr/include/freetype2 -lXft -lX11

CentOS 5.xの場合、libXft-develおよびfreetype-develパッケージで提供されるヘッダーファイルが必要となります。