備忘録:rubyでttc & ttfファイルからフォント名を取り出す。
覚え直すのは多分三回目くらい。毎度の如く、「よし出力だ、ただし詳細説明&コードの最適化してからな!」と思ってたら、
案の定の浦島メソッドにつき、結局雑把なコードと簡易コメントのみで。
# coding: utf-8 FONTS_PATH = {Windows_NT: "フォントフォルダパス"}[ENV["OS"].to_sym] Dir.chdir(FONTS_PATH) def parse_ttf(str, ttf_head_offset = 0) # j = ttc内index、ttf単独なら当然0 # TTFヘッダ(ttf_head_offset)先頭4byte〜 2byte整数で、オフセットテーブル数 (ttf_table_num = str[ttf_head_offset + 4, 2].unpack("n").pop).times { |j| # TTFヘッダ(12byte分)後、各オフセットテーブルの構造が続く(ここではnameのみをターゲットに) table_offset = ttf_head_offset + 12 + 16 * j # テーブル先頭〜 4byte符号無し整数でタグ名の文字列 if str[table_offset, 4] == "name" # テーブル先頭8byte〜 4byte符号無し整数で、タグ(name)の実体を指すオフセット name_offset = str[table_offset + 8, 4].unpack("N")[0] # タグの実体に飛び、その先頭〜 2byte符号無し整数でレコード数、 # さらに2byteでレコード情報の文字列実体へのオフセット record_count, storage_offset = str[name_offset + 2, 4].unpack("nn") # レコード開始位置 record_offset = name_offset + 6 record_count.times { |k| # レコード開始+4byteの位置〜 2byteで言語(japanese等)ID、さらに2byteで名前(フォント名等)ID language_id, name_id = str[record_offset + 4 + 12 * k, 4].unpack("nn") # 日本語フォントまたはフォントのフルネーム情報以外はスキップ next if language_id != 0x411 or name_id != 4 # レコード開始+8byteの位置〜 2byteでフォント名の長さ、さらに2byteでそのオフセット string_length, string_offset = str[record_offset + 8 + 12 * k, 4].unpack("nn") # 各オフセットを合計して、フォント名への位置を取得 pos = name_offset + storage_offset + string_offset # 最終的に得られたバイナリ文字列をUTF_16BEに変換して、大域脱出と共に返す。 # rescue節は変換失敗の際に無理くりスキップさせるため。 # ここではさらにutf-8に変換して、ファイルエンコーディングに合わせている。 font_name = str[pos,string_length].force_encoding(Encoding::UTF_16BE).encode(Encoding::UTF_8) rescue next return font_name } end } end result = {} # フォント名: [ フォントパス, インデックス ] Dir.glob("*.tt[fc]") { |path| # バイナリ文字列として変数に格納しておく # 全部読み込みたく無い場合は、このまま中でファイルオブジェクト使って良しなに。 str="";open(path, "rb") { |f| str = f.read } # .ttc = 複数のttfデータを格納する形式 if path =~ /\.ttc/ # 先頭8byte〜 32bit整数で、ttc内に含まれるフォント(ttfデータ)数 (ttf_num = str[8,4].unpack("N*")[0]).times { |i| # 先頭12byte〜 32bit整数で、フォントの数だけ各フォントの開始オフセットが並ぶ ttf_head_offset = str[12 + i * 4, 4].unpack("N")[0] # オフセット位置を元に、ttfデータの解析を行い、戻り値(結果)を格納する。 font_name = parse_ttf(str, ttf_head_offset) result[font_name] = [path, i] if font_name.kind_of?(String) } else # こちらはttfデータそのままなので、一気に解析して結果まで。 font_name = parse_ttf(str) result[font_name] = [path, 0] if font_name.kind_of?(String) end } # 結果出力 result.each {|r| puts r.join(",") }
今回はフォント名の取得のみでしたが、その他の情報を得たい場合は、http://www.microsoft.com/typography/otspec/otff.htmを参照して辿るべし。