2008年12月8日月曜日

MonoDevelop 2.0 AlphaのMac OS X版で日本語を表示する方法

Twitter@atsushienoさんから、@hayashihさんが、MonoDevelop 2.0 Alpha版のMac OS X版で日本語が化けて表示されて困っているという話を聞いたので調べてみたのですが、あまり良い方法ではない、というかむしろ最後の手段に近いのですが、

/Library/Frameworks/Mono.Framework/Version/Current/etc/pango/pango.aliases

というファイルを管理者権限で作成し、

"Lucida Grande" = "Hiragino Kaku Gothic Pro"

という行を書けば、とりあえず日本語が表示できることが分かりました。

結局のところ、初期状態のMonoDevelop.appで日本語が化けてしまう「根本的な」原因は、

Mac OS X版のMonoが依存しているPangoライブラリのpango-atsuiモジュールが、フォントファイルから表示可能なコードポイントの範囲を取得できないという理由で、全てのフォントファイルが全てのコードポイントのグリフを持っているという嘘の情報を作成し、Pangoがそれを信じてしまうので、フォントがフォールバックされないから。

ということになるのですが、この説明ではいくらなんでも不親切過ぎるので、まずは、Mac OS X版のMonoはどうやってフォントを表示しているのか?という話から始めたいと思います。

Monoは、GTK#, WinForms, libgdiplus, Cocoa#, Moonlight, etc... と様々な方法で文字をレンダリングできますが、今回問題になっているMonoDevelop.appの日本語は GTK# を使っているはずです。

GTK#は、簡単に言えば GTK+2 のラッパーで、基本的にはOSの違いの影響を受けないようになっています。
しかし、Monoが利用するGTK+2の実体は、OSによって様々です。

Mac OS X版の場合は、Monoの本体であり、/Library/Framework にインストールされる Mono.Framework内に、Imendio社Gtk+ on OSX というプロジェクトの成果(以下、gtk-osx)を同梱することで、.NETアプリケーションGTK#アプリケーションを、X11を起動せずに単独のMac OS Xアプリケーションとして起動し、GUIを表示する事ができます。

尚、gtk-osx は、環境変数を設定する事で、従来のようにX11アプリケーションとして動作させる事も可能のようなのですが、私はそっちはまだ試していません。

さて、GTK+2はテキストをレンダリングする際にPangoというライブラリを使う事になっていて、Mono.Frameworkのgtk-osxもPangoライブラリを自前で持っています。

Pangoがテキストをレンダリングする処理をもう少し具体的に説明すると、
  1. UTF-8で符号化された文字列とレイアウトに関する情報を受け取る
  2. 必要に応じて適切なレイアウトエンジンでレイアウトを決定し、バックエンドのフォントレンダリングシステムにグリフのレンダリングを指示する
  3. 返ってきた結果を元に、テキスト全体をレンダリングした最終的な結果を返す
という流れになります。

最近のPangoはバックエンドのフォントレンダリングシステムをCairo, Xft, FT2(FreeType2)の3種類から選択できます(core X font protocol は選択できなくなりました)が、Xftバックエンドと(何故かはよく知りませんが)FT2バックエンドはXサーバを必要とするので、「X11を使わずに」フォントをレンダリングするには、Cairoを使うしかありません。
ちなみに、Mono.Frameworkは、Cairoライブラリも自前で持っています。

CairoはCairoで、フォントのラスタライズをバックエンドに丸投げしてしまうのですが、こちらも選択が可能で、FreeType, Win32, Quartzの3種類があります。

FreeTypeは上記のとおりX11が必要ですし、Win32はWindowsのフォントラスタライザ、つまりGDIなのでMac OS Xでは当然使えませんから、結局「Mac OS X」で「X11を使わずに」フォントをラスタライズするには、Quartzフォントバックエンドを使うしかありません。Quartzは、Mac OS X 10.4から導入された、CoreTextフレームワークを使用します。

