rfil 0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,414 @@
1
+ # Last Change: Mi 24 Mai 2006 16:42:25 CEST
2
+
3
+ # require 'rfi'
4
+
5
+ require 'strscan'
6
+ require 'pathname'
7
+
8
+ require 'rfil/font/metric'
9
+
10
+ module RFIL # :nodoc:
11
+ module Font # :nodoc:
12
+ # = AFM -- Access type1 font metric files
13
+ #
14
+ # == General information
15
+ #
16
+ # Read and parse a (type1) afm file. The afm file must be compliant to
17
+ # the afm specification as described in 'Adobe Font Metrics File
18
+ # Format Specification' Version 4.1, dated October 7 1998.
19
+ #
20
+ # == Example usage
21
+ #
22
+ # === Read an afm file
23
+ # filename = "/opt/tetex/3.0/texmf/fonts/afm/urw/palatino/uplb8a.afm"
24
+ # afm=AFM.new
25
+ # afm.read(filename)
26
+ # afm.filename # => "/opt/..../uplb8a.afm"
27
+ # afm.count_charmetrics # => 316
28
+ # afm.encodingscheme # => "AdobeStandardEncoding"
29
+ # # ....
30
+ #
31
+ class AFM < Metric
32
+
33
+ # This is set to true if there is something wrong in the afm file.
34
+ # Diagnostics can be turned on with <tt>:verbose</tt> set to true
35
+ # when creating the object.
36
+ attr_reader :something_strange
37
+
38
+ # Number of characters found in the afm file.
39
+ attr_accessor :count_charmetrics
40
+
41
+ # Number of encoded character found in the afm file.
42
+ attr_accessor :count_charmetrics_encoded
43
+
44
+ # Number of unencoded character found in the afm file.
45
+ attr_accessor :count_charmetrics_unencoded
46
+
47
+ # The default encoding of the font.
48
+ attr_accessor :encodingscheme
49
+
50
+ # Boundingbox of the font. Array of for elements.
51
+ attr_accessor :fontbbox
52
+
53
+ # Underline position of the font.
54
+ attr_accessor :underlineposition
55
+
56
+ # Underline thickness.
57
+ attr_accessor :underlinethickness
58
+
59
+ # Height of caps.
60
+ attr_accessor :capheight
61
+
62
+ # Height of ascender.
63
+ attr_accessor :ascender
64
+
65
+ # Height of descender.
66
+ attr_accessor :descender
67
+
68
+
69
+ # Create an empty afm file. If _afm_ is set, use this to initialize
70
+ # the object. _afm_ is either a string with the contents of an afm
71
+ # file or a File object that points to the afm file. _options_
72
+ # currently only accepts <tt>:verbose</tt> (true/false), that prints
73
+ # out some diagnostic information on STDERR.
74
+ def initialize(options={})
75
+ @something_strange = false
76
+ super()
77
+ @outlinetype=:type1
78
+ @comment = ""
79
+ @verbose=options[:verbose]==true
80
+ end
81
+
82
+ # return a Hash of all relevant filenames. Keys are outlinetypes,
83
+ def fontfilenames
84
+ f=File.basename(@fontfilename || filename)
85
+ sans_ext=case f
86
+ when /\.afm$/
87
+ f.chomp(".afm")
88
+ when /\.pfb$/
89
+ f.chomp("pfb")
90
+ end
91
+ return {:afm => sans_ext + ".afm", :type1 => sans_ext + ".pfb"}
92
+ end
93
+
94
+ # Read the afm file given with _filename_. _filename_ must be full
95
+ # path to the afm file, it does not perform any lookups. Returns self.
96
+ def read (filename)
97
+ @filename=File.basename(filename)
98
+ @name=@filename.chomp(".afm")
99
+ self.pathname=Pathname.new(filename).realpath.to_s
100
+ parse(File.read(filename))
101
+ end
102
+
103
+ # Return a string representation of the afm file that is compliant
104
+ # with the afm spec.
105
+ def to_s
106
+ s ="StartFontMetrics 2.0\n"
107
+ s << "Comment Generated using the RFI Library\n"
108
+ %w( FontName FullName FamilyName Weight Notice ItalicAngle
109
+ IsFixedPitch UnderlinePosition UnderlineTickness Version
110
+ EncodingScheme CapHeight XHeight Descender Ascender ).each {|kw|
111
+
112
+ meth=kw.downcase.to_sym
113
+ value=self.send(meth) if self.respond_to?(meth)
114
+ if value
115
+ s << kw << " " << value.to_s << "\n"
116
+ end
117
+ }
118
+ s << "FontBBox " << @fontbbox.join(" ") << "\n"
119
+ s << "StartCharMetrics #@count_charmetrics\n"
120
+ @chars.sort{ |a,b|
121
+ # puts "a=#{a[1].c}, b=#{b[1].c}"
122
+ if a[1].c == -1
123
+ b[1].c == -1 ? 0 : 1
124
+ else
125
+ b[1].c == -1 ? -1 : a[1].c <=> b[1].c
126
+ end
127
+ }.each { |a,b|
128
+ s << "C #{b.c} ; WX #{b.wx} ; N #{a} ; B #{b.b.join(" ")}\n"
129
+ }
130
+ s << "EndCharMetrics\nStartKernData\nStartKernPairs"
131
+ count=0
132
+ @chars.each_value { |c|
133
+ count += c.kern_data.size
134
+ }
135
+ s << " #{count}\n"
136
+ @chars.sort{ |a,b| a[0] <=> b[0] }.each { |name,char|
137
+ char.kern_data.each { |destname, value|
138
+ s << "KPX #{name} #{destname} #{value[0]}\n"
139
+ }
140
+ }
141
+ s << "EndKernPairs\nEndKernData\nEndFontMetrics\n"
142
+ s
143
+ end
144
+
145
+ # Parse the contents of the String _txt_. Returns self.
146
+ def parse(txt)
147
+ @chars ||= Hash.new
148
+ @s=StringScanner.new(txt.gsub(/\r\n/,"\n"))
149
+ @s.scan(/StartFontMetrics/)
150
+ get_fontmetrics
151
+ self
152
+ end
153
+
154
+ #######
155
+ private
156
+ #######
157
+
158
+ def get_keyword
159
+ @s.skip_until(/\s+/)
160
+ @s.scan(/[A-Z][A-Za-z0-9]+/)
161
+ end
162
+
163
+ def get_integer
164
+ @s.skip(/\s+/)
165
+ @s.scan(/-?\d+/).to_i
166
+ end
167
+
168
+ def get_number
169
+ @s.skip(/\s+/)
170
+ @s.scan(/-?\d+(?:\.\d+)?/).to_f
171
+ end
172
+
173
+ def get_boolean
174
+ @s.skip(/\s+/)
175
+ @s.scan(/(true|false)/) == 'true'
176
+ end
177
+
178
+ def get_name
179
+ @s.skip(/\s+/)
180
+ @s.scan(/[^\s]+/)
181
+ end
182
+
183
+ def get_string
184
+ @s.skip(/\s+/)
185
+ @s.scan(/.*/)
186
+ end
187
+
188
+ def get_fontmetrics
189
+ @version = get_number
190
+ loop do
191
+ kw=get_keyword
192
+ STDERR.puts "KW: " + kw if @verbose
193
+ case kw
194
+ when "FontName"
195
+ @fontname=get_string
196
+ when "FamilyName"
197
+ @familyname = get_string
198
+ when "FullName"
199
+ @fullname = get_string
200
+ when "EncodingScheme"
201
+ @encodingscheme = get_string
202
+ when "ItalicAngle"
203
+ @italicangle = get_number
204
+ when "IsFixedPitch"
205
+ @isfixedpitch = get_boolean
206
+ when "Weight"
207
+ @weight = get_string
208
+ when "XHeight"
209
+ @xheight= get_number
210
+ when "Comment"
211
+ @comment << get_string << "\n"
212
+ when "FontBBox"
213
+ @fontbbox = [get_number,get_number, get_number, get_number]
214
+ when "Version"
215
+ @version = get_string
216
+ when "Notice"
217
+ @notice = get_string
218
+ when "MappingScheme"
219
+ @mappingscheme = get_integer
220
+ when "EscChar"
221
+ @escchar = get_integer
222
+ when "CharacterSet"
223
+ @characterset = get_string
224
+ when "Characters"
225
+ @characters = get_integer
226
+ when "IsBaseFont"
227
+ @isbasefont = get_boolean
228
+ when "VVector"
229
+ @vvector = [get_number,get_number]
230
+ when "IsFixedV"
231
+ @isfixedv = get_boolean
232
+ when "CapHeight"
233
+ @capheight = get_number
234
+ when "Ascender"
235
+ @ascender = get_number
236
+ when "Descender"
237
+ @descender = get_number
238
+ when "UnderlinePosition"
239
+ @underlineposition = get_number
240
+ when "UnderlineThickness"
241
+ @underlinethickness = get_number
242
+ when "StartDirection"
243
+ get_direction
244
+ when "StartCharMetrics"
245
+ get_charmetrics
246
+ when "StartKernData"
247
+ get_kerndata
248
+ when "StartComposites"
249
+ get_composites
250
+ when "EndFontMetrics"
251
+ break
252
+ end
253
+ end
254
+ end
255
+ def get_direction
256
+ # ignored
257
+ end
258
+ def get_charmetrics
259
+ @count_charmetrics = get_integer
260
+ @count_charmetrics_encoded = 0
261
+ @count_charmetrics_unencoded = 0
262
+ loop do
263
+ @s.skip_until(/\n/)
264
+ nextstring = @s.scan_until(/(?:StopCharMetrics|.*)/)
265
+ return if nextstring=="EndCharMetrics"
266
+ a=nextstring.split(';')
267
+ # ["C 32 ", " WX 250 ", " N space ", " B 125 0 125 0 "]
268
+ a.collect! { |elt|
269
+ elt.strip.split(/ /,2)
270
+ }
271
+ # [["C", "32"], ["WX", "250"], ["N", "space"], ["B", "125 0 125 0"]]
272
+ char=new_glyph
273
+ a.each { |elt|
274
+ key,value = elt
275
+ case key
276
+ when "N"
277
+ char.name=value
278
+ when "B"
279
+ #special treatment for bounding box
280
+ char.b = value.split.collect { |e| e.to_i }
281
+ char.llx = char.llx
282
+ char.urx = char.urx
283
+ # We need to avoid negative heights or depths. They break
284
+ # accents in math mode, among other things.
285
+ char.lly = 0 if char.lly > 0
286
+ char.ury = 0 if char.ury < 0
287
+ when "C"
288
+ char.c = value.to_i
289
+ when "CH"
290
+ # hex: '<20>' -> '0x20' -> .to_i -> 32
291
+ char.c = value.sub(/</,'0x').sub(/>/,'').to_i(16)
292
+ when "WX"
293
+ char.wx = value.to_i
294
+ # for "L", check /var/www/mirror/system/tex/texmf-local/fonts/afm/jmn/hans/hans.afm
295
+ when "L", nil
296
+ #ignore
297
+ else
298
+ char.send((key.downcase + "=").to_sym,value.to_i)
299
+ end
300
+ }
301
+
302
+ @chars[char.name]=char
303
+ # update information about encoded/unencoded
304
+ if char.c > -1
305
+ @count_charmetrics_encoded += 1
306
+ else
307
+ @count_charmetrics_unencoded += 1
308
+ end
309
+ end
310
+ raise "never reached"
311
+ end
312
+ def get_kerndata
313
+ loop do
314
+ kw = get_keyword
315
+ STDERR.puts "kw=" + kw if @verbose
316
+ case kw
317
+ when "EndKernData"
318
+ return
319
+ when "StartKernPairs"
320
+ get_kernpairs
321
+ when "StartTrackKern"
322
+ # TrackKern
323
+ get_trackkern
324
+ else
325
+ # KernPairs0
326
+ # KernPairs1
327
+ raise "not implemented"
328
+ end
329
+ end
330
+ raise "never reached"
331
+ end
332
+ def get_composites
333
+ count = get_integer
334
+ loop do
335
+ kw = get_keyword
336
+ STDERR.puts "get_composites keyword = '" + kw + "'" if @verbose
337
+ case kw
338
+ when "CC"
339
+ get_composite
340
+ when "EndComposites"
341
+ return
342
+ else
343
+ STDERR.puts "next to read = " + @s.string[@s.pos,40]
344
+ raise "AFM error"
345
+ end
346
+ end
347
+ raise "never reached"
348
+ end
349
+ def get_composite
350
+ glyphname = get_name
351
+ count = get_integer
352
+ @s.skip_until(/;\s+/)
353
+ count.times do
354
+ nextstring = get_name
355
+ raise "AFM Error" unless nextstring == "PCC"
356
+ [get_number,get_number]
357
+ @s.skip_until(/;/)
358
+ end
359
+ end
360
+
361
+ def get_trackkern
362
+ count = get_integer
363
+ loop do
364
+ case get_keyword
365
+ when "EndTrackKern"
366
+ return
367
+ when "TrackKern"
368
+ # TrackKern degree min-ptsize min-kern max-ptsize max-kern
369
+ [get_integer,get_number,get_number,get_number,get_number]
370
+ else
371
+ raise "afm error"
372
+ end
373
+ end
374
+ raise "never reached"
375
+ end
376
+
377
+ def get_kernpairs
378
+ count = get_integer
379
+ loop do
380
+ case get_keyword
381
+ when "KPX" # y is 0
382
+ name=get_name
383
+ # if @info['chars'][name]
384
+ if @chars[name]
385
+ # array is [x,y] kerning
386
+ destname,num=get_name,get_number
387
+ # somethimes something stupid like
388
+ # KPX .notdef y -26
389
+ # KPX A .notdef -43
390
+ # is in the afm data... :-( -> reject those entries
391
+ # if @info['chars'][destname]
392
+ if @chars[destname]
393
+ @chars[name].kern_data[destname] = [num,0]
394
+ else
395
+ STDERR.puts "info: unused kern data for " + name if @verbose
396
+ end
397
+ else
398
+ # ignore this entry, print a message
399
+ STDERR.puts "info: unused kern data for " + name if @verbose
400
+ @something_strange=true
401
+ [get_name,get_number] # ignored
402
+ end
403
+ when "EndKernPairs"
404
+ return
405
+ else
406
+ STDERR.puts @s.pos
407
+ raise "not implmented"
408
+ end
409
+ end
410
+ raise "never reached"
411
+ end
412
+ end
413
+ end
414
+ end
@@ -0,0 +1,198 @@
1
+
2
+ module RFIL
3
+ module Font
4
+ class Glyph
5
+
6
+ # to make Rdoc and Ruby happy: [ruby-talk:147778]
7
+ def self.documented_as_accessor(*args) #:nodoc:
8
+ end
9
+
10
+ # Glyphname
11
+ attr_accessor :name
12
+
13
+ # Advance width
14
+ attr_accessor :wx
15
+
16
+ # Standard code slot (0-255 or -1 for unencoded)
17
+ attr_accessor :c
18
+
19
+ # bounding box (llx, lly, urx, ury). Array of size 4.
20
+ # You should use the methods llx, lly, urx, ury to access the
21
+ # bounding box.
22
+ attr_accessor :b
23
+
24
+ # Kern_data (Hash). The key is the glyph name, the entries are
25
+ # _[x,y]_ arrays. For ltr and rtl typesetting only the x entry
26
+ # should be interesting. This is raw information from the font
27
+ # metric file. Does not change when efactor et al. are set in any
28
+ # way.
29
+ attr_accessor :kern_data
30
+
31
+ # Information about ligatures - unknown datatype yet
32
+ attr_accessor :lig_data
33
+
34
+ # Composite characters. Array [['glyph1',xshift,yshift],...]
35
+ attr_accessor :pcc_data
36
+
37
+ # Upper right x value of glyph.
38
+ documented_as_accessor :urx
39
+
40
+ # Upper right y value of glyph.
41
+ documented_as_accessor :ury
42
+
43
+ # Lower left x value of glyph.
44
+ documented_as_accessor :llx
45
+
46
+ # Lower left y value of glyph.
47
+ documented_as_accessor :lly
48
+
49
+ # the name of the uppercase glyph (nil if there is no uppercase glyph)
50
+ attr_accessor :uc
51
+
52
+ # the name of the lowercase glyph (nil if there is no lowercase glyph)
53
+ attr_accessor :lc
54
+
55
+ # Optional argument sets the name of the glyph.
56
+ def initialize (glyphname=nil)
57
+ @name=glyphname
58
+ @lig_data={}
59
+ @kern_data={}
60
+ @wx=0
61
+ @b=[0,0,0,0]
62
+ @efactor=1.0
63
+ @slant=0.0
64
+ end
65
+
66
+ # Lower left x position of glyph.
67
+ def llx # :nodoc:
68
+ @b[0]
69
+ end
70
+ def llx=(value) # :nodoc:
71
+ @b[0]=value
72
+ end
73
+
74
+ # Lower left y position of glyph.
75
+ def lly # :nodoc:
76
+ @b[1]
77
+ end
78
+ def lly=(value) # :nodoc:
79
+ @b[1]=value
80
+ end
81
+ # Upper right x position of glyph.
82
+ def urx # :nodoc:
83
+ @b[2]
84
+ end
85
+ def urx=(value) # :nodoc:
86
+ @b[2]=value
87
+ end
88
+
89
+ # Upper right y position of glyph.
90
+ def ury # :nodoc:
91
+ @b[3]
92
+ end
93
+ def ury=(value) # :nodoc:
94
+ @b[3]=value
95
+ end
96
+
97
+ # Return height of the char used for tfm file.
98
+ def charht
99
+ ury
100
+ end
101
+
102
+ # Return width of the char used for tfm file.
103
+ def charwd
104
+ wx
105
+ end
106
+
107
+ # Return depth of the char.
108
+ def chardp
109
+ lly >= 0 ? 0 : -lly
110
+ end
111
+
112
+ # Return italic correction of the char.
113
+ def charic
114
+ (urx - wx) > 0 ? (urx - wx) : 0
115
+ end
116
+ # Return an array with all kerning information (x-direction only)
117
+ # of this glyph. Kerning information is an Array where first
118
+ # element is the destchar, the second element is the kerning amount.
119
+ def kerns_x
120
+ ret=[]
121
+ @kern_data.each { |destchar,kern|
122
+ ret.push([destchar,kern[0]])
123
+ }
124
+ ret
125
+ end
126
+
127
+ # Return an array with all ligature information (LIG objects) of
128
+ # this glyph.
129
+ def ligs
130
+ ret=[]
131
+ @lig_data.each { |destchar,lig|
132
+ ret.push(lig)
133
+ }
134
+ ret
135
+ end
136
+
137
+ # Return true if this char has ligature or kerning information. If
138
+ # glyphindex is supplied, only return true if relevant. This means
139
+ # that the second parameter of a kerning information or the second
140
+ # parameter and the result of a ligature information must be in
141
+ # the glyphindex. glyphindex must respond to <em>include?</em>.
142
+ def has_ligkern?(glyphindex=nil)
143
+ if glyphindex and not glyphindex.respond_to? :include?
144
+ raise ArgumentError, "glyphindex does not respod to include?"
145
+ end
146
+ return false if (lig_data == {} and kern_data=={})
147
+ # this one is easy, just look at lig_data and kern_data
148
+ # more complicated, we have to take glyphindex into account
149
+ if glyphindex
150
+ return false unless glyphindex.include? self.name
151
+ # right kerningpair not in glyphindex? -> false
152
+ # right lig not in glyphindex? -> false
153
+ # result lig not in glyphindex? -> false
154
+ if lig_data
155
+ lig_data.each { |otherchar,lig|
156
+ if (glyphindex.include?(lig.right) and glyphindex.include?(lig.result))
157
+ return true
158
+ end
159
+ }
160
+ end
161
+ if kern_data
162
+ kern_data.each { |otherchar,krn|
163
+ return true if glyphindex.include?(otherchar)
164
+ }
165
+ end
166
+ return false
167
+ else
168
+ # no glyphindex
169
+ return true
170
+ end
171
+ raise "never reached"
172
+ end # has_ligkern?
173
+
174
+ # Return true if glyph is an uppercase char, such as AE.
175
+ def is_uppercase?
176
+ return @lc != nil
177
+ end
178
+
179
+ # Return true if glyph is a lowercase char, such as germandbls,
180
+ # but not hyphen.
181
+ def is_lowercase?
182
+ return @uc != nil
183
+ end
184
+
185
+ # Return the uppercase variant of the glyph. Undefined behaviour if
186
+ # glyph cannot be uppercased.
187
+ def capitalize
188
+ @uc
189
+ end
190
+
191
+ # Return the lowercase variant of the glyph. Undefined behaviour if
192
+ # glyph cannot be lowercased.
193
+ def downcase
194
+ @lc
195
+ end
196
+ end
197
+ end
198
+ end