#!/usr/bin/env ruby
#--
# Last Change: Tue May 16 18:09:09 2006
#++
=begin rdoc

== afminfo - print out information about an afm or truetype font file

 Usage: afminfo [options] afm-file.afm | truetypefont.ttf
   -m, --metrics                    show global metrics information
   -v, --verbose                    verbose output
       --[no-]info                  list general information (default)
   -l, --list-glyphs                list all available glyphs
   -g, --glyphinfo g                information about a specific glyph
   -h, --help                       Show this help

---
Author:: Patrick Gundlach <patrick@gundla.ch>
License::  Copyright (c) 2005 Patrick Gundlach.
           Released under the terms of the GNU General Public License
=end

# :enddoc:

require 'optparse'
require 'ostruct'
require 'rfil/font/afm'


# sorry for german - one day I'll change that into engish. --pg
class TextTable # :nodoc:
  def initialize (data,maxchars=80)
    raise "Data must be an array of strings" unless data.kind_of? Array
    @data = data
    
    @maxchars = 90
    @maxwidth = 0
    @data.each { |elt|
      @maxwidth = @maxwidth > elt.length ? @maxwidth : elt.length
    }
    @spalten = (@maxchars/(@maxwidth + 1)).floor
  end
  
  def zeilenweise
    (0...@data.size).each { |x|
      print sprintf("%-" + @maxwidth.to_s + "s",@data[x]) +
        if (x+1) % @spalten == 0
          "\n"
        else
          " "
        end
    }
    puts
  end
  
  def spaltenweise
    zeilenmin = (@data.size * 1.0 / @spalten).floor
    zeilenmax = (@data.size * 1.0 / @spalten).ceil
    anz_volle_spalten = @data.size % @spalten

    verteilung=Array.new()
    verteilung[0]=0
    count = 0
    (0...@spalten).each { |col|
      if col < anz_volle_spalten
        count += zeilenmax
      else 
        count += zeilenmin
      end
      verteilung[col]= count
    }
    verteilung.unshift 0
    (0...zeilenmax).each { |zeile|
      puts((0...@spalten).collect { |spalte| 
             w = @data[verteilung[spalte] + zeile]
             if (spalte >= anz_volle_spalten) and (zeile >= zeilenmin)
               w = ""
             end
             sprintf("%-" + @maxwidth.to_s + "s",w)
           }.join(' '))
    }
  end
end

