X Window System, Client Programming, No.2


フォント

コンピュータで使われる文字のデザインやその形状のことを『フォント』と いいます。

どのようなフォントが利用できるかは、Xサーバの設定によって 異なっています。 Xサーバで利用することのできるフォントの一覧は、 xlsfonts コマンドによって知ることができます。

フォント一覧
PROMPT$ xlsfonts 
    ...
-adobe-times-medium-r-normal--0-0-75-75-p-0-iso8859-1
    ...
-adobe-times-medium-r-normal--14-140-75-75-p-74-iso8859-1
    ...
-misc-fixed-medium-r-normal--0-0-75-75-c-0-jisx0201.1976-0
-misc-fixed-medium-r-normal--0-0-75-75-c-0-jisx0208.1983-0
-misc-fixed-medium-r-normal--14-130-75-75-c-70-jisx0201.1976-0
-misc-fixed-medium-r-normal--14-130-75-75-c-140-jisx0208.1983-0
   ...

私達が使っているシステム(Solaris2.X)では、Xサーバが 参照するフォントは/usr/openwin/lib/X11/fonts/以下の ディレクトリに置かれています。 Linuxの場合は、普通、/usr/X11R6/lib/X11/fontsの下の ディレクトリに置かれています。 xsetコマンドでフォントの設定を調べたり、変更したりできます。 xsetで表示されるFontPathの値がフォントの置かれている ディレクトリへのパスになります。

フォント・パスを調べる
PROMPT$ xset q 
   ...
Font Path:
  /usr/openwin/lib/X11/fonts/F3/,
  /usr/openwin/lib/X11/fonts/F3bitmaps/,
  /usr/openwin/lib/X11/fonts/Type1/,
  /usr/openwin/lib/X11/fonts/Speedo/,
  /usr/openwin/lib/X11/fonts/misc/,
  /usr/openwin/lib/X11/fonts/75dpi/,
  /usr/openwin/lib/X11/fonts/100dpi/

ファイルとフォント名の対応はそのディレクトリに置かれている fonts.dir というファイルで定義されています。 また、フォントの別名は fonts.alias というファイルで定義されています。

/usr/openwin/lib/X11/fonts/misc/fonts.dir
6x12.pcf.Z -misc-fixed-medium-r-semicondensed--12-110-75-75-c-60-iso646.1991-irv
6x13.pcf.Z -misc-fixed-medium-r-semicondensed--13-120-75-75-c-60-iso8859-1
    ...

/usr/openwin/lib/X11/fonts/misc/fonts.alias
k14          -misc-fixed-medium-r-normal--14-*-*-*-*-*-jisx0208.1983-0
a14          -misc-fixed-medium-r-normal--14-*-*-*-*-*-iso8859-1
r14          -misc-fixed-medium-r-normal--14-*-*-*-*-*-jisx0201.1976-0
    ...

ちなみに"jisx0208"が名前に付くのは日本語の漢字用のフォントです。

X-Windows System におけるフォント名は XLFD (X Logical Font Description Convention) という 規則にしたがっています。 XLFD では、フォント名の - (ハイフン)に囲まれた14個のフィールドの 意味はそれぞれ以下のように定義されています。

  1. FOUNDRY --- フォントを供給する会社や組織の名前
  2. FAMILY_NAME --- 活字書体デザインの範囲、すなわち「ファミリ」の名称
  3. WEIGHT_NAME --- フォントの活字製版ウェイト、 すなわち「フォントの太さ」
  4. SLANT --- 活字書体デザインの全体的な姿を示すコード文字列。
    コード英語名説明
    RRoman直立
    IItalicイタリック、垂直軸から時計方向に傾斜
    OOblique斜め直立、垂直軸から時計方向に傾斜
  5. SETWIDTH_NAME --- 活字製版幅比率、すなわち「フォントの 水平単位量に対する幅」
  6. ADD_STYLE_NAME --- 追加スタイル情報
  7. PIXEL_SIZE --- デバイス・ピクセル単位による活字製版寸法を示す数字
  8. POINT_SIZE --- デバイスとは独立した単位による活字製版寸法を示す数字
  9. RESOLUTION_X --- インチあたりのピクセル数を単位とする水平方向解像度
  10. RESOFUTION_Y --- インチあたりのピクセル数を単位とする垂直方向解像度
  11. SPACING --- 文字送りの種類
    コード英語名説明
    PProportional文字幅が可変のフォント
    MMonospaced文字幅が一定のフォント
    CCharCellタイプライタ文字セルのモデルに準拠する固定幅フォント
  12. AVERAGE_WIDTH --- ウェイトを無視した、全フォント・グリフの1/10ピクセル 単位の平均幅
  13. CHARSET_REGISTRY --- フォントのグリフの符号化に使用される文字セット
  14. CHARSET_ENCODING --- フォントのグリフの符号化に使用される文字セット

