diff options
Diffstat (limited to 'support/rfil/lib/rfil/tex/vf.rb')
-rw-r--r-- | support/rfil/lib/rfil/tex/vf.rb | 848 |
1 files changed, 848 insertions, 0 deletions
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 |