ここまでの話をまとめると、Mac OS X版のMonoでGTK#を使って多言語のテキストをレンダリングする場合の処理は、

MonoDevelop.app→Mono→GTK# → GTK+ 2(gtk-osx) → Pango → Cairo → CoreText

という流れになります。

上記の処理の中でテキストのレイアウトを決定するのは、先ほど述べたようにPangoです。

テキストを、単なる文字の並びではなくテキストとしてレイアウトするには、テキスト内の個々の文字のグリフの情報が必要になりますが、その際に得られる情報の内容は、同じフォントファイルから得られるグリフであっても、ラスタライザに依存します。

そのため、Pangoでフォント情報を管理する機能は、fontconfig, Win32, ATSUIの3種類のそれぞれに対応したモジュールに分かれています。
この中でATSUIを受け持つモジュールが、pango-atsuiモジュールです。

しかし、現在のpango-atsuiモジュールは、ATSUIで使用されるフォントが描画可能なコードポイントの範囲を正しく取得する事ができません。

何故かというと、ATSUIは元々単独で完結しているシステムなので、FreeTypeのようにフォントの詳細を得るためのAPIが存在しないからです。(この辺、ちょっと自信なし)

だからと言って、FreeTypeを使って個々のフォントファイルから直接情報を得ようとしても、ATSUIに対して指定したフォントの実体となるフォントファイルのパスを知ってるのはATSUIだけで、それを外から知る(真っ当な)手段がありません。ちなみに、この問題についてはWindowsのGDIでも似た状況らしく、吉里吉里の作者のW.Deeさんも苦労したそうです。

結局、pango-atsuiモジュールは、全フォントファイルが全てのコードポイントのグリフを持っているという嘘の情報をでっちあげるという方法をとってしまいました。

ですから、gtkrcやpango.aliasesでフォントのリストを与えて、描画できない文字をリストの次のフォントで描画(この仕組みをフォントのフォールバックと言います)させようとしても、Pangoがフォント情報の管理にpango-atsuiモジュールを使っている限り、リストの先頭で指定したフォントが全てのコードポイントの文字を描画可能ということになっているので、リストの先頭で指定したフォントを常に使い続けてしまうのです。

もう少し具体的な例で説明してみましょう。
Pangoに対して、Mac OS Xの欧文フォントである Lucida Grande で「あ」という文字を描画しろと指示した場合を考えてみます。
もしpango-atsuiモジュールが物理フォントで描画可能な文字の範囲を取得することができれば、PangoはLucida Grandeに「あ」という文字のグリフはない事が分かりますので、別の物理フォントにフォールバックすることができます。
しかし、Mono.Frameworkに含まれているPangoのpango-atsuiモジュールは、Lucida Grandeにも「あ」という文字のグリフがあるという嘘の情報を流し、Pangoはそれを信じてしまうために、フォールバックすることができません。

pango-atsuiが今回の問題の根本的な原因であることを納得していただけたでしょうか?

ちょっと長くなったので、この続きは次のエントリーに移して、
  • 以前のMonoDevelopでは有効だったgtcrcの書き換えが何故使えなくなったのか?
  • 現状のpango-atsuiの制限下で、Monoはどうすれば良かったのか?
の2点について考えてみたいと思います。

……えーと、多分、今週中には何とか(汗

(2008/12/08 07:15 追記) @atsushienoさんから、MonoのWinformsなアプリケーションはGTK+2に依存しないとの指摘があったので、.NETアプリケーションをGTK#アプリケーションに修正しました。

1 コメント:

スイッチ切った さんのコメント...

2.6出ましたね。
Mono.FrameWork以下書き換えで日本語表示が無効化されましたが
同様にpangoフォルダ以下にpango.aliasesを作成し内容に"Lucida Grande" = "Hiragino Kaku Gothic Pro"を入れると行けました。
報告でした。

コメントを投稿