open (PDF::Writer::FontMetrics)

         # File lib/pdf/writer/fontmetrics.rb, line 39
     39:   def self.open(font_name)
     40:     file  = font_name.gsub(/\\/o, "/")
     41:     dir   = File.dirname(file)
     42:     name  = File.basename(file)
     43:
     44:     metrics_path = []
     45:
     46:       # Check to see if the directory is "." or a non-path
     47:     if dir == "."
     48:       metrics_path << dir << METRICS_PATH << $LOAD_PATH
     49:     elsif dir !~ %r{^(\w:|/)}o and dir.index("/")
     50:       METRICS_PATH.each { |path| metrics_path << File.join(path, dir) }
     51:       $LOAD_PATH.each { |path| metrics_path << File.join(path, dir) }
     52:     else
     53:       metric_path = [ dir ]
     54:     end
     55:     metrics_path.flatten!
     56:
     57:     font = nil
     58:     afm = nil
     59:
     60:     metrics_path.each do |path|
     61:       afm_file  = File.join(path, "#{name}.afm").gsub(/\.afm\.afm$/o, ".afm")
     62:       rfm_file  = "#{afm_file}.rfm"
     63:
     64:         # Attempt to unmarshal an .afm.rfm file first. If it is loaded,
     65:         # we're in good shape.
     66:       begin
     67:         if File.exists?(rfm_file)
     68:           data = File.open(rfm_file, "rb") { |file| file.read }
     69:           font = Marshal.load(data)
     70:           return font
     71:         end
     72:       rescue
     73:         nil
     74:       end
     75:
     76:         # Attempt to open and process the font.
     77:       File.open(afm_file, "rb") do |file|
     78:         font = PDF::Writer::FontMetrics.new
     79:
     80:           # An AFM file contains key names followed by valuees.
     81:         file.each do |line|
     82:           line.chomp!
     83:           line.strip!
     84:           key, *values = line.split
     85:           op = "#{key.downcase}=".to_sym
     86:
     87:             # I probably need to deal with MetricsSet. The default value is
     88:             # 0, which is writing direction 0 (W0X). If MetricsSet 1 is
     89:             # specified, then only writing direction 1 is present (W1X). If
     90:             # MetricsSet 2 is specified, then both W0X and W1X are present.
     91:
     92:             # Measurements are always 1/1000th of a scale factor (point
     93:             # size). So a 12pt character with a width of 222 is going to be
     94:             # 222 * 12 / 1000 or 2.664 points wide.
     95:           case key
     96:           when 'FontName', 'FullName', 'FamilyName', 'Weight',
     97:             'IsFixedPitch', 'CharacterSet', 'Version', 'EncodingScheme'
     98:               # These values are string values.
     99:             font.__send__(op, values.join(" "))
    100:           when 'ItalicAngle', 'UnderlinePosition', 'UnderlineThickness',
    101:             'CapHeight', 'XHeight', 'Ascender', 'Descender', 'StdHW',
    102:             'StdVW', 'StartCharMetrics'
    103:               # These values are floating point values.
    104:             font.__send__(op, values.join(" ").to_f)
    105:           when 'FontBBox'
    106:               # These values are an array of floating point values
    107:             font.fontbbox = values.map { |el| el.to_f }
    108:           when 'C', 'CH'
    109:               # Individual Character Metrics Values:
    110:               #   C  <character number>
    111:               #   CH <hex character number>
    112:               #     One of C or CH must be provided. Specifies the encoding
    113:               #     number for the character. -1 if the character is not
    114:               #     encoded in the font.
    115:               #
    116:               #   WX  <x width number>
    117:               #   W0X <x0 width number>
    118:               #   W1X <x1 width number>
    119:               #     Character width in x for writing direction 0 (WX, W0X)
    120:               #     or 1 (W1X) where y is 0. Optional.
    121:               #
    122:               #   WY  <y width number>
    123:               #   W0Y <y0 width number>
    124:               #   W1Y <y1 width number>
    125:               #     Character width in y for writing direction 0 (WY, W0Y)
    126:               #     or 1 (W1Y) where x is 0. Optional.
    127:               #
    128:               #   W  <x width> <y width>
    129:               #   W0 <x0 width> <y0 width>
    130:               #   W1 <x1 width> <y1 width>
    131:               #     Character width in x, y for writing direction 0 (W, W0)
    132:               #     or 1 (W1). Optional.
    133:               #
    134:               #   VV <x number> <y number>
    135:               #     Same as VVector in the global font definition, but for
    136:               #     this single character. Optional.
    137:               #
    138:               #   N <name>
    139:               #     The PostScript name of the font. Optional.
    140:               #
    141:               #   B <llx> <lly> <urx> <ury>
    142:               #     Character bounding box for the lower left corner and the
    143:               #     upper right corner. Optional.
    144:               #
    145:               #   L <sucessor> <ligature>
    146:               #     Ligature sequence where both <successor> and <ligature>
    147:               #     are N <names>. For the fragment "N f; L i fi; L l fl",
    148:               #     two ligatures are defined: fi and fl. Optional,
    149:               #     multiples permitted.
    150:               #
    151:               # C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
    152:             bits = line.chomp.strip.split(/;/).collect { |r| r.strip }
    153:             dtmp = {}
    154:
    155:             bits.each do |bit|
    156:               b = bit.split
    157:               if b.size > 2
    158:                 dtmp[b[0]] = []
    159:                 b[1..-1].each do |z|
    160:                   if z =~ NUMBER
    161:                     dtmp[b[0]] << z.to_f
    162:                   else
    163:                     dtmp[b[0]] << z
    164:                   end
    165:                 end
    166:               elsif b.size == 2
    167:                 if b[0] == 'C' and b[1] =~ NUMBER
    168:                   dtmp[b[0]] = b[1].to_i
    169:                 elsif b[0] == 'CH'
    170:                   dtmp['C'] = b[1].to_i(16)
    171:                 elsif b[1] =~ NUMBER
    172:                   dtmp[b[0]] = b[1].to_f
    173:                 else
    174:                   dtmp[b[0]] = b[1]
    175:                 end
    176:               end
    177:             end
    178:
    179:             font.c[dtmp['N']] = dtmp
    180:             font.c[dtmp['C']] = dtmp unless dtmp['C'].nil?
    181:           when 'KPX' # KPX Adieresis yacute -40
    182:             # KPX: Kerning Pair
    183:             font.kpx[values[0]] = { }
    184:             font.kpx[values[0]][values[1]] = values[2].to_f
    185:           end
    186:         end
    187:         font.path = afm_file
    188:       end rescue nil # Ignore file errors
    189:       break unless font.nil?
    190:     end
    191:
    192:     raise ArgumentError, "Font #{font_name} not found." if font.nil?
    193:     font
    194:   end