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.
data/lib/rfil/rfi.rb ADDED
@@ -0,0 +1,472 @@
1
+ # rfi.rb -- general use classes
2
+ #--
3
+ # Last Change: Tue May 16 19:21:51 2006
4
+ #++
5
+
6
+ require 'rfil/font/glyph'
7
+
8
+ module RFIL # :nodoc:
9
+
10
+ # = RFI
11
+ # Everything that does not fit somewhere else gets included in the
12
+ # wrapper class RFI.
13
+
14
+ # This class contains methods and other classes that are pretty much
15
+ # useless of their own or are accessed in different classes.
16
+
17
+ class RFI # :nodoc:
18
+
19
+ # Super class for plugins. Just subclass this Plugin, set the name
20
+ # when calling Plugin#new and implement run_plugin.
21
+ class Plugin
22
+ # Name of the plugin. A Symbol or a String.
23
+ attr_reader :name
24
+
25
+ attr_reader :filetypes
26
+
27
+ # Create a new plugin. _name_ is the name of the plugin (it must
28
+ # be a Symbol or a String). _filetypes_ is a list of symbols, of
29
+ # what files the plugin is capable of writing.
30
+ def initialize(name,*filetypes)
31
+ @name=name
32
+ @filetypes=filetypes
33
+ end
34
+
35
+ # Return an Array of files that should be written on the user's
36
+ # harddrive. The Hash entries are
37
+ # [<tt>:type</tt>] Type of the file (<tt>:fd</tt>, <tt>:typescript</tt> etc.)
38
+ # [<tt>:filename</tt>] The filename (without a path) of the file.
39
+ # [<tt>:contents</tt>] The contents of the file.
40
+ def run_plugin
41
+ #dummy
42
+ end
43
+ end
44
+
45
+ # Some instructions to remove kerning information from digits and
46
+ # other things. -> sort this out
47
+ STDLIGKERN = ["space l =: lslash",
48
+ "space L =: Lslash", "question quoteleft =: questiondown",
49
+ "exclam quoteleft =: exclamdown", "hyphen hyphen =: endash",
50
+ "endash hyphen =: emdash", "quoteleft quoteleft =: quotedblleft",
51
+ "quoteright quoteright =: quotedblright", "space {} *", "* {} space",
52
+ "zero {} *", "* {} zero",
53
+ "one {} *", "* {} one",
54
+ "two {} *", "* {} two",
55
+ "three {} *","* {} three",
56
+ "four {} *", "* {} four",
57
+ "five {} *", "* {} five",
58
+ "six {} *", "* {} six",
59
+ "seven {} *", "* {} seven",
60
+ "eight {} *", "* {} eight",
61
+ "nine {} *", "* {} nine",
62
+ "comma comma =: quotedblbase",
63
+ "less less =: guillemotleft",
64
+ "greater greater =: guillemotright"]
65
+
66
+ # Metric information about a glyph. Does not contain the glyph
67
+ # (outlines) itself.
68
+ class Char < Font::Glyph
69
+
70
+ # fontnumber is used in Font class
71
+ attr_accessor :fontnumber
72
+
73
+ # If not nil, _mapto_ is the glyphname that should be used instead
74
+ # of the current one.
75
+ attr_accessor :mapto
76
+
77
+ # Sets the extension factor. This is used by calculations of _wx_,
78
+ # _llx_ and _urx_.
79
+ attr_accessor :efactor
80
+
81
+ # Sets the slant factor. This is used by calculations of _wx_,
82
+ # _llx_ and _urx_.
83
+ attr_accessor :slant
84
+
85
+ def wx # :nodoc:
86
+ transform(@wx,0)
87
+ end
88
+ def wx=(obj) # :nodoc:
89
+ @wx=obj
90
+ end
91
+ # Lower left x position of glyph.
92
+ def llx # :nodoc:
93
+ transform(@b[0],b[1])
94
+ end
95
+
96
+ # Upper right x position of glyph.
97
+ def urx # :nodoc:
98
+ transform(@b[2],ury)
99
+ end
100
+
101
+ private
102
+
103
+ def transform (x,y)
104
+ (@efactor * x + @slant * y)
105
+ end
106
+
107
+ end # class Char
108
+
109
+
110
+ # Represent the different ligatures possible in tfm.
111
+ class LIG
112
+
113
+ @@encligops = ["=:", "|=:", "|=:>", "=:|", "=:|>", "|=:|", "|=:|>", "|=:|>>"]
114
+ @@vpligops = ["LIG", "/LIG", "/LIG>", "LIG/", "LIG/>", "/LIG/",
115
+ "/LIG/>", "/LIG/>>"]
116
+ @@symligops = [:lig, :"lig/", :"/lig", :"/lig/", :"lig/>", :"/lig>", :"/lig/>", :"/lig/>>"]
117
+
118
+ # First glyph of a two glyph sequence before it is turned into a
119
+ # ligature.
120
+ attr_accessor :left
121
+
122
+ # Second glyph of a two glyph sequence before it is turned into a
123
+ # ligature.
124
+ attr_accessor :right
125
+
126
+ # The ligature that gets inserterd instead of the left and right glyph.
127
+ attr_accessor :result
128
+
129
+ # <tt>[0, 1, 2, 3, 4, 5, 6, 7 ]</tt>
130
+ #
131
+ # <tt>[=: , |=: , |=:> , =:| , =:|>, |=:|, |=:|>, |=:|>> ]</tt>
132
+ #
133
+ # <tt>[LIG, /LIG, /LIG>, LIG/, LIG/>, /LIG/, /LIG/>, /LIG/>>]</tt>
134
+ attr_accessor :type
135
+
136
+
137
+
138
+ # call-seq:
139
+ # new
140
+ # new(left,[right,[result,[type]]])
141
+ # new(hash)
142
+ # new(otherlig)
143
+ #
144
+ # When called with left, right, result or type parameters, take
145
+ # these settings for the LIG object. When called with a hash as an
146
+ # argument, the keys should look like: :left,:right,:result,:type.
147
+ # When called with an existing LIG object, the values are taken
148
+ # from the old object.
149
+ def initialize(left=nil,right=nil,result=nil,type=nil)
150
+ case left
151
+ when Hash
152
+ [:left,:right,:result,:type].each { |sym|
153
+ if left.has_key?(sym)
154
+ self.send((sym.to_s+"=").to_sym,left[sym])
155
+ end
156
+ }
157
+ when LIG
158
+ [:left,:right,:result,:type].each { |sym|
159
+ self.send((sym.to_s+"=").to_sym,left.send(sym))
160
+ }
161
+ # warning!!!!! LIG accepts a String as well as Fixnum as
162
+ # parameters, this might have side effects!?
163
+ when Fixnum,nil,String
164
+ @left=left
165
+ @right=right
166
+ @result=result
167
+ @type=type
168
+ else
169
+ raise "unknown argument for new() in LIG: #{left}"
170
+ end
171
+ # test!
172
+ #unless @type.instance_of?(Fixnum)
173
+ # raise "type must be a fixnum"
174
+ #end
175
+ end
176
+ def ==(lig)
177
+ @left=lig.left and
178
+ @right=lig.right and
179
+ @result=lig.result and
180
+ @type=lig.type
181
+ end
182
+
183
+ def to_pl(encoding)
184
+ encoding.glyph_index[@right].sort.collect { |rightslot|
185
+ left=encoding.glyph_index[@left].min
186
+ # right=encoding.glyph_index[@right].min
187
+ result=encoding.glyph_index[@result].min
188
+ type=@@vpligops[@type]
189
+ LIG.new(:left=>left, :right=>rightslot, :result=>result, :type=>type)
190
+ }
191
+ end
192
+ # Return an array that is suitable for tfm
193
+ def to_tfminstr(encoding)
194
+ encoding.glyph_index[@right].sort.collect { |rightslot|
195
+ left=encoding.glyph_index[@left].min
196
+ # right=encoding.glyph_index[@right].min
197
+ result=encoding.glyph_index[@result].min
198
+ type=@@symligops[@type]
199
+ [type,rightslot,result]
200
+ }
201
+ end
202
+ def inspect
203
+ "[#{@type.to_s.upcase} #@left + #@right => #@result]"
204
+ end
205
+ end
206
+
207
+ require 'forwardable'
208
+
209
+ # Stores information about kerning and ligature information. Allows
210
+ # deep copy of ligature and kerning information. Obsolete. Don't use.
211
+ class LigKern
212
+ extend Forwardable
213
+ # Optional parameter initializes the new LigKern object.
214
+ def initialize(h={})
215
+ @h=h
216
+ end
217
+
218
+ def_delegators(:@h, :each, :[], :[]=,:each_key,:has_key?)
219
+
220
+ def initialize_copy(obj) # :nodoc:
221
+ tmp={}
222
+ if obj[:lig]
223
+ tmp[:lig]=Array.new
224
+ obj[:lig].each { |elt|
225
+ tmp[:lig].push(elt.dup)
226
+ }
227
+ end
228
+ if obj[:krn]
229
+ tmp[:krn]=Array.new
230
+ obj[:krn].each { |elt|
231
+ tmp[:krn].push(elt.dup)
232
+ }
233
+ end
234
+ if obj[:alias]
235
+ tmp[:alias]=obj[:alias].dup
236
+ end
237
+ @h=tmp
238
+ end
239
+ # Compare this object to another object of the same class.
240
+ def ==(obj)
241
+ return false unless obj.respond_to?(:each)
242
+ # the krn needs to be compared one by one, because they are floats
243
+ if obj.has_key?(:krn)
244
+ obj[:krn].each { |destchar,value|
245
+ return false unless @h[:krn].assoc(destchar)
246
+ return false if (value - @h[:krn].assoc(destchar)[1]).abs > 0.01
247
+ }
248
+ end
249
+ obj.each { |key,value|
250
+ next if key==:krn
251
+ return false unless @h[key]==value
252
+ }
253
+ true
254
+ end
255
+ end
256
+
257
+
258
+ # The Glyphlist is a actually a Hash with some special methods
259
+ # attached.
260
+ class Glyphlist < Hash
261
+ @@encligops = ["=:", "|=:", "|=:>", "=:|", "=:|>", "|=:|", "|=:|>", "|=:|>>"]
262
+ @@vpligops = ["LIG", "/LIG", "/LIG>", "LIG/", "LIG/>", "/LIG/",
263
+ "/LIG/>", "/LIG/>>"]
264
+
265
+ # Return an array with name of glyphs that are represented by the
266
+ # symbol _glyphlist_.
267
+ # These symbols are defined: :lowercase, :uppercase, :digits
268
+ def get_glyphlist(glyphlist)
269
+ ret=[]
270
+ unless glyphlist.instance_of? Symbol
271
+ raise ArgumentError, "glyphlist must be a symbol"
272
+ end
273
+ case glyphlist
274
+ when :lowercase
275
+ update_uc_lc_list
276
+
277
+ self.each { |glyphname,char|
278
+ if char.uc != nil
279
+ ret.push glyphname
280
+ end
281
+ }
282
+ when :uppercase
283
+ update_uc_lc_list
284
+
285
+ self.each { |glyphname,char|
286
+ if char.lc != nil
287
+ ret.push glyphname
288
+ end
289
+ }
290
+ when :digits
291
+ ret=%w(one two three four five six seven eight nine zero)
292
+ end
293
+ ret
294
+ end
295
+
296
+ # instructions.each must yield string objects (i.e. an array of
297
+ # strings, an IO object, a single string, ...). Instruction is like:
298
+ # "space l =: lslash" or "two {} *"
299
+ def apply_ligkern_instructions (instructions)
300
+ instructions.each { |instr|
301
+ s = instr.split(' ')
302
+
303
+ if @@encligops.member?(s[2]) # one of =:, |=: |=:> ...
304
+ if self[s[0]]
305
+ self[s[0]].lig_data[s[1]]=LIG.new(s[0],s[1],s[3],@@encligops.index(s[2]))
306
+ else
307
+ # puts "glyphlist#apply_ligkern_instructions: char not found: #{s[0]}"
308
+ end
309
+ elsif s[1] == "{}"
310
+ remove_kern(s[0],s[2])
311
+ end
312
+ }
313
+ end
314
+ # _left_ and _right_ must be either a glyphname or a '*'
315
+ # (asterisk) which acts like a wildcard. So ('one','*') would
316
+ # remove all kerns of glyph 'one' where 'one' is the left glyph in
317
+ # a kerning pair.
318
+ def remove_kern(left,right)
319
+ raise ArgumentError, "Only one operand may be '*'" if left=='*' and right=='*'
320
+ if right == "*"
321
+ self[left].kern_data={} if self[left]
322
+ elsif left == "*"
323
+ if self[right]
324
+ self.each { |name,chardata|
325
+ chardata.kern_data.delete(right)
326
+ }
327
+ end
328
+ else
329
+ if self[right] and self[left]
330
+ self[left].kern_data.delete(right)
331
+ end
332
+ end
333
+ end
334
+
335
+ # Update all glyph entries to see what the uppercase or the
336
+ # lowercase variants are. Warning!! Tcaron <-> tquoteright in
337
+ # non-unicode fonts.
338
+ def update_uc_lc_list
339
+ # we need this list only when faking small caps (which will, of
340
+ # course, never happen!)
341
+
342
+ # make a list of all uppercase and lowercase glyphs. Be aware of
343
+ # ae<->AE, oe<->OE, germandbls<-->SS, dotlessi->I, dotlessj->J
344
+ # do the
345
+ # @upper_lower={}
346
+ # @lower_upper={}
347
+ self.each_key {|glyphname|
348
+ thischar=self[glyphname]
349
+ if glyphname =~ /^[a-z]/
350
+ if glyphname =~ /^(a|o)e$/ and self[glyphname.upcase]
351
+ thischar.uc = glyphname.upcase
352
+ elsif glyphname =~ /^dotless(i|j)$/
353
+ thischar.uc = glyphname[-1].chr.upcase
354
+ elsif self[glyphname.capitalize]
355
+ thischar.uc = glyphname.capitalize
356
+ end
357
+ else
358
+ if glyphname =~ /^(A|O)e$/ and self[glyphname.dowcase]
359
+ thischar.lc = glyphname.downcase
360
+ elsif self[glyphname.downcase]
361
+ thischar.lc = glyphname.downcase
362
+ end
363
+ end
364
+ }
365
+ if self['germandbls']
366
+ self['germandbls'].uc='S'
367
+ end
368
+ end
369
+
370
+ # Modify the charmetrics and the kerning/ligatures so that the
371
+ # lowercase chars are made from scaling uppercase chars.
372
+ def fake_caps (factor)
373
+ update_uc_lc_list
374
+ # we need to do the following
375
+ # 1. adapt kerning pairs
376
+ # 2. change font metrics (wd)
377
+ # 3. remove ligatures from sc
378
+ @fake_caps=true
379
+ @capheight=factor
380
+ self.each { |glyphname,char|
381
+ if char.is_lowercase?
382
+ # remove ligatures from sc
383
+ char.lig_data={}
384
+ char.kern_data={}
385
+ char.mapto=char.capitalize
386
+ self[char.uc].kern_data.each { |destglyph,kerndata|
387
+ unless self[destglyph].is_lowercase?
388
+ char.kern_data[destglyph.downcase]=[kerndata[0] * factor,0]
389
+ end
390
+ }
391
+ char.b = self[char.capitalize].b.clone
392
+ char.wx = self[char.capitalize].wx * @capheight
393
+ char.lly *= @capheight
394
+ char.urx *= @capheight
395
+ char.ury *= @capheight
396
+ else # char is something like Aring, semicolon, ...
397
+ # if destchar is uppercase letter (A, Aring, ...)
398
+ # 1. delete all kerns to lowercase letters (not e.g. semicolon)
399
+ # 2. duplicate all uc kerns, multiply by factor and insert this
400
+ # as lc kern
401
+ char.kern_data.delete_if { |destglyph,kerndata|
402
+ self[destglyph].is_lowercase?
403
+ }
404
+
405
+ new_kern_data={}
406
+ char.kern_data.each { |destglyph,kerndata|
407
+ if self[destglyph].is_uppercase?
408
+ new_kern_data[self[destglyph].downcase]=[kerndata[0]*factor,kerndata[1]]
409
+ end
410
+ new_kern_data[destglyph]=kerndata
411
+ }
412
+ char.kern_data=new_kern_data
413
+ end
414
+ # 2.
415
+ }
416
+ if self['germandbls']
417
+ s=self['S']
418
+ d=self['germandbls']
419
+
420
+ d.b = s.b.dup
421
+ d.wx = s.wx * 2 * @capheight
422
+ d.urx += s.wx
423
+
424
+
425
+ d.kern_data={}
426
+ s.kern_data.each { |destglyph,kerndata|
427
+ unless self[destglyph].is_lowercase?
428
+ # we are looking at non-lowercase chars. These might be
429
+ # ones that are uppercase or are 'something else', e.g.
430
+ # hyphen...
431
+ # since we only replace the lc variants, keep the uc and
432
+ # others intact.
433
+ if self[destglyph].is_uppercase?
434
+ d.kern_data[self[destglyph].downcase]=[kerndata[0] * @capheight,0]
435
+
436
+ else
437
+ d.kern_data[destglyph]=[kerndata[0] * @capheight,0]
438
+
439
+ end
440
+ end
441
+ }
442
+
443
+ d.pcc_data=[['S',0,0],['S',s.wx,0]]
444
+ d.lly *= @capheight
445
+ d.urx *= @capheight
446
+
447
+ end
448
+ end # fake_caps
449
+
450
+ def fix_height(xheight)
451
+ # this is what afm2tfm does. I am not sure if it is clever.
452
+ self.each { |name,data|
453
+
454
+ # xheight <= 50 -> @chars[char].ury
455
+ # char.size > 1 -> @chars[char].ury
456
+ # char+accentname (ntilde, udieresis,...) exists?
457
+ # then calculate else @chars[char].ury
458
+ # calculate := ntilde.ury - tilde.ury + xheight
459
+ # same as texheight in afm2tfm source
460
+ unless name.size>1 or xheight < 50
461
+ %w(acute tilde caron dieresis).each {|accent|
462
+ naccent=name + accent
463
+ next unless self[naccent]
464
+ data.ury = self[naccent].ury - self[accent].ury + xheight
465
+ break
466
+ }
467
+ end
468
+ }
469
+ end # fix_height
470
+ end # class Glyphlist
471
+ end # class RFI
472
+ end