EmacsのX Windowフォントの設定方法(2)

  • 投稿日:
  • by
  • カテゴリ:

次に、日本語とかASCIIとかの文字集合の単位で別々のフォントを設定してみる。
日本語と英語しか使わなくても、日本語文字の分しか無いフォントとか、ASCII文字の分しか無いフォントとかを使う場合、そういうことが必要になることがある。アラビア語とかハングルとか、日英以外の文字も表示したいのにフォントが自動的に読み込まれないような時も、必要になる。
Xftが有効なEmacs23を使う場合、fontconfigの設定が適切であれば、そのような事態はあまり起こらないような気がするし、起こってもEmacs側で対処するよりfontconfig側で直すべき話になるかも知れないが、筆者の環境ではEmacs22が現役であるので、そのような事態は基本的に発生するのである。

Emacsのフォントは文字セット(文字集合、charset)とフォント名の組で管理される。その文字セットとフォント名の組のリストをフォントセットと言う。文字セットは、JIS X 0208とかiso8859-1等の単位で指定するのが基本であるが、Emacs23では文字コードの範囲で指定することもできる。Emacsで定義されている文字セットは、M-x list-character-setsするとわかる。日本語文字とASCII文字に関係するのは以下のものである。
・ascii: 言うまでもなくASCII文字
・japanese-jisx0208: 日本語、大体第1〜2水準漢字を含む
・japanese-jisx0212: 大体第3〜4水準漢字
・japanese-jisx0213: JIS X0208 + JIS X0212
・katakana-jisx0201: 日本語のいわゆる半角カナ、Emacs23ではjisx0201
・unicode-bmp: Unicodeの基本面(0群0面、UCS-2で表現できる範囲)(Emacs23のみ)
半角カナを使わなければ、asciiとjapanese-jisx0208のフォントを設定すれば十分である。
フォントセットを意識せずにset-frame-fontとかで1つのフォントのみを指定してフォントを変更しても、裏では何らかのフォントセットが自動的に構成される。

現在使用されているフォントセットは、M-x describe-fontsetでcurrent frameを指定すると表示される。これの出力形式は、Emacs22と23とで全然異なる。Emacs22の方が単純でわかり易い。Emacs23の方がきめ細かな設定ができるので仕方ないのだろうが、非常にわかりづらい。困ったものである。Emacs22で実行すると、以下のような感じになる。

Fontset: -etl-*-medium-r-normal-*-16-*-*-*-*-*-fontset-16
CHARSET or CHAR RANGE FONT NAME
--------------------- ---------
ascii -etl-fixed-medium-r-normal--16-160-72-72-c-80-iso8859-1
[-ETL-fixed-bold-r-normal--16-160-72-72-C-80-ISO8859-1]
[-ETL-Fixed-Medium-R-Normal--16-160-72-72-C-80-ISO8859-1]
latin-iso8859-1 -etl-fixed-*-iso8859-1
latin-iso8859-2 -*-iso8859-2
latin-iso8859-3 -*-iso8859-3
(中略、以下抜粋)
katakana-jisx0201 -*-jisx0201-*
[-Shinonome-Gothic-Medium-R-Normal--16-150-75-75-C-80-JISX0201.1976-0]
japanese-jisx0208 -*-jisx0208.1990-*
japanese-jisx0212 -*-jisx0212-*
[-Misc-Fixed-Medium-R-Normal--16-150-75-75-C-160-JISX0212.1990-0]
japanese-jisx0213-1 -*-jisx0213.2000-1
[-Misc-Fixed-Medium-R-Normal--16-150-75-75-C-160-JISX0213.2000-1]
japanese-jisx0213-2 -*-jisx0213.2000-2
[-Misc-Fixed-Medium-R-Normal--16-150-75-75-C-160-JISX0213.2000-2]
文字セットの右側が指定したフォント名、[]内が実際にロードされたフォント名である。ちなみに、上の出力は、FreeBSD7.3でintlfontsというpackageをインストールして、intlfontsのdocに書かれている設定をそのまま使った状態である。

Emacs23だとこれを読み取るのは困難であるが、同じような意味の設定をすれば十分である。Emacs23だとiso10646とか"unicode-bmp"とかの名前もあって、これを使ってもまあ悪くない。

フォントセットの定義は、Xリソースでも.emacsでもできる。
今回は、Emacs23用に.emacsでやってみた。(筆者のXリソースはEmacs22に合わせている為)
まずは、フォントセットを定義せずに、自動的に作られたフォントセットのフォントを変更してみる。

