# ttfdump # Copyright (C) 2006 Mathieu Blondel # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA $LOAD_PATH.unshift("../lib/") require 'ttf' require 'pp' def title_box(title) puts "" bar = "-" * (title.length + 4) puts bar puts " " + title puts "#{bar}\n\n" end def define(title, definition) if definition.class == Float puts "%s: %d" % [title, definition] elsif definition.kind_of? Array puts(title + ": " + definition.join(", ")) else puts(title + ": " + definition.to_s) end end def define_methods(obj, methods=nil) methods = obj.class.instance_methods(false) if methods.nil? methods = methods.find_all { |m| m !~ /=$/ and m != "dump" } methods.sort.each do |meth| define(meth, obj.send(meth)) end end def usage puts "Usage:" puts "ttfdump ttf_file [table]" puts "Ex: ttfdump font.ttf name" end if ARGV.length == 0 or ["h", "-h", "help", "--help"].include? ARGV[0] usage exit! else ttf = TTFFont::TTF::File.new(ARGV[0]) end if ARGV.length == 1 or (ARGV.length == 2 and ARGV[1] == "all") # General Informations title_box("General informations") define("File dumped", ARGV[0]) define("Version", ttf.version) if ttf.tables_include? :name name = ttf.get_table(:name) fontname = name.name_records.find do |nr| [TTFFont::TTF::Table::Name::NameRecord::UNIQUE_FONT_IDENTIFIER, TTFFont::TTF::Table::Name::NameRecord::FONT_FAMILY_NAME, TTFFont::TTF::Table::Name::NameRecord::FULL_FONT_NAME, TTFFont::TTF::Table::Name::NameRecord::POSTSCRIPT_NAME].include? \ nr.name_id and (nr.unicode? or nr.roman?) end define("Font Name", fontname.utf8_str) unless fontname.nil? fontcopyright = name.name_records.find do |nr| nr.name_id == TTFFont::TTF::Table::Name::NameRecord::COPYRIGHT_NOTICE \ and (nr.unicode? or nr.roman?) end define("Copyright", fontcopyright.utf8_str) unless fontcopyright.nil? end define("Number of tables", ttf.table_list.length) define("Tables", "") ttf.table_list.each_with_index do |key, i| val = ttf.tables_infos[key.to_sym] puts "%2u. %4s - checksum = 0x%08x, offset = 0x%08x, len = %9u \n" % \ [i, key, val[:checksum], val[:offset], val[:len]] end end if ARGV.length == 2 and (ARGV[1] == "cmap" or ARGV[1] == "all") and ttf.tables_include? :cmap # Cmap title_box("Character to Glyph mapping (cmap) table") cmap = ttf.get_table(:cmap) define("Version", cmap.version) define("Number of encoding tables", cmap.encoding_tables.length) define("Encoding tables summary", "") tables = cmap.encoding_tables tables.each_with_index do |tbl, i| puts "%2u. format = %2u offset = 0x%08x, len = %d" % [i, tbl.format, tbl.offset_from_table, tbl.len] end puts "" tables.each_with_index do |tbl, i| define("Encoding table", i) define("Format", tbl.format) define("Platform ID", tbl.platform_id) define("Encoding ID", tbl.encoding_id) define("Unicode?", tbl.unicode?) define("Offset from table", "0x%08x" % tbl.offset_from_table) define("Length", tbl.len) if tbl.format == 0 define("Version", tbl.version) puts "" tbl.glyph_id_array.each_with_index do |glyph_index, char_code| puts "Char %d -> Glyph Index %d" % [char_code, glyph_index] end elsif tbl.format == 4 define("Version", tbl.version) define("Segment count", tbl.segments.length) define("Search range", tbl.search_range) define("Entry selector", tbl.entry_selector) define("Range shift", tbl.range_shift) puts "" tbl.segments.length.times do |i| puts \ "Segment %4d. start = %d, end = %d, delta = %d, range = %d" % [i, tbl.start_count_array[i], tbl.end_count_array[i], tbl.id_delta_array[i], tbl.id_range_offset_array[i]] end puts "" if tbl.glyph_index_array.length > 0 define("Number of glyph indices", tbl.glyph_index_array.length) tbl.glyph_index_array.each_with_index do |glyph_index, i| puts "glyph_index_array[%d] = %d" % [i, glyph_index] end end tbl.segments.each_with_index do |seg, i| puts "Segment #{i.to_s}" seg.keys.sort.each do |char_code| glyph_index = seg[char_code] puts "Char %d (%s) (%s)-> Glyph index %d" % \ [char_code, "0x%08x" % char_code, [char_code].pack("U"), glyph_index] end puts "" end end puts "" end end if ARGV.length == 2 and (ARGV[1] == "cvt" or ARGV[1] == "all") and ttf.tables_include? :cvt # Cvt title_box("Control Value table (cvt)") cvt = ttf.get_table(:cvt) cvt.instructions.each_with_index do |inst, i| puts "Instruction %d: %d" % [i, inst] end end if ARGV.length == 2 and (ARGV[1] == "gasp" or ARGV[1] == "all") and ttf.tables_include? :gasp # gasp title_box("Grid-fitting and scan-conversion procedure (gasp)") gasp = ttf.get_table(:gasp) gasp.gasp_ranges.each_with_index do |gr, i| define("Gasp range", i) define("Range max ppem", gr.range_max_ppem) define("Range gasp behavior", gr.range_gasp_behavior) puts "" end end if ARGV.length == 2 and (ARGV[1] == "glyf" or ARGV[1] == "all") and ttf.tables_include? :glyf # glyf title_box("Glyph data (glyf)") glyf = ttf.get_table(:glyf) i = 0 glyf.each_glyph do |glyph| define("Glyph index", i) define("Glyph type", glyph.class.to_s) if glyph.simple? methods = glyph.class.instance_methods(false) + \ glyph.class.superclass.instance_methods(false) define_methods(glyph, methods) else methods = glyph.class.superclass.instance_methods(false) define_methods(glyph, methods) print("Glyph components: ") pp glyph.components end puts "" i += 1 end end if ARGV.length == 4 and ARGV[1] == "glyf" and ttf.tables_include? :glyf # glyf title_box("Glyph data (glyf)") glyf = ttf.get_table(:glyf) enc_tbl = ttf.get_table(:cmap).encoding_tables.find do |t| t.class == TTFFont::TTF::Table::Cmap::EncodingTable4 end if ARGV[2] == "-g" offs = ttf.get_table(:loca).glyph_offsets[ARGV[3].to_i] glyph = glyf.get_glyph_at_offset(offs) elsif ARGV[2] == "-c" char_code = ARGV[3].unpack("U")[0] glyph = enc_tbl.get_glyph_for_unicode(char_code) elsif ARGV[2] == "-o" glyph = glyf.get_glyph_at_offset(ARGV[3].hex) else exit! end define("Glyph type", glyph.class.to_s) if glyph.simple? methods = glyph.class.instance_methods(false) + \ glyph.class.superclass.instance_methods(false) define_methods(glyph, methods) else methods = glyph.class.superclass.instance_methods(false) define_methods(glyph, methods) print("Glyph components: ") pp glyph.components end puts "" end if ARGV.length == 2 and (ARGV[1] == "head" or ARGV[1] == "all") and ttf.tables_include? :head # Head title_box("Head table") head = ttf.get_table(:head) define_methods(head) end if ARGV.length == 2 and (ARGV[1] == "hhea" or ARGV[1] == "all") and ttf.tables_include? :hhea # Hhea title_box("Horizontal header table") hhea = ttf.get_table(:hhea) methods = ["version", "ascender", "descender", "line_gap", "advance_width_max", "min_left_side_bearing", "min_right_side_bearing", "x_max_extent", "caret_slope_rise", "caret_slope_run", "metric_data_format", "number_of_hmetrics"] define_methods(hhea, methods) end if ARGV.length == 2 and (ARGV[1] == "hmtx" or ARGV[1] == "all") and ttf.tables_include? :hmtx # Hmtx title_box("Horizontal metrics table") hmtx = ttf.get_table(:hmtx) hmetrics = hmtx.hmetrics define("Number of Hmetrics", hmetrics.length) define("Hmetrics", "") hmetrics.each_with_index do |hmetric, i| puts "%2u. advanced width = %5u, left side bearing = %5d" % \ [i, hmetric[0], hmetric[1]] end end if ARGV.length == 2 and (ARGV[1] == "kern" or ARGV[1] == "all") and ttf.tables_include? :kern # Kern title_box("Kerning (kern)") kern = ttf.get_table(:kern) kern.subtables.each do |subtbl| if subtbl.format == 0 subtbl.kerning_pairs.each do |kp| puts "left = %5d, right = %5d, value = %5d" % \ [kp.left, kp.right, kp.value] end end end end if ARGV.length == 2 and (ARGV[1] == "loca" or ARGV[1] == "all") and ttf.tables_include? :loca # Loca title_box("Index to location table (loca)") loca = ttf.get_table(:loca) loca.glyph_offsets.each_with_index do |off, i| puts "Glyph index %6d -> Offset 0x%08x" % [i, off] end end if ARGV.length == 2 and (ARGV[1] == "maxp" or ARGV[1] == "all") and ttf.tables_include? :maxp # Maxp title_box("Max Profile table") maxp = ttf.get_table(:maxp) define_methods(maxp) end if ARGV.length == 2 and (ARGV[1] == "name" or ARGV[1] == "all") and ttf.tables_include? :name # Name title_box("Name table") name = ttf.get_table(:name) define("Number of name records", name.name_records.length) name.name_records.each_with_index do |nr, i| define("Name record number", i) define("Platform id", "%d (%s)" % [nr.platform_id, TTFFont::TTF::Encodings::Platform::ID2NAME[nr.platform_id]]) if nr.platform_id == \ TTFFont::TTF::Encodings::Platform::MICROSOFT define("Encoding id", "%d (%s)" % [nr.encoding_id, TTFFont::TTF::Encodings::MicrosoftEncoding::ID2NAME[nr.encoding_id]]) elsif nr.platform_id == \ TTFFont::TTF::Encodings::Platform::MACINTOSH define("Encoding id", "%d (%s)" % [nr.encoding_id, TTFFont::TTF::Encodings::MacintoshEncoding::ID2NAME[nr.encoding_id]]) end define("Language id", nr.language_id) define("Name id", "%d (%s)" % [nr.name_id, TTFFont::TTF::Table::Name::NameRecord::ID2NAME[nr.name_id]]) define("String length", nr.to_s.length) define("String offset", nr.str_offset) define("String value", nr.utf8_str) puts "" end end if ARGV.length == 2 and (ARGV[1] == "os2" or ARGV[1] == "all") and ttf.tables_include? :"OS/2" # Post title_box("OS/2 and Windows metrics") os2 = ttf.get_table(:"OS/2") define_methods(os2) end if ARGV.length == 2 and (ARGV[1] == "post" or ARGV[1] == "all") and ttf.tables_include? :post # Post title_box("Postscript (post)") post = ttf.get_table(:post) methods = %w[format_type italic_angle underline_position underline_thickness is_fixed_pitch min_mem_type_42 max_mem_type_42 min_mem_type_1 max_mem_type_1] define_methods(post, methods) post.names.each_with_index do |name, i| puts "Glyph %6d: %s (postscript id: %d)" % [i, name.str, name.id] end end if ARGV.length == 2 and (ARGV[1] == "vhea" or ARGV[1] == "all") and ttf.tables_include? :vhea # Vhea title_box("Vertical header table") vhea = ttf.get_table(:vhea) methods = ["version", "ascender", "descender", "line_gap", "advance_height_max", "min_top_side_bearing", "min_bottom_side_bearing", "y_max_extent", "caret_slope_rise", "caret_slope_run", "metric_data_format", "number_of_vmetrics"] define_methods(vhea, methods) end if ARGV.length == 2 and (ARGV[1] == "vmtx" or ARGV[1] == "all") and ttf.tables_include? :vmtx # Vmtx title_box("Vertical metrics table") vmtx = ttf.get_table(:vmtx) vmetrics = vmtx.vmetrics define("Number of Vmetrics", vmetrics.length) define("Vmetrics", "") vmetrics.each_with_index do |vmetric, i| puts "%2u. advanced width = %5u, top side bearing = %5d" % \ [i, vmetric[0], vmetric[1]] end end