フォント名を指定するときは必ずしも全てのフィールドを 指定しなくても構いません。 フィールドを省略するときは '*' (アスタリスク)という文字を使います。 '*' は1つまたは複数のフィールドを表します。

    [例] -*-times-medium-r-normal--14-140-*-*-*-*-iso8859-1
         -*-times-medium-r-normal--14-*

スケーラブルフォント

Xサーバがロードできるフォントの一覧は XListFonts()関数によって 得ることができます。 たとえば、上で述べた xlsfonts コマンドは XListFonts() 関数を使って 実現されています。

X11R5以降のXサーバでは、PIXEL_SIZE, POINT_SIZE, AVERATE_WIDTH の フィールド (14個のフィールドをもつXLFDのフォント名の7, 8, 12番目の フィールド)に 0 を指定すると、 XListFonts() はスケーラブルフォントを返すようになりました。 渡されたフォント名パターンがXLFDの妥当な形式(= 14個のハイフンが全て 指定されている形式)で指定されている場合に限ってXサーバは スケーラブルフォントを選択します。

スケーラブルフォントをリストするには、PIXEL_SIZE, POINT_SIZE, AVERATE_WIDTH を 0 または * にした妥当な形式のパターンを指定してXListFonts()を呼び出します。

xlistfonts.c
#include <stdio.h>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

int main() {
    Display *dpy;
    char **list;
    int count, i;
    
    if ((dpy = XOpenDisplay(NULL)) == NULL) {
	fprintf(stderr,"can not open display\n");
	exit(-1);
    }
    list=XListFonts(dpy,"-*-courier-*-*-*-*-0-0-*-*-*-0-*-*",200,&count);
    printf("count=%d\n",count);
    for (i=0; i<count; ++i) {
	printf("%s\n",*(list++));
    }
}


実行結果
PROMPT$ gcc -I/usr/X11R6.4/include xlistfonts.c -L/usr/X11R6.4/lib -lX11 -lnsl -lsocket  ←Solarisの場合
または
PROMPT$ gcc -I/usr/X11R6/include xlistfonts.c -L/usr/X11R6/lib -lX11  ← Linuxの場合
PROMPT$ xlistfonts 
count=96
--courier-bold-o-normal--0-0-72-72-m-0-iso8859-1
--courier-bold-o-normal--0-0-0-0-m-0-iso8859-1
--courier-bold-r-normal--0-0-72-72-m-0-iso8859-1
...

文字の表示

Xサーバにフォントをロードするには関数XLoadQueryFont()を使います。 表示する文字列がウィンドウ上でどれだけの幅になるかを計算するには、 1バイト文字に対しては関数 XTextWidth() を、 2バイト文字に対しては関数 XTextWidth16() を 使います。 ウィンドウ上に文字列を表示するには Xサーバにロードしたフォントを 設定したグラフィックコンテクストを作成しておいてから、 関数 XDrawString() または関数 XDrawString16() を使います。

