summaryrefslogtreecommitdiff
path: root/support/rfil/lib/rfil/tex/tfm.rb
diff options
context:
space:
mode:
Diffstat (limited to 'support/rfil/lib/rfil/tex/tfm.rb')
-rw-r--r--support/rfil/lib/rfil/tex/tfm.rb1200
1 files changed, 1200 insertions, 0 deletions
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