文字描画関連その4

Win32でフォントを扱う場合、APIのCreateFontIndirectにフォント情報構造体を渡してフォントハンドルを受け取り、それをつかってGDIで描画する。このフォント情報構造体というのが微妙にあいまいで、指定のフォントが無い場合に最も近いフォントをOSが選択してくれるということになっている。フォントそのものはファイルになっていてOSにインストールするものだから、フォント名を指定したところでそれが存在しないかもしれないわけで、そのへんの仕様に関してはそういうものだと思うしかないのだが、まさかRubyから細かいフォント情報を渡すわけにもいかず、なんだか適当にならざるを得ない。
そんな感じでわりと適当に作られたのがDXRubyのFontクラスである。

今回はFontクラスについて。


まず、Fontオブジェクトが保持するC構造体から。

/* フォントデータ */
struct DXRubyFont {
    LPD3DXFONT pD3DXFont;       /* フォントオブジェクト   */
    HFONT hFont;                /* Image描画に使うフォント  */
    int size;                   /* フォントサイズ */
    VALUE fontname;             /* フォント名称 */
    VALUE weight;               /* 太さ */
    VALUE italic;               /* イタリックフラグ */
};

LPD3DXFONTというのはDirectXのD3DXFont用オブジェクトで、RenderTarget#draw_fontでのみ使う。HFONTのほうはWin32のフォントオブジェクトで、GDIで描画するときに使う。まあ、GetGlyphOutline用だ。あと、サイズと名称と太さ、斜体の指定を持つ。キャッシュを持つならここにVALUE型でHashオブジェクトを追加することになるだろう。キーはStringの一文字、データはビットマップ情報を持つクラスをこっそり追加するか、String化して持つか、そんなところかと。
この構造体に持っているデータはWin32やD3DXのフォントオブジェクトを作るのに必要な情報で、これらを変更するにはフォントオブジェクトの作り直しが必要になる。DXRubyでFontオブジェクトを作るときにこれらの情報が必要なのも描画時に指定できないのもこれが原因だ。初期の頃はWindow.draw_fontしかなかったから、描画のたびにフォントオブジェクトを生成するようなことはしたくなかったという話である。

Fontクラスの機能はD3DXとWin32のフォントオブジェクトとRuby用フォント情報の保持なので、処理としてはFont#initializeに書いてあるものぐらいしかない。

/*--------------------------------------------------------------------
   FontクラスのInitialize
 ---------------------------------------------------------------------*/
static VALUE Font_initialize( int argc, VALUE *argv, VALUE obj )
{
    struct DXRubyFont *font;
    HRESULT hr;
    D3DXFONT_DESC desc;
    VALUE size, vfontname, voption, hash, vweight, vitalic;
    LOGFONT logfont;
    int weight, italic;

    g_iRefAll++;

    rb_scan_args( argc, argv, "12", &size, &vfontname, &hash );

    if( hash == Qnil )
    {
        voption = rb_hash_new();
    }
    else
    {
        Check_Type( hash, T_HASH );
        voption = hash;
    }

    vweight = hash_lookup( voption, symbol_weight );
    vitalic = hash_lookup( voption, symbol_italic );

    if( vweight == Qnil || vweight == Qfalse )
    {
        weight = 400;
    }
    else if( vweight == Qtrue )
    {
        weight = 1000;
    }
    else
    {
        weight = NUM2INT( vweight );
    }

    if( vitalic == Qnil || vitalic == Qfalse )
    {
        italic = FALSE;
    }
    else
    {
        italic = TRUE;
    }

    desc.Height          = NUM2INT( size );
    desc.Width           = 0;
    desc.Weight          = weight;
    desc.MipLevels       = 0;
    desc.Italic          = italic;
    desc.CharSet         = DEFAULT_CHARSET;
    desc.OutputPrecision = 0;
    desc.Quality         = DEFAULT_QUALITY;
    desc.PitchAndFamily  = DEFAULT_PITCH || FF_DONTCARE;

    ZeroMemory( &logfont, sizeof(logfont) );
    logfont.lfHeight          = NUM2INT( size );
    logfont.lfWidth           = 0;
    logfont.lfWeight          = weight;
    logfont.lfItalic          = italic;
    logfont.lfCharSet         = DEFAULT_CHARSET;
    logfont.lfQuality         = ANTIALIASED_QUALITY;

    if( vfontname == Qnil )
    {
        lstrcpy(desc.FaceName, "");
        lstrcpy(logfont.lfFaceName, "");
    }
    else
    {
        VALUE vsjisstr;
        Check_Type(vfontname, T_STRING);
#ifdef HAVE_RB_ENC_STR_NEW
        if( rb_enc_get_index( vfontname ) != 0 )
        {
            vsjisstr = rb_funcall( vfontname, rb_intern( "encode" ), 1, rb_str_new2( sys_encode ) );
        }
        else
        {
            vsjisstr = vfontname;
        }
#else
        vsjisstr = vfontname;
#endif
        lstrcpy(desc.FaceName, RSTRING_PTR( vsjisstr ));
        lstrcpy(logfont.lfFaceName, RSTRING_PTR( vsjisstr ));
    }

    /* フォントオブジェクト取り出し */
    Data_Get_Struct( obj, struct DXRubyFont, font );

    hr = D3DXCreateFontIndirect( g_pD3DDevice, &desc, &font->pD3DXFont );

    if( FAILED( hr ) )
    {
        rb_raise( eDXRubyError, "フォントの作成に失敗しました - D3DXCreateFontIndirect" );
    }

    font->hFont = CreateFontIndirect( &logfont );

    if( font->hFont == NULL )
    {
        rb_raise( eDXRubyError, "フォントの作成に失敗しました - CreateFontIndirect" );
    }

    font->size = NUM2INT( size );
    font->italic = vitalic;
    font->weight = vweight;
#ifdef HAVE_RB_ENC_STR_NEW
    font->fontname = vfontname == Qnil ? Qnil : rb_str_new_shared( vfontname );
#else
    font->fontname = vfontname == Qnil ? Qnil : rb_str_new2( RSTRING_PTR(vfontname) );
#endif

    return obj;
}

上のほうはお約束のコードみたいな感じなので置いといて、まんなかあたりの構造体の設定がD3DXとWin32用それぞれのフォント情報構造体となっている。何を設定したらどうなるというのはヘルプを見てもらえばわかる。キャラセットの指定がDEFAULT_CHARSETとなっているのはシステムのキャラセットを使うという意味で、外国語版のWindowsになると選択されるキャラセットが変わるからマイクロソフト的には推奨していない。なぜこうしているのかというと、SJISを指定すると外国の人が母国語を使えなくなるからだ。エラーメッセージやマニュアルが日本語のライブラリを外国人が使うのか、というと、これが一度そういうことが実際にあって、最初SJISだったから「タイ語が表示できません」みたいな問い合わせがきて対応するために変えたのだ。世界は広いので色んなことが起こる。簡単なマニュアルを英語で書くだけでもたぶん色んな人が使ってくれるのだろうが、俺は英語は見事にダメなのでそのような予定は無い。でもせめて日本語Windows以外ではエラーメッセージを英語にしようかとか、そういうことは地味に考えている。