summaryrefslogtreecommitdiff
path: root/support/rfil/lib/rfil/tex/enc.rb
blob: d2bb21a620e6b37771536e69f5f7d5ebb1ef0a8e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
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