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:           next if key.nil?
     86:           op = "#{key.downcase}=".to_sym
     87:
     88:             # I probably need to deal with MetricsSet. The default value is
     89:             # 0, which is writing direction 0 (W0X). If MetricsSet 1 is
     90:             # specified, then only writing direction 1 is present (W1X). If
     91:             # MetricsSet 2 is specified, then both W0X and W1X are present.
     92:
     93:             # Measurements are always 1/1000th of a scale factor (point
     94:             # size). So a 12pt character with a width of 222 is going to be
     95:             # 222 * 12 / 1000 or 2.664 points wide.
     96:           case key
     97:           when 'FontName', 'FullName', 'FamilyName', 'Weight',
     98:             'IsFixedPitch', 'CharacterSet', 'Version', 'EncodingScheme'
     99:               # These values are string values.
    100:             font.__send__(op, values.join(" "))
    101:           when 'ItalicAngle', 'UnderlinePosition', 'UnderlineThickness',
    102:             'CapHeight', 'XHeight', 'Ascender', 'Descender', 'StdHW',
    103:             'StdVW', 'StartCharMetrics'
    104:               # These values are floating point values.
    105:             font.__send__(op, values.join(" ").to_f)
    106:           when 'FontBBox'
    107:               # These values are an array of floating point values
    108:             font.fontbbox = values.map { |el| el.to_f }
    109:           when 'C', 'CH'
    110:               # Individual Character Metrics Values:
    111:               #   C  <character number>
    112:               #   CH <hex character number>
    113:               #     One of C or CH must be provided. Specifies the encoding
    114:               #     number for the character. -1 if the character is not
    115:               #     encoded in the font.
    116:               #
    117:               #   WX  <x width number>
    118:               #   W0X <x0 width number>
    119:               #   W1X <x1 width number>
    120:               #     Character width in x for writing direction 0 (WX, W0X)
    121:               #     or 1 (W1X) where y is 0. Optional.
    122:               #
    123:               #   WY  <y width number>
    124:               #   W0Y <y0 width number>
    125:               #   W1Y <y1 width number>
    126:               #     Character width in y for writing direction 0 (WY, W0Y)
    127:               #     or 1 (W1Y) where x is 0. Optional.
    128:               #
    129:               #   W  <x width> <y width>
    130:               #   W0 <x0 width> <y0 width>
    131:               #   W1 <x1 width> <y1 width>
    132:               #     Character width in x, y for writing direction 0 (W, W0)
    133:               #     or 1 (W1). Optional.
    134:               #
    135:               #   VV <x number> <y number>
    136:               #     Same as VVector in the global font definition, but for
    137:               #     this single character. Optional.
    138:               #
    139:               #   N <name>
    140:               #     The PostScript name of the font. Optional.
    141:               #
    142:               #   B <llx> <lly> <urx> <ury>
    143:               #     Character bounding box for the lower left corner and the
    144:               #     upper right corner. Optional.
    145:               #
    146:               #   L <sucessor> <ligature>
    147:               #     Ligature sequence where both <successor> and <ligature>
    148:               #     are N <names>. For the fragment "N f; L i fi; L l fl",
    149:               #     two ligatures are defined: fi and fl. Optional,
    150:               #     multiples permitted.
    151:               #
    152:               # C 39 ; WX 222 ; N quoteright ; B 53 463 157 718 ;
    153:             bits = line.chomp.strip.split(/;/).collect { |r| r.strip }
    154:             dtmp = {}
    155:
    156:             bits.each do |bit|
    157:               b = bit.split
    158:               if b.size > 2
    159:                 dtmp[b[0]] = []
    160:                 b[1..-1].each do |z|
    161:                   if z =~ NUMBER
    162:                     dtmp[b[0]] << z.to_f
    163:                   else
    164:                     dtmp[b[0]] << z
    165:                   end
    166:                 end
    167:               elsif b.size == 2
    168:                 if b[0] == 'C' and b[1] =~ NUMBER
    169:                   dtmp[b[0]] = b[1].to_i
    170:                 elsif b[0] == 'CH'
    171:                   dtmp['C'] = b[1].to_i(16)
    172:                 elsif b[1] =~ NUMBER
    173:                   dtmp[b[0]] = b[1].to_f
    174:                 else
    175:                   dtmp[b[0]] = b[1]
    176:                 end
    177:               end
    178:             end
    179:
    180:             font.c[dtmp['N']] = dtmp
    181:             font.c[dtmp['C']] = dtmp unless dtmp['C'].nil?
    182:           when 'KPX' # KPX Adieresis yacute -40
    183:             # KPX: Kerning Pair
    184:             font.kpx[values[0]] = { }
    185:             font.kpx[values[0]][values[1]] = values[2].to_f
    186:           end
    187:         end
    188:         font.path = afm_file
    189:       end rescue nil # Ignore file errors
    190:       break unless font.nil?
    191:     end
    192:
    193:     raise ArgumentError, "Font #{font_name} not found." if font.nil?
    194:     font
    195:   end