#-- # 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 (.enc-file) and associated # ligkern instructions. The encoding slot are accessible via [] # and []=, 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 new.enc # 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']=# in # ec.enc. Automatically updated when changing the encoding # vector via []=. 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