レンダリング済みフォントサポートクラスサンプル

できた。
プロポーショナルフォントを扱う場合にはフォントのメトリクス構造体から情報を取ってきて描画位置を調整する必要があるのだが、アルファベットの小文字に特殊なやつ(fとかj)がいて、通常の領域をはみ出して描画することがある。
こういった文字を扱うのは難しい話で、画像を生成する処理と描画する処理が一体になっていれば少し大きな画像を作ってズラして描画することで対策できるのだが、画像を作るだけの処理だとそうもいかない。座標補正情報が画像に含まれていないのでそのまま描画すると位置がズレるし、画像を大きくしないと文字が切れる。厄介な話である。
問題の対処のためにFont#infoメソッドを追加してフォントのメトリクスを取得できるようにした。このサンプルではそれを使っている。このあたりは非常にややこしいので詳細を説明する気は起こらない。GetGlyphOutlineとかでぐぐれば出てくる。

このサンプルはレンダリング済みフォントを作るクラスと使うクラスが入っている。ImageFontMakerオブジェクトに文字列を突っ込んでoutputメソッドを呼ぶと文字列をシャッフルして画像を一枚作り、それをバイナリで読み込んで対応表をくっつけてマーシャルで吐き出す。ImageFontのほうはファイル名とdraw_font_exのハッシュ部分のパラメータを渡すとファイルを読み込んで情報を構築するので、あとはdrawメソッドで文字を描画する。という感じになっている。
基本そのまま使えるかもしれないが、改行処理とか入れようとすると幅とか取得するメソッドが必要になってくるんじゃないかと思う。

#!ruby -Ks
require 'dxruby'

# レンダリング済みフォントを作るクラス
class ImageFontMaker
  ImageFontData = Struct.new(:x, :y, :width, :ox)
  ImageFontSaveData = Struct.new(:data_hash, :image, :height)

  def initialize(size, fontname = nil)
    @font = Font.new(size, fontname)
    @hash = {}
  end

  def add_data(str)
    str.each_char do |c|
      @hash[c] = @font.info(c)
    end
  end

  def [](c)
    @hash[c]
  end

  def output(filename)
    # 必要な画像サイズを調べる
    # 計算がややこしいことになってるのはfやjなど左右にはみ出す特殊な文字の対応
    width = 0
    height = 0
    keys = @hash.keys.shuffle
    keys.each do |k|
      v = @hash[k]
      ox = v.gmpt_glyphorigin_x < 0 ? -v.gmpt_glyphorigin_x : 0
      cx = (v.gm_blackbox_x + v.gmpt_glyphorigin_x) > v.gm_cellinc_x + ox ? (v.gm_blackbox_x + v.gmpt_glyphorigin_x) : v.gm_cellinc_x + ox
      width += cx
      if width > 640
        height += @font.size
        width = cx
      end
    end
    height += @font.size

    # 画像生成
    image = Image.new(640, height, C_BLACK)

    # 文字描き込み
    x = 0
    y = 0
    data_hash = {}
    keys.each do |k|
      v = @hash[k]
      ox = v.gmpt_glyphorigin_x < 0 ? -v.gmpt_glyphorigin_x : 0
      cx = (v.gm_blackbox_x + v.gmpt_glyphorigin_x) > v.gm_cellinc_x + ox ? (v.gm_blackbox_x + v.gmpt_glyphorigin_x) : v.gm_cellinc_x + ox
      if x + cx > 640
        x = 0
        y += @font.size
      end
      image.draw_font(x + ox, y, k, @font)
      data_hash[k] = ImageFontData.new(x, y, cx, ox)
      x += cx
    end

    # いったん保存
    image.save(filename + ".png")

    # バイナリで読み込み
    image_binary = nil
    open(filename + ".png", "rb") do |fh|
      image_binary = fh.read
    end

    # マーシャルしてファイルへ
    imagefont = ImageFontSaveData.new(data_hash, image_binary, @font.size)

    open(filename, "wb") do |fh|
      fh.write(Marshal.dump(imagefont))
    end
  end
end

# レンダリング済みフォントを使うクラス
class ImageFont
  # 生成時のパラメータはdraw_font_exのハッシュ部分と同じ
  def initialize(filename, param = nil)
    open(filename, "rb") do |fh|
      temp = Marshal.load(fh.read)
      @data_hash = temp.data_hash
      @image = Image.load_from_file_in_memory(temp.image)
      @height = temp.height
    end
    @cache = {}
    @param = param
  end

  # 文字列描画
  def draw(x, y, s, rt = Window)
    s.each_char do |c|
      temp = @data_hash[c]
      if !@cache.has_key?(c)
        image = @image.slice(temp.x, temp.y, temp.width, @height)
        @cache[c] = image.effect_image_font(@param)
        image.delayed_dispose
      end
      rt.draw(x - temp.ox, y, @cache[c])
      x += temp.width - temp.ox
    end
  end
end

Window.bgcolor = [100,100,100]

# レンダリング済みフォント作成
imagefontmaker = ImageFontMaker.new(48, "MS Pゴシック")
imagefontmaker.add_data(" ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefさghijklmnopqrstuvwxyzあいうえおかきくけこがぎぐげごしすせそざじずぜぞたちつてとだぢづでどなにぬねのはひふへほばびぶべぼぱぴぷぺぽまみむめもやゆよらりるれろわをんっぁぃぅぇぉゃゅょ、。")
imagefontmaker.output("ImageFont.dat")

# レンダリング済みフォント読み込み
imagefont = ImageFont.new("ImageFont.dat", :shadow=>true, :edge=>true, :edge_color=>C_CYAN)

Window.loop do
  imagefont.draw(50, 60, "きょうはあさからよるだった。")
  imagefont.draw(50, 108, "fあいうえおかきくけこ")
  imagefont.draw(50, 156, "jさしすせそたちつてと")
  imagefont.draw(50, 204, "kなにぬねのはひふへほ")
end