class PDF::Writer::FontMetrics

Constants

KEYS
METRICS_PATH
NUMBER

Attributes

differences[RW]
encoding[RW]
font_num[RW]
path[RW]

Public Class Methods

new() click to toggle source
   # File lib/pdf/writer/fontmetrics.rb
21 def initialize
22   @kpx      = {}
23   @c        = {}
24   @version  = 1
25   @font_num = nil
26 end
open(font_name) click to toggle source

Open the font file and return a PDF::Writer::FontMetrics object containing it. The font_name may specify just a font file or a full path. If a path is specified, that is the only place where the font file will be looked for.

    # File lib/pdf/writer/fontmetrics.rb
 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_line 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

Public Instance Methods

save_as_rfm() click to toggle source

Save the loaded font metrics file as a binary marshaled value.

    # File lib/pdf/writer/fontmetrics.rb
198 def save_as_rfm
199   rfm = File.basename(@path).gsub(/\.afm.*$/, '')
200   rfm << ".afm.rfm"
201   File.open(rfm, "wb") { |file| file.write Marshal.dump(self) }
202 end