class AFMinfo # :nodoc:
  def initialize (afm)
    @afm = afm
    @uppercase_letters = ('A'..'Z').collect { |l|  l }
    @lowercase_letters = ('a'..'z').collect { |l|  l }
    @digits = %w(one two three four five six seven eight nine zero)
  end
  def stdformat
    "%-18s "
  end
  def printout (str,value)
    puts sprintf(stdformat + "%s", str + ":" ,value.to_s)
  end

  def num_to_string (num,reservespace=true)
    formatstring = "%" 
    formatstring << " " if reservespace
    formatstring << (num.to_i == num ? "d" : "f")
    # output "integer" if value after decimal point is 0
    sprintf(formatstring,num)
  end
  
  def dump_maininfo
    puts "General font information:"
    puts "========================="
    printout("Filename",@afm.filename)
    [ 
      ["Fontname",:fontname],
      ["FullName",:fullname],
      ["Family name",:familyname],
      ["Weight",:weight],
      ["EncodingScheme",:encodingscheme],
#      ["·","·"],
    ].each { |s,m|
      printout(s,@afm.send(m))
    }
    puts sprintf(stdformat,'Number of gylphs:') +  @afm.count_charmetrics.to_s + 
      " (encoded: " + @afm.count_charmetrics_encoded.to_s +  ", unencoded: " + @afm.count_charmetrics_unencoded.to_s + ")"

  end
  def dump_metrics
    puts "\n\nGlobal metrics information:"
    puts "=========================="
    puts sprintf(stdformat,"FontBBox:") + @afm.fontbbox.collect { |f|
      num_to_string(f)
    }.join(' ') + ' (llx,lly,urx,ury)'
    printout('IsFixedPitch',@afm.isfixedpitch)

    [ 
      ["ItalicAngle", :italicangle],
      ["UnderlinePosition", :underlineposition],
      ["UnderlineThickness", :underlinethickness],
      ["CapHeight", :capheight],
      ["XHeight", :xheight],
      ["Ascender", :ascender],
      ["Descender", :descender]
    ].each { |s,m|
      puts sprintf(stdformat,s) +  num_to_string(@afm.send(m))
    }
  end
  def dump_glyphinfo (glyph)
    chars=@afm.chars[glyph]
    puts "\n\nGlyph information (" + glyph + ")"
    puts "===================="
    puts sprintf(stdformat,"Encoding pos:") +
      if chars.c == -1 
        "--" 
      else 
        sprintf("%d (dec), %x (hex), %o (oct)",chars.c,chars.c,chars.c)
      end
    puts sprintf(stdformat,"Width x (wx)")  + chars.wx.to_s
    puts sprintf(stdformat,"Bounding box")  + chars.b.collect { |f|
      num_to_string(f,false)
    }.join(' ') + ' (llx,lly,urx,ury)'
    puts "Kerning pairs: (x,y)"
    puts "--------------------"
    chars.kern_data.each { |k,v|
      puts sprintf(stdformat,"  " + k) + num_to_string(v[0]) + "," +   
        num_to_string(v[1]) 
    }
  end
  def dump_glyphs
    chars=@afm.chars
    puts "\n\nList of glyphs"
    puts "================"
    removefromlist=[]
    if @uppercase_letters.all? { |glyph|
        chars[glyph]
      }
      puts sprintf(stdformat,"A-Z") + "available"
      removefromlist += @uppercase_letters
    else
      puts sprintf(stdformat,"A-Z") + "some missing"
    end
    if @lowercase_letters.all? { |glyph|
        chars[glyph]
      }
      puts sprintf(stdformat,"a-z") + "available"
      removefromlist += @lowercase_letters
    else
      puts sprintf(stdformat,"a-z") + "some missing"
    end
    if @digits.all? { |glyph|
        chars[glyph]
      }
      puts sprintf(stdformat,"one, two, .., zero") + "available"
      removefromlist += @digits
    else
      puts sprintf(stdformat,"one, two, .., zero") + "some missing"
    end
    puts
    glyphlist=[]
    chars.each { |glyph,h|
      glyphlist.push(glyph + (h.c == -1 ? "*" : ""))
    } 
    glyphlist = glyphlist - removefromlist
    glyphlist.sort! { |a,b|
      a.casecmp b
    }
    t = TextTable.new(glyphlist - removefromlist)
    t.spaltenweise
    puts "\n* = unencoded - only glyphs not mentioned above listed"
  end

end

options=OpenStruct.new
options.verbose = false
options.metrics = false
options.glyph = nil
options.listglyphs = false
options.generalinfo = true

ARGV.options { |opt|
  opt.banner = "Usage: #{File.basename($0)} [options] afm-file[.afm]"
  opt.on("--metrics", "-m", "show global metrics information") { options.metrics=true}
  opt.on("--verbose", "-v", "verbose output") { options.verbose=true }
  opt.on("--[no-]info", "list general information (default)") { |x|
    options.generalinfo=x }
  opt.on("--list-glyphs", "-l", "list all available glyphs") { options.listglyphs=true }
  opt.on("--glyphinfo g" , "-g", 
         "information about a specific glyph") { |arg| 
    options.glyph = arg
  }
    
  opt.on("--help", "-h", "Show this help") { puts opt; exit 0 }
  opt.separator  ""
  opt.separator "Copyright (c) 2005 Patrick Gundlach"
  opt.parse!
}

unless ARGV.size==1
  puts "Please specify one afm-file to read"  if ARGV
  exit 1
end


# afm=Font::AFM.new(:verbose => options.verbose)
afm=RFIL::Font::Metric.read(ARGV[0],:verbose => options.verbose)

ai = AFMinfo.new(afm)
ai.dump_maininfo if options.generalinfo
ai.dump_metrics if options.metrics
options.glyph && ai.dump_glyphinfo(options.glyph)
ai.dump_glyphs if options.listglyphs