ウィンドウに文字を表示する手順は以下の通りです。

  1. Xサーバにフォントをロードします。
  2. フォントを登録してグラフィックコンテクストを作成します。
  3. (必要に応じて)テキストの表示幅を計算します。
  4. グラフィックコンテクストを指定してテキストを表示します。

  1. Xサーバにフォントをロードします。
    Xサーバ上のウィンドウに文字を描画するためには、描画要求よりも前に Xサーバにフォントをロードしておく必要があります。
    クライアントがXサーバに接続した後で、関数 XLoadQueryFont() を 使うとXサーバにフォントのデータがロードされ、クライアントには 返り値としてXFontStruct構造体が返されます。
    サーバへのフォントのロード --- XLoadQueryFont()関数
    XFontStruct *XLoadQueryFont(Display *dpy, char *name)
        Display *dpy;   Xサーバを指定する
        char *name;     フォント名を指定する
    
    
    XFontStruct構造体
    typedef struct {
        Font        fid;            /* Font id for this font */
        ...
        int		ascent;		/* log. extent above baseline for spacing */
        int		descent;	/* log. descent below baseline for spacing */
    } XFontStruct;
    
    
    Xサーバ上にロードされたフォントは、 このFontStruct構造体の fid フィールドで識別されます。 描画関数でフォントを指定するには、 FontStruct構造体の fid フィールドを指定して作成した グラフィックコンテクストを使います。
  2. フォントを設定してグラフィックコンテクストを作成します。
    関数 XLoadQueryFont() の返り値として得られたFontStruct構造体の fidフィールドを指定してグラフィックコンテクストを作成します。 グラフィックコンテクストは 関数 XCreateGC() で生成します。
    
        XGCValues gcv;
        XFontStruct *font;
        ...
        font=XLoadQueryFont(dpy,フォント名);
        ...
        gcv.font = font->fid;
        gc = XCreateGC(dpy,win, ... | GCFont, &gcv);
        ...
    
    
  3. (必要に応じて)テキストの表示幅を計算します。
    表示する文字列がウィンドウ上でどれだけの幅になるかを計算するには、 1バイト文字に対しては関数 XTextWidth()を、2バイト文字に対しては 関数 XTextWidth16() を使います。
    2バイト文字用構造体 --- XChar2b構造体
    typedef struct {		/* normal 16 bit characters are two bytes */
        unsigned char byte1;
        unsigned char byte2;
    } XChar2b;
    
    テキストの幅を計算する関数
    int XTextWidth(XFontStruct *fs, char *s, int count) /* テキストの幅を返す */
    int XTextWidth16(XFontStruct *fs, XChar2b *str, int count) /* 2バイト文字テキストの幅を返す */
        XFontStruct *fs; /* フォント構造体 */
        XChar2b *str;    /* 出力する2バイト文字列 */
        char *str;       /* 出力する文字列 */
        int count;       /* データの個数 */
    
    
  4. グラフィックコンテクストを指定してテキストを描画します。
    ウィンドウ上に文字列を表示するには 1バイト文字に対しては 関数 XDrawString() を、2バイト文字に対しては 関数 XDrawString16() を使います。
    描画関数
    XDrawString(Display *dpy, Window *win, GC gc, int x, int y, char *s, int len)
    XDrawString16(Display *dpy, Window *win, GC gc, int x, int y, XChar2b *s, int len)
    
    
    XChar2b構造体は2バイトの大きさを持っています。 XDrawString16の第7引数で渡すのはデータの個数ですから、 漢字を普通の文字列に入れておいてその文字列を表示する場合は、 XDrawString16の第7引数に文字列の長さの半分である strlen(s)/2 を指定することになります。

サンプル・プログラム

フォントを使うクライアント・プログラムを例を以下に示します。
x2.c
#include <stdio.h>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

#define FONTNAME "-adobe-times-medium-r-normal--14-140-75-75-p-74-iso8859-1"
#define KFONTNAME "-misc-fixed-medium-r-normal--14-130-75-75-c-140-jisx0208.1983-0"

