summaryrefslogtreecommitdiff
path: root/support/rfil/lib
diff options
context:
space:
mode:
authorNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
committerNorbert Preining <norbert@preining.info>2019-09-02 13:46:59 +0900
commite0c6872cf40896c7be36b11dcc744620f10adf1d (patch)
tree60335e10d2f4354b0674ec22d7b53f0f8abee672 /support/rfil/lib
Initial commit
Diffstat (limited to 'support/rfil/lib')
-rw-r--r--support/rfil/lib/rfil/font.rb720
-rw-r--r--support/rfil/lib/rfil/font/afm.rb402
-rw-r--r--support/rfil/lib/rfil/font/glyph.rb198
-rw-r--r--support/rfil/lib/rfil/font/metric.rb129
-rw-r--r--support/rfil/lib/rfil/font/truetype.rb29
-rw-r--r--support/rfil/lib/rfil/fontcollection.rb182
-rw-r--r--support/rfil/lib/rfil/helper.rb155
-rw-r--r--support/rfil/lib/rfil/rfi.rb472
-rw-r--r--support/rfil/lib/rfil/rfi_plugin_context.rb90
-rw-r--r--support/rfil/lib/rfil/rfi_plugin_latex.rb95
-rw-r--r--support/rfil/lib/rfil/tex/enc.rb225
-rw-r--r--support/rfil/lib/rfil/tex/kpathsea.rb65
-rw-r--r--support/rfil/lib/rfil/tex/tfm.rb1200
-rw-r--r--support/rfil/lib/rfil/tex/vf.rb848
14 files changed, 4810 insertions, 0 deletions
diff --git a/support/rfil/lib/rfil/font.rb b/support/rfil/lib/rfil/font.rb
new file mode 100644
index 0000000000..349021146a
--- /dev/null
+++ b/support/rfil/lib/rfil/font.rb
@@ -0,0 +1,720 @@
+# font.rb - Implements Font. See that class for documentaton.
+#--
+# Last Change: Tue May 16 19:21:33 2006
+#++
+require 'set'
+
+require 'rfil/helper'
+require 'rfil/font/afm'
+require 'rfil/font/truetype'
+require 'rfil/tex/enc'
+require 'rfil/tex/kpathsea'
+require 'rfil/tex/tfm'
+require 'rfil/tex/vf'
+require 'rfil/rfi'
+
+
+
+module RFIL # :nodoc:
+ class RFI # :nodoc:
+
+ # Main class to manipulate and combine font metrics. This is mostly a
+ # convenience class, if you don't want to do the boring stuff
+ # yourself. You can 'load' a font, manipulate the data and create a tfm
+ # and vf file. It is used in conjunction with FontCollection, a class
+ # that contains several Font objects (perhaps a font family).
+ # The Font class relys on TFM/VF to write out the tfm and vf files, on the
+ # subclasses of RFI, especially on RFI::Glyphlist that knows about a
+ # lot of things about the char metrics, ENC for handling the encoding
+ # information and, of course, FontMetric and its subclasses to read a
+ # font.
+ class Font
+ include TeX
+ def self.documented_as_accessor(*args) # :nodoc:
+ end
+
+ # lookup_meth
+ def self.lookup_meth(*args)
+ args.each do |arg|
+ define_method(arg) do
+ if @fontcollection
+ @fontcollection.instance_variable_get("@#{arg}")
+ else
+ instance_variable_get("@#{arg}")
+ end
+ end
+ define_method("#{arg}=") do |v|
+ instance_variable_set("@#{arg}", v)
+ end
+ end
+ end
+
+
+ lookup_meth :style, :write_vf
+
+ include Helper
+
+ RULE=[:setrule, 0.4, 0.4]
+
+ # The encoding that the PDF/PS expects (what is put before
+ # "ReEncodeFont" in the mapfile). If not set, use the setting from
+ # the fontcollection. You can specify at most one encoding. If you
+ # set it to an array of encodings, only the first item in the array
+ # will be used. The assignment to _mapenc_ can be an Enc object or a
+ # string that is a filename of the encoding. If unset, use all the
+ # encoding mentioned in #texenc. In this case, a one to one mapping
+ # will be done: 8r -> 8r, t1 -> t1 etc. (like the -T switch in
+ # afm2tfm).
+ documented_as_accessor :mapenc
+
+ # Array of encodings that TeX spits out. If it is not set, take
+ # the settings from the fontcollection.
+ documented_as_accessor :texenc
+
+ # The fontmetric of the default font
+ attr_accessor :defaultfm
+
+ # extend font with this factor
+ attr_accessor :efactor
+
+ # slantfactor
+ attr_accessor :slant
+
+ # Don't write out virtual fonts if write_vf is set to false here or
+ # in the fontcollection.
+ documented_as_accessor :write_vf
+
+ # sans, roman, typewriter
+ documented_as_accessor :style
+
+ # :regular, :bold, :black, :light
+ attr_accessor :weight
+
+ # :regular, :italic, :slanted, :smallcaps
+ attr_accessor :variant
+
+ # :dryrun, :verbose, see also fontcollection
+ attr_accessor :options
+
+ # all the loaded
+ attr_accessor :variants
+ # If fontcollection is supplied, we are now part as the
+ # fontcollection. You can set mapenc and texenc in the fontcollection
+ # and don't bother about it here. Settings in a Font object will
+ # override settings in the fontcollection.
+
+ def initialize (fontcollection=nil)
+ # we are part of a fontcollection
+ @fontcollection=fontcollection
+ # @defaultfm=FontMetric.new
+ @weight=:regular
+ @variant=:regular
+ @defaultfm=nil
+ @efactor=1.0
+ @slant=0.0
+ @capheight=nil
+ @write_vf=true
+ @texenc=nil
+ @mapenc=nil
+ @variants=[]
+ @style=nil
+ @dirs={}
+ @origsuffix="-orig"
+ @kpse=Kpathsea.new
+ if fontcollection
+ unless @fontcollection.respond_to?(:register_font)
+ raise ArgumentError, "parameter does not look like a fontcollection"
+ end
+ @colnum=@fontcollection.register_font(self)
+ else
+ # the default dirs
+ set_dirs(Dir.getwd)
+ end
+ @options=Options.new(fontcollection)
+ end
+
+ # hook run after font has been loaded by load_variant
+ def after_load_hook
+ end
+ # Read a font(metric file). Return a number that identifies the font.
+ # The first font read is the default font.
+ def load_variant(fontname)
+ fm=nil
+
+ if fontname.instance_of? String
+ if File.exists?(fontname)
+ case File.extname(fontname)
+ when ".afm"
+ fm=RFIL::Font::AFM.new
+ when ".ttf"
+ fm=RFIL::Font::TrueType.new
+ else
+ raise ArgumentError, "Unknown filetype: #{File.basename(fontname)}"
+ end
+ else
+ # let us guess the inputfile
+ %w( .afm .ttf ).each { |ext|
+ if File.exists?(fontname+ext)
+ fontname += ext
+ case ext
+ when ".afm"
+ fm=RFIL::Font::AFM.new
+ when ".ttf"
+ fm=RFIL::Font::TrueType.new
+ end
+ break
+ end
+ }
+ end
+ raise Errno::ENOENT,"Font not found: #{fontname}" unless fm
+ # We need more TeX-specific classes:
+ fm.glyph_class=RFIL::RFI::Char
+ # fm.glyph_class=::Font::Glyph
+ fm.chars=RFIL::RFI::Glyphlist.new
+ fm.read(fontname)
+ raise ScriptError, "Fontname is not set" unless fm.name
+ elsif fontname.respond_to? :charwd
+ # some kind of font metric
+ fm=fontname
+ end
+ class << fm
+ # scalefactor of font (1=don't scale)
+ attr_accessor :fontat
+
+ # auxiliary attribute to store the name of the 'original' font
+ attr_accessor :mapto
+ end
+
+
+ @variants.push(fm)
+ fontnumber=@variants.size - 1
+
+ # the first font loaded is the default font
+ if fontnumber == 0
+ @defaultfm = fm
+ end
+
+ fm.chars.each { |name,chardata|
+ chardata.fontnumber=fontnumber
+ }
+
+ fm.chars.fix_height(fm.xheight)
+ fm.fontat=1 # default scale factor
+ after_load_hook
+ fontnumber
+ end # load_variant
+
+
+ # change the metrics (and glyphs) of the default font so that
+ # uppercase variants are mapped onto the lowercase variants.
+ def fake_caps(fontnumber,capheight)
+ raise ScriptError, "no font loaded" unless @defaultfm
+ # first, make a list of uppercase and lowercase glyphs
+ @defaultfm.chars.update_uc_lc_list
+ @capheight=capheight
+ v=@variants[fontnumber]
+ v.fontat=capheight
+ v.chars.fake_caps(capheight)
+ end
+
+
+ # Return tfm object for that font. _enc_ is the encoding of the
+ # tfm file, which must be an ENC object. Ligature and kerning
+ # information is put into the tfm file unless <tt>:noligs</tt> is
+ # set to true in the opts.
+ def to_tfm(enc,opts={})
+ tfm=TFM.new
+ tfm.fontfamily=@defaultfm.familyname
+ tfm.codingscheme=enc.encname
+ tfm.designsize=10.0
+
+ tfm.params[1]=(@slant - @efactor * Math::tan(@defaultfm.italicangle * Math::PI / 180.0)) / 1000.0
+
+ tfm.params[2]=(transform(@defaultfm.space,0)) / 1000.0
+ tfm.params[3]=(@defaultfm.isfixedpitch ? 0 : transform(0.3,0))
+ tfm.params[4]=(@defaultfm.isfixedpitch ? 0 : transform(0.1,0))
+ tfm.params[5]=@defaultfm.xheight / 1000.0
+ tfm.params[6]=transform(1.0,0)
+
+ charhash=enc.glyph_index.dup
+
+ enc.each_with_index{ |char,i|
+ next if char==".notdef"
+
+ thischar=@defaultfm.chars[char]
+ next unless thischar
+
+ # ignore those chars we have already encountered
+ next unless charhash.has_key?(char)
+
+ thischar.efactor=@efactor
+ thischar.slant=@slant
+
+ c={}
+ charhash[char].each { |slot|
+ tfm.chars[slot]=c
+ }
+ charhash.delete(char)
+
+ [:charwd, :charht, :chardp, :charic].each { |sym|
+ c[sym]=thischar.send(sym) / 1000.0
+ }
+ }
+ if opts[:noligs] != true
+ tfm_lig(tfm,enc)
+ end
+
+ return tfm
+ end
+
+ # Return vf object for that font. _mapenc_l_ and _texenc_ must be an
+ # ENC object. _mapenc_l_ is the destination encoding (of the fonts
+ # in the mapfile) and _texenc_ is is the encoding of the resulting
+ # tfm file. They may be the same.
+ def to_vf(mapenc_l,texenc)
+ raise ArgumentError, "mapenc must be an ENC object" unless mapenc_l.respond_to? :encname
+ raise ArgumentError, "texenc must be an ENC object" unless texenc.respond_to? :encname
+ vf=VF.new
+ vf.vtitle="Installed with rfi library"
+ vf.fontfamily=@defaultfm.familyname
+ vf.codingscheme= if mapenc_l.encname != texenc.encname
+ mapenc_l.encname + " + " + texenc.encname
+ else
+ mapenc_l.encname
+ end
+ vf.designsize=10.0
+ fm=@defaultfm
+
+ vf.params[1]=(@slant - @efactor * Math::tan(@defaultfm.italicangle * Math::PI / 180.0)) / 1000.0
+ vf.params[2]=(transform(@defaultfm.space,0)) / 1000.0
+ vf.params[3]=(@defaultfm.isfixedpitch ? 0 : transform(0.3,0))
+ vf.params[4]=(@defaultfm.isfixedpitch ? 0 : transform(0.1,0))
+ vf.params[5]=@defaultfm.xheight / 1000.0
+ vf.params[6]=transform(1,0)
+ vf.params[7]==fm.isfixedpitch ? fm.space : transform(0.111,0)
+
+ # mapfont
+ find_used_fonts.each_with_index { |fontnumber,i|
+ fl=vf.fontlist[fontnumber]={}
+ tfm=fl[:tfm]=TFM.new
+ tfm.tfmpathname=map_fontname(mapenc_l,fontnumber)
+ fl[:scale]=@variants[fontnumber].fontat
+ }
+
+ charhash=texenc.glyph_index.dup
+ texenc.each_with_index { |char,i|
+ next if char==".notdef"
+
+ thischar=@defaultfm.chars[char]
+ next unless thischar
+
+ # ignore those chars we have already encountered
+ next unless charhash.has_key?(char)
+
+ thischar.efactor=@efactor
+ thischar.slant=@slant
+
+ c={}
+ charhash[char].each { |slot|
+ vf.chars[slot]=c
+ }
+ charhash.delete(char)
+ c[:dvi]=dvi=[]
+
+ if thischar.fontnumber > 0
+ dvi << [:selectfont,thischar.fontnumber]
+ end
+
+ if thischar.pcc_data
+ thischar.pcc_data.each { |pcc|
+ if mapenc_l.glyph_index[pcc[0]]
+ dvi << [:setchar,mapenc_l.glyph_index[pcc[0]].min]
+ else
+ dvi << RULE
+ end
+ }
+ elsif thischar.mapto
+ if mapenc_l.glyph_index[thischar.mapto]
+ if mapenc_l.glyph_index[thischar.mapto]
+ dvi << [:setchar, mapenc_l.glyph_index[thischar.mapto].min]
+ else
+ dvi << RULE
+ end
+ else
+ dvi << [:special, "unencoded glyph '#{char}'"]
+ dvi << RULE
+ end
+ elsif mapenc_l.glyph_index[char]
+ dvi << [:setchar, mapenc_l.glyph_index[char].min]
+ else
+ dvi << RULE
+ end
+ [:charwd, :charht, :chardp, :charic].each { |sym|
+ c[sym]=thischar.send(sym) / 1000.0
+ }
+ }
+ tfm_lig(vf,texenc)
+
+ return vf
+ end
+
+
+ # Todo: document and test!
+ def apply_ligkern_instructions(what)
+ @defaultfm.chars.apply_ligkern_instructions(what)
+ end
+
+ # Return a string or an array of strings that should be put in a mapfile.
+ def maplines()
+ # "normally" (afm2tfm)
+ # savorg__ Savoy-Regular " mapenc ReEncodeFont " <savorg__ <mapenc.enc
+
+ # enc-fontname[-variant]*.tfm
+
+ # or without the "ReEncodeFont" (check!)
+
+
+ # we default to ase (Adobe Standard Encoding), on your TeX system
+ # as 8a.enc
+
+ # if mapenc (the encoding TeX writes to the dvi file) is not set
+ texenc_loc = texenc
+ unless texenc_loc
+ f=@kpse.open_file("8a.enc","enc")
+ texenc_loc=[ENC.new(f)]
+ f.close
+ end
+ ret=[]
+ encodings=Set.new
+ texenc.each { |te|
+ encodings.add mapenc ? mapenc : te
+ }
+ fontsused=find_used_fonts
+ encodings.each { |te|
+ fontsused.each { |f|
+ str=map_fontname(te,f)
+ str << " #{@variants[f].fontname} "
+ instr=[]
+ if @slant != 0.0
+ instr << "#@slant SlantFont"
+ end
+ if @efactor != 1.0
+ instr << "#@efactor ExtendFont"
+ end
+ unless te.filename == "8a.enc"
+ instr << "#{te.encname} ReEncodeFont"
+ end
+
+ str << "\"" << instr.join(" ") << "\"" if instr.size > 0
+ unless te.filename == "8a.enc"
+ str << " <#{te.filename}"
+ end
+ str << " <#{@variants[f].fontfilename}"
+ str << "\n"
+ ret.push str
+ }
+ }
+ # FIXME: remove duplicate lines in a more sensible way
+ # no fontname (first entry) should appear twice!
+ ret.uniq
+ end
+
+ # Creates all the necessary files to use the font. This is mainly a
+ # shortcut if you are too lazy to program. _opts_:
+ # [:dryrun] true/false
+ # [:verbose] true/false
+ # [:mapfile] true/false
+
+ def write_files(opts={})
+
+
+ tfmdir=get_dir(:tfm); ensure_dir(tfmdir)
+ vfdir= get_dir(:vf) ; ensure_dir(vfdir)
+ unless opts[:mapfile]==false
+ mapdir=get_dir(:map); ensure_dir(mapdir)
+ end
+
+ encodings=Set.new
+ texenc.each { |te|
+ encodings.add mapenc ? mapenc : te
+ }
+ encodings.each { |enc|
+ find_used_fonts.each { |var|
+ tfmfilename=File.join(tfmdir,map_fontname(enc,var) + ".tfm")
+
+ if options[:verbose]==true
+ puts "tfm: writing tfm: #{tfmfilename}"
+ end
+ unless options[:dryrun]==true
+ tfm=to_tfm(enc)
+ tfm.tfmpathname=tfmfilename
+ tfm.save(true)
+ end
+ }
+ }
+
+ if write_vf
+ encodings=Set.new
+ texenc.each { |te|
+ encodings.add mapenc ? mapenc : te
+ }
+ texenc.each { |te|
+ outenc = mapenc ? mapenc : te
+ vffilename= File.join(vfdir, tex_fontname(te) + ".vf")
+ tfmfilename=File.join(tfmdir,tex_fontname(te) + ".tfm")
+ if options[:verbose]==true
+ puts "vf: writing tfm: #{tfmfilename}"
+ puts "vf: writing vf: #{vffilename}"
+ end
+ unless options[:dryrun]==true
+ vf=to_vf(outenc,te)
+ vf.tfmpathname=tfmfilename
+ vf.vfpathname=vffilename
+ vf.save(true)
+ end
+ }
+ end
+
+ unless opts[:mapfile]==false
+ # mapfile
+ if options[:verbose]==true
+ puts "writing #{mapfilename}"
+ end
+ unless options[:dryrun]==true
+ File.open(mapfilename,"w") { |f|
+ f << maplines
+ }
+ end
+ end
+ end
+
+ # Return a directory where files of type _type_ will be placed in.
+ # Default to current working directory.
+ def get_dir(type)
+ if @dirs.has_key?(type)
+ @dirs[type]
+ elsif @fontcollection and @fontcollection.dirs.has_key?(type)
+ @fontcollection.dirs[type]
+ else
+ Dir.getwd
+ end
+ end
+
+ def mapenc # :nodoc:
+ return nil if @mapenc == :none
+ if @mapenc==nil and @fontcollection
+ @fontcollection.mapenc
+ else
+ @mapenc
+ end
+ end
+ def mapenc=(enc) # :nodoc:
+ set_mapenc(enc)
+ end
+
+ def texenc # :nodoc:
+ if @texenc
+ @texenc
+ else
+ # @texenc not set
+ if @fontcollection
+ @fontcollection.texenc
+ else
+ ret=nil
+ @kpse.open_file("8a.enc","enc") { |f|
+ ret = [ENC.new(f)]
+ }
+ # puts "returning #{ret}"
+ return ret
+ end
+ end
+ end
+ def texenc=(enc) # :nodoc:
+ @texenc=[]
+ if enc
+ set_encarray(enc,@texenc)
+ end
+ end
+
+ # Return the full path to the mapfile.
+ def mapfilename
+ File.join(get_dir(:map),@defaultfm.name + ".map")
+ end
+
+
+
+ # Copy glyphs from one font to the default font. _fontnumber_ is the
+ # number that is returned from load_variant, _glyphlist_ is whatever
+ # you want to copy. Overwrites existing chars. _opts_ is one of:
+ # [:ligkern] copy the ligature and kerning information with the glyphs stated in glyphlist. This will remove all related existing ligature and kerning information the default font.
+ # *needs testing*
+ def copy(fontnumber,glyphlist,opts={})
+ tocopy=[]
+ case glyphlist
+ when Symbol
+ tocopy=@defaultfm.chars.get_glyphlist(glyphlist)
+ when Array
+ tocopy=glyphlist
+ end
+
+ tocopy.each { |glyphname|
+ @defaultfm.chars[glyphname]=@variants[fontnumber].chars[glyphname]
+ @defaultfm.chars[glyphname].fontnumber=fontnumber
+ }
+ if opts[:ligkern]==true
+ # assume: copying lowercase letters.
+ # we need to copy *all* lowercase -> * data and replace all
+ # we need to remove all uppercase -> lowercase data first
+ # we need to copy all uppercase -> lowercase data
+ @variants[fontnumber].chars.each { |glyphname,data|
+ if tocopy.member?(glyphname)
+ #puts "font#copy: using kern_data for #{glyphname}"
+ @defaultfm.chars[glyphname].kern_data=data.kern_data.dup
+ else
+ # delete all references to the 'tocopy'
+ @defaultfm.chars[glyphname].kern_data.each { |destchar,kern|
+ if tocopy.member?(destchar)
+ #puts "font#copy: removing kern_data for #{glyphname}->#{destchar}"
+ @defaultfm.chars[glyphname].kern_data.delete(destchar)
+ end
+ }
+ data.kern_data.each { |destchar,kern|
+ if tocopy.member?(destchar)
+ @defaultfm.chars[glyphname].kern_data[destchar]=kern
+ end
+ }
+ end
+ }
+ end
+ end # copy
+
+ # Return an array with all used fontnumbers loaded with
+ # load_variant. If, for example, fontnubmer 0 and 3 are used,
+ # find_used_fonts would return [0,3].
+ def find_used_fonts
+ fonts=Set.new
+ @defaultfm.chars.each{ |glyph,data|
+ fonts.add(data.fontnumber)
+ }
+ fonts.to_a.sort
+ end
+
+
+ # Return the name of the font in the mapline. If we don't write
+ # virtual fonts, this is the name of the tfm file written. If we
+ # write vf's, than this is the name used in the mapfont section of
+ # the virtual font as well as the name of the tfm file, but both
+ # with some marker that this font 'should' not be used directly.
+ def map_fontname (texenc,varnumber=0,opts={})
+ mapenc_loc=mapenc
+ suffix=""
+ suffix << @origsuffix if write_vf
+ if mapenc_loc
+ # use the one in mapenc_loc
+ construct_fontname(mapenc,varnumber) + suffix
+ else
+ construct_fontname(texenc,varnumber) + suffix
+ end
+ end
+
+ # Return the name
+ def tex_fontname (encoding)
+ tf=construct_fontname(encoding)
+ tf << "-capitalized-#{(@capheight*1000).round}" if @capheight
+ tf
+ end
+ def guess_weight_variant
+ fm=@defaultfm
+ # fm[:smallcaps] = false
+ # fm[:expert] = false
+ [fm.fontname,fm.familyname,fm.weight].each { |fontinfo|
+ case fontinfo
+ when /italic/i
+ @variant=:italic
+ when /bold/i
+ @weight=:bold
+ when /smcaps/i
+ @variant=:smallcaps
+ # when /expert/i
+ # f[:expert] = true
+ # puts "expert"
+ end
+ }
+ end
+
+ #######
+ private
+ #######
+ def tfm_lig(tfm,enc)
+ charhash=enc.glyph_index.dup
+
+ enc.each_with_index { |char,i|
+ next if char==".notdef"
+
+ thischar=@defaultfm.chars[char]
+ next unless thischar
+
+ # ignore those chars we have already encountered
+ next unless charhash.has_key?(char)
+ lk=[]
+
+ thischar.lig_data.each_value { |lig|
+ if (enc.glyph_index.has_key? lig.right) and
+ (enc.glyph_index.has_key? lig.result)
+ # lig is like "hyphen ..." but needs to be in a format like
+ # "45 .."
+ lk += lig.to_tfminstr(enc)
+ end
+ }
+ thischar.kern_data.each { |dest,kern|
+ if (enc.glyph_index.has_key? dest)
+ enc.glyph_index[dest].each { |slot|
+ lk << [:krn, slot,(kern[0]*@efactor)/1000.0]
+ }
+ end
+ }
+ next if lk.size==0
+ instrnum = tfm.lig_kern.size
+ tfm.lig_kern << lk
+
+ charhash[char].each { |slot|
+ c=tfm.chars[slot] ||= {}
+ c[:lig_kern]=instrnum
+ }
+ charhash.delete(char)
+ }
+ return tfm
+ end
+
+ def construct_fontname(encoding,varnumber=0)
+ encodingname=if String === encoding
+ encoding
+ else
+ if encoding.filename
+ encoding.filename.chomp(".enc").downcase
+ else
+ encoding.encname
+ end
+ end
+
+ fontname=@variants[varnumber].name
+ # default
+ tf=if encodingname == "8a"
+ "#{fontname}"
+ else
+ "#{encodingname}-#{fontname}"
+ end
+ tf << "-slanted-#{(@slant*100).round}" if @slant != 0.0
+ tf << "-extended-#{(@efactor*100).round}" if @efactor != 1.0
+
+ tf
+
+ end
+
+ def transform (x,y)
+ @efactor * x + @slant * y
+ end
+
+ end # class Font
+ end # class RFI
+end #module RFIL
diff --git a/support/rfil/lib/rfil/font/afm.rb b/support/rfil/lib/rfil/font/afm.rb
new file mode 100644
index 0000000000..3a087d36c8
--- /dev/null
+++ b/support/rfil/lib/rfil/font/afm.rb
@@ -0,0 +1,402 @@
+# Last Change: Tue May 16 19:11:20 2006
+
+# require 'rfi'
+
+require 'strscan'
+require 'pathname'
+
+require 'rfil/font/metric'
+
+module RFIL # :nodoc:
+ module Font # :nodoc:
+ # = AFM -- Access type1 font metric files
+ #
+ # == General information
+ #
+ # Read and parse a (type1) afm file. The afm file must be compliant to
+ # the afm specification as described in 'Adobe Font Metrics File
+ # Format Specification' Version 4.1, dated October 7 1998.
+ #
+ # == Example usage
+ #
+ # === Read an afm file
+ # filename = "/opt/tetex/3.0/texmf/fonts/afm/urw/palatino/uplb8a.afm"
+ # afm=AFM.new
+ # afm.read(filename)
+ # afm.filename # => "/opt/..../uplb8a.afm"
+ # afm.count_charmetrics # => 316
+ # afm.encodingscheme # => "AdobeStandardEncoding"
+ # # ....
+ #
+ class AFM < Metric
+
+ # This is set to true if there is something wrong in the afm file.
+ # Diagnostics can be turned on with <tt>:verbose</tt> set to true
+ # when creating the object.
+ attr_reader :something_strange
+
+ # Number of characters found in the afm file.
+ attr_accessor :count_charmetrics
+
+ # Number of encoded character found in the afm file.
+ attr_accessor :count_charmetrics_encoded
+
+ # Number of unencoded character found in the afm file.
+ attr_accessor :count_charmetrics_unencoded
+
+ # The default encoding of the font.
+ attr_accessor :encodingscheme
+
+ # Boundingbox of the font. Array of for elements.
+ attr_accessor :fontbbox
+
+ # Underline position of the font.
+ attr_accessor :underlineposition
+
+ # Underline thickness.
+ attr_accessor :underlinethickness
+
+ # Height of caps.
+ attr_accessor :capheight
+
+ # Height of ascender.
+ attr_accessor :ascender
+
+ # Height of descender.
+ attr_accessor :descender
+
+
+ # Create an empty afm file. If _afm_ is set, use this to initialize
+ # the object. _afm_ is either a string with the contents of an afm
+ # file or a File object that points to the afm file. _options_
+ # currently only accepts <tt>:verbose</tt> (true/false), that prints
+ # out some diagnostic information on STDERR.
+ def initialize(options={})
+ @something_strange = false
+ super()
+ @outlinetype=:type1
+ @comment = ""
+ @verbose=options[:verbose]==true
+ end
+
+ # Read the afm file given with _filename_. _filename_ must be full
+ # path to the afm file, it does not perform any lookups. Returns self.
+ def read (filename)
+ @filename=File.basename(filename)
+ @name=@filename.chomp(".afm")
+ self.pathname=Pathname.new(filename).realpath.to_s
+ parse(File.read(filename))
+ end
+
+ # Return a string representation of the afm file that is compliant
+ # with the afm spec.
+ def to_s
+ s ="StartFontMetrics 2.0\n"
+ s << "Comment Generated using the RFI Library\n"
+ %w( FontName FullName FamilyName Weight Notice ItalicAngle
+ IsFixedPitch UnderlinePosition UnderlineTickness Version
+ EncodingScheme CapHeight XHeight Descender Ascender ).each {|kw|
+
+ meth=kw.downcase.to_sym
+ value=self.send(meth) if self.respond_to?(meth)
+ if value
+ s << kw << " " << value.to_s << "\n"
+ end
+ }
+ s << "FontBBox " << @fontbbox.join(" ") << "\n"
+ s << "StartCharMetrics #@count_charmetrics\n"
+ @chars.sort{ |a,b|
+ # puts "a=#{a[1].c}, b=#{b[1].c}"
+ if a[1].c == -1
+ b[1].c == -1 ? 0 : 1
+ else
+ b[1].c == -1 ? -1 : a[1].c <=> b[1].c
+ end
+ }.each { |a,b|
+ s << "C #{b.c} ; WX #{b.wx} ; N #{a} ; B #{b.b.join(" ")}\n"
+ }
+ s << "EndCharMetrics\nStartKernData\nStartKernPairs"
+ count=0
+ @chars.each_value { |c|
+ count += c.kern_data.size
+ }
+ s << " #{count}\n"
+ @chars.sort{ |a,b| a[0] <=> b[0] }.each { |name,char|
+ char.kern_data.each { |destname, value|
+ s << "KPX #{name} #{destname} #{value[0]}\n"
+ }
+ }
+ s << "EndKernPairs\nEndKernData\nEndFontMetrics\n"
+ s
+ end
+
+ # Parse the contents of the String _txt_. Returns self.
+ def parse(txt)
+ @chars ||= Hash.new
+ @s=StringScanner.new(txt.gsub(/\r\n/,"\n"))
+ @s.scan(/StartFontMetrics/)
+ get_fontmetrics
+ self
+ end
+
+ #######
+ private
+ #######
+
+ def get_keyword
+ @s.skip_until(/\s+/)
+ @s.scan(/[A-Z][A-Za-z0-9]+/)
+ end
+
+ def get_integer
+ @s.skip(/\s+/)
+ @s.scan(/-?\d+/).to_i
+ end
+
+ def get_number
+ @s.skip(/\s+/)
+ @s.scan(/-?\d+(?:\.\d+)?/).to_f
+ end
+
+ def get_boolean
+ @s.skip(/\s+/)
+ @s.scan(/(true|false)/) == 'true'
+ end
+
+ def get_name
+ @s.skip(/\s+/)
+ @s.scan(/[^\s]+/)
+ end
+
+ def get_string
+ @s.skip(/\s+/)
+ @s.scan(/.*/)
+ end
+
+ def get_fontmetrics
+ @version = get_number
+ loop do
+ kw=get_keyword
+ STDERR.puts "KW: " + kw if @verbose
+ case kw
+ when "FontName"
+ @fontname=get_string
+ when "FamilyName"
+ @familyname = get_string
+ when "FullName"
+ @fullname = get_string
+ when "EncodingScheme"
+ @encodingscheme = get_string
+ when "ItalicAngle"
+ @italicangle = get_number
+ when "IsFixedPitch"
+ @isfixedpitch = get_boolean
+ when "Weight"
+ @weight = get_string
+ when "XHeight"
+ @xheight= get_number
+ when "Comment"
+ @comment << get_string << "\n"
+ when "FontBBox"
+ @fontbbox = [get_number,get_number, get_number, get_number]
+ when "Version"
+ @version = get_string
+ when "Notice"
+ @notice = get_string
+ when "MappingScheme"
+ @mappingscheme = get_integer
+ when "EscChar"
+ @escchar = get_integer
+ when "CharacterSet"
+ @characterset = get_string
+ when "Characters"
+ @characters = get_integer
+ when "IsBaseFont"
+ @isbasefont = get_boolean
+ when "VVector"
+ @vvector = [get_number,get_number]
+ when "IsFixedV"
+ @isfixedv = get_boolean
+ when "CapHeight"
+ @capheight = get_number
+ when "Ascender"
+ @ascender = get_number
+ when "Descender"
+ @descender = get_number
+ when "UnderlinePosition"
+ @underlineposition = get_number
+ when "UnderlineThickness"
+ @underlinethickness = get_number
+ when "StartDirection"
+ get_direction
+ when "StartCharMetrics"
+ get_charmetrics
+ when "StartKernData"
+ get_kerndata
+ when "StartComposites"
+ get_composites
+ when "EndFontMetrics"
+ break
+ end
+ end
+ end
+ def get_direction
+ # ignored
+ end
+ def get_charmetrics
+ @count_charmetrics = get_integer
+ @count_charmetrics_encoded = 0
+ @count_charmetrics_unencoded = 0
+ loop do
+ @s.skip_until(/\n/)
+ nextstring = @s.scan_until(/(?:StopCharMetrics|.*)/)
+ return if nextstring=="EndCharMetrics"
+ a=nextstring.split(';')
+ # ["C 32 ", " WX 250 ", " N space ", " B 125 0 125 0 "]
+ a.collect! { |elt|
+ elt.strip.split(/ /,2)
+ }
+ # [["C", "32"], ["WX", "250"], ["N", "space"], ["B", "125 0 125 0"]]
+ char=new_glyph
+ a.each { |elt|
+ key,value = elt
+ case key
+ when "N"
+ char.name=value
+ when "B"
+ #special treatment for bounding box
+ char.b = value.split.collect { |e| e.to_i }
+ char.llx = char.llx
+ char.urx = char.urx
+ # We need to avoid negative heights or depths. They break
+ # accents in math mode, among other things.
+ char.lly = 0 if char.lly > 0
+ char.ury = 0 if char.ury < 0
+ when "C"
+ char.c = value.to_i
+ when "CH"
+ # hex: '<20>' -> '0x20' -> .to_i -> 32
+ char.c = value.sub(/</,'0x').sub(/>/,'').to_i(16)
+ when "WX"
+ char.wx = value.to_i
+ # for "L", check /var/www/mirror/system/tex/texmf-local/fonts/afm/jmn/hans/hans.afm
+ when "L", nil
+ #ignore
+ else
+ char.send((key.downcase + "=").to_sym,value.to_i)
+ end
+ }
+
+ @chars[char.name]=char
+ # update information about encoded/unencoded
+ if char.c > -1
+ @count_charmetrics_encoded += 1
+ else
+ @count_charmetrics_unencoded += 1
+ end
+ end
+ raise "never reached"
+ end
+ def get_kerndata
+ loop do
+ kw = get_keyword
+ STDERR.puts "kw=" + kw if @verbose
+ case kw
+ when "EndKernData"
+ return
+ when "StartKernPairs"
+ get_kernpairs
+ when "StartTrackKern"
+ # TrackKern
+ get_trackkern
+ else
+ # KernPairs0
+ # KernPairs1
+ raise "not implemented"
+ end
+ end
+ raise "never reached"
+ end
+ def get_composites
+ count = get_integer
+ loop do
+ kw = get_keyword
+ STDERR.puts "get_composites keyword = '" + kw + "'" if @verbose
+ case kw
+ when "CC"
+ get_composite
+ when "EndComposites"
+ return
+ else
+ STDERR.puts "next to read = " + @s.string[@s.pos,40]
+ raise "AFM error"
+ end
+ end
+ raise "never reached"
+ end
+ def get_composite
+ glyphname = get_name
+ count = get_integer
+ @s.skip_until(/;\s+/)
+ count.times do
+ nextstring = get_name
+ raise "AFM Error" unless nextstring == "PCC"
+ [get_number,get_number]
+ @s.skip_until(/;/)
+ end
+ end
+
+ def get_trackkern
+ count = get_integer
+ loop do
+ case get_keyword
+ when "EndTrackKern"
+ return
+ when "TrackKern"
+ # TrackKern degree min-ptsize min-kern max-ptsize max-kern
+ [get_integer,get_number,get_number,get_number,get_number]
+ else
+ raise "afm error"
+ end
+ end
+ raise "never reached"
+ end
+
+ def get_kernpairs
+ count = get_integer
+ loop do
+ case get_keyword
+ when "KPX" # y is 0
+ name=get_name
+ # if @info['chars'][name]
+ if @chars[name]
+ # array is [x,y] kerning
+ destname,num=get_name,get_number
+ # somethimes something stupid like
+ # KPX .notdef y -26
+ # KPX A .notdef -43
+ # is in the afm data... :-( -> reject those entries
+ # if @info['chars'][destname]
+ if @chars[destname]
+ @chars[name].kern_data[destname] = [num,0]
+ else
+ STDERR.puts "info: unused kern data for " + name if @verbose
+ end
+ else
+ # ignore this entry, print a message
+ STDERR.puts "info: unused kern data for " + name if @verbose
+ @something_strange=true
+ [get_name,get_number] # ignored
+ end
+ when "EndKernPairs"
+ return
+ else
+ STDERR.puts @s.pos
+ raise "not implmented"
+ end
+ end
+ raise "never reached"
+ end
+ end
+ end
+end
diff --git a/support/rfil/lib/rfil/font/glyph.rb b/support/rfil/lib/rfil/font/glyph.rb
new file mode 100644
index 0000000000..91b1d69422
--- /dev/null
+++ b/support/rfil/lib/rfil/font/glyph.rb
@@ -0,0 +1,198 @@
+
+module RFIL
+ module Font
+ class Glyph
+
+ # to make Rdoc and Ruby happy: [ruby-talk:147778]
+ def self.documented_as_accessor(*args) #:nodoc:
+ end
+
+ # Glyphname
+ attr_accessor :name
+
+ # Advance with
+ attr_accessor :wx
+
+ # Standard code slot (0-255 or -1 for unencoded)
+ attr_accessor :c
+
+ # bounding box (llx, lly, urx, ury). Array of size 4.
+ # You should use the methods llx, lly, urx, ury to access the
+ # bounding box.
+ attr_accessor :b
+
+ # Kern_data (Hash). The key is the glyph name, the entries are
+ # _[x,y]_ arrays. For ltr and rtl typesetting only the x entry
+ # should be interesting. This is raw information from the font
+ # metric file. Does not change when efactor et al. are set in any
+ # way.
+ attr_accessor :kern_data
+
+ # Information about ligatures - unknown datatype yet
+ attr_accessor :lig_data
+
+ # Composite characters. Array [['glyph1',xshift,yshift],...]
+ attr_accessor :pcc_data
+
+ # Upper right x value of glyph.
+ documented_as_accessor :urx
+
+ # Upper right y value of glyph.
+ documented_as_accessor :ury
+
+ # Lower left x value of glyph.
+ documented_as_accessor :llx
+
+ # Lower left y value of glyph.
+ documented_as_accessor :lly
+
+ # the name of the uppercase glyph (nil if there is no uppercase glyph)
+ attr_accessor :uc
+
+ # the name of the lowercase glyph (nil if there is no lowercase glyph)
+ attr_accessor :lc
+
+ # Optional argument sets the name of the glyph.
+ def initialize (glyphname=nil)
+ @name=glyphname
+ @lig_data={}
+ @kern_data={}
+ @wx=0
+ @b=[0,0,0,0]
+ @efactor=1.0
+ @slant=0.0
+ end
+
+ # Lower left x position of glyph.
+ def llx # :nodoc:
+ @b[0]
+ end
+ def llx=(value) # :nodoc:
+ @b[0]=value
+ end
+
+ # Lower left y position of glyph.
+ def lly # :nodoc:
+ @b[1]
+ end
+ def lly=(value) # :nodoc:
+ @b[1]=value
+ end
+ # Upper right x position of glyph.
+ def urx # :nodoc:
+ @b[2]
+ end
+ def urx=(value) # :nodoc:
+ @b[2]=value
+ end
+
+ # Upper right y position of glyph.
+ def ury # :nodoc:
+ @b[3]
+ end
+ def ury=(value) # :nodoc:
+ @b[3]=value
+ end
+
+ # Return height of the char used for tfm file.
+ def charht
+ ury
+ end
+
+ # Return width of the char used for tfm file.
+ def charwd
+ wx
+ end
+
+ # Return depth of the char.
+ def chardp
+ lly >= 0 ? 0 : -lly
+ end
+
+ # Return italic correction of the char.
+ def charic
+ (urx - wx) > 0 ? (urx - wx) : 0
+ end
+ # Return an array with all kerning information (x-direction only)
+ # of this glyph. Kerning information is an Array where first
+ # element is the destchar, the second element is the kerning amount.
+ def kerns_x
+ ret=[]
+ @kern_data.each { |destchar,kern|
+ ret.push([destchar,kern[0]])
+ }
+ ret
+ end
+
+ # Return an array with all ligature information (LIG objects) of
+ # this glyph.
+ def ligs
+ ret=[]
+ @lig_data.each { |destchar,lig|
+ ret.push(lig)
+ }
+ ret
+ end
+
+ # Return true if this char has ligature or kerning information. If
+ # glyphindex is supplied, only return true if relevant. This means
+ # that the second parameter of a kerning information or the second
+ # parameter and the result of a ligature information must be in
+ # the glyphindex. glyphindex must respond to <em>include?</em>.
+ def has_ligkern?(glyphindex=nil)
+ if glyphindex and not glyphindex.respond_to? :include?
+ raise ArgumentError, "glyphindex does not respod to include?"
+ end
+ return false if (lig_data == {} and kern_data=={})
+ # this one is easy, just look at lig_data and kern_data
+ # more complicated, we have to take glyphindex into account
+ if glyphindex
+ return false unless glyphindex.include? self.name
+ # right kerningpair not in glyphindex? -> false
+ # right lig not in glyphindex? -> false
+ # result lig not in glyphindex? -> false
+ if lig_data
+ lig_data.each { |otherchar,lig|
+ if (glyphindex.include?(lig.right) and glyphindex.include?(lig.result))
+ return true
+ end
+ }
+ end
+ if kern_data
+ kern_data.each { |otherchar,krn|
+ return true if glyphindex.include?(otherchar)
+ }
+ end
+ return false
+ else
+ # no glyphindex
+ return true
+ end
+ raise "never reached"
+ end # has_ligkern?
+
+ # Return true if glyph is an uppercase char, such as AE.
+ def is_uppercase?
+ return @lc != nil
+ end
+
+ # Return true if glyph is a lowercase char, such as germandbls,
+ # but not hyphen.
+ def is_lowercase?
+ return @uc != nil
+ end
+
+ # Return the uppercase variant of the glyph. Undefined behaviour if
+ # glyph cannot be uppercased.
+ def capitalize
+ @uc
+ end
+
+ # Return the lowercase variant of the glyph. Undefined behaviour if
+ # glyph cannot be lowercased.
+ def downcase
+ @lc
+ end
+ end
+ end
+end
diff --git a/support/rfil/lib/rfil/font/metric.rb b/support/rfil/lib/rfil/font/metric.rb
new file mode 100644
index 0000000000..fb9afb66ab
--- /dev/null
+++ b/support/rfil/lib/rfil/font/metric.rb
@@ -0,0 +1,129 @@
+# font/metric.rb - superclass for different font metric formats
+# Last Change: Tue May 16 18:08:49 2006
+
+require 'rfil/font/glyph'
+#require 'font/afm'
+#require 'font/truetype'
+
+module RFIL
+ module Font
+ # FontMetric is the superclass for font metrics. All information that
+ # is not specific to a certain kind of file format is accessible via
+ # this class.
+
+ class Metric
+ # to make Rdoc and Ruby happy: [ruby-talk:147778]
+ def self.documented_as_accessor(*args) # :nodoc:
+ end
+ def self.documented_as_reader(*args) # :nodoc:
+ end
+
+ # :type1, :truetype
+ attr_accessor :outlinetype
+
+ # Hash of glyphs in the font.
+ attr_accessor :chars
+
+ # The filename of the just read metric file. Does not contain the
+ # path. To set, change the pathname
+ documented_as_reader :filename
+
+ # Absolute pathname of the metric file. Not checked when set.
+ attr_accessor :pathname
+
+ # File name of the font containing the outlines (the file that needs
+ # to be put into the pdf-file). The .tt or the .pfb file. If unset
+ # use the value of filename, but changed to the correct extension in
+ # case of Type 1 fonts.
+ documented_as_accessor :fontfilename
+
+ # Some unique name of the font. Use the filename of the font or a
+ # name after the KB naming schema. Do not add an extension such as
+ # afm or tt.
+ attr_accessor :name
+
+ # family name of the font
+ attr_accessor :familyname
+
+ # xheight in 1/1000 of an em
+ attr_accessor :xheight
+
+ attr_accessor :weight
+
+ # natural width of a space
+ documented_as_reader :space
+
+ attr_accessor :italicangle
+
+ # True if the font is a monospaced font (courier for example).
+ attr_accessor :isfixedpitch
+
+ # The official name of the font as supplied by the vendor. Written
+ # as FontName in the afm file.
+ attr_accessor :fontname
+
+ # The full name of the font, whatever this means. Written as
+ # FullName in the afm file.
+ attr_accessor :fullname
+
+ # Class for new glyphs. Default is Glyph
+ attr_accessor :glyph_class
+
+ def Metric.read(filename,options={})
+ case File.extname(filename)
+ when ".afm"
+ require 'rfil/font/afm'
+ return AFM.new(options).read(filename)
+ when ".ttf"
+ require 'rfil/font/truetype'
+ return TrueType.new(options).read(filename)
+ else
+ raise ArgumentError, "Unknown filetype: #{File.basename(filename)}"
+ end
+ end
+
+ def initialize
+ @chars=Hash.new
+ @xheight=nil
+ @glyph_class=Glyph
+ @outlinetype=nil
+ @info={}
+ @fontfilename=nil
+ @efactor=1.0
+ @slantfactor=0.0
+ @pathname=nil
+ end
+
+ # Factory for new glyphs. Return new instance of glyph_class (see
+ # Attributes).
+ def new_glyph
+ @glyph_class.new
+ end
+
+
+ def space # :nodoc:
+ chars['space'].wx
+ end
+
+ def filename # :nodoc:
+ File.basename(@pathname)
+ end
+
+ def fontfilename= (obj) # :nodoc:
+ @fontfilename=obj
+ end
+
+ # This one is documented in the 'attributes' section. If the global
+ # variable is unset, just use @filename, perhaps change afm to pfb
+ def fontfilename # :nodoc:
+ return @fontfilename if @fontfilename
+ case filename
+ when /\.afm$/
+ return filename.chomp(".afm") + ".pfb"
+ when /\.tt$/
+ return filename
+ end
+ end
+ end
+ end
+end
diff --git a/support/rfil/lib/rfil/font/truetype.rb b/support/rfil/lib/rfil/font/truetype.rb
new file mode 100644
index 0000000000..6c26287eb4
--- /dev/null
+++ b/support/rfil/lib/rfil/font/truetype.rb
@@ -0,0 +1,29 @@
+# truetype.rb -- read truetype font metrics
+#--
+# Last Change: Tue May 16 17:16:56 2006
+#++
+
+require 'rfil/font/afm'
+
+module RFIL
+ module Font
+ # Read TrueType fonts. Use like the AFM class.
+ class TrueType < AFM
+ def initialize(options={})
+ super
+ @outlinetype=:truetype
+ end
+ def read(filename)
+ @filename=File.basename(filename)
+ @fontfilename=filename
+ @name=@filename.chomp(".ttf")
+ self.pathname=Pathname.new(filename).realpath.to_s
+ a=`ttf2afm #{@fontfilename}`
+ parse(a)
+ # ttf2afm does not give an xheight!?
+ @xheight=@chars['x'].ury unless @xheight
+ self
+ end
+ end
+ end
+end
diff --git a/support/rfil/lib/rfil/fontcollection.rb b/support/rfil/lib/rfil/fontcollection.rb
new file mode 100644
index 0000000000..e206653d65
--- /dev/null
+++ b/support/rfil/lib/rfil/fontcollection.rb
@@ -0,0 +1,182 @@
+#--
+# fontcollection.rb
+# Last Change: Tue May 16 19:02:11 2006
+#++
+
+require 'rfil/rfi'
+require 'rfil/font'
+require 'rfil/helper'
+
+module RFIL # :nodoc:
+ class RFI
+ # A set of fonts (regular,bold,italic). Used to write an fd-file for
+ # LaTeX or a typescript for ConTeXt. Register different fonts and set
+ # encodings, so you dont't have to specify them in each font.
+
+ class FontCollection
+ include Helper
+ def self.documented_as_accessor(*args) # :nodoc:
+ end
+ def self.documented_as_reader(*args) # :nodoc:
+ end
+
+ attr_accessor :vendor
+
+ # attr_accessor :fontname
+
+ # Name of the font (collection)
+ attr_accessor :name
+
+ # One object or one or more objects in an array that describe the
+ # encoding of the postscript font. Object can either be a string
+ # that represents the filename of the encoding ("8r.enc", "8r") or
+ # an ENC object that already contains the encoding
+ documented_as_accessor :mapenc
+
+ # One object or one ore more objects in an array that describe the
+ # encoding of the font TeX expects. Object can either be a string
+ # that represents the filename of the encoding ("8r.enc", "8r") or
+ # an ENC object that already contains the encoding
+ documented_as_accessor :texenc
+
+ # hash of directories for writing files. Default to current working
+ # directory. The setting in the Font object overrides the setting here.
+ attr_accessor :dirs
+
+ attr_accessor :fonts
+
+ # sans, roman, typewriter
+ attr_accessor :style
+
+ attr_accessor :write_vf
+
+ attr_accessor :options
+
+ # list of temps
+ documented_as_reader :plugins
+
+ def initialize()
+ @kpse=Kpathsea.new
+ @basedir=nil
+ @name=nil
+ @texenc=nil
+ @mapenc=nil
+ @write_vf=true
+ @fonts=[]
+ @options={:verbose=>false,:dryrun=>false}
+ @style=nil
+ @dirs={}
+ @vendor=nil
+ @fontname=nil
+ set_dirs(Dir.getwd)
+ @plugins={}
+ # find temps-plugins
+ $:.each{ |d|
+ a=Dir.glob(d+"/rfil/rfi_plugin_*.rb")
+ a.each{ |f|
+ require f
+ }
+ }
+ ObjectSpace.each_object(Class){ |x|
+ if Plugin > x
+ t = x.new(self)
+ n = t.name
+ if @plugins.has_key?(n)
+ raise "Name already registered"
+ end
+ @plugins[n]=t
+ end
+ }
+ # done initializing plugins
+ end
+
+ # Add a font to the collection. Return the number of the font.
+ def register_font (font)
+ unless font.respond_to?(:maplines)
+ raise ArgumentError, "parameter does not look like a font"
+ end
+ fontnumber=@fonts.size
+ @fonts << font
+ return fontnumber
+ end
+
+ def run_plugin(name)
+
+ raise "don't know plugin #{name}" unless @plugins.has_key?(name)
+
+ # doc for run_plugin
+ # Return the contents of the file that should be used by the TeX
+ # macro package, i.e a typescript for ConTeXt or an fd-file for
+ # Context. Return value is an Array of Hashes. The Hash has three
+ # different keys:
+ # [<tt>:type</tt>] The type of the file, should be either <tt>:fd</tt> or <tt>:typescript</tt>.
+ # [<tt>:filename</tt>] the filename (without a path) of the file
+ # [<tt>:contents</tt>] the contents of the file.
+
+ files=@plugins[name].run_plugin
+ if files.respond_to?(:each)
+ files.each { |fh|
+ dir=get_dir(fh[:type])
+ filename=File.join(dir,fh[:filename])
+ puts "writing file #{filename}" if @options[:verbose]
+
+ unless @options[:dryrun]
+ ensure_dir(dir)
+ File.open(filename,"w") { |f| f << fh[:contents] }
+ end
+ }
+ end
+ end
+ def plugins #:nodoc:
+ @plugins.keys
+ end
+ def mapfile
+ mapfile=[]
+ @fonts.each {|font|
+ mapfile << font.maplines
+ }
+ mapfile.flatten
+ end
+ def mapenc # :nodoc:
+ return nil if @mapenc==:none
+ @mapenc
+ end
+ def mapenc= (enc) # :nodoc:
+ set_mapenc(enc)
+ end
+ def texenc # :nodoc:
+ if @texenc
+ @texenc
+ else
+ # @texenc not set
+ ret=nil
+ @kpse.open_file("8a.enc","enc") { |f|
+ ret = [ENC.new(f)]
+ }
+ return ret
+ end
+ end
+ def texenc= (enc) # :nodoc:
+ @texenc=[]
+ set_encarray(enc,@texenc)
+ end
+ def get_dir(type)
+ @dirs[type]
+ end
+ def write_files(options={})
+ mapdir=get_dir(:map); ensure_dir(mapdir)
+
+ @fonts.each {|font|
+ font.write_files(:mapfile => false)
+ mapfile << font.maplines
+ }
+ mapfilename=File.join(mapdir,@name+".map")
+ unless options[:dryrun]==true
+ File.open(mapfilename, "w") {|file|
+ file << mapfile.to_s
+ }
+ end
+ end
+ end
+ end # class RFI
+end # module RFIL
diff --git a/support/rfil/lib/rfil/helper.rb b/support/rfil/lib/rfil/helper.rb
new file mode 100644
index 0000000000..fc8fb807a0
--- /dev/null
+++ b/support/rfil/lib/rfil/helper.rb
@@ -0,0 +1,155 @@
+#--
+# helper.rb Last Change: Fri Jul 1 14:29:09 2005
+#++
+# Helper module for Font and FontCollection.
+
+# Here we define methods that are used in Font and FontCollection.
+
+require 'fileutils'
+require 'rfil/tex/kpathsea'
+
+module RFIL # :nodoc:
+ class RFI
+ module Helper
+ include TeX
+ def set_encarray(enc,where) #:nodoc:
+ if enc.instance_of?(ENC)
+ where.push(enc)
+ else
+ enc.each { |e|
+ if e.instance_of?(String)
+ e = e.chomp(".enc") + ".enc"
+ f=@kpse.open_file(e,"enc")
+ where.push(ENC.new(f))
+ f.close
+ elsif e.instance_of?(ENC)
+ where.push(e)
+ end
+ }
+ end
+ end
+ def set_mapenc(enc) # :nodoc:
+ @mapenc=enc
+
+ # nil/:none is perfectly valid
+ return if enc==nil or enc==:none
+
+ if enc.instance_of?(ENC)
+ @mapenc = enc
+ else
+ enc.find { |e|
+ if e.instance_of?(String)
+ e = e.chomp(".enc") + ".enc"
+ @kpse.open_file(e,"enc") { |f|
+ @mapenc = ENC.new(f)
+ }
+ elsif e.instance_of?(ENC)
+ @mapenc = e
+ end
+ }
+ end
+ end
+ # call-seq:
+ # set_dirs(string)
+ # set_dirs(hash)
+ #
+ # Set the base dir of all font related files. Acts only as a storage
+ # for the information. The automatic font installation method in
+ # Font#write_files uses this information. When a _string_ is passed,
+ # use this as the base dir for all files, when a hash is given, the
+ # keys must be one of <tt>:afm</tt>, <tt>:tfm</tt>,
+ # <tt>:vf</tt>,<tt>:map</tt>, <tt>:pfb</tt>, <tt>:tt</tt>, <tt>:tds</tt>.
+ def set_dirs(arg)
+ # tds needs testing! set vendor/fonname before/after set_dirs
+ types=[:afm, :tfm, :vpl, :vf, :pl, :map, :type1,:truetype, :fd, :typescript]
+ if arg.instance_of? String
+ @basedir=arg
+ types.each { |sym|
+ @dirs[sym]=arg
+ }
+ elsif arg.instance_of? Hash
+ if arg[:base]
+ @basedir=arg[:base]
+ end
+ if arg[:tds]==true
+ suffix = if @vendor and @name
+ File.join(@vendor,@name)
+ else
+ ""
+ end
+ types.each { |t|
+ subdir= case t
+ when :afm
+ File.join("fonts/afm",suffix)
+ when :tfm
+ File.join("fonts/tfm",suffix)
+ when :vpl
+ File.join("fonts/source/vpl",suffix)
+ when :vf
+ File.join("fonts/vf",suffix)
+ when :pl
+ File.join("fonts/source/pl",suffix)
+ when :map
+ "fonts/map/dvips"
+ when :type1
+ File.join("fonts/type1",suffix)
+ when :truetype
+ File.join("fonts/truetype",suffix)
+ when :fd
+ File.join("tex/latex",suffix)
+ when :typescript
+ File.join("tex/context",suffix)
+ else
+ raise "unknown type"
+ end
+ @dirs[t] = File.join(@basedir,subdir)
+ }
+ else
+ arg.each { |key,value|
+ @dirs[key] = value
+ }
+ end
+ end
+ end
+ def ensure_dir(dirname) # :nodoc:
+ if File.exists?(dirname)
+ if File.directory?(dirname)
+ # nothing to do
+ else
+ # exists, but not dir
+ raise "File exists, but is not a directory: #{dirname}"
+ end
+ else
+ # file does not exist, we can create a directory (hopefully)
+
+ puts "Creating directory hierarchy #{dirname}" if @options[:verbose]
+
+ unless @options[:dryrun]
+ FileUtils.mkdir_p(dirname)
+ end
+ end
+ end #ensure_dir
+ end # helper
+ # options is a hash, but with lookup to a fontcollection
+ class Options # :nodoc:
+ def initialize(fontcollection)
+ @fc=fontcollection
+ @options={}
+ end
+ def [](idx)
+ if @options[idx]
+ return @options[idx]
+ end
+ if @fc
+ @fc.options[idx]
+ else
+ nil
+ end
+ end
+ def []=(idx,obj)
+ @options[idx]=obj
+ end
+
+ end
+ end # RFI
+end
diff --git a/support/rfil/lib/rfil/rfi.rb b/support/rfil/lib/rfil/rfi.rb
new file mode 100644
index 0000000000..2ea1f50bd1
--- /dev/null
+++ b/support/rfil/lib/rfil/rfi.rb
@@ -0,0 +1,472 @@
+# 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
+ # [<tt>:type</tt>] Type of the file (<tt>:fd</tt>, <tt>:typescript</tt> etc.)
+ # [<tt>:filename</tt>] The filename (without a path) of the file.
+ # [<tt>:contents</tt>] 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
+
+ # <tt>[0, 1, 2, 3, 4, 5, 6, 7 ]</tt>
+ #
+ # <tt>[=: , |=: , |=:> , =:| , =:|>, |=:|, |=:|>, |=:|>> ]</tt>
+ #
+ # <tt>[LIG, /LIG, /LIG>, LIG/, LIG/>, /LIG/, /LIG/>, /LIG/>>]</tt>
+ 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
diff --git a/support/rfil/lib/rfil/rfi_plugin_context.rb b/support/rfil/lib/rfil/rfi_plugin_context.rb
new file mode 100644
index 0000000000..08a904b3ef
--- /dev/null
+++ b/support/rfil/lib/rfil/rfi_plugin_context.rb
@@ -0,0 +1,90 @@
+=begin rdoc
+Plugin for RFIL to create a typescript usable for ConTeXt.
+=end
+
+# :enddoc:
+
+class TypescriptWriterConTeXt < RFIL::RFI::Plugin
+
+ def initialize(fontcollection)
+ @fc=fontcollection
+ super(:context,:typescript)
+ end
+
+ STOPTYPESCRIPT="\\stoptypescript\n\n"
+
+ def run_plugin
+ ret=[]
+ str=""
+ puts "running context plugin" if @fc.options[:verbose]
+ @fc.texenc.each { |e|
+ str << typescript(e)
+ str << "\n"
+ }
+ h={}
+ h[:type]=:typescript
+ h[:filename],h[:contents]=["type-#{@fc.name}.tex",str]
+ ret.push(h)
+ ret
+ end
+
+ # Returns hash: Style, font
+ def find_fonts
+ ret={}
+ @fc.fonts.each { |font|
+ ret[""]=font if font.variant==:regular and font.weight==:regular
+# ret[""]=font if font.variant==:regular and font.weight==:regular and font.style!=:sans
+ ret["Bold"]=font if font.variant==:regular and font.weight==:bold
+ ret["Italic"]=font if font.variant==:italic and font.weight==:regular
+ ret["Caps"]=font if font.variant==:smallcaps and font.weight==:regular
+ }
+ ret
+ end
+ def typescript(e)
+ contextenc=case e.encname
+ when "ECEncoding"
+ "ec"
+ when "TS1Encoding"
+ "ts1"
+ when "T1Encoding"
+ "tex256"
+ when "TeXBase1Encoding"
+ "8r"
+ else
+ raise "unknown context encoding: #{e.encname}"
+ end
+ # i know that this is crap, it's just a start
+ contextstyle=case @fc.style
+ when :sans
+ "Sans"
+ when :roman, :serif
+ "Serif"
+ when :typewriter
+ "Typewriter"
+ else
+ raise "unknown style found: #{@fc.style}"
+ end
+ tmp = ""
+ fontname=@fc.name
+ tmp << "\\starttypescript[#{@fc.style}][#{fontname}][name]\n"
+ find_fonts.sort{ |a,b| a[0] <=> b[0]}.each { |style,font|
+ tmp << "\\definefontsynonym [#{contextstyle}"
+ p style
+ tmp << "#{style}" if style.length > 0
+ tmp << "] [#{fontname}"
+ tmp << "-#{style}" if style.length > 0
+ tmp << "]\n"
+ }
+ tmp << STOPTYPESCRIPT
+
+ tmp << "\\starttypescript[#{@fc.style}][#{fontname}][#{contextenc}]\n"
+ find_fonts.sort{ |a,b| a[0] <=> b[0]}.each { |style,font|
+ tmp << "\\definefontsynonym [#{fontname}"
+ tmp << "-#{style}" if style.length > 0
+ tmp << "][#{font.tex_fontname(e)}]\n"
+ }
+ tmp << STOPTYPESCRIPT
+
+ return tmp
+ end
+end
diff --git a/support/rfil/lib/rfil/rfi_plugin_latex.rb b/support/rfil/lib/rfil/rfi_plugin_latex.rb
new file mode 100644
index 0000000000..86f2e9a1ad
--- /dev/null
+++ b/support/rfil/lib/rfil/rfi_plugin_latex.rb
@@ -0,0 +1,95 @@
+=begin rdoc
+Plugin for RFIL to create a fontdefinition file (<tt>.fd</tt>) for LaTeX
+=end
+
+# :enddoc:
+
+class FDWriterLaTeX < RFIL::RFI::Plugin
+
+ def initialize(fontcollection)
+ @fc=fontcollection
+ super(:latex,:sty)
+ end
+
+ def run_plugin
+ ret=[]
+ @fc.texenc.each { |e|
+ h={}
+ h[:type]=:fd
+ h[:filename],h[:contents]=latex_fd(e)
+ ret.push(h)
+ }
+ ret
+ end
+
+
+ # example, should be an extra plugin
+ def latex_fd(e)
+ latexenc=case e.encname
+ when "ECEncoding","T1Encoding"
+ "T1"
+ when "TeXBase1Encoding"
+ "8r"
+ when "TS1Encoding"
+ "TS1"
+ when "OT2AdobeEncoding"
+ "OT2"
+ else
+ raise "unknown latex encoding: #{e.encname}"
+ end
+ filename="#{latexenc}#{@fc.name}.fd"
+
+ fd="\\ProvidesFile{#{filename}}
+\\DeclareFontFamily{#{latexenc}}{#{@fc.name}}{}
+"
+ weight=[:m,:b,:bx]
+ variant=[:n,:sc,:sl,:it]
+ for i in 0..11
+ w=weight[i/4]
+ v=variant[i % 4]
+ f=find_font(w,v)
+ if f
+ name = f.tex_fontname(e)
+ else
+ if i < 4
+ name = "ssub * #{@fc.name}/m/n"
+ else
+ name = "ssub * #{@fc.name}/#{weight[i/4 - 1]}/#{v}"
+ end
+ end
+
+# [[:m,:n],[:m,:sc],[:m,:sl],[:m,:it],
+# [:b,:n],[:b,:sc],[:b,:sl],[:b,:it],
+# [:bx,:n],[:bx,:sc],[:bx,:sl],[:bx,:it]].each{ |w,v|
+# f=find_font(w,v)
+
+# name = f ? f.tex_fontname(e) : "<->ssub * #{@fc.name}/m/n"
+ fd << "\\DeclareFontShape{#{latexenc}}{#{@fc.name}}{#{w}}{#{v}}{
+ <-> #{name}
+}{}
+"
+ end
+ return [filename,fd]
+end
+ def find_font(w,v)
+ weight={}
+ variant={}
+ weight[:m]=:regular
+ weight[:b]=:bold
+ variant[:n]=:regular
+ variant[:it]=:italic
+ variant[:sl]=:slanted
+ variant[:sc]=:smallcaps
+
+ # w is one of :m, :b, :bx
+ # v is one of :n, :sc, :sl, :it
+ @fc.fonts.each { |font|
+ #p b[:weight]==weight[w]
+ if font.variant ==variant[v] and font.weight==weight[w]
+ return font
+ end
+ }
+ return nil
+ end
+
+end
diff --git a/support/rfil/lib/rfil/tex/enc.rb b/support/rfil/lib/rfil/tex/enc.rb
new file mode 100644
index 0000000000..d2bb21a620
--- /dev/null
+++ b/support/rfil/lib/rfil/tex/enc.rb
@@ -0,0 +1,225 @@
+#--
+# enc.rb - read and parse TeX's encoding files
+# Last Change: Tue May 16 17:24:31 2006
+#++
+# See the class ENC for the api description.
+
+require 'strscan'
+require 'set'
+require 'forwardable'
+
+module RFIL
+ module TeX
+
+ # = ENC -- Access encoding files
+ #
+ # == General information
+ #
+ # Read a TeX encoding vector (<tt>.enc</tt>-file) and associated
+ # ligkern instructions. The encoding slot are accessible via <em>[]</em>
+ # and <em>[]=</em>, just like an Array.
+ #
+ # == Example usage
+ #
+ # === Read an encoding file
+ # filename = "/opt/tetex/3.0/texmf/fonts/enc/dvips/base/EC.enc"
+ # File.open(filename) { |encfile|
+ # enc=ENC.new(encfile)
+ # enc.encname # => "ECEncoding"
+ # enc # => ['grave','acute',...]
+ # enc.filename # => "EC.enc"
+ # enc.ligkern_instructions # => ["space l =: lslash","space L =: Lslash",... ]
+ # }
+ # === Create an encoding
+ # enc=ENC.new
+ # enc.encname="Exampleenc"
+ # enc[0]="grave"
+ # # all undefined slots are ".notdef"
+ # ....
+ #
+ # # write encoding to <tt>new.enc</tt>
+ # File.open("new.enc") do |f|
+ # f << enc.to_s
+ # end
+ # ---
+ # Remark: This interface is pretty much fixed.
+ #--
+ # dont't subclass Array directly, it might be a bad idea. See for
+ # example [ruby-talk:147327]
+ #++
+
+ class ENC # < DelegateClass(Array)
+ def self.documented_as_accessor(*args) # :nodoc:
+ end
+
+ extend Forwardable
+ def_delegators(:@encvector, :size, :[],:each, :each_with_index)
+
+ # _encname_ is the PostScript name of the encoding vector.
+ attr_accessor :encname
+
+ # ligkern_instructions is an array of strings (instructions) as
+ # found in the encoding file, such as:
+ # "quoteright quoteright =: quotedblright"
+ # "* {} space"
+ attr_accessor :ligkern_instructions
+
+ # Hash: key is glyph name, value is a Set of indexes.
+ # Example: glyph_index['hyphen']=#<Set: {45, 127}> in
+ # <tt>ec.enc</tt>. Automatically updated when changing the encoding
+ # vector via <em>[]=</em>.
+ attr_reader :glyph_index
+
+ # Filename of the encoding vector. Used for creating mapfile
+ # entries. Always ends with ".enc" if read (unless it is unset).
+ documented_as_accessor :filename
+
+ # Optional enc is either a File object or a string with the contents
+ # of a file. If set, the object is initialized with the given
+ # encoding vector.
+ def initialize (enc=nil)
+ @glyph_index={}
+ @ligkern_instructions=[]
+ # File, Tempfile, IO respond to read
+ if enc
+ @encvector=[]
+ string = enc.respond_to?(:read) ? enc.read : enc
+ if enc.respond_to?(:path)
+ self.filename= enc.path
+ end
+ parse(string)
+ else
+ @encvector=Array.new(256,".notdef")
+ end
+ end
+
+ def filename # :nodoc:
+ @filename
+ end
+
+ def filename=(fn) # :nodoc:
+ @filename=File.basename(fn.chomp(".enc")+".enc")
+ end
+
+ # Return true if the encoding name and the encoding Array are the
+ # same. If _obj_ is an Array, only compare the Array elements.
+ def ==(obj)
+ return false if obj==nil
+ if obj.instance_of?(ENC)
+ return false unless @encname==obj.encname
+ end
+
+ return false unless obj.respond_to?(:[])
+ 0.upto(255) { |i|
+ return false if @encvector[i]!=obj[i]
+ }
+ true
+ end
+
+ # todo: document and test
+ def -(obj)
+ tmp=[]
+ for i in 0..255
+ tmp[i]=obj[i]
+ end
+ @encvector - tmp
+ end
+
+ # also updates the glyph_index
+ def []=(i,obj) # :nodoc:
+ if obj==nil and @encvector[i] != nil
+ @glyph_index.delete(@encvector[i])
+ return obj
+ end
+
+ @encvector[i]=obj
+ addtoindex(obj,i)
+ return obj
+ end
+
+ # Return a string representation of the encoding that is compatible
+ # with dvips and alike.
+ def to_s
+ str = ""
+ @ligkern_instructions.each { |instr|
+ str << "% LIGKERN #{instr} ;\n"
+ }
+ str << "%\n"
+ str << "/#@encname [\n"
+ @encvector.each_with_index { |glyphname,i|
+ str << "% #{i}\n" if (i % 16 == 0)
+ str << " " unless (i % 8 == 0)
+ str << "/#{glyphname}"
+ str << "\n" if (i % 8 == 7)
+ }
+ str << "] def\n"
+ str
+ end
+
+ #######
+ private
+ #######
+
+ # creates the glyph_index from the encvector. Use this method after
+ # you made changes to the encvector.
+ def update_glyph_index
+ @encvector.each_with_index { |name,i|
+ next if name==".notdef"
+ addtoindex(name,i)
+ }
+ end
+
+ # Adds position i to glyph_index for glyph _glyph_.
+ def addtoindex(glyph,i)
+ return if glyph==".notdef"
+ if @glyph_index[glyph]
+ @glyph_index[glyph].add i
+ else
+ @glyph_index[glyph]=Set.new().add(i)
+ end
+ end
+
+ # return the next postscript element (e.g. /name or [ )
+ def tok(s)
+ unless s.peek(1) == "/"
+ s.skip_until(/[^\/\[\]]+/) # not '/' '[' or ']'
+ end
+ s.scan(/(?:\/\.?\w+|\[|\])/)
+ end
+
+ # fill Array with contents of string.
+ def parse(str)
+ count=0
+ s=StringScanner.new(str)
+ ligkern=""
+ while s.skip_until(/^%\s+LIGKERN\s+/)
+ ligkern << s.scan_until(/$/)
+ end
+ ligkern.split(';').each { |instruction|
+ @ligkern_instructions.push instruction.strip
+ }
+ s.string=(str.gsub(/%.*/,''))
+ t=tok(s)
+ @encname=t[1,t.length-1]
+ loop do
+ t = tok(s)
+ case t
+ when "["
+ # ignore
+ when "]"
+ unless @encvector.size == 256
+ raise "Unexpected size of encoding. It should contain 256 entries, but has #{@encvector.size} entries."
+ end
+ update_glyph_index
+ return
+ else
+ name = t[1,t.length-1]
+ @encvector.push(name)
+ end
+ end
+ # never reached
+ raise "Internal ENC error"
+ end
+ end #class Enc
+ end
+end
diff --git a/support/rfil/lib/rfil/tex/kpathsea.rb b/support/rfil/lib/rfil/tex/kpathsea.rb
new file mode 100644
index 0000000000..d8bc3d4cb7
--- /dev/null
+++ b/support/rfil/lib/rfil/tex/kpathsea.rb
@@ -0,0 +1,65 @@
+#--
+# kpathsea.rb - libkpathsea access for ruby
+# Last Change: Tue May 16 17:23:14 2006
+#++
+
+
+module RFIL
+ module TeX
+
+ # Find TeX related files with help of the 'kpsewhich' program.
+ class Kpathsea
+ # _progname_ defaults to the name of the main Ruby script.
+ # _progname_ is used to find program specific files as in
+ # <tt>TEXINPUT.progname</tt> in the <tt>texmf.cnf</tt>.
+ def initialize (progname=File.basename($0))
+ raise ArgumentError if progname.match(/('|")/)
+ @progname=progname
+ end
+
+ def reset_program_name(suffix)
+ @progname=suffix
+ end
+
+ # Return the complete path of the file _name_. _name_ must not
+ # contain single or double quotes.
+ def find_file(name,fmt="tex",mustexist=false)
+ raise ArgumentError if name.match(/('|")/)
+ raise ArgumentError if fmt.match(/('|")/)
+ runkpsewhich(name,fmt,mustexist)
+ end
+
+ # Return a File object. Raise Errno::ENOENT if file is not found. If
+ # block is given, a File object is passed into the block and the
+ # file gets closed when leaving the block. It behaves exactly as
+ # the File.open method.
+ def open_file(name,fmt="tex")
+ loc=self.find_file(name,fmt)
+ raise Errno::ENOENT, "#{name}" unless loc
+ if block_given?
+ File.open(loc) { |file|
+ yield file
+ }
+ else
+ File.open(loc)
+ end
+ end
+
+ private
+
+ def runkpsewhich(name,fmt,mustexist)
+ fmt.untaint
+ name.untaint
+ @progname.untaint
+ # path or amok XXX
+ cmdline= "kpsewhich -progname=\"#{@progname}\" -format=\"#{fmt}\" #{name}"
+ # puts cmdline
+ lines = ""
+ IO.popen(cmdline) do |io|
+ lines = io.readlines
+ end
+ return $? == 0 ? lines.to_s.chomp.untaint : nil
+ end
+ end
+ end
+end
diff --git a/support/rfil/lib/rfil/tex/tfm.rb b/support/rfil/lib/rfil/tex/tfm.rb
new file mode 100644
index 0000000000..22465fe92b
--- /dev/null
+++ b/support/rfil/lib/rfil/tex/tfm.rb
@@ -0,0 +1,1200 @@
+# tfm.rb - Access information of a TeX font metric file.
+#--
+# Last Change: Tue May 16 19:12:26 2006
+#++
+
+module RFIL # :nodoc:
+ module TeX # :nodoc:
+
+ # TFM (TeX font metric) reader/writer class
+ class TFM
+ class TFMReader
+ # reading a tfm file is about 10 times faster than doing
+ # `tftop xyz.pl` and using PL#parse. And only a bit slower than
+ # `tftop xyz.pl > /dev/null` alone. (1.3 secs. vs. 0.9 secs. - 10 times)
+
+ # Output more information
+ attr_accessor :verbose
+
+ LIGTAG=1
+ STOPFLAG=128
+ KERNFLAG=128
+ LIGSIZE=5000
+ class TFMError < Exception
+ end
+
+ def initialize(tfmobject=nil)
+ # type of font: textfont (:vanilla), math symbols (:mathsy), math
+ # extension (:mathex)
+ @font_type=nil
+
+ @perfect=true
+
+ # this is where we store all our data
+ @tfm=if tfmobject
+ tfmobject
+ else
+ TFM.new
+ end
+ end # initialize
+
+ # _tfmdata_ is a string with the contents of the tfm (binary) file.
+ def parse(tfmdata)
+ @tfmdata=tfmdata.unpack("C*")
+ @index=0
+
+ @lf=get_dbyte
+ @lh=get_dbyte
+ @bc=get_dbyte
+ @ec=get_dbyte
+ @nw=get_dbyte
+ @nh=get_dbyte
+ @nd=get_dbyte
+ @ni=get_dbyte
+ @nl=get_dbyte
+ @nk=get_dbyte
+ @ne=get_dbyte
+ @np=get_dbyte
+
+ if @verbose
+ puts "lf=#{@lf}"
+ puts "lh=#{@lh}"
+ puts "bc=#{@bc}"
+ puts "ec=#{@ec}"
+ puts "nw=#{@nw}"
+ puts "nh=#{@nh}"
+ puts "nd=#{@nd}"
+ puts "ni=#{@ni}"
+ puts "nl=#{@nl}"
+ puts "nk=#{@nk}"
+ puts "ne=#{@ne}"
+ puts "np=#{@np}"
+ end
+ raise TFMError, "The following condition is not true: bc-1 <= ec and ec <= 255" unless @bc-1 <= @ec and @ec <= 255
+ raise TFMError, "The following condition is not true: ne <= 256" unless @ne <= 256
+ raise TFMError, "The following condition is not true: lf == 6+lh+(ec-bc+1)+nw+nh+nd+ni+nl+nk+ne+np" unless @lf == 6+@lh+(@ec-@bc+1)+@nw+@nh+@nd+@ni+@nl+@nk+@ne+@np
+
+ # § 23
+ @header_base = 6
+ @char_base = @header_base + @lh
+ @width_base = @char_base + (@ec - @bc) + 1
+ @height_base = @width_base + @nw
+ @depth_base = @height_base + @nh
+ @italic_base = @depth_base + @nd
+ @lig_kern_base = @italic_base + @ni
+ @kern_base = @lig_kern_base + @nl
+ @exten_base = @kern_base + @nk
+ @param_base = @exten_base + @ne
+
+ parse_header
+ parse_params
+ parse_char_info
+ parse_lig_kern
+ # exten?
+
+ return @tfm
+ end # parse
+
+ #######
+ private
+ #######
+
+ def parse_header
+ @index = @header_base * 4
+ @tfm.checksum=get_qbyte
+ @tfm.designsize=get_fix_word
+ if @lh >= 3
+ count = get_byte
+ @tfm.codingscheme=get_chars(count)
+ @font_type= case @tfm.codingscheme
+ when "TeX math symbols"
+ :mathsy
+ when "TeX math extension"
+ :mathex
+ else
+ :vanilla
+ end
+ end
+ @index = (@header_base + 12) * 4
+ if @lh > 12
+ count = get_byte
+ @tfm.fontfamily=get_chars(count)
+ end
+ @index = (@header_base + 17 ) * 4
+ if @lh >= 17
+ @tfm.sevenbitsafeflag=get_byte > 127
+ # two bytes ignored
+ get_byte ; get_byte
+ @tfm.face=get_byte
+ end
+ # let us ignore the rest of the header (TeX ignores it, so we may
+ # do the same)
+ end # parse_header
+
+ def parse_params
+ @index=@param_base * 4
+ @tfm.params << nil
+ @np.times {
+ @tfm.params << get_fix_word
+ }
+ end # parse_params
+
+ # §78 TFtoPL
+ def parse_char_info
+ @index=@char_base *4
+ (@bc..@ec).each { |n|
+ tmp=if @tfm.chars[n]
+ @tfm.chars[n]
+ else
+ Hash.new
+ end
+ index=get_byte
+ tmp[:charwd]=get_fix_word((@width_base + index)*4)
+ b=get_byte
+ tmp[:charht]=get_fix_word((@height_base + (b >> 4))*4)
+ tmp[:chardp]=get_fix_word((@depth_base + (b % 16))*4)
+ tmp[:charic]=get_fix_word((@italic_base + (get_byte >> 2))*4)
+ # we ignore the remainder and look it up on demand
+ get_byte
+ if index == 0
+ @tfm.chars[n]=nil
+ else
+ @tfm.chars[n]=tmp
+ end
+ }
+ end
+
+ # now for the ugly part in the tfm, §63 pp
+ # Hey, we do a more clever implementation: we do not check for any
+ # errors. So coding is only a few lines instead of a few sections.
+ # this one took me so much time (the original, not this
+ # implementation), I am really frustrated.
+ def parse_lig_kern
+ # array that stores 'instruction that starts at x can be found in
+ # @tfm.lig_kern at position y'
+ start_instr=[]
+ @bc.upto(@ec) { |c|
+ next unless @tfm.chars[c]
+ if char_tag(c) == LIGTAG
+ start=get_lig_starting_point(c)
+ if start_instr[start] != nil
+ # we have already stored this ligkern
+ @tfm.chars[c][:lig_kern]=start_instr[start]
+ next
+ end
+ tmp=[]
+
+ start_instr[start]=@tfm.lig_kern.size
+ @tfm.lig_kern.push tmp
+ @tfm.chars[c][:lig_kern]=start_instr[start]
+
+ begin
+ s=get_byte(lig_step(start))
+ puts "warning: skip > 128 (#{s}) I don't know what to do." if s > 128
+ n,op,rem=get_byte(lig_step(start)+1),get_byte(lig_step(start)+2),get_byte(lig_step(start)+3)
+
+ if op >= 128
+ # kern!
+ kernamount=get_fix_word((@kern_base + (256 * (op-128) +rem)) *4)
+ tmp.push [:krn, n, kernamount]
+ else
+ tmp.push [TFM::LIGOPS[op], n, rem ]
+ end
+ tmp.push [:skip, s] if s > 0 and s < 128
+ start += 1
+ end until s >= 128
+ end
+ }
+ end
+
+
+ # --------------------------------------------------
+ def char_tag(c)
+ @tfmdata[((@char_base + c - @bc ) *4 + 2)] % 2
+ end
+ def char_remainder(c)
+ @tfmdata[((@char_base + c - @bc ) *4 + 3)]
+ end
+ def get_lig_starting_point(char)
+ # warning: had some wine
+ return nil unless char_tag(char) == LIGTAG
+ r = char_remainder(char)
+ s=get_byte(lig_step(r))
+ if s > 128
+ # it does not start here, it starts somewhere else
+ n,op,rem=get_byte(lig_step(r)+1),get_byte(lig_step(r)+2),get_byte(lig_step(r)+3)
+
+ 256*op+rem
+ else
+ r
+ end
+ end
+
+ def get_byte(i=nil)
+ global = i == nil
+ i = @index if global
+ r=@tfmdata[i]
+ @index += 1 if global
+ r
+ end
+ # 16 bit integer
+ def get_dbyte
+ r = (@tfmdata[@index] << 8) + @tfmdata[@index + 1]
+ @index += 2
+ r
+ end
+ # 32 bit integer
+ def get_qbyte
+ r = (@tfmdata[@index] << 24) + (@tfmdata[@index+1] << 16) + (@tfmdata[@index+2] << 8) + @tfmdata[@index+3]
+ @index += 4
+ r
+ end
+ def get_chars(count)
+ ret=""
+ count.times { |count|
+ c=@tfmdata[@index + count]
+ ret << c.chr if c > 0
+ }
+ @index += count
+ ret
+ end
+ def get_fix_word(i=nil)
+ global = i==nil
+ i = @index if global
+ b=@tfmdata[(i..i+3)]
+ @index += 4 if global
+ a= (b[0] * 16) + (b[1].div 16)
+ f= ((b[1] % 16) * 256 + b[2] ) * 256 + b[3]
+
+ str = ""
+ if a > 03777
+ str << "-"
+ a = 010000 - a
+ if f > 0
+ f = 04000000 - f
+ a -= 1
+ end
+ end
+ # Knuth, TFtoPL §42
+
+ delta = 10
+ f=10*f+5
+
+ str << a.to_s + "."
+ begin
+ if delta > 04000000
+ f = f + 02000000 - ( delta / 2 )
+ end
+ str << (f / 04000000).to_s
+ f = 10 * ( f % 04000000)
+ delta *= 10
+ end until f <= delta
+ str.to_f
+ end
+ def lig_step(num)
+ (@lig_kern_base + num )*4
+ end
+ end
+
+
+
+ class TFMWriter
+ # More output to stdout
+ attr_accessor :verbose
+
+ WIDTH=1
+ HEIGHT=2
+ DEPTH=3
+ ITALIC=4
+
+ def initialize(tfmobject)
+ @tfm=tfmobject
+ @chars=[]
+ @lig_kern=nil
+ # for the sorting
+ @memsize=1028 + 4
+ # @memory=Array.new(@memsize)
+ @memory=[]
+ @whdi_index=[]
+ @mem_ptr=nil
+ @link=Array.new(@memsize)
+ @index=[]
+ @memory[0]=017777777777
+ @memory[WIDTH]=0
+ @memory[HEIGHT]=0
+ @memory[DEPTH]=0
+ @memory[ITALIC]=0
+ @link[WIDTH]=0
+ @link[HEIGHT]=0
+ @link[DEPTH]=0
+ @link[ITALIC]=0
+ @mem_ptr = ITALIC
+ @next_d=nil
+
+
+ @bchar_label=077777
+
+ @data=[]
+ @lf = 0
+ @lh = 0 # ok
+ @bc = 0 # ok
+ @ec = 0 # ok
+ @nw = 0 # ok
+ @nh = 0 # ok
+ @nd = 0 # ok
+ @ni = 0 # ok
+ @nl = 0 # ok
+ @nk = 0 # ok
+ @ne = 0 # ingore
+ @np = 0 # ok
+ end
+ def to_data
+ update_bc_ec
+ calculate_header
+ # width,heigt,dp,ic index
+ update_whdi_index
+ # @widths, @heights, @depths, @italics finished
+ update_lig_kern
+ # @kerns finished
+ update_parameters
+ # @parameters finished
+ @lf = 6 + @lh + (@ec - @bc + 1) + @nw + @nh + @nd + @ni + @nl + @nk + @ne + @np
+ @data += out_dbyte(@lf)
+ @data += out_dbyte(@lh)
+ @data += out_dbyte(@bc)
+ @data += out_dbyte(@ec)
+ @data += out_dbyte(@nw)
+ @data += out_dbyte(@nh)
+ @data += out_dbyte(@nd)
+ @data += out_dbyte(@ni)
+ @data += out_dbyte(@nl)
+ @data += out_dbyte(@nk)
+ @data += out_dbyte(@ne)
+ @data += out_dbyte(@np)
+ @data += @header
+ calculate_chars
+ @data += @chars
+ @data += @widths
+ @data += @heights
+ @data += @depths
+ @data += @italics
+ @data += @lig_kern
+ @data += @kerns
+ @data += @parameters
+
+ @data.pack("C*")
+ end
+
+ def calculate_chars
+ (@bc..@ec).each { |n|
+ if @tfm.chars[n]
+ wd_idx=@index[@widths_orig[n]]
+ ht_idx=@index[@heights_orig[n]] ? @index[@heights_orig[n]] << 4 : 0
+ dp_idx=@index[@depths_orig[n]] ? @index[@depths_orig[n]] : 0
+ ic_idx= @index[@italics_orig[n]] ? (@index[@italics_orig[n]] << 2) : 0
+ tag = @tfm.chars[n][:lig_kern] ? 1 : 0
+ remainder= @tfm.chars[n][:lig_kern] ? @instr_index[@tfm.chars[n][:lig_kern]] : 0
+ @chars += [wd_idx,ht_idx + dp_idx, ic_idx + tag, remainder]
+ else
+ @chars += [0,0,0,0]
+ end
+ }
+ end
+
+ def update_parameters
+ @parameters=[]
+
+ @tfm.params.each_with_index { |p,i|
+ next if i==0
+ @parameters += out_fix_word(p)
+ }
+ @np=@parameters.size / 4
+ end
+
+ def update_whdi_index
+ @widths_orig=[]
+ @heights_orig=[]
+ @depths_orig=[]
+ @italics_orig=[]
+
+ (@bc..@ec).each { |c|
+ if @tfm.chars[c]
+ @widths_orig[c]= sort_in(WIDTH,@tfm.chars[c][:charwd])
+ @heights_orig[c] = sort_in(HEIGHT,@tfm.chars[c][:charht] || 0)
+ @depths_orig[c] = sort_in(DEPTH,@tfm.chars[c][:chardp] || 0 )
+ @italics_orig[c] = sort_in(ITALIC,@tfm.chars[c][:charic] || 0 )
+ else
+ @widths_orig[c] = 0
+ @depths_orig[c] = 0
+ @heights_orig[c] = 0
+ @italics_orig[c] = 0
+ end
+
+ }
+ delta=shorten(WIDTH,200)
+ set_indices(WIDTH,delta)
+ delta=shorten(HEIGHT,15)
+ set_indices(HEIGHT,delta)
+ delta=shorten(DEPTH,15)
+ set_indices(DEPTH,delta)
+ delta=shorten(ITALIC,63)
+ set_indices(ITALIC,delta)
+
+
+ @widths = fill_index(WIDTH)
+ @heights = fill_index(HEIGHT)
+ @depths = fill_index(DEPTH)
+ @italics = fill_index(ITALIC)
+ @nw= @widths.size/4
+ @nh= @heights.size/4
+ @nd= @depths.size/4
+ @ni= @italics.size/4
+ end
+
+ def update_lig_kern
+ kerns=[]
+ instructions=[]
+ (@bc..@ec).each { |n|
+ next unless @tfm.chars[n]
+ next unless @tfm.chars[n][:lig_kern]
+ # we can skip aliases
+ next if instructions[@tfm.chars[n][:lig_kern]]
+ newinstr=[]
+ @tfm.lig_kern[@tfm.chars[n][:lig_kern]].each { |instr,*rest|
+ skip=nextchar=op=remainder=0
+ case instr
+ when :krn
+ i=nil
+ unless i = kerns.index(rest[1])
+ kerns << rest[1]
+ i=kerns.size - 1
+ end
+ skip=0
+ nextchar=rest[0]
+ remainder=i % 256
+ op = remainder / 256 + 128
+ # :stopdoc:
+ when :lig, :"lig/", :"/lig", :"/lig/", :"lig/>", :"/lig>", :"/lig/>", :"/lig/>>"
+ # :startdoc:
+ skip=0
+ nextchar,remainder=rest
+ op=TFM::LIGOPS.index(instr)
+ when :skip
+ # todo: test for incorrect situations
+ newinstr[-4] = rest[0]
+ next
+ else
+ raise "don't know instruction #{instr}"
+ end
+ newinstr += [skip,nextchar,op,remainder]
+ }
+ newinstr[-4] = 128
+ instructions[@tfm.chars[n][:lig_kern]] = newinstr
+ }
+
+ # we have all instructions collected in an array. The problem now
+ # is to fill the @lig_kern array so that all start of instruction
+ # programs are within the first 256 words of @lig_kern. So we keep
+ # filling the @lig_kern array until there would not be enough room
+ # left for the indirect nodes for the remaining count of
+ # instructions. Say, we have 50 instructions left to go and there
+ # are 60 words free in the first 256 words of @lig_kern, but the
+ # current instruction would take more then 10 words, we need to
+ # stop and fill the @lig_kern array with the indirect nodes and
+ # then continue with the instructions. The following
+ # implementation seems to work, but I refuse to prove it and it is
+ # definitely not the most beautiful piece of code I have written.
+
+ @instr_index=[]
+ @lig_kern=[]
+
+ total_instr=instructions.size
+ if total_instr > 0
+ instr_left=total_instr
+ thisinstr=instructions.shift
+
+ while (256 - @lig_kern.size / 4) - instr_left - thisinstr.size / 4 > 0
+ @instr_index[total_instr-instr_left]=@lig_kern.size / 4
+ @lig_kern += thisinstr
+ thisinstr=instructions.shift
+ instr_left -= 1
+ break if instr_left.zero?
+ end
+
+ unless instr_left.zero?
+ # undo last changes, since these don't fit into the @lig_kern
+ # array (first 256 elements) (yes, this is ugly)
+ instructions.unshift thisinstr
+
+
+
+ pos=@lig_kern.size / 4 + instr_left
+ count=@instr_index.size
+
+ # now fill the indirect nodes, calculate the starting points of
+ # the instructions
+ instructions.each { |i|
+ @instr_index[count]=@lig_kern.size / 4
+ count += 1
+ @lig_kern += [ 129, 0, (pos / 256) , (pos % 256) ]
+ pos += i.size / 4
+ }
+
+ # now we continue with the instructions
+ instructions.each { |i|
+ @lig_kern += i
+ }
+ end
+ end
+ @nl = @lig_kern.size / 4
+
+ @kerns=[]
+ kerns.each { |k|
+ @kerns += out_fix_word(k)
+ }
+ @nk=@kerns.size / 4
+ end
+
+
+ def fill_index(start)
+ i=start
+ what=[0,0,0,0]
+ while (i=@link[i]) > 0
+ what += out_fix_word(@memory[i])
+ end
+ return what
+ end
+
+ def calculate_header
+ @header=[]
+ # checksum
+ @header += checksum
+ # dsize
+ @header += out_fix_word(@tfm.designsize)
+ # 2..11 coding scheme, bcpl
+ out_bcpl(@tfm.codingscheme,40)
+ # 12..16 font identifier
+ out_bcpl(@tfm.fontfamily,20)
+ # calculate 7bitflag!
+ # 7bitflag, byte, byte, face
+ if @tfm.sevenbitsafeflag
+ @header << 128
+ else
+ @header << 0
+ end
+ @header << 0
+ @header << 0
+ @header << @tfm.face
+ @lh = @header.size / 4
+ end
+ def update_bc_ec
+ @bc=nil
+ @tfm.chars.each_with_index{ |elt,i|
+ @bc=i if @bc==nil and elt!=nil
+ @ec=i if elt
+ }
+ end
+ def checksum
+ return out_qbyte(@tfm.checksum)
+ end
+
+ def out_bcpl(string,len)
+ str=string
+ l = str.length
+ if l > 39
+ str=string[0..38]
+ end
+ l = str.length
+ @header << l
+ count=1
+ str.each_byte { |x|
+ count += 1
+ @header << x
+ }
+ while len - count > 0
+ @header << 0
+ count += 1
+ end
+ end
+ def out_dbyte(int)
+ a1=int % 256
+ a0=int / 256
+ return [a0,a1]
+ end
+ def out_qbyte(int)
+ a3=int % 256
+ int = int / 256
+ a2 = int % 256
+ int = int / 256
+ a1=int % 256
+ a0=int / 256
+ return [a0,a1,a2,a3]
+ end
+
+ # looks ok
+ def out_fix_word(b)
+ # a=int part, f=after dec point
+ a=b.truncate
+ f=b-a
+ if b < 0
+ f = 1 - f.abs
+ a = a -1
+ end
+ x=(2**20.0*f).round
+ a3=x.modulo(256)
+ # x >>= 8
+ x=x/256
+ a2=x % 256
+ # x >>= 8
+ x = x >> 8
+ a1=x % 16
+ a1 += (a % 16) << 4
+ a0=b < 0 ? 256 + a / 16 : a / 16
+ [a0,a1, a2, a3]
+ end
+
+ def sort_in(h,d)
+ if d==0 and h!=WIDTH
+ return 0
+ end
+ p=h
+ while d >= @memory[@link[p]]
+ p=@link[p]
+ end
+ if d==@memory[p] and p!=h
+ return p
+ end
+ raise "Memory overflow: more than 1028 widths etc." if @mem_ptr==@memsize
+ @mem_ptr += 1
+ @memory[@mem_ptr]=d
+ @link[@mem_ptr]=@link[p]
+ @link[p]=@mem_ptr
+ @memory[h]+=1
+ return @mem_ptr
+ end
+
+ # see PLtoTF, §75pp
+ def min_cover(h,d)
+ m=0
+ p=@link[h]
+ @next_d=@memory[0] # large value
+ while p!=0
+ m += 1
+ l = @memory[p]
+ while @memory[@link[p]]<=l+d
+ p=@link[p]
+ end
+ p=@link[p]
+ if @memory[p]-l < @next_d
+ @next_d=@memory[p]-l
+ end
+ end
+ return m
+ end
+
+ def shorten(h,m)
+ if @memory[h] <= m
+ return 0
+ end
+ @excess=@memory[h]-m
+ if @excess > 0 and @verbose
+ puts "We need to shorten the list by #@excess"
+ end
+ k=min_cover(h,0)
+ d=@next_d
+ begin
+ d=d+d
+ k=min_cover(h,d)
+ end until k <= m
+ d = d / 2
+ k=min_cover(h,d)
+ while k > m
+ d=@next_d
+ k=min_cover(h,d)
+ end
+ return d
+ end
+
+ def set_indices(h,d)
+ q=h
+ p=@link[q]
+ m=0
+ while p!=0
+ m+=1
+ l=@memory[p]
+ @index[p]=m
+ while @memory[@link[p]] <= l+d
+ p=@link[p]
+ @index[p]=m
+ @excess -= 1
+ if @excess == 0
+ d=0
+ end
+ end
+ @link[q]=p
+ @memory[p] = l+(@memory[p]-l) / 2
+ q=p
+ p=@link[p]
+ end
+ @memory[h]=m
+ end
+
+ end
+
+
+
+ # Parse a pl (property list) file.
+ class PLParser
+ require 'strscan'
+
+ # _tfmobj_ is an Object of the TFM class.
+ def initialize(tfmobj)
+ @tfm=tfmobj
+ @s=nil
+ @syntax={
+ "COMMENT" => :get_balanced,
+ "FAMILY" => :get_family,
+ "FACE" => :get_face,
+ "CODINGSCHEME" => :get_codingscheme,
+ "DESIGNSIZE" => :get_designsize,
+ "CHECKSUM" => :get_checksum,
+ "FONTDIMEN" => :get_fontdimen,
+ "LIGTABLE" => :get_ligtable,
+ "CHARACTER" => :get_character,
+ }
+ end
+
+ # Parse the given pl file. _obj_ should be a string.
+ def parse (obj)
+ @s=StringScanner.new(obj)
+ @level=0
+ while k=keyword
+ if m=@syntax[k]
+ r=self.send(m)
+ else
+ raise "unknown property #{k}"
+ end
+ end
+ end
+
+ #######
+ private
+ #######
+
+ def get_character
+ thischar = @tfm.chars[get_num] ||= {}
+ # [:charwd, :charht, :chardp, :charic].each do |s|
+ # thischar[s]=0.0
+ # end
+ thislevel=@level
+ while @level >= thislevel
+ case k=keyword
+ when "COMMENT"
+ get_balanced
+ eat_closing_paren
+ when "CHARWD","CHARHT","CHARDP","CHARIC"
+ thischar[k.downcase.to_sym]=get_num
+ else
+ raise "Unknown property in pl file/character section: #{k}"
+ end
+ end
+ end
+ def get_ligtable
+ thislevel=@level
+ @tfm.lig_kern = []
+ instruction=[]
+ instrnum=[]
+ while @level==thislevel
+ case kw=keyword
+ when "LABEL"
+ instrnum.push get_num
+ when /LIG/
+ instruction << [kw.downcase.to_sym, get_num, get_num]
+ when "KRN"
+ instruction << [:krn, get_num,get_num]
+ when "STOP"
+ n=@tfm.lig_kern.size
+ instrnum.each { |x|
+ t = @tfm.chars[x] ||= {}
+ t[:lig_kern] = n
+ }
+ instrnum=[]
+ @tfm.lig_kern.push instruction
+ instruction=[]
+ eat_closing_paren
+ else
+ puts "unknown element in ligtable #{kw}, stop"
+ exit
+ end
+ end
+ end
+
+ def get_fontdimen
+ thislevel=@level
+ while @level==thislevel
+ n=case keyword
+ when "SLANT" then 1
+ when "SPACE" then 2
+ when "STRETCH" then 3
+ when "SHRINK" then 4
+ when "XHEIGHT" then 5
+ when "QUAD" then 6
+ when "EXTRASPACE" then 7
+ when "NUM1", "DEFAULT_RULE_THICKNESS" then 8
+ when "NUM2", "BIG_OP_SPACING1" then 9
+ when "NUM3", "BIG_OP_SPACING2" then 10
+ when "DENOM1", "BIG_OP_SPACING3" then 11
+ when "DENOM2", "BIG_OP_SPACING4" then 12
+ when "SUP1", "BIG_OP_SPACING5" then 13
+ when "SUP2" then 14
+ when "SUP3" then 15
+ when "SUB1" then 16
+ when "SUB2" then 17
+ when "SUPDROP" then 18
+ when "PARAMETER"
+ get_num
+ else
+ raise "unknown instruction in fontdimen"
+ end
+ @tfm.params[n]=get_num
+ end
+ end
+ def get_checksum
+ @tfm.checksum=get_num
+ end
+ def get_designsize
+ @tfm.designsize=get_num
+ end
+ def get_family
+ @tfm.fontfamily=get_string
+ end
+ def get_face
+ @tfm.face=get_num
+ end
+ def get_codingscheme
+ @tfm.codingscheme=get_balanced
+ eat_closing_paren
+ end
+ def eat_closing_paren
+ while @s.scan(/\s*\n?\)\n?/)
+ @level -= 1
+ end
+ end
+ # we are just before an open paren
+ def keyword
+ @s.skip_until(/\(/)
+ @level += 1
+ @s.skip(/\s+/)
+ ret= @s.scan(/[A-Za-z\/>]+/)
+ @s.skip(/\s+/)
+ return ret
+ end
+
+ def get_balanced
+ str=""
+ startlevel=@level
+ while @level >= startlevel
+ str << @s.scan(/[^\(\)]*/)
+ if (tmp = @s.scan(/(\(|\))/)) == "("
+ @level += 1
+ else
+ @level -= 1
+ end
+ str << tmp if @level >= startlevel
+ end
+ @s.skip(/\n/)
+ str
+ end
+ def get_string
+ @s.skip(/\s/)
+ s= @s.scan(/[[:alnum:]`'_\- :]+/)
+ @s.scan(/\)\s*\n/)
+ @level -= 1
+ return s
+ end
+ def get_num
+ @s.skip(/\s+/)
+ s=@s.scan(/(R|C|D|O|F|H)/)
+ @s.skip(/\s+/)
+ value=case s
+ when "R"
+ @s.scan(/-?\d+(\.\d+)?/).to_f
+ when "C"
+ @s.scan(/[[:alnum:]]/)[0]
+ when "D"
+ @s.scan(/\d+/).to_i
+ when "O"
+ @s.scan(/\d+/).to_i(8)
+ when "F"
+ t=@s.scan(/(M|B|L)(R|I)(R|C|E)/)
+ ['MRR','MIR','BRR','BIR','LRR','LIR','MRC','MIC','BRC','BIC',
+ 'LRC','LIC','MRE','MIE','BRE','BIE','LRE','LIE'].index(t)
+ else
+ raise "not implemented yet"
+ end
+ eat_closing_paren
+ value
+ end
+ end #class pl parser
+
+
+
+ # :stopdoc:
+ LIGOPS= [ :lig, :"lig/", :"/lig", :"/lig/",
+ nil, :"lig/>", :"/lig>", :"/lig/>",
+ nil, nil, nil, :"/lig/>>" ]
+
+ FACE = ['MRR','MIR','BRR','BIR','LRR','LIR','MRC','MIC','BRC','BIC',
+ 'LRC','LIC','MRE','MIE','BRE','BIE','LRE','LIE']
+
+ NOTAG=0
+ LIGTAG=1
+ LISTTAG=2
+ EXTTAG=3
+
+ def self.documented_as_accessor(*args) #:nodoc:
+ end
+ def self.documented_as_reader(*args) #:nodoc:
+ end
+ # :startdoc:
+
+ # Print diagnostics
+ attr_accessor :verbose
+
+ # Filename sans path of the tfm file. To change this attribute, set
+ # pathname.
+ documented_as_reader :tfmfilename
+
+ # Path to the tfm file.
+ attr_accessor :tfmpathname
+
+ # Checksum of the tfm file.
+ attr_accessor :checksum
+
+ # The designsize (Float). Must be >= 1.0.
+ attr_accessor :designsize
+
+ # Coding scheme of the font. One of "TeX math symbols", "TeX math
+ # extension" or anything else. The two have special meaning (more
+ # parameters). Maximum length is 40
+ attr_accessor :codingscheme
+
+ # Font family is an arbitrary String. Default is "UNSPECIFIED".
+ # Maximum length is 20.
+ attr_accessor :fontfamily
+
+ # This boolean flag denotes if the font has chars with index > 127.
+ attr_accessor :sevenbitsafeflag
+
+ # Face code. 0 <= 17.
+ attr_accessor :face
+
+ # Array of chars. Each entry is a Hash with the following keys:
+ # <tt>:charwd</tt> <tt>:charht</tt>, <tt>:chardp</tt>,
+ # <tt>:charic</tt> and <tt>:lig_kern</tt>. The first four are in
+ # designsize units. The <tt>:lig_kern</tt> key is the instruction
+ # number pointing to the entry in the lig_kern attribute of the TFM
+ # class.
+ attr_accessor :chars
+
+ # Array of ligkern instructions. Each instruction is an Array of
+ # Arrays where the first element is either <tt>:krn</tt> or one of
+ # <tt>:lig</tt>, <tt>:lig/</tt>, <tt>:/lig</tt>, <tt>:/lig/</tt>,
+ # <tt>:lig/></tt>, <tt>:/lig></tt>, <tt>:/lig/></tt> or
+ # <tt>:/lig/>></tt>. If it is <tt>:krn</tt>, then the second
+ # element is the next char and the third element must be the amount
+ # of kerning in multiples of the designsize. If it is a
+ # <tt>:lig</tt> (or similar), then the second element is the
+ # nextchar. The third element is the resulting char.
+
+ # Example for an instruction:
+ #
+ #
+ # [[:"lig/", 39, 148],
+ # [:krn, 121, -0.029993],
+ # [:krn, 39, -0.159998],
+ # [:krn, 148, -0.13999],
+ # [:krn, 89, -0.13999]]
+ #
+ # The complete <em>lig_kern</em> would be an Array of such instructions.
+ attr_accessor :lig_kern
+
+ # The fontdimensions, index starts at 1.
+ attr_accessor :params
+
+ def initialize
+ @chars=[]
+ @lig_kern=[]
+ @params=[]
+ @face=0
+ @designsize=10.0
+ @checksum=0
+ @fontfamily="UNSPECIFIED"
+ @verbose=false
+ end
+ def tfmfilename # :nodoc:
+ File.basename(@tfmpathname)
+ end
+
+
+ # _plfile_ is a filename (String). (Future: File and String (pathname))
+ def read_pl(plfilename)
+ File.open(plfilename) { |f|
+ parse_pl(f.read)
+ }
+ return self
+ end
+ def parse_pl(plstring)
+ p=PLParser.new(self)
+ p.parse(plstring)
+ return self
+ end
+
+ # _file_ is either a File object (or something similar, it must
+ # respond to :read) or a string containing the full pathname to the
+ # tfm file. Returns the TFM object.
+ def read_tfm(file)
+ p=TFMReader.new(self)
+ p.verbose=@verbose
+ if file.respond_to? :read
+ if file.respond_to? :path
+ @tfmpathname=file.path
+ end
+ p.parse(file.read)
+ else
+ # we assume it is a string
+ @tfmpathname=file
+ case file
+ when /\.tfm$/
+ File.open(file) { |f|
+ p.parse(f.read)
+ }
+ else
+ raise ArgumentError, "unknown Filetype: #{file}"
+ end
+ end
+ return self
+ end # read_file
+
+ # If _overwrite_ is true, we will replace existing files without
+ # raising Errno::EEXIST.
+ def save(overwrite=false)
+ raise Errno::EEXIST if File.exists?(@tfmpathname) and not overwrite
+ puts "saving #{@tfmpathname}..." if @verbose
+ File.open(@tfmpathname,"wb") { |f|
+ write_file(f)
+ }
+ puts "saving #{@tfmpathname}...done" if @verbose
+ end
+
+ # _file_ is a File object (or something similar, it must
+ # respond to <<).
+ def write_file(file)
+ tfmwriter=TFMWriter.new(self)
+ tfmwriter.verbose=@verbose
+ file << tfmwriter.to_data
+ end
+
+ # Return pltotf compatible output.
+ def to_s
+ indent=" "
+ str=""
+ str << out_head(indent)
+ str << out_parameters(indent)
+ str << out_ligtable(indent)
+ str << out_chars(indent)
+ str
+ end
+
+ #######
+ private
+ #######
+
+ def out_head(indent)
+ str ="(FAMILY #{fontfamily.upcase})\n"
+ str << "(FACE F #{FACE[face]})\n"
+ str << "(CODINGSCHEME #{codingscheme.upcase})\n"
+ str << "(DESIGNSIZE R #{designsize})\n"
+ str << "(CHECKSUM O #{sprintf("%o",checksum)})\n"
+ end
+ def out_chars(indent)
+ str = ""
+ chars.each_with_index { |c,i|
+ next unless c
+ # str << "(CHARACTER O #{sprintf("%o",i)}\n"
+ str << "(CHARACTER D %d\n" % i
+ [:charwd,:charht,:chardp,:charic].each { |dim|
+ str << indent + "(#{dim.to_s.upcase} R #{c[dim]})\n" if c[dim]!=0.0
+ }
+ str << indent + ")\n"
+ }
+ str
+ end
+ def out_parameters(indent)
+ paramname=%w( X SLANT SPACE STRETCH SHRINK XHEIGHT QUAD EXTRASPACE )
+ if codingscheme=="TeX math symbols"
+ paramname += %w(NUM1 NUM2 NUM3 DENOM1 DENOM2 SUP1 SUP2 SUP3
+ SUB1 SUB2 SUPDROP)
+ elsif codingscheme=="TeX math extension"
+ paramname += %w(DEFAULT_RULE_THICKNESS BIG_OP_SPACING1
+ BIG_OP_SPACING2 BIG_OP_SPACING3 BIG_OP_SPACING4 BIG_OP_SPACING5)
+ end
+
+ str = "(FONTDIMEN\n"
+ @params.each_with_index { |p,i|
+ next if i==0
+ if paramname[i]
+ str << indent + "(#{paramname[i]} R #{p})\n"
+ else
+ str << indent + "(PARAMETER D #{i} R #{p})\n"
+ end
+ }
+ str << indent + ")\n"
+ str
+ end
+ def out_ligtable(indent)
+ return "" if @lig_kern.size==0
+ str = "(LIGTABLE\n"
+ lk_char=[]
+ # first appearance of a char is the index, all chars for the same
+ # instructions is the value
+ # e.g. firstchar_chars[8]=[8,9] if chars 8 and 9 point to the same instr.
+ firstchar_chars=[]
+ @chars.each_with_index {|c,i|
+ next unless c
+ next unless instr=c[:lig_kern]
+ # we need to find duplicates
+ # some chars point to the same instruction
+ if lk_char[instr]
+ lk_char[instr].push i
+ else
+ lk_char[instr] = [i]
+ end
+ }
+
+ lk_char.each{ |a|
+ firstchar_chars[a[0]]=a
+ }
+ firstchar_chars.each { |a|
+ next unless a
+ a.each { |l|
+ str << indent + "(LABEL D #{l})\n"
+ }
+ @lig_kern[@chars[a[0]][:lig_kern]].each {|la|
+ case la[0]
+ when :skip
+ str << indent + "(SKIP D #{la[1]})\n"
+ when :krn
+ str << indent + "(KRN D #{la[1]} R #{la[2]})\n"
+ when :lig, :"lig/", :"/lig", :"/lig/", :"lig/>", :"/lig>", :"/lig/>", :"/lig/>>"
+ str << indent + "(#{la[0].to_s.upcase} O #{sprintf("%o",la[1])} O #{sprintf("%o",la[2])})\n"
+ end
+ }
+ str << indent + "(STOP)\n"
+ }
+ str << indent + ")\n"
+ str
+ end
+ end # class TFM
+ end # module TeX
+ end
diff --git a/support/rfil/lib/rfil/tex/vf.rb b/support/rfil/lib/rfil/tex/vf.rb
new file mode 100644
index 0000000000..8116be52b0
--- /dev/null
+++ b/support/rfil/lib/rfil/tex/vf.rb
@@ -0,0 +1,848 @@
+# vf.rb -- Class that models TeX's virtual fonts.
+#--
+# Last Change: Tue May 16 17:32:53 2006
+#++
+
+require 'rfil/tex/tfm'
+require 'rfil/tex/kpathsea'
+
+module RFIL
+ module TeX
+
+ # The vf (virtual font) files are described in vftovp and vptovf. They
+ # are always connected with a tfm file that hold the font metric. The
+ # vf contain some redundant information copied from the tfm file.
+ # Since the VF class is derived from the TFM class, there is no need
+ # to duplicate these pieces of information.
+
+ class VF < TFM
+ # This class is not meant to be used directly by the programmer. It
+ # is used in the VF class to read a virtual font from a file.
+
+ class VFReader
+ def initialize(vfobj)
+ @vfobj= vfobj || VF.new
+ @stack=[[0,0,0,0]]
+ push
+ @index=0
+ @dviindex=nil
+ end
+
+ # _vfdata_ is a string with the contents of the vf (binary) file.
+ # Return a VF object filled with the information of the virtual
+ # font. Does not read the tfm data. It is safe to parse tfm data
+ # after parsing the virtual font.
+ def parse(vfdata)
+ raise ArgumentError, "I expect a string" unless vfdata.respond_to?(:unpack)
+ @index=0
+ @kpse=Kpathsea.new
+ @data=vfdata.unpack("C*")
+
+ raise VFError, "This does not look like a vf to me" if 247 != get_byte
+ raise VFError, "Unknown VF version" unless 202 == get_byte
+
+ @vfobj.vtitle=get_chars(get_byte)
+
+ tfmcksum = get_qbyte
+ tfmdsize = get_fix_word
+
+ while b=get_byte
+ case b
+ when 0..241
+ @index -= 1
+ parse_char(:short)
+ when 242
+ parse_char(:long)
+ when 243,244,245,246
+ # jippie, a (map)font
+ fontnumber=get_bytes(243-b+1,false)
+ # perhaps we should actually load the tfm instead of saving
+ # the metadata?
+ @vfobj.fontlist[fontnumber]={}
+ checksum=get_qbyte
+ # @vfobj.fontlist[fontnumber][:checksum]=checksum
+ scale=get_fix_word
+ @vfobj.fontlist[fontnumber][:scale]=scale
+ dsize = get_fix_word
+ # @vfobj.fontlist[fontnumber][:designsize]=dsize
+ a = get_byte # length of area (directory?)
+ l = get_byte # length of fontname
+ area=get_chars(a)
+ name=get_chars(l)
+ # @vfobj.fontlist[fontnumber][:name]=name
+ @kpse.open_file(name,'tfm') { |file|
+ @vfobj.fontlist[fontnumber][:tfm]=TFM.new.read_tfm(file)
+ }
+ when 248
+ parse_postamble
+ else
+ raise VFError, "unknown instruction number #{b.inspect}"
+ end
+ end
+ return @vfobj
+ end # parse
+
+
+ #######
+ private
+ #######
+
+ def parse_postamble
+ while get_byte
+ end
+ end
+ # type: :long, :short
+ def parse_char(type)
+ instructions=[]
+ case type
+ when :long
+ pl=get_qbyte
+ cc=get_qbyte
+ tfm=out_as_fix(get_bytes(4,true,@index))
+ @index+=4
+ when :short
+ pl=get_byte
+ cc=get_byte
+ tfm=out_as_fix(get_bytes(3,true,@index))
+ @index+=3
+ else
+ raise ArgumentError,"unknown type: #{type}"
+ end
+ dvi=@data[(@index..@index+pl-1)]
+ @dviindex=@index
+ @index += pl
+ while i = get_byte(@dviindex) and @dviindex < @index
+ case i
+ when 0
+ # setchar 0
+ raise "not implementd"
+ when 1..127
+ instructions << [:setchar, i]
+ when 128..131
+ c=4-(131-i)
+ instructions << [:setchar, get_bytes(c,false,@dviindex+1)]
+ @dviindex += c
+ when 132,137
+ x=out_as_fix(get_bytes(4,true,@dviindex+1))
+ y=out_as_fix(get_bytes(4,true,@dviindex+5))
+ instructions << [:setrule,x,y]
+ @dviindex += 8
+ when 133..136
+ # are these ever used?
+ c=4-(136-i)
+ instructions << [:setchar, get_bytes(c,false,@dviindex+1)]
+ @dviindex += c
+ when 138
+ # nop
+ when 139,140
+ raise VFError, "illegal instruction in VF: #{i}"
+ when 141
+ instructions << [:push]
+ push
+ when 142
+ instructions << [:pop]
+ pop
+ when 143..146
+ c=4-(146-i)
+ b=out_as_fix(get_bytes(c,true,@dviindex+1))
+ instructions << [:moveright, b]
+ @dviindex += c
+ when 147
+ instructions << [:moveright, _w]
+ when 148..151
+ c=4-(151-i)
+ self._w=out_as_fix(get_bytes(c,true,@dviindex+1))
+ instructions << [:moveright,_w]
+ @dviindex += c
+ when 152
+ instructions << [:moveright, _x]
+ when 153..156
+ c=4-(156-i)
+ x=out_as_fix(get_bytes(c,true,@dviindex+1))
+ self._x=x
+ instructions << [:moveright,x]
+ @dviindex += c
+ when 157..160
+ # are these really used?
+ c=i-157+1
+ v=out_as_fix(get_bytes(c,true,@dviindex+1))
+ instructions << [:movedown,v]
+ @dviindex += c
+ when 161
+ instructions << [:movedown, _y]
+ when 162..165
+ c=i-162+1
+ self._y = out_as_fix(get_bytes(c,true,@dviindex+1))
+ instructions << [:movedown,_y]
+ @dviindex += c
+ # puts "#{i} movedown y #{_y}"
+ when 166
+ instructions << [:movedown, _z]
+ when 167..170
+ c=i-167+1
+ self._z = out_as_fix(get_bytes(c,true,@dviindex+1))
+ instructions << [:movedown,_z]
+ @dviindex += c
+ # puts "#{i} movedown z #{_z}"
+ when 171..234
+ instructions << [:selectfont, 63-234+i]
+ when 235..238
+ c=i-235+1
+ instructions << [:selectfont, get_bytes(c,true,@dviindex+1)]
+ @dviindex += c
+ when 239..242
+ c=i-239+1
+ k=get_bytes(c,true,@dviindex+1)
+ if k < 0
+ raise VFError, "length of special is negative"
+ end
+ instructions << [:special, get_chars(k,@dviindex+2)]
+ @dviindex += 1+k
+ when 243..255
+ raise VFError, "illegal instruction in VF: #{i}"
+ else
+ raise "not implemented: #{i}"
+ end
+ @dviindex += 1
+ end
+ # puts "charcode=#{cc} (octal #{sprintf("%o",cc)})"
+ tmp=if @vfobj.chars[cc]
+ @vfobj.chars[cc]
+ else
+ Hash.new
+ end
+ @vfobj.chars[cc]=tmp
+ tmp[:dvi]=instructions
+ end
+ def push
+ top=@stack[-1]
+ @stack.push top.dup
+ end
+ def pop
+ if @stack.size < 2
+ raise VFError, "more pop then push on stack"
+ end
+ return @stack.pop
+ end
+ def _w=(value)
+ @stack[-1][0]=value
+ end
+ def _w
+ @stack[-1][0]
+ end
+ def _x=(value)
+ @stack[-1][1]=value
+ end
+ def _x
+ @stack[-1][1]
+ end
+
+ def _y=(value)
+ @stack[-1][2]=value
+ end
+ def _y
+ @stack[-1][2]
+ end
+
+ def _z=(value)
+ @stack[-1][3]=value
+ end
+ def _z
+ @stack[-1][3]
+ end
+
+ def get_byte(i=nil)
+ global = i==nil
+ i = @index if global
+ r=@data[i]
+ @index += 1 if global
+ r
+ end
+ # 16 bit integer
+ def get_dbyte(i=nil)
+ global = i == nil
+ i = @index if global
+ r = (@data[i] << 8) + @data[i + 1]
+ @index += 2 if global
+ r
+ end
+ # 24 bit int
+ def get_tbyte(i=nil)
+ global = i == nil
+ i = @index if global
+ r = (@data[i] << 16) + (@data[i] << 8) + @data[i + 1]
+ @index += 3 if global
+ r
+ end
+ # signed 24 bit int
+ def get_stbyte(i=nil)
+ global = i == nil
+ i = @index if global
+ r = if @data[i] < 128
+ (@data[i] << 16) + (@data[i] << 8) + @data[i + 1]
+ else
+ ((256 - @data[i]) << 16) + (@data[i] << 8) + @data[i + 1]
+ end
+ @index += 3 if global
+ r
+ end
+
+ # 32 bit integer
+ def get_qbyte
+ r = (@data[@index] << 24) + (@data[@index+1] << 16) + (@data[@index+2] << 8) + @data[@index+3]
+ @index += 4
+ r
+ end
+ # Read a string with at most count bytes. Does not add \0 to the string.
+ def get_chars(count,i=nil)
+ ret=""
+ global = i==nil
+ i = @index if global
+
+ count.times { |coumt|
+ c=@data[i + coumt]
+ ret << c.chr if c > 0
+ }
+ @index += count if global
+ return ret.size==0 ? nil : ret
+ end
+ def get_bytes(count,signed,i=nil)
+ global = i==nil
+ i=@index if global
+ a=@data[i]
+ if (count==4) or signed
+ if a >= 128
+ a -= 256
+ end
+ end
+ i +=1
+ while count > 1
+ a = a * 256 + @data[i]
+ i +=1
+ count -=1
+ end
+ @index += count if global
+ return a
+ end
+ def out_as_fix(x)
+ raise VFError if x.abs >= 0100000000
+ # let's misuse @data -> change
+ if x>=0 then @data[0]=0
+ else
+ @data[0]=255
+ x += 0100000000
+ end
+ 3.downto(1) { |k|
+ @data[k]=x % 256
+ x = x.div(256)
+ }
+ get_fix_word(0)
+ end
+ def get_fix_word(i=nil)
+ global = i==nil
+ i = @index if global
+ b=@data[(i..i+3)]
+ @index += 4 if global
+ a= (b[0] * 16) + (b[1].div 16)
+ f= ((b[1] % 16) * 0400 + b[2] ) * 0400 + b[3]
+
+ str = ""
+ if a > 03777
+ str << "-"
+ a = 010000 - a
+ if f > 0
+ f = 04000000 - f
+ a -= 1
+ end
+ end
+ # Knuth, TFtoPL §42
+
+ delta = 10
+ f=10*f+5
+
+ str << a.to_s + "."
+ begin
+ if delta > 04000000
+ f = f + 02000000 - ( delta / 2 )
+ end
+ str << (f / 04000000).to_s
+ f = 10 * ( f % 04000000)
+ delta *= 10
+ end until f <= delta
+ str.to_f
+ end
+
+ end
+
+
+ class VFWriter
+ attr_accessor :verbose
+ def initialize(vfobject)
+ @vf=vfobject
+ end
+
+ def to_data
+ # preamble
+ @data=[247,202]
+ @data += out_string(@vf.vtitle)
+ @data += out_qbyte(@vf.checksum)
+ @data += out_fix_word(@vf.designsize)
+
+ # fonts
+ @vf.fontlist.each_with_index { |f,i|
+ count,*bytes=out_n_bytes(i)
+ @data += [242+count]
+ @data += bytes
+
+ @data+=out_qbyte(f[:tfm].checksum)
+ @data+=out_fix_word(f[:scale])
+ @data+=out_fix_word(f[:tfm].designsize)
+ @data+=[0]
+ @data += out_string(f[:tfm].tfmfilename.chomp('.tfm'))
+ }
+
+ # now for the chars
+ @vf.chars.each_with_index { |c,i|
+ next unless c
+ dvi=out_instructions(c[:dvi])
+ pl=dvi.length
+ tfm=c[:charwd]
+ if pl < 242 and tfm < 16.0 and tfm > 0 and i < 256
+ @data << pl
+ @data << i
+ @data += out_fix_word(tfm,3)
+ else
+ @data << 242
+ @data += out_qbyte(pl)
+ @data += out_qbyte(i)
+ @data += out_fix_word(tfm)
+ end
+ @data += dvi
+ }
+ @data << 248
+ while @data.size % 4 != 0
+ @data << 248
+ end
+ return @data.pack("C*")
+ end
+
+ #######
+ private
+ #######
+
+ def out_instructions(instructionlist)
+ ret=[]
+ instructionlist.each { |i|
+ case i[0]
+ when :setchar
+ charnum=i[1]
+ if charnum < 128
+ ret << charnum
+ elsif charnum > 255
+ raise VFError, "TeX does not know about chars > 8bit"
+ else
+ ret << 128
+ ret << charnum
+ end
+ when :setrule
+ ret << 132
+ ret += out_fix_word(i[1])
+ ret += out_fix_word(i[2])
+ when :noop
+ ret << 138
+ when :push
+ ret << 141
+ when :pop
+ ret << 142
+ when :moveright
+ # should we choose another moveright? --pg
+ ret << 156
+ ret += out_fix_word(i[1])
+ when :movedown
+ ret << 165
+ ret += out_fix_word(i[1])
+ when :special
+ len,*data=out_string(i[1])
+ blen,bytes = out_n_bytes(len)
+ ret << blen+238
+ ret << bytes
+ ret += data
+ else
+ raise VFError, "not implemented"
+ end
+ }
+ ret
+ end
+
+ def out_n_bytes(int)
+ case
+ when (int < 0), (int >= 0100000000)
+ [4] + out_sqbyte(int)
+ when int >= 0200000
+ [3] + out_tbyte(int)
+ when int >= 0400
+ [2] + out_dbyte(int)
+ else
+ [1,int]
+ end
+ end
+ def out_dbyte(int)
+ a1=int % 256
+ a0=int / 256
+ return [a0,a1]
+ end
+ def out_tbyte(int)
+ a2 = int % 256
+ int = int / 256
+ a1=int % 256
+ a0=int / 256
+ return [a0,a1,a2]
+ end
+ def out_qbyte(int)
+ a3=int % 256
+ int = int / 256
+ a2 = int % 256
+ int = int / 256
+ a1=int % 256
+ a0=int / 256
+ return [a0,a1,a2,a3]
+ end
+ # signed four bytes
+ def out_sqbyte(int)
+ a3=int % 256
+ int = int / 256
+ a2 = int % 256
+ int = int / 256
+ a1=int % 256
+ a0=int / 256
+ if int < 0
+ a0 = 256 + a0
+ end
+ return [a0,a1,a2,a3]
+ end
+ def out_fix_word(b,bytes=4)
+ # a=int part, f=after dec point
+ a=b.truncate
+ f=b-a
+ if b < 0
+ f = 1 - f.abs
+ a = a -1
+ end
+ x=(2**20.0*f).round
+ a3=x.modulo(256)
+ # x >>= 8
+ x=x/256
+ a2=x % 256
+ # x >>= 8
+ x = x >> 8
+ a1=x % 16
+ a1 += (a % 16) << 4
+ a0=b < 0 ? 256 + a / 16 : a / 16
+ if bytes == 3
+ [a1, a2, a3]
+ else
+ [a0,a1, a2, a3]
+ end
+ end
+
+ def out_string(string)
+ unless string
+ return [0]
+ end
+ ret=[string.length]
+ string.each_byte { |s|
+ ret << s
+ }
+ return ret
+ end
+ end
+
+
+ # Parse a vpl (virtual property list) file. See also TFM::PLParser.
+ class VPLParser < PLParser
+ # _vfobj_ is an initialized object of the VF class. Call
+ # parse(fileobj) to fill the VF object.
+ def initialize(vfobj)
+ @vf=vfobj
+ super
+ @syntax["VTITLE"] =:get_vtitle
+ @syntax["MAPFONT"]=:get_mapfont
+ end
+
+ #######
+ private
+ #######
+
+ def get_vtitle
+ @vf.vtitle=get_string
+ end
+ def get_mapfont
+ @vf.fontlist=[]
+ t = @vf.fontlist[get_num] = {}
+ t[:tfm]=TFM.new
+ thislevel=@level
+ while @level >= thislevel
+ case k=keyword
+ when "FONTNAME"
+ t[:tfm].tfmpathname=get_string
+ when "FONTCHECKSUM"
+ t[:tfm].checksum=get_num
+ when "FONTAT"
+ t[:scale]=get_num
+ when "FONTDSIZE"
+ t[:tfm].designsize=get_num
+ else
+ raise "Unknown property in MAPFONT section: #{k}"
+ end
+ end
+ end # get_mapfont
+
+ # we copy this from tfm.rb, because now MAP is also allowed
+ def get_character
+ thischar = @tfm.chars[get_num] ||= {}
+ thislevel=@level
+ while @level >= thislevel
+ case k=keyword
+ when "COMMENT"
+ get_balanced
+ eat_closing_paren
+ when "CHARWD","CHARHT","CHARDP","CHARIC"
+ thischar[k.downcase.to_sym]=get_num
+ when "MAP"
+ instr=thischar[:dvi]=[]
+ maplevel=@level
+ while @level >= maplevel
+ case ik=keyword
+ when "SELECTFONT"
+ instr << [:selectfont, get_num]
+ when "SETCHAR"
+ instr << [:setchar, get_num]
+ when "SETRULE"
+ instr << [:setrule, get_num, get_num]
+ when "MOVEDOWN"
+ instr << [:movedown, get_num]
+ when "MOVERIGHT"
+ instr << [:moveright, get_num]
+ when "PUSH"
+ instr << [:push]
+ eat_closing_paren
+ when "POP"
+ instr << [:pop]
+ eat_closing_paren
+ when "SPECIAL"
+ instr << [:special, get_balanced]
+ # puts "special, #{get_balanced}"
+ eat_closing_paren
+ else
+ raise "Unknown instruction in character/map section: #{ik}"
+ end
+ end
+ else
+ raise "Unknown property in pl file/character section: #{k}"
+ end
+ end
+ end # get_character
+ end
+
+
+
+ # VF class
+
+ # Raise this exception if an error related to the virtual font is
+ # encountered. Don't expect this library to be too clever at the beginning.
+ class VFError < Exception
+ end
+
+ def self.documented_as_accessor(*args) #:nodoc:
+ end
+ def self.documented_as_reader(*args) #:nodoc:
+ end
+
+ # Filename sans path of the vf file. To change this attribute, set
+ # vfpathname.
+ documented_as_reader :vffilename
+
+ # Path to the vf file.
+ attr_accessor :vfpathname
+
+
+ # fontlist is an array of Hashes with the following keys:
+ # [<tt>:scale</tt>] Relative size of the font
+ # [<tt>:tfm</tt>] TFM object.
+ attr_accessor :fontlist
+
+ # This is the same Array as in TFM. Besides the keys <tt>:charwd</tt>,
+ # <tt>:charht</tt>, <tt>:chardp</tt> and <tt>:charic</tt>, we now have a key
+ # <tt>:dvi</tt> that holds all vf instructions.
+ documented_as_accessor :chars
+
+ # Comment at the beginning of the vf file. Must be < 256 chars.
+ attr_accessor :vtitle
+
+ # Return an empty VF object
+ def initialize
+ super
+ @vtitle=""
+ @fontlist=[]
+ end
+
+ def vffilename # :nodoc:
+ File.basename(@vfpathname)
+ end
+
+ # _vplfile_ is a filename (String). (Future: File and String (pathname))
+ def read_vpl(vplfilename)
+ File.open(vplfilename) { |f|
+ parse_vpl(f.read)
+ }
+ return self
+ end
+ def parse_vpl(vplstring)
+ v=VPLParser.new(self)
+ v.parse(vplstring)
+ return self
+ end
+
+ # _file_ is either a string (pathname) of a File object (must
+ # respond to read)
+ def read_vf(file)
+ p=VFReader.new(self)
+ if file.respond_to? :read
+ if file.respond_to? :path
+ @vfpathname=file.path
+ end
+ p.parse(file.read)
+ else
+ # we assume it is a string
+ @vfpathname=file
+ case file
+ when /\.vf$/
+ File.open(file) { |f|
+ p.parse(f.read)
+ }
+ else
+ raise ArgumentError, "unknown Filetype: #{file}"
+ end
+ end
+ t=TFMReader.new(self)
+ @tfmpathname=@vfpathname.chomp(".vf")+".tfm"
+ File.open(@tfmpathname){ |f|
+ t.parse(f.read)
+ }
+ return self
+ end #read_vf
+
+ # If _overwrite_ is true, we will replace existing files without
+ # raising Errno::EEXIST.
+ def save(overwrite=false)
+ # tfmpathname=@vfpathname.chomp(".vf")+".tfm"
+ raise "tfmpathname not set" unless @tfmpathname
+ raise Errno::EEXIST if File.exists?(@vfpathname) and not overwrite
+ raise Errno::EEXIST if File.exists?(@tfmpathname) and not overwrite
+ puts "saving #{@vfpathname}..." if @verbose
+ File.open(@vfpathname,"wb") { |f|
+ write_vf_file(f)
+ }
+ puts "saving #{@vfpathname}...done" if @verbose
+ puts "saving #{@tfmpathname}..." if @verbose
+ File.open(@tfmpathname,"wb") { |f|
+ write_tfm_file(f)
+ }
+ puts "saving #{@tfmpathname}...done" if @verbose
+
+ end
+
+
+ # _file_ is a File object (or something similar, it must
+ # respond to <<). Will be moved.
+ def write_vf_file(file)
+ vfwriter=VFWriter.new(self)
+ vfwriter.verbose=@verbose
+ file << vfwriter.to_data
+ end
+
+ # _file_ is a File object (or something similar, it must
+ # respond to <<). Will be moved.
+ def write_tfm_file(file)
+ tfmwriter=TFMWriter.new(self)
+ tfmwriter.verbose=@verbose
+ file << tfmwriter.to_data
+ end
+ # Return vptovf compatible output
+ def to_s
+ indent=" "
+ str=""
+ str << out_head(indent)
+ str << "(VTITLE #{vtitle})\n"
+ str << out_parameters(indent)
+ str << out_mapfont(indent)
+ str << out_ligtable(indent)
+ str << out_chars(indent)
+ str
+ end
+
+ #######
+ private
+ #######
+
+ def out_chars(indent)
+ str = ""
+ chars.each_with_index { |c,i|
+ next unless c
+ # str << "(CHARACTER O #{sprintf("%o",i)}\n"
+ str << "(CHARACTER D %d\n" % i
+ [:charwd,:charht,:chardp,:charic].each { |dim|
+ str << indent + "(#{dim.to_s.upcase} R #{c[dim]})\n" if c[dim]!=0.0
+ }
+ if c[:dvi]
+ str << indent + "(MAP\n"
+ c[:dvi].each { |instr,*rest|
+
+ case instr
+ when :setchar
+ str << indent*2 + "(SETCHAR D %d)\n" % rest[0].to_i
+ when :setrule
+ str << indent*2 + "(SETRULE R #{rest[0]} R #{rest[1]})\n"
+ when :noop
+ # ignore
+ when :push
+ str << indent*2 + "(PUSH)\n"
+ when :pop
+ str << indent*2 + "(POP)\n"
+ when :moveright
+ str << indent*2 + "(MOVERIGHT R #{rest[0]})\n"
+ when :movedown
+ str << indent*2 + "(MOVEDOWN R #{rest[0]})\n"
+ when :selectfont
+ str << indent*2 + "(SELECTFONT D #{rest[0]})\n"
+ when :special
+ str << indent*2 + "(SPECIAL #{rest[0]})\n"
+ else
+ raise "unknown dvi instruction #{instr}"
+ end
+ }
+ str << indent*2 + ")\n"
+ end
+ str << indent + ")\n"
+ }
+ str
+ end
+
+ def out_mapfont(indent)
+ return "" if fontlist.size == 0
+ str=""
+
+ fontlist.each_with_index { |f,i|
+ str << "(MAPFONT D %d\n" % i
+ str << indent + "(FONTNAME %s)\n" % f[:tfm].tfmfilename
+ str << indent + "(FONTCHECKSUM O %o)\n" % f[:tfm].checksum
+ str << indent + "(FONTAT R %f)\n" % (f[:scale] ? f[:scale].to_f : 1.0)
+ str << indent + "(FONTDSIZE R %f)\n" % f[:tfm].designsize
+ str << indent + ")\n"
+ }
+ str
+ end
+ end #class VF
+
+ end #module TeX
+end