変愚蛮怒をXft対応する話 Part1 X11における変愚蛮怒の描画コード
変愚蛮怒というRoguelikeゲームはLinuxでも動作するけれど, TrueTypeフォントが扱えなくてフォント指定がかなりだるいし, レンダリングがびみょう. 変愚蛮怒の描画をXftで行うようにした. フォントがきれいになって, お好きなフォントを使えて超ハッピー. この経験をふまえて, libX11のものをXft対応ってどうやったらいいのかを書く.
リポジトリはここ
変愚蛮怒ってなに
変愚蛮怒はMoria/Angbandから始まる*band系ローグライクゲームのバリアント(変種)の一種です。直接にはZangbandから派生しています。 鉄獄100Fに潜むラストボス『混沌のサーペント』を撃破して『*勝利*』を遂げるためには、キャラクターのレベルや装備だけでなく、*あなた*自身の習熟が求められます。
まあ日本で派生したこんな感じなローグライクゲームで, 日本で派生しているので斬鉄剣持ったサムライっぽい人とか, 光の剣をはなつ不老不死にして史上最強のエスパーとか, 石仮面を持った吸血鬼とか出てくる. 主人公の方も剣術家になって天翔龍閃したり, 鏡使いになってラフノールの鏡したりできる.
最近だと東方成分の入ったバリアントもあるみたい. 当方東方ネタはあんまわからんのだけど, わかる人はよさそう.
Linuxにおける変愚蛮怒
変愚蛮怒はLinuxでも動いてとてもサイコー. ただコードが昔に書かれたものであるため, 現状とあわない点が様々ある
- 内部コードがEUC (これは簡単に文字幅とか計算するのにはしかたなかったり)
- TrueTypeフォントがうまくあつかえない
- フォント指定には昔ながらの, XLFD (
-*-*-medium-r-normal--24-*-*-*-*-*-iso8859-1
ってやつ)を使う- めちゃ複雑わからん
- mkfontdir, mkfontscaleなど使うとTTFも使えるらしい?んだけどうまく動かんわ
- 昔動いてたんだけど…もうきえた?
- bitmapフォントは…見た目やっぱアレ
- フォント指定には昔ながらの, XLFD (
現在のX11用描画コード
ということで, 変愚蛮怒をXftというTrueTypeフォントを扱えるライブラリで描画するように書き変えよう, という話.
今回は現在の描画コードについて整理する.
変愚蛮怒はWindowsやらX11やらCursesやら様々な環境に対応できるように, 描画対象をTerm
と抽象化している. 各環境では以下のhookを実装して, 描画を行う.
いろいろhookはあるけれど, 主に仕事をするのはtext_hook
になる. X11での実装は以下の通り:
(x, y)
の位置に, 属性番号a
で, n
バイト長の文字列s
を描画します, ということですね. Infoclr_set(clr[a]);
で描画色を設定して, Infofnt_text_std(x, y, s, n);
でテキスト描画している感じ. ここの(x, y)
は文字単位での座標. ピクセル単位での座標へはInfofnt_text_std()
内で変換する.
Infofnt_text_std()
は以下のあたりから. ifdefとかが多いので抜粋する.
https://github.com/naota/hengband/blob/master/src/main-x11.c#L1605
static errr Infofnt_text_std(int x, int y, cptr str, int len) { /*** Decide where to place the string, vertically ***/ /* Ignore Vertical Justifications */ y = (y * Infofnt->hgt) + Infofnt->asc + Infowin->oy; /*** Decide where to place the string, horizontally ***/ /* Line up with x at left edge of column 'x' */ x = (x * Infofnt->wid) + Infowin->ox; /*** Actually draw 'str' onto the infowin ***/ ... /*** Handle the fake mono we can enforce on fonts ***/ /* Monotize the font */ if (Infofnt->mono) ... /* Assume monoospaced font */ else { ... XmbDrawImageString(Metadpy->dpy, Infowin->win, Infofnt->info, Infoclr->gc, x, y, kanji, kp-kanji); free(kanji); } /* Success */ return (0); }
まず, 引数の文字単位の座標である(x, y)
をピクセル単位に変換して, 描画位置を決める. フォントの高さ, 幅はどのフォントでも一定(monospaceフォント)と仮定しているので, 基本的にはx
, y
にそれぞれフォントの幅とフォントの高さを掛ければよい. これに描画の開始座標の(Infowin->ox, Infowin->oy)
を足す. また, Y座標にはフォントのアセント(フォントのベースラインから上端までの高さ)Infofnt->asc
を足す. これで, (x * フォントの幅 + ox, y * フォントの高さ + oy)
のピクセルに文字の左上が当たるように描画されることになる.
あとはkanji
にUTF-8が入っているので, そいつをXmbDrawImageString()
で描画する. XサーバがMetadpy->dpy
(XのDISPLAYのやつ)で, 描画先がInfowin->win
になる. フォントがInfofnt->info
で指定され, 色がInfoclr->gc
で指定される.
Xft化することを考えると, Infowin
, Infofnt
, Infoclr
の初期化とかを見ておくとべんりそう.
(しかし, #define DPY (Metadpy->dpy)
とかしてるのに全然使ってなくてあんまりコードの治安はよくない)
ウィンドウの管理: Infowin
struct infowin Infowin
は以下のような変数. ウィンドウの管理をする.
struct infowin { Window win; #ifdef USE_XIM XIC xic; long xic_mask; #endif ... };
Infowin
のデータはInfowin_prepare()
で初期化されている.
Xft化する時に, ウィンドウ関係でなにか追加でほしい時は, 上の構造体に変数増やして, Infowin_prepare()
で初期化したらいいかな, という感じ.
フォントの管理: Infofnt
struct infofnt Infofnt
は以下のような変数. フォントの管理をする.
struct infofnt
{
XFontSet info;
cptr name;
s16b wid;
s16b twid;
s16b hgt;
s16b asc;
...
};
ここに, フォント, その幅・高さ・アセントなどが保持されている. 初期化はInfofnt_prepare()
で行う.
static errr Infofnt_prepare(XFontSet info) { infofnt *ifnt = Infofnt; XCharStruct *cs; XFontStruct **fontinfo; char **fontname; int n_fonts; int ascent, descent, width; /* Assign the struct */ ifnt->info = info; n_fonts = XFontsOfFontSet(info, &fontinfo, &fontname); ascent = descent = width = 0; while(n_fonts-- > 0){ cs = &((*fontinfo)->max_bounds); if(ascent < (*fontinfo)->ascent) ascent = (*fontinfo)->ascent; if(descent < (*fontinfo)->descent) descent = (*fontinfo)->descent; if(((*fontinfo)->max_byte1) > 0){ /* 多バイト文字の場合は幅半分(端数切り上げ)で評価する */ if(width < (cs->width+1)/2) width = (cs->width+1)/2; }else{ if(width < cs->width) width = cs->width; } fontinfo++; fontname++; } ifnt->asc = ascent; ifnt->hgt = ascent + descent; ifnt->wid = width; ... /* Success */ return (0); }
XFontsOfFontSet()
でフォント一覧をとってきて, そのリストをなめて, 最大のアセント・デセントを求める. 幅は多バイト文字なら, フォント幅の半分と計算していて, 多バイト文字の幅が単一バイト文字の幅の2倍と仮定しているとわかる.
Xftでも, このへんでアセントとかを計算したらいいね, ということになる.
色の管理: Infoclr
Infoclr
は以下の通り. GC
がXlibのグラフィックコンテキストで, 描画色・背景色などを保持してくれる. ついでに, fg
, bg
でも描画色・背景色を保持しているけど, べつに使っていない.
struct infoclr
{
GC gc;
Pixell fg;
Pixell bg
...
};
これまでの予想に反して, Infoclr
のデータの初期化はinit_x11()
の中で行われている.
errr init_x11(int argc, char *argv[]) { ... /* Prepare normal colors */ for (i = 0; i < 256; ++i) { Pixell pixel; MAKE(clr[i], infoclr); Infoclr_set(clr[i]); /* Acquire Angband colors */ color_table[i][0] = angband_color_table[i][0]; color_table[i][1] = angband_color_table[i][1]; color_table[i][2] = angband_color_table[i][2]; color_table[i][3] = angband_color_table[i][3]; /* Default to monochrome */ pixel = ((i == 0) ? Metadpy->bg : Metadpy->fg); /* Handle color */ if (Metadpy->color) { /* Create pixel */ pixel = create_pixel(Metadpy->dpy, color_table[i][1], color_table[i][2], color_table[i][3]); } /* Initialize the color */ Infoclr_init_ppn(pixel, Metadpy->bg, "cpy", 0); }
angband_color_table[]
にARGBが入っている. create_pixel
でPixell
(X11の場合, XColor
のtypedef)を作り, その色を描画色としてGC
を設定する. Infoclr_set(clr[i])
でInfoclr = clr[i]
となって, Infoclr_init_ppn()
で色が設定される. 結局,のところ配列clr[]
に各色でGCが格納される.
(ここで256個のGC作っているけど, 実際には8色しかない. alpha == 0xFF
あたりでbreakしちゃってもいい気もする)
まとめ
Infowin
, Infofnt
, Infoclr
がそれぞれ現在のウィンドウ・フォント・描画色を管理する.
Infowin
はInfowin_prepare()
で, Infofnt
はInfofnt_prepare()
で, Infoclr
(clr[]
)はinit_x11()
で初期化される.
Term_text_x11()
が指定位置にテキストを描画する. Infoclr_set(clr[a]);
で色を変えて, Infofnt_text_std(x, y, s, n);
でテキスト描画する. 実際に描画しているのは, XmbDrawImageString()
.
次回はXmbDrawImageString()
をXftのテキスト描画関数に置き換える. それにあたって, 描画先・フォント・色の構造体が変わるので必要な処理を初期化関数に追加するなどについて書く.