(when (>= emacs-major-version 23)
(when window-system
;;(1)ASCIIフォント設定
(setq default-frame-alist
(append '(
(font . "Sazanami Mincho:style=Regular:size=22") ;ASCII文字のフォント
(width . 80) ;文字が大きいので、ついでにウィンドウサイズ変更
(height . 24)
)
default-frame-alist))

;;(2)最初のフレームのサイズ変更
(set-frame-size (selected-frame) 80 24)

;;(3)日本語フォント変更
(add-hook 'window-setup-hook
'(lambda ()
;;日本語→VLゴシック
(set-fontset-font (frame-parameter nil 'font)
'japanese-jisx0208
(font-spec :family "VL Gothic"))
;;全角カタカナのみ、さざなみ明朝に戻す
(set-fontset-font (frame-parameter nil 'font)
;;'(#x3041 . #x309f) ;ひらがな
'(#x30a0 . #x30ff) ;カタカナ
(font-spec :family "Sazanami Mincho"))
;;半角カナ→Kappaのイタリック体
(set-fontset-font (frame-parameter nil 'font)
'jisx0201
"-kappa-*-medium-i-*-*-20-*-*-*-*-*-jisx0201.1976-*")
))
))

結果

ASCIIフォントの設定はset-frame-font、現在のフォントセットの一部フォント変更は(set-fontset-font (frame-parameter nil 'font) ...)でできる((frame-parameter nil 'font)は現在のフレームのフォントセット名を返す)ので、それらを単に並べればできそうなものであるが、色々ややこしいことがあってそれではうまくいかなかったので、色々工夫してみた。
(1)のASCIIフォント設定は、default-frame-alistに登録するのでなくset-frame-fontを使うと、少なくとも筆者の環境では、.emacs実行後に(おそらくface設定の処理でdefault-frame-alist等のフォントに)また変えられてしまう。もし変えられなくても、C-x 5 2で開いたフレームにはフォント設定が引き継がれなくなってしまう。initial-frame-alistに登録するのでも同様の問題がある。
(2)のウィンドウサイズ変更は、せっかくdefault-frame-alistに書いても、筆者の環境では、最初のフレームについてはこの後default-frame-alistのwidthやheightが反映されない(fontは反映される)ので、仕方なく加えた。initial-frame-alistに登録してもだめだった。
(3)は、set-fontset-fontをすぐに実行すると、この時点ではまだdefault-frame-alistに登録したフォントに切り替わっていないため、無意味になってしまう(おそらくfontset-startupに対する設定になってしまう)ので、強引だがwindow-setup-hookに登録して後で実行させるようにした。

(face-set-after-frame-default)
(set-fontset-font ...)
とすれば、default-frame-alistのフォントに切り替わり、それに対するフォントセットも作られるようで、(3)と同じ結果が得られることを確認したが、どうせ後でdefault-frame-alistのフォントへの切替が発生するし、筆者がfaceの動作を理解していないので、今回は見送った。
なお、(1)でフォントサイズを22ドットとやたらでかくしているのは、さざなみフォントが20ドット以下だとアンチエイリアスされない(20ドット以下はビットマップフォントが内蔵されているため、TrueTypeでなくそちらが使われる)為である。同様に、東風フォントだと16ドット以下と20ドットがアンチエイリアスされない。
また、筆者の環境(FreeBSD 7.3のデフォルトのfontconfig)では、さざなみフォントの場合は(3)をしなくても日本語や半角カナが表示されない訳ではない。


さて、実際にフォントが切り替わってから現在のフレームのフォントセットに対して変更を加えるのは、set-fontset-fontだけで済むのである意味単純だが、あまり美しくない。上のように苦しくなってwindow-setup-hookのコールバックでフォントを切り替えるのでは敗北感すら漂う汚さである。そもそも、普通は先にフォントセットを定義してからフォントをそのフォントセットに切り替えるものである。Xのリソースでの設定ではそのようにしかできない。

そこで、次にフォントセットの定義をやってみる。フォントセットの定義は、create-fontset-from-ascii-fontしてからset-fontset-fontするのが定跡である。次のように書くと、上の例と同じ設定になる。

(when (>= emacs-major-version 23)
(when window-system
;;(4)フォントセット"fontset-test0"の定義
(create-fontset-from-ascii-font
"Sazanami Mincho:style=Regular:size=22" nil "test0")
;;日本語→VLゴシック
(set-fontset-font "fontset-test0"
'japanese-jisx0208
(font-spec :family "VL Gothic"))
;;全角カタカナのみ、さざなみ明朝に戻す
(set-fontset-font "fontset-test0"
'(#x30a0 . #x30ff) ;カタカナ
(font-spec :family "Sazanami Mincho"))
;;半角カナ→Kappaのイタリック体
(set-fontset-font "fontset-test0"
'jisx0201
"-kappa-*-medium-i-*-*-20-*-*-*-*-*-jisx0201.1976-*")

;;(5)フォント設定
(setq default-frame-alist
(append '(
(font . "fontset-test0")
(width . 80)
(height . 24)
)
default-frame-alist))

;;(2)最初のフレームのサイズ変更
(set-frame-size (selected-frame) 80 24)
))

(2)の部分は不満だが、かなりマシになった。

参考:Emacs InfoのDefining fontsetsの章


ついでに、C-x 5 2で新たなフレームを作る度に次のフレームのフォントが切り替わるようにしてみた。試行錯誤の実験用に作ったものだが、気に入ったので公開する。