char *s="sample text";
char *s2="漢字とひらがなのテスト";

char *boff(char *s) {
    char *p, *q;
    p = (char *) malloc(strlen(s)+1);
    for (q=p; *s; ) *q++ = (*s++) & 0x7f;
    *q='\0';
    return(p);
}

int main() {
    Display *dpy;
    int scr;
    Window win;
    XSetWindowAttributes xswa;
    char *xservname=NULL;
    GC gc, gc2;
    XGCValues gcv;
    XFontStruct *font,*font2;

    if ((dpy=XOpenDisplay(xservname)) == NULL) {
	fprintf(stderr,"can not open %s\n",XDisplayName(xservname));
	exit(-1);
    }
    scr = DefaultScreen(dpy);
    xswa.background_pixel = WhitePixel(dpy,scr);
    xswa.border_pixel = BlackPixel(dpy,scr);
    xswa.event_mask = ExposureMask;
    win = XCreateWindow(dpy,RootWindow(dpy,scr),0,0,320,240,1,
			CopyFromParent,CopyFromParent,CopyFromParent,
			CWBackPixel | CWBorderPixel | CWEventMask, &xswa);
    if ((font=XLoadQueryFont(dpy,FONTNAME)) == NULL) {
	fprintf(stderr,"cannot find font %s\n",FONTNAME);
	exit(-1);
    }
    if ((font2=XLoadQueryFont(dpy,KFONTNAME)) == NULL) {
	fprintf(stderr,"cannot find font %s\n",KFONTNAME);
	exit(-1);
    }
    gcv.foreground = BlackPixel(dpy,scr);
    gcv.background = WhitePixel(dpy,scr);
    gcv.font = font->fid;
    gc = XCreateGC(dpy,win,GCForeground|GCBackground|GCFont,&gcv);
    gcv.font = font2->fid;
    gc2 = XCreateGC(dpy,win,GCForeground|GCBackground|GCFont,&gcv);
    XMapWindow(dpy,win);
    s2 = boff(s2);
    for (;;) {
	XEvent ev;
	XNextEvent(dpy,&ev);
	switch (ev.type) {
	  case Expose:
	    XClearWindow(dpy,win);
	    XDrawString(dpy,win,gc,10,50,s,strlen(s));
	    XDrawString16(dpy,win,gc2,10,100,(XChar2b *)s2,strlen(s2)/2);
	    break;
	  default:
	    printf("unknown event %d\n",ev.type);
	    break;
	}
    }
}

イベント

イベント・マスクは、サーバから送ってもらいたいイベントを 指定するときに使います。 送られてきたイベントの種類を判断するには、XEvent共用体の 先頭のフィールド type を参照します。 キーボードに関するイベントは KeyPress(Mask) と KeyRelease(Mask) です。

    XSetWindowAttributes xswa;
    ...
    xswa.event_mask = イベントマスク | ... ;
    win = XCreateWindow(..., ...|CWEventMask, &xswa);
    ...
    for (;;) {
        XEvent ev;
        XNextEvent(dpy,&ev);
        if (ev.type == イベントタイプ) ...
    ...
    }

イベントマスクイベントタイプ説明
KeyPressMaskKeyPressキーボードのキーを押す
KeyReleaseMaskKeyReleaseキーボードのキーを放す
ButtonPressMaskButtonPressマウスボタンを押す
ButtonReleaseMaskButtonReleaseマウスボタンを放す
ButtonMotionMaskMotionNotifyマウスをドラッグ

キー・コードから文字コードへの変換

X サーバが Key に関して送ってくる XKeyEvent 構造体の中には、 keycode フィールド(キーの番号)および state (Control, Shift, Meta キーの押下状態) という情報があり、 Key に対応する文字コードはこれらを使って決定します。 そのための関数が XLookupString() です。 一つの KeyEvent が一文字になるとは限らないので バッファ buffer の長さには注意が必要です。
KeyEvent から ASCII 文字列を得る関数
int XLookupString(event, buffer, num_bytes, keysym, status)
  XKeyEvent *event;
  char *buffer;    /* RETURN */
  int num_bytes;
  KeySym *keysym;  /* RETURN */
  XComposeStatus *status; /* not implemented */

