public
Description: Fast, Nimble PDF Writer for Ruby
Homepage: http://prawn.majesticseacreature.com
Clone URL: git://github.com/sandal/prawn.git
prawn / vendor / font_ttf / ttf / table / cmap.rb
100644 408 lines (355 sloc) 14.792 kb
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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
# TTF/Ruby, a library to read and write TrueType fonts in Ruby.
# Copyright (C) 2006 Mathieu Blondel
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 
module Font
module TTF
module Table
 
# Cmap is the character to glyph index mapping table.
class Cmap < Font::TTF::FontChunk
 
    # Base class for encoding table classes. It provides attributes which are
    # common to those classes such as platform_id and encoding_id.
    class EncodingTable < Font::TTF::FontChunk
 
        attr_accessor :table, :platform_id, :encoding_id, :offset_from_table
        
        def initialize(table, offset=nil, len=nil, platform_id=nil,
                       encoding_id=nil)
            @table = table
            @font = table.font
            
            if not offset.nil?
                @offset_from_table = offset
                @platform_id = platform_id
                @encoding_id = encoding_id
                super(@table.font, @table.offset + offset, len)
            else
                super(@table.font)
            end
        end
 
        def unicode?
            @platform_id == Font::TTF::Encodings::Platform::UNICODE or \
            (@platform_id == Font::TTF::Encodings::Platform::MICROSOFT and \
            @encoding_id == Font::TTF::Encodings::MicrosoftEncoding::UNICODE)
        end
 
        # Returns the format (a Fixnum) of the encoding table.
        def format
            if self.class.superclass == EncodingTable
                self.class.name.split(//).last.to_i
            else
                nil # Not implemented table format
            end
        end
 
    end
 
    # Encoding table in format 0, the Apple standard character to glyph
    # mapping table.
    class EncodingTable0 < EncodingTable
 
        attr_accessor :version
        # An array of 256 elements with simple one to one mapping of
        # character codes to glyph indices.
        #
        # Char 0 => Index glyph_id_array[0]
        # Char 1 => Index glyph_id_array[1]
        # ...
        attr_accessor :glyph_id_array
 
        def initialize(*args)
            super(*args)
            if exists_in_file?
                # 2 * IO::SIZEOF_USHORT corresponds to format and len
                # that we want to skip
                @table.font.at_offset(@offset + 2 * IO::SIZEOF_USHORT) do
                    @version = @font.read_ushort
                    @glyph_id_array = @font.read_bytes(256)
                end
            else
                # This is to ensure that it will be an array
                @glyph_id_array = []
            end
        end
 
        # Dumps the table in binary raw format as may be found in a font
        # file.
        def dump
            raw = (format || 0).to_ushort
            len = 3 * IO::SIZEOF_USHORT + 256
            raw += len.to_ushort
            raw += (@version || 0).to_ushort
            raw += @glyph_id_array.to_bytes
        end
    end
 
    # Encoding table in format 2. Not implemented.
    class EncodingTable2 < EncodingTable
    end
 
    # Encoding table in format 4. This format is well-suited to map
    # several contiguous ranges, possibly with holes of characters.
    class EncodingTable4 < EncodingTable
 
        attr_accessor :version
 
        attr_reader :search_range, :entry_selector, :range_shift,
                    :end_count_array, :reserved_pad, :start_count_array,
                    :id_delta_array, :id_range_offset_array,
                    :glyph_index_array, :segments
 
        def initialize(*args)
            super(*args)
 
            if exists_in_file?
                # 2 * IO::SIZEOF_USHORT corresponds to format and len
                # that we want to skip
                @table.font.at_offset(@offset + 2 * IO::SIZEOF_USHORT) do
                    @version = @font.read_ushort
                    @seg_count_x2 = @font.read_ushort
                    @seg_count = @seg_count_x2 / 2
                    @search_range = @font.read_ushort
                    @entry_selector = @font.read_ushort
                    @range_shift = @font.read_ushort
                    @end_count_array = @font.read_ushorts(@seg_count)
                    @reserved_pad = @font.read_ushort
                    @start_count_array = @font.read_ushorts(@seg_count)
                    @id_delta_array = @font.read_ushorts(@seg_count)
                    @id_range_offset_array = @font.read_ushorts(@seg_count)
 
                    @nb_glyph_indices = len - 8 * IO::SIZEOF_USHORT \
                                          - 4 * @seg_count * IO::SIZEOF_USHORT
                    @nb_glyph_indices /= IO::SIZEOF_USHORT;
 
                    if @nb_glyph_indices > 0
                        @glyph_index_array = \
                            @font.read_ushorts(@nb_glyph_indices)
                    else
                        @glyph_index_array = []
                    end
                    
                    # Keep them in memory so we don't need to
                    # recalculate them every time
                    @segments = get_segments
                end # end at_offset
            else
                @segments = []
                @glyph_index_array = []
            end
        end # end initialize
 
        def get_segments
            segments = []
 
            # For each segment...
            @start_count_array.each_with_index do |start, curr_seg|
                endd = @end_count_array[curr_seg]
                delta = @id_delta_array[curr_seg]
                range = @id_range_offset_array[curr_seg]
    
                segments[curr_seg] = {}
 
                start.upto(endd) do |curr_char|
                    if range == 0
                        index = (curr_char + delta)
                    else
                        gindex = range / 2 + (curr_char - start) - \
                                (@seg_count - curr_seg)
                        index = @glyph_index_array[gindex]
                        index = 0 if index.nil?
                        index += delta if index != 0
                    end
                    index = index % 65536
                    # charcode => glyph index
                    segments[curr_seg][curr_char] = index
                end
            end
            segments
        end
        private :get_segments
 
        # Returns a Hash. Its keys are characters codes and associated values
        # are glyph indices.
        def charmaps
            hsh = {}
            segments.each do |seg|
                seg.each do |char_code, glyph_index|
                    hsh[char_code] = glyph_index
                end
            end
            hsh
        end
 
        # Sets the charmaps. cmps is a Hash in the same fashion as the one
        # returned by charmaps.
        def charmaps=(cmps)
            # TODO: we would need fewer segments if we ensured that
            # glyphs with id in ascending order were associated
            # with char codes in ascending order
            raise "Charmaps is an empty array" if cmps.length == 0
 
            # Order is important since we will rebuild segments
            char_codes = cmps.keys.sort
 
            @start_count_array = []
            @end_count_array = []
            @id_delta_array = []
            curr_seg = 0
            i = 0
 
            @start_count_array[0] = char_codes.first
            @id_delta_array[0] = cmps[char_codes.first] - char_codes.first
 
            last_char = 0
            char_codes.each do |char_code|
                glyph_id = cmps[char_code]
                curr_delta = glyph_id - char_code
                if i > 0 and curr_delta != @id_delta_array.last
                    # Need to create a new segment
                    @end_count_array[curr_seg] = last_char
                    curr_seg += 1
                    @start_count_array[curr_seg] = char_code
                    @id_delta_array[curr_seg] = curr_delta
                end
                last_char = char_code
                i += 1
            end
            seg_count = @start_count_array.length
            @end_count_array[seg_count - 1] = last_char
            @id_range_offset_array = [0] * seg_count # Range offsets not used
            @segments = get_segments # Recalculate segments
 
            # Values below are calculated
            # to allow faster computation by font rasterizers
            res = 1
            power = 0
            while res <= seg_count
                res *= 2
                power += 1
            end
            @search_range = res
            @entry_selector = power - 1
            @range_shift = 2 * seg_count - @search_range
        end
 
        # Returns index/id of glyph associated with unicode.
        def get_glyph_id_for_unicode(unicode)
            id = 0
            @segments.length.times do |i|
                if @start_count_array[i] <= unicode and \
                   unicode <= @end_count_array[i]
                    @segments[i].each do |char_code, glyph_id|
                        if char_code == unicode
                            id = glyph_id
                            break
                        end
                    end
                end
            end
            id
        end
 
        # Returns the Font::TTF::Table::Glyf::SimpleGlyph or
        # Font::TTF::Table::Glyf::CompositeGlyph associated with unicode.
        def get_glyph_for_unicode(unicode)
            id = get_glyph_id_for_unicode(unicode)
            offs = @font.get_table(:loca).glyph_offsets[id]
            @font.get_table(:glyf).get_glyph_at_offset(offs)
        end
 
        # Returns the unicode of glyph with index id.
        def get_unicode_for_glyph_id(id)
            # TODO: this method could be rewritten much more efficiently
            # by using @start_count_array and @end_count_array
            unicode = 0
            charmaps.each do |char_code, glyph_index|
                if glyph_index == id
                    unicode = char_code
                    break
                end
            end
            unicode
        end
    
        # Dumps the table in binary raw format as may be found in a font
        # file.
        def dump
            raw = (format || 0).to_ushort
            seg_count = @segments.length || 0
            len = 8 * IO::SIZEOF_USHORT + 4 * seg_count * IO::SIZEOF_USHORT + \
                  @glyph_index_array.length * IO::SIZEOF_USHORT
            raw += len.to_ushort
            raw += (@version || 0).to_ushort
            raw += (seg_count * 2).to_ushort
            raw += (@search_range || 0).to_ushort
            raw += (@entry_selector || 0).to_ushort
            raw += (@range_shift || 0).to_ushort
            raw += (@end_count_array || []).to_ushorts
            raw += (@reserved_pad || 0).to_ushort
            raw += (@start_count_array || []).to_ushorts
            raw += (@id_delta_array || []).to_ushorts
            raw += (@id_range_offset_array || []).to_ushorts
            raw += (@glyph_index_array || []).to_ushorts
        end
    end
 
    # Encoding table in format 6. Not implemented.
    class EncodingTable6 < EncodingTable
    end
 
    # Encoding table in format 8. Not implemented.
    class EncodingTable8 < EncodingTable
    end
 
    # Encoding table in format 10. Not implemented.
    class EncodingTable10 < EncodingTable
    end
 
    # Encoding table in format 12. Not implemented.
    class EncodingTable12 < EncodingTable
    end
 
    attr_accessor :version
    # An Array of encoding_tables. You may add or remove encoding tables
    # from this array.
    attr_accessor :encoding_tables
 
    def initialize(*args)
        super(*args)
 
        if exists_in_file?
            @font.at_offset(@offset) do
                @version = @font.read_ushort
                @encoding_table_num = @font.read_ushort
                @encoding_tables = []
                @encoding_table_num.times do
                    platform_id = @font.read_ushort
                    encoding_id = @font.read_ushort
                    offset = @font.read_ulong
                
                    @font.at_offset(@offset + offset) do
                        format = @font.read_ushort
                        len = @font.read_ushort
 
                        if [0, 2, 4, 6, 8, 10, 12].include? format
                            klass = eval("EncodingTable%d" % format)
                        else
                            klass = EncodingTable
                        end
 
                        @encoding_tables << klass.new(self,
                                                      offset,
                                                      len,
                                                      platform_id,
                                                      encoding_id)
                    end
                end
                
            end
        else
            @encoding_tables = []
        end
    end
 
    # Returns a new empty EncodingTable0 object that you may add to the
    # encoding_tables array.
    def get_new_encoding_table0
        EncodingTable0.new(self)
    end
 
    # Returns a new empty EncodingTable4 object that you may add to the
    # encoding_tables array.
    def get_new_encoding_table4
        EncodingTable4.new(self)
    end
 
    # Dumps the cmap table in binary raw format as may be found in a font
    # file.
    def dump
        raw = (@version || 0).to_ushort
        raw += (@encoding_tables.length || 0).to_ushort
        dumps = []
        offs = 2 * IO::SIZEOF_USHORT + @encoding_tables.length * \
                (2 * IO::SIZEOF_USHORT + IO::SIZEOF_ULONG)
        @encoding_tables.each do |enc_tbl|
            raw += (enc_tbl.platform_id || 3).to_ushort
            raw += (enc_tbl.encoding_id || 1).to_ushort
            dump = enc_tbl.dump
            dumps << dump
            raw += offs.to_ulong
            offs += dump.length
        end
        dumps.each do |dump|
            raw += dump
        end
        raw
    end
 
end
 
end
end
end