(when (>= emacs-major-version 23)
(when window-system
;;フォントセットの定義
;;テスト1: Vn ccti10 + TakaoP明朝 + Ayu(半角カナのみ)
(create-fontset-from-ascii-font "Vn ccti10:style=Roman" nil "test1")
(set-fontset-font "fontset-test1"
'japanese-jisx0208
(font-spec :family "TakaoPMincho"))
(set-fontset-font "fontset-test1"
'jisx0201
"-ayu-*-*-i-*-*-20-*-*-*-*-*-jisx0201.1976-*")

;;テスト2: Luxi Mono + VLゴシック + Kappa(半角カナのみ)
(create-fontset-from-ascii-font "Luxi Mono" nil "test2")
(set-fontset-font "fontset-test2"
'japanese-jisx0208
'("VL Gothic" . "jisx0208"))
(set-fontset-font "fontset-test2"
'jisx0201
"-kappa-*-medium-r-*-*-20-*-*-*-*-*-jisx0201.1976-*")

;;テスト3: URW Chancery L + 東風明朝 + 正体不明の半角カナ
(create-fontset-from-ascii-font
"URW Chancery L:style=Medium Italic"
nil "test3")
(set-fontset-font "fontset-test3"
'japanese-jisx0208
(font-spec :family "Kochi Mincho"))
(set-fontset-font "fontset-test3"
'jisx0201
"-misc-*-medium-r-*-*-16-*-*-*-*-*-jisx0201.1976-*")

;;フレーム作成時のデフォルト設定
(setq default-frame-alist
(append '(
(font . "fontset-test1") ;最初のフレームにも適用される
(width . 80)
(height . 30)
)
default-frame-alist))

;;最初のフレームに対する設定
;;普通に書くと後で書き換わってしまう
;;回避する方法がよくわからないので、フレーム設定の最後にまとめて行う
(add-hook 'window-setup-hook
'(lambda ()
(set-frame-font "M+2VM+IPAG circle")
(set-frame-size (selected-frame) 40 32)
))

;;C-x 5 2する度に次にC-x 5 2した時のフォントが変わるようにする
(add-hook 'after-make-frame-functions
'(lambda (frame)
;;font-parameter := default-frame-alistのcarがfontのcons cell
(let ((font-parameter (assq 'font default-frame-alist)))
;;font-parameterの値(cdr)の末尾の文字を1→2→3→1と切り替える
(setcdr font-parameter
(concat (substring (cdr font-parameter) 0 -1)
(number-to-string
;;x=(x%3)+1
(+ (%
(string-to-number
(substring (cdr font-parameter) -1))
3) 1)))))
))

))

結果
・最初のフレームのフォント(M+2VM+IPAG circle)

・2枚目のフレームのフォント(fontset-test1)

・3枚目のフレームのフォント(fontset-test2)

・4枚目のフレームのフォント(fontset-test3)


フォントセットの定義方法としてはcreate-fontset-from-ascii-fontしてset-fontset-fontする方法が最善だと理解しているのだが、Emacs Infoには書かれていなかった。これは裏技の類なのだろうか?
フォントセットの定義の方法としては、他にはcreate-fontset-from-fontset-spec(Emacs Infoにあるのはこれ)やnew-fontsetを使う方法があるが、いずれもXftのフォント名を使うのは困難だと思う。筆者はそれらでも一応同じ設定を書いてみようと試みたが、内部で起こる複数のx-resolve-font-nameのエラーを全て同時に解決する方法が思いつかず、挫折した。

Webを見てると、Emacsのバージョンやwindow-systemでの条件分岐をifやwhenでなくcondで書いてることが多いようだが、何かメリットがあるのだろうか?
Emacsのバージョンはまだ21の時、22の時、23の時と処理を分ける可能性があるのでわからないでもないが、特にwindow-systemはcondで書いてる場合もbooleanの扱いにしてることが多いので、条件が成り立たない時の処理を書く場所の確保の為だとしてもifの方が良さそうに思えるのだが…
一般にcondの方がelispっぽいのだろうか、単にprognと書きたくないということなのだろうか?
筆者は、悩んだ末、たとえ組み込み関数でなくマクロであっても、たとえ素人っぽくても、読みやすさを重視して、whenを活用することに決めた。

今回、山のように試行錯誤をする過程で、定義したフォントセットをset-frame-fontすることができないことが何度も発生した。そのフォントセットを選択しても、"fontset-auto??"というフォントセットが新たに作られ、指定したフォントセットの内のasciiの部分だけが使われ、他のcharsetについては何か別のものが割り当てられてしまうのである。文字列のフォント名に":size=??"を加えたりfont-spec引数に":size ??"を加えたりしてフォントサイズを変更すると出やすいようであるが、上記の例では成功しており、フォントサイズを変更すると必ず失敗するという訳でもないようである。もちろん、M-x describe-fontsetするとascii以外も設定されている。set-frame-fontすることができるfontsetの要件のようなものがあるのだと推測しているが、いくらやっても規則性が見つからず、エラーメッセージが出ないので原因の手掛かりが無い。一度そのような状況に陥るとなかなか抜け出せず、成功することの方が稀であるような気さえしてしまう。