XKeyEvent 構造体
typedef struct {
        int type;               /* of event */
        unsigned long serial;   /* number of last request processed by server */
        Bool send_event;        /* true if this came from a SendEvent request */
        Display *display;       /* Display the event was read from */
        Window window;          /* "event" window it is reported relative to */
        Window root;            /* root window that the event occured on */
        Window subwindow;       /* child window */
        Time time;              /* milliseconds */
        int x, y;               /* pointer x, y coordinates in event window */
        int x_root, y_root;     /* coordinates relative to root */
        unsigned int state;     /* key or button mask */
        unsigned int keycode;   /* detail */
        Bool same_screen;       /* same screen flag */
} XKeyEvent;

(注) X11R5以降、国際化によって国際化テキストの入出力も できるようになりました。 しかし、まだシステムのlocaleのサポートの状況がまちまち ですので、日本語入力を必要とするプログラムでは注意が必要となります。 今回の授業では国際化機能を使わないプログラミングを説明しましたが、 興味のある人は

    Volume One: Xlib Programming Manual, O'Reilly & Associations, Inc
の 11章 「Internationalized Text Input」 および 12 章 「Internationalization」などを 読んでみるとよいでしょう。 日本語入力用フロントエンドプロセッサ kinput2 を使って日本語入力 可能なサンプルプログラムはたとえば http://nw.tsuda.ac.jp/lec/x/readi18n.c にあります。 この例は RedHat7.1 の環境でテストはされていますが、全ての X-Window System の環境でうごくわけではありません。


漢字コード

日本語の平仮名や漢字は2バイトコードで表現されます。 このコード体系には3通りあります。

kanji.txt
ABC漢字DEF


kanji.txtを JIS で表した場合


kanji.txtを EUC で表した場合


kanji.txtを MS漢字コード で表した場合


kanji.txtを utf-8 で表した場合


課題

[1]上記のクライアント(x2.c)を動作させて下さい。

[2]上記のクライアント(x3.c)を動作させて下さい。

[3]英語の文字(iso8859)と「EUC日本語(jisx0208)」の文字が 混ざっている文字列があります。 これをウィンドウに表示する関数DrawEUC()をfont.cとして作成して下さい。 以下の x3.c とリンクして動作確認をして下さい。

x3.c と font.c のコンパイル
PROMPT$ gcc -I/usr/X11R6.4/include x3.c font.c -o x3 \
                -L/usr/X11R6.4/lib -lX11 -lsocket -lnsl    ←Solarisの場合
または
PROMPT$ gcc -I/usr/X11R6/include x3.c font.c -o x3 -L/usr/X11R6/lib -lX11 
                         ←Linuxの場合

int DrawEUC(Display *dpy, Window win, GC gc1, GC gc2,
	    XFontStruct *font1, XFontStruct *font2,
	    int x, int y, unsigned char *s)
    dpy: ディスプレイ
    win: ウィンドウ
    gc1 : 1バイト文字用のGC
    gc2 : 2バイト文字用のGC
    font1 : 1バイト文字用のXFontStruct構造体
    font2 : 2バイト文字用のXFontStruct構造体
    x: 最初の文字が表示されるX座標
    y: 最初の文字が表示されるY座標
    返り値: 文字列の画面上での幅
font.c
#include <stdio.h>
#include <X11/Xos.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>

