# rfi.rb -- general use classes #-- # Last Change: Tue May 16 19:21:51 2006 #++ require 'rfil/font/glyph' module RFIL # :nodoc: # = RFI # Everything that does not fit somewhere else gets included in the # wrapper class RFI. # This class contains methods and other classes that are pretty much # useless of their own or are accessed in different classes. class RFI # :nodoc: # Super class for plugins. Just subclass this Plugin, set the name # when calling Plugin#new and implement run_plugin. class Plugin # Name of the plugin. A Symbol or a String. attr_reader :name attr_reader :filetypes # Create a new plugin. _name_ is the name of the plugin (it must # be a Symbol or a String). _filetypes_ is a list of symbols, of # what files the plugin is capable of writing. def initialize(name,*filetypes) @name=name @filetypes=filetypes end # Return an Array of files that should be written on the user's # harddrive. The Hash entries are # [:type] Type of the file (:fd, :typescript etc.) # [:filename] The filename (without a path) of the file. # [:contents] The contents of the file. def run_plugin #dummy end end # Some instructions to remove kerning information from digits and # other things. -> sort this out STDLIGKERN = ["space l =: lslash", "space L =: Lslash", "question quoteleft =: questiondown", "exclam quoteleft =: exclamdown", "hyphen hyphen =: endash", "endash hyphen =: emdash", "quoteleft quoteleft =: quotedblleft", "quoteright quoteright =: quotedblright", "space {} *", "* {} space", "zero {} *", "* {} zero", "one {} *", "* {} one", "two {} *", "* {} two", "three {} *","* {} three", "four {} *", "* {} four", "five {} *", "* {} five", "six {} *", "* {} six", "seven {} *", "* {} seven", "eight {} *", "* {} eight", "nine {} *", "* {} nine", "comma comma =: quotedblbase", "less less =: guillemotleft", "greater greater =: guillemotright"] # Metric information about a glyph. Does not contain the glyph # (outlines) itself. class Char < Font::Glyph # fontnumber is used in Font class attr_accessor :fontnumber # If not nil, _mapto_ is the glyphname that should be used instead # of the current one. attr_accessor :mapto # Sets the extension factor. This is used by calculations of _wx_, # _llx_ and _urx_. attr_accessor :efactor # Sets the slant factor. This is used by calculations of _wx_, # _llx_ and _urx_. attr_accessor :slant def wx # :nodoc: transform(@wx,0) end def wx=(obj) # :nodoc: @wx=obj end # Lower left x position of glyph. def llx # :nodoc: transform(@b[0],b[1]) end # Upper right x position of glyph. def urx # :nodoc: transform(@b[2],ury) end private def transform (x,y) (@efactor * x + @slant * y) end end # class Char # Represent the different ligatures possible in tfm. class LIG @@encligops = ["=:", "|=:", "|=:>", "=:|", "=:|>", "|=:|", "|=:|>", "|=:|>>"] @@vpligops = ["LIG", "/LIG", "/LIG>", "LIG/", "LIG/>", "/LIG/", "/LIG/>", "/LIG/>>"] @@symligops = [:lig, :"lig/", :"/lig", :"/lig/", :"lig/>", :"/lig>", :"/lig/>", :"/lig/>>"] # First glyph of a two glyph sequence before it is turned into a # ligature. attr_accessor :left # Second glyph of a two glyph sequence before it is turned into a # ligature. attr_accessor :right # The ligature that gets inserterd instead of the left and right glyph. attr_accessor :result # [0, 1, 2, 3, 4, 5, 6, 7 ] # # [=: , |=: , |=:> , =:| , =:|>, |=:|, |=:|>, |=:|>> ] # # [LIG, /LIG, /LIG>, LIG/, LIG/>, /LIG/, /LIG/>, /LIG/>>] attr_accessor :type # call-seq: # new # new(left,[right,[result,[type]]]) # new(hash) # new(otherlig) # # When called with left, right, result or type parameters, take # these settings for the LIG object. When called with a hash as an # argument, the keys should look like: :left,:right,:result,:type. # When called with an existing LIG object, the values are taken # from the old object. def initialize(left=nil,right=nil,result=nil,type=nil) case left when Hash [:left,:right,:result,:type].each { |sym| if left.has_key?(sym) self.send((sym.to_s+"=").to_sym,left[sym]) end } when LIG [:left,:right,:result,:type].each { |sym| self.send((sym.to_s+"=").to_sym,left.send(sym)) } # warning!!!!! LIG accepts a String as well as Fixnum as # parameters, this might have side effects!? when Fixnum,nil,String @left=left @right=right @result=result @type=type else raise "unknown argument for new() in LIG: #{left}" end # test! #unless @type.instance_of?(Fixnum) # raise "type must be a fixnum" #end end def ==(lig) @left=lig.left and @right=lig.right and @result=lig.result and @type=lig.type end def to_pl(encoding) encoding.glyph_index[@right].sort.collect { |rightslot| left=encoding.glyph_index[@left].min # right=encoding.glyph_index[@right].min result=encoding.glyph_index[@result].min type=@@vpligops[@type] LIG.new(:left=>left, :right=>rightslot, :result=>result, :type=>type) } end # Return an array that is suitable for tfm def to_tfminstr(encoding) encoding.glyph_index[@right].sort.collect { |rightslot| left=encoding.glyph_index[@left].min # right=encoding.glyph_index[@right].min result=encoding.glyph_index[@result].min type=@@symligops[@type] [type,rightslot,result] } end def inspect "[#{@type.to_s.upcase} #@left + #@right => #@result]" end end require 'forwardable' # Stores information about kerning and ligature information. Allows # deep copy of ligature and kerning information. Obsolete. Don't use. class LigKern extend Forwardable # Optional parameter initializes the new LigKern object. def initialize(h={}) @h=h end def_delegators(:@h, :each, :[], :[]=,:each_key,:has_key?) def initialize_copy(obj) # :nodoc: tmp={} if obj[:lig] tmp[:lig]=Array.new obj[:lig].each { |elt| tmp[:lig].push(elt.dup) } end if obj[:krn] tmp[:krn]=Array.new obj[:krn].each { |elt| tmp[:krn].push(elt.dup) } end if obj[:alias] tmp[:alias]=obj[:alias].dup end @h=tmp end # Compare this object to another object of the same class. def ==(obj) return false unless obj.respond_to?(:each) # the krn needs to be compared one by one, because they are floats if obj.has_key?(:krn) obj[:krn].each { |destchar,value| return false unless @h[:krn].assoc(destchar) return false if (value - @h[:krn].assoc(destchar)[1]).abs > 0.01 } end obj.each { |key,value| next if key==:krn return false unless @h[key]==value } true end end # The Glyphlist is a actually a Hash with some special methods # attached. class Glyphlist < Hash @@encligops = ["=:", "|=:", "|=:>", "=:|", "=:|>", "|=:|", "|=:|>", "|=:|>>"] @@vpligops = ["LIG", "/LIG", "/LIG>", "LIG/", "LIG/>", "/LIG/", "/LIG/>", "/LIG/>>"] # Return an array with name of glyphs that are represented by the # symbol _glyphlist_. # These symbols are defined: :lowercase, :uppercase, :digits def get_glyphlist(glyphlist) ret=[] unless glyphlist.instance_of? Symbol raise ArgumentError, "glyphlist must be a symbol" end case glyphlist when :lowercase update_uc_lc_list self.each { |glyphname,char| if char.uc != nil ret.push glyphname end } when :uppercase update_uc_lc_list self.each { |glyphname,char| if char.lc != nil ret.push glyphname end } when :digits ret=%w(one two three four five six seven eight nine zero) end ret end # instructions.each must yield string objects (i.e. an array of # strings, an IO object, a single string, ...). Instruction is like: # "space l =: lslash" or "two {} *" def apply_ligkern_instructions (instructions) instructions.each { |instr| s = instr.split(' ') if @@encligops.member?(s[2]) # one of =:, |=: |=:> ... if self[s[0]] self[s[0]].lig_data[s[1]]=LIG.new(s[0],s[1],s[3],@@encligops.index(s[2])) else # puts "glyphlist#apply_ligkern_instructions: char not found: #{s[0]}" end elsif s[1] == "{}" remove_kern(s[0],s[2]) end } end # _left_ and _right_ must be either a glyphname or a '*' # (asterisk) which acts like a wildcard. So ('one','*') would # remove all kerns of glyph 'one' where 'one' is the left glyph in # a kerning pair. def remove_kern(left,right) raise ArgumentError, "Only one operand may be '*'" if left=='*' and right=='*' if right == "*" self[left].kern_data={} if self[left] elsif left == "*" if self[right] self.each { |name,chardata| chardata.kern_data.delete(right) } end else if self[right] and self[left] self[left].kern_data.delete(right) end end end # Update all glyph entries to see what the uppercase or the # lowercase variants are. Warning!! Tcaron <-> tquoteright in # non-unicode fonts. def update_uc_lc_list # we need this list only when faking small caps (which will, of # course, never happen!) # make a list of all uppercase and lowercase glyphs. Be aware of # ae<->AE, oe<->OE, germandbls<-->SS, dotlessi->I, dotlessj->J # do the # @upper_lower={} # @lower_upper={} self.each_key {|glyphname| thischar=self[glyphname] if glyphname =~ /^[a-z]/ if glyphname =~ /^(a|o)e$/ and self[glyphname.upcase] thischar.uc = glyphname.upcase elsif glyphname =~ /^dotless(i|j)$/ thischar.uc = glyphname[-1].chr.upcase elsif self[glyphname.capitalize] thischar.uc = glyphname.capitalize end else if glyphname =~ /^(A|O)e$/ and self[glyphname.dowcase] thischar.lc = glyphname.downcase elsif self[glyphname.downcase] thischar.lc = glyphname.downcase end end } if self['germandbls'] self['germandbls'].uc='S' end end # Modify the charmetrics and the kerning/ligatures so that the # lowercase chars are made from scaling uppercase chars. def fake_caps (factor) update_uc_lc_list # we need to do the following # 1. adapt kerning pairs # 2. change font metrics (wd) # 3. remove ligatures from sc @fake_caps=true @capheight=factor self.each { |glyphname,char| if char.is_lowercase? # remove ligatures from sc char.lig_data={} char.kern_data={} char.mapto=char.capitalize self[char.uc].kern_data.each { |destglyph,kerndata| unless self[destglyph].is_lowercase? char.kern_data[destglyph.downcase]=[kerndata[0] * factor,0] end } char.b = self[char.capitalize].b.clone char.wx = self[char.capitalize].wx * @capheight char.lly *= @capheight char.urx *= @capheight char.ury *= @capheight else # char is something like Aring, semicolon, ... # if destchar is uppercase letter (A, Aring, ...) # 1. delete all kerns to lowercase letters (not e.g. semicolon) # 2. duplicate all uc kerns, multiply by factor and insert this # as lc kern char.kern_data.delete_if { |destglyph,kerndata| self[destglyph].is_lowercase? } new_kern_data={} char.kern_data.each { |destglyph,kerndata| if self[destglyph].is_uppercase? new_kern_data[self[destglyph].downcase]=[kerndata[0]*factor,kerndata[1]] end new_kern_data[destglyph]=kerndata } char.kern_data=new_kern_data end # 2. } if self['germandbls'] s=self['S'] d=self['germandbls'] d.b = s.b.dup d.wx = s.wx * 2 * @capheight d.urx += s.wx d.kern_data={} s.kern_data.each { |destglyph,kerndata| unless self[destglyph].is_lowercase? # we are looking at non-lowercase chars. These might be # ones that are uppercase or are 'something else', e.g. # hyphen... # since we only replace the lc variants, keep the uc and # others intact. if self[destglyph].is_uppercase? d.kern_data[self[destglyph].downcase]=[kerndata[0] * @capheight,0] else d.kern_data[destglyph]=[kerndata[0] * @capheight,0] end end } d.pcc_data=[['S',0,0],['S',s.wx,0]] d.lly *= @capheight d.urx *= @capheight end end # fake_caps def fix_height(xheight) # this is what afm2tfm does. I am not sure if it is clever. self.each { |name,data| # xheight <= 50 -> @chars[char].ury # char.size > 1 -> @chars[char].ury # char+accentname (ntilde, udieresis,...) exists? # then calculate else @chars[char].ury # calculate := ntilde.ury - tilde.ury + xheight # same as texheight in afm2tfm source unless name.size>1 or xheight < 50 %w(acute tilde caron dieresis).each {|accent| naccent=name + accent next unless self[naccent] data.ury = self[naccent].ury - self[accent].ury + xheight break } end } end # fix_height end # class Glyphlist end # class RFI end