| Class | PDF::Writer::FontMetrics |
| In: |
lib/pdf/writer/fontmetrics.rb
|
| Parent: | Object |
| METRICS_PATH | = | [ File.join(File.dirname(File.expand_path(__FILE__)), 'fonts') ] |
| KEYS | = | %w{FontName FullName FamilyName Weight ItalicAngle IsFixedPitch CharacterSet UnderlinePosition UnderlineThickness Version EncodingScheme CapHeight XHeight Ascender Descender StdHW StdVW StartCharMetrics FontBBox C KPX} |
| NUMBER | = | /^[+\-0-9.]+$/o |
| differences | [RW] | |
| encoding | [RW] | |
| font_num | [RW] | |
| path | [RW] |
# File lib/pdf/writer/fontmetrics.rb, line 21
21: def initialize
22: @kpx = {}
23: @c = {}
24: @version = 1
25: @font_num = nil
26: end
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, 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