int DrawEUC(Display *dpy, Window win, GC gc1, GC gc2,
	    XFontStruct *font1, XFontStruct *font2,
	    int x, int y, unsigned char *s)
{
    int eucflag=0;
    unsigned char c, buf[0x100], *p=buf;
    
    while ((c=*s++) != '\0') {
	if (c & 0x80) {		/* euc */
	    if (!eucflag) {	/* ascii -> euc */
		XDrawString(dpy,win,gc1,x,y,buf,p-buf);
		x += XTextWidth(font1,buf,p-buf);
		p = buf;
	    }
	    eucflag = 1;
	    *p++ = c & 0x7f;
	    if (p-buf == sizeof(buf)) {	/* buffer overflow */
		XDrawString16(dpy,win,gc2,x,y,(XChar2b *)buf,(p-buf)/2);
		x += XTextWidth16(font2,(XChar2b *)buf,(p-buf)/2);
		p=buf;
	    }
	} else {
	    if (eucflag) {	/* euc -> ascii */
		XDrawString16(dpy,win,gc2,x,y,(XChar2b *)buf,(p-buf)/2);
		x += XTextWidth16(font2,(XChar2b *)buf,(p-buf)/2);
		p=buf;
	    }
	    eucflag = 0;
	    *p++ = c;
	    if (p-buf == sizeof(buf)) {	/* buffer overflow */
		XDrawString(dpy,win,gc1,x,y,buf,p-buf);
		x += XTextWidth(font1,buf,p-buf);
		p=buf;
	    }
	}
    }
    if (p-buf > 0) {
	if (eucflag) {
	    XDrawString16(dpy,win,gc2,x,y,(XChar2b *)buf,(p-buf)/2);
	    x += XTextWidth16(font2,(XChar2b *)buf,(p-buf)/2);
	} else {
	    XDrawString(dpy,win,gc1,x,y,buf,p-buf);
	    x += XTextWidth(font1,buf,p-buf);
	}
	p=buf;
    }
    return(x);
}


diff -c x2.c x3.c
*** x2.c	Mon May 22 19:51:41 2006
--- x3.c	Mon May 22 19:53:11 2006
***************
*** 8,13 ****
--- 8,14 ----
  
  char *s="sample text";
  char *s2="漢字とひらがなのテスト";
+ char *s3="漢字とASCII Characterのまざったテキスト";
  
  char *boff(char *s) {
      char *p, *q;
***************
*** 60,67 ****
  	switch (ev.type) {
  	  case Expose:
  	    XClearWindow(dpy,win);
! 	    XDrawString(dpy,win,gc,10,50,s,strlen(s));
! 	    XDrawString16(dpy,win,gc2,10,100,(XChar2b *)s2,strlen(s2)/2);
  	    break;
  	  default:
  	    printf("unknown event %d\n",ev.type);
--- 61,67 ----
  	switch (ev.type) {
  	  case Expose:
  	    XClearWindow(dpy,win);
! 	    DrawEUC(dpy,win,gc,gc2,font,font2,10,30,s3);
  	    break;
  	  default:
  	    printf("unknown event %d\n",ev.type);

[オプション課題4] いろいろなフォントで文字を表示させてみましょう。 とくに大きな文字でテキストを表示するとどうなるか試してみて下さい。

(例)"-misc-fixed-medium-r-normal--80-*-75-75-c-*-jisx0208.1983-0"

[オプション課題5] キー操作によって、文字が上下左右に移動するようにしてみましょう。

[提出物] 課題が完成したら、font.c をp2r8@nw.tsuda.ac.jp へ送って 下さい。 コンパイルできなかったり、きちんと動作しないものを送っても 提出とは認めないので注意して下さい。

オプション課題ができた人は、subjectを option3 または option4 として p2r8@nw.tsuda.ac.jp へ送って下さい。

上記の Email アドレス宛に送ったメイルは http://nw.tsuda.ac.jp/cgi-bin/cgiwrap/p2r8/mailhead で参照できます。Emailを送ったあとで、正しく提出されたかどうか 必ず確認しておいて下さい。

提出期限は来週木曜日の8:50a.m. です。


Yoshihisa Nitta

http://nw.tsuda.ac.jp/