sandal / prawn

Fast, Nimble PDF Writer for Ruby

This URL has Read+Write access

prawn / vendor / font_ttf / ttf / table / cmap.rb
a8533a06 » yob 2008-05-29 vendored ttf-ruby (http://r... 1 # TTF/Ruby, a library to read and write TrueType fonts in Ruby.
2 # Copyright (C) 2006 Mathieu Blondel
3 #
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17
18 module Font
19 module TTF
20 module Table
21
22 # Cmap is the character to glyph index mapping table.
23 class Cmap < Font::TTF::FontChunk
24
25 # Base class for encoding table classes. It provides attributes which are
26 # common to those classes such as platform_id and encoding_id.
27 class EncodingTable < Font::TTF::FontChunk
28
29 attr_accessor :table, :platform_id, :encoding_id, :offset_from_table
30
31 def initialize(table, offset=nil, len=nil, platform_id=nil,
32 encoding_id=nil)
33 @table = table
34 @font = table.font
35
36 if not offset.nil?
37 @offset_from_table = offset
38 @platform_id = platform_id
39 @encoding_id = encoding_id
40 super(@table.font, @table.offset + offset, len)
41 else
42 super(@table.font)
43 end
44 end
45
46 def unicode?
47 @platform_id == Font::TTF::Encodings::Platform::UNICODE or \
48 (@platform_id == Font::TTF::Encodings::Platform::MICROSOFT and \
49 @encoding_id == Font::TTF::Encodings::MicrosoftEncoding::UNICODE)
50 end
51
52 # Returns the format (a Fixnum) of the encoding table.
53 def format
54 if self.class.superclass == EncodingTable
55 self.class.name.split(//).last.to_i
56 else
57 nil # Not implemented table format
58 end
59 end
60
61 end
62
63 # Encoding table in format 0, the Apple standard character to glyph
64 # mapping table.
65 class EncodingTable0 < EncodingTable
66
67 attr_accessor :version
68 # An array of 256 elements with simple one to one mapping of
69 # character codes to glyph indices.
70 #
71 # Char 0 => Index glyph_id_array[0]
72 # Char 1 => Index glyph_id_array[1]
73 # ...
74 attr_accessor :glyph_id_array
75
76 def initialize(*args)
77 super(*args)
78 if exists_in_file?
79 # 2 * IO::SIZEOF_USHORT corresponds to format and len
80 # that we want to skip
81 @table.font.at_offset(@offset + 2 * IO::SIZEOF_USHORT) do
82 @version = @font.read_ushort
83 @glyph_id_array = @font.read_bytes(256)
84 end
85 else
86 # This is to ensure that it will be an array
87 @glyph_id_array = []
88 end
89 end
90
91 # Dumps the table in binary raw format as may be found in a font
92 # file.
93 def dump
94 raw = (format || 0).to_ushort
95 len = 3 * IO::SIZEOF_USHORT + 256
96 raw += len.to_ushort
97 raw += (@version || 0).to_ushort
98 raw += @glyph_id_array.to_bytes
99 end
100 end
101
102 # Encoding table in format 2. Not implemented.
103 class EncodingTable2 < EncodingTable
104 end
105
106 # Encoding table in format 4. This format is well-suited to map
107 # several contiguous ranges, possibly with holes of characters.
108 class EncodingTable4 < EncodingTable
109
110 attr_accessor :version
111
112 attr_reader :search_range, :entry_selector, :range_shift,
113 :end_count_array, :reserved_pad, :start_count_array,
114 :id_delta_array, :id_range_offset_array,
115 :glyph_index_array, :segments
116
117 def initialize(*args)
118 super(*args)
119
120 if exists_in_file?
121 # 2 * IO::SIZEOF_USHORT corresponds to format and len
122 # that we want to skip
123 @table.font.at_offset(@offset + 2 * IO::SIZEOF_USHORT) do
124 @version = @font.read_ushort
125 @seg_count_x2 = @font.read_ushort
126 @seg_count = @seg_count_x2 / 2
127 @search_range = @font.read_ushort
128 @entry_selector = @font.read_ushort
129 @range_shift = @font.read_ushort
130 @end_count_array = @font.read_ushorts(@seg_count)
131 @reserved_pad = @font.read_ushort
132 @start_count_array = @font.read_ushorts(@seg_count)
133 @id_delta_array = @font.read_ushorts(@seg_count)
134 @id_range_offset_array = @font.read_ushorts(@seg_count)
135
136 @nb_glyph_indices = len - 8 * IO::SIZEOF_USHORT \
137 - 4 * @seg_count * IO::SIZEOF_USHORT
138 @nb_glyph_indices /= IO::SIZEOF_USHORT;
139
140 if @nb_glyph_indices > 0
141 @glyph_index_array = \
142 @font.read_ushorts(@nb_glyph_indices)
143 else
144 @glyph_index_array = []
145 end
146
147 # Keep them in memory so we don't need to
148 # recalculate them every time
149 @segments = get_segments
150 end # end at_offset
151 else
152 @segments = []
153 @glyph_index_array = []
154 end
155 end # end initialize
156
157 def get_segments
158 segments = []
159
160 # For each segment...
161 @start_count_array.each_with_index do |start, curr_seg|
162 endd = @end_count_array[curr_seg]
163 delta = @id_delta_array[curr_seg]
164 range = @id_range_offset_array[curr_seg]
165
166 segments[curr_seg] = {}
167
168 start.upto(endd) do |curr_char|
169 if range == 0
170 index = (curr_char + delta)
171 else
172 gindex = range / 2 + (curr_char - start) - \
173 (@seg_count - curr_seg)
174 index = @glyph_index_array[gindex]
175 index = 0 if index.nil?
176 index += delta if index != 0
177 end
178 index = index % 65536
179 # charcode => glyph index
180 segments[curr_seg][curr_char] = index
181 end
182 end
183 segments
184 end
185 private :get_segments
186
187 # Returns a Hash. Its keys are characters codes and associated values
188 # are glyph indices.
189 def charmaps
190 hsh = {}
191 segments.each do |seg|
192 seg.each do |char_code, glyph_index|
193 hsh[char_code] = glyph_index
194 end
195 end
196 hsh
197 end
198
199 # Sets the charmaps. cmps is a Hash in the same fashion as the one
200 # returned by charmaps.
201 def charmaps=(cmps)
202 # TODO: we would need fewer segments if we ensured that
203 # glyphs with id in ascending order were associated
204 # with char codes in ascending order
205 raise "Charmaps is an empty array" if cmps.length == 0
206
207 # Order is important since we will rebuild segments
208 char_codes = cmps.keys.sort
209
210 @start_count_array = []
211 @end_count_array = []
212 @id_delta_array = []
213 curr_seg = 0
214 i = 0
215
216 @start_count_array[0] = char_codes.first
217 @id_delta_array[0] = cmps[char_codes.first] - char_codes.first
218
219 last_char = 0
220 char_codes.each do |char_code|
221 glyph_id = cmps[char_code]
222 curr_delta = glyph_id - char_code
223 if i > 0 and curr_delta != @id_delta_array.last
224 # Need to create a new segment
225 @end_count_array[curr_seg] = last_char
226 curr_seg += 1
227 @start_count_array[curr_seg] = char_code
228 @id_delta_array[curr_seg] = curr_delta
229 end
230 last_char = char_code
231 i += 1
232 end
233 seg_count = @start_count_array.length
234 @end_count_array[seg_count - 1] = last_char
235 @id_range_offset_array = [0] * seg_count # Range offsets not used
236 @segments = get_segments # Recalculate segments
237
238 # Values below are calculated
239 # to allow faster computation by font rasterizers
240 res = 1
241 power = 0
242 while res <= seg_count
243 res *= 2
244 power += 1
245 end
246 @search_range = res
247 @entry_selector = power - 1
248 @range_shift = 2 * seg_count - @search_range
249 end
250
251 # Returns index/id of glyph associated with unicode.
252 def get_glyph_id_for_unicode(unicode)
253 id = 0
254 @segments.length.times do |i|
255 if @start_count_array[i] <= unicode and \
256 unicode <= @end_count_array[i]
257 @segments[i].each do |char_code, glyph_id|
258 if char_code == unicode
259 id = glyph_id
260 break
261 end
262 end
263 end
264 end
265 id
266 end
267
268 # Returns the Font::TTF::Table::Glyf::SimpleGlyph or
269 # Font::TTF::Table::Glyf::CompositeGlyph associated with unicode.
270 def get_glyph_for_unicode(unicode)
271 id = get_glyph_id_for_unicode(unicode)
272 offs = @font.get_table(:loca).glyph_offsets[id]
273 @font.get_table(:glyf).get_glyph_at_offset(offs)
274 end
275
276 # Returns the unicode of glyph with index id.
277 def get_unicode_for_glyph_id(id)
278 # TODO: this method could be rewritten much more efficiently
279 # by using @start_count_array and @end_count_array
280 unicode = 0
281 charmaps.each do |char_code, glyph_index|
282 if glyph_index == id
283 unicode = char_code
284 break
285 end
286 end
287 unicode
288 end
289
290 # Dumps the table in binary raw format as may be found in a font
291 # file.
292 def dump
293 raw = (format || 0).to_ushort
294 seg_count = @segments.length || 0
295 len = 8 * IO::SIZEOF_USHORT + 4 * seg_count * IO::SIZEOF_USHORT + \
296 @glyph_index_array.length * IO::SIZEOF_USHORT
297 raw += len.to_ushort
298 raw += (@version || 0).to_ushort
299 raw += (seg_count * 2).to_ushort
300 raw += (@search_range || 0).to_ushort
301 raw += (@entry_selector || 0).to_ushort
302 raw += (@range_shift || 0).to_ushort
303 raw += (@end_count_array || []).to_ushorts
304 raw += (@reserved_pad || 0).to_ushort
305 raw += (@start_count_array || []).to_ushorts
306 raw += (@id_delta_array || []).to_ushorts
307 raw += (@id_range_offset_array || []).to_ushorts
308 raw += (@glyph_index_array || []).to_ushorts
309 end
310 end
311
312 # Encoding table in format 6. Not implemented.
313 class EncodingTable6 < EncodingTable
314 end
315
316 # Encoding table in format 8. Not implemented.
317 class EncodingTable8 < EncodingTable
318 end
319
320 # Encoding table in format 10. Not implemented.
321 class EncodingTable10 < EncodingTable
322 end
323
324 # Encoding table in format 12. Not implemented.
325 class EncodingTable12 < EncodingTable
326 end
327
328 attr_accessor :version
329 # An Array of encoding_tables. You may add or remove encoding tables
330 # from this array.
331 attr_accessor :encoding_tables
332
333 def initialize(*args)
334 super(*args)
335
336 if exists_in_file?
337 @font.at_offset(@offset) do
338 @version = @font.read_ushort
339 @encoding_table_num = @font.read_ushort
340 @encoding_tables = []
341 @encoding_table_num.times do
342 platform_id = @font.read_ushort
343 encoding_id = @font.read_ushort
344 offset = @font.read_ulong
345
346 @font.at_offset(@offset + offset) do
347 format = @font.read_ushort
348 len = @font.read_ushort
349
350 if [0, 2, 4, 6, 8, 10, 12].include? format
351 klass = eval("EncodingTable%d" % format)
352 else
353 klass = EncodingTable
354 end
355
356 @encoding_tables << klass.new(self,
357 offset,
358 len,
359 platform_id,
360 encoding_id)
361 end
362 end
363
364 end
365 else
366 @encoding_tables = []
367 end
368 end
369
370 # Returns a new empty EncodingTable0 object that you may add to the
371 # encoding_tables array.
372 def get_new_encoding_table0
373 EncodingTable0.new(self)
374 end
375
376 # Returns a new empty EncodingTable4 object that you may add to the
377 # encoding_tables array.
378 def get_new_encoding_table4
379 EncodingTable4.new(self)
380 end
381
382 # Dumps the cmap table in binary raw format as may be found in a font
383 # file.
384 def dump
385 raw = (@version || 0).to_ushort
386 raw += (@encoding_tables.length || 0).to_ushort
387 dumps = []
388 offs = 2 * IO::SIZEOF_USHORT + @encoding_tables.length * \
389 (2 * IO::SIZEOF_USHORT + IO::SIZEOF_ULONG)
390 @encoding_tables.each do |enc_tbl|
391 raw += (enc_tbl.platform_id || 3).to_ushort
392 raw += (enc_tbl.encoding_id || 1).to_ushort
393 dump = enc_tbl.dump
394 dumps << dump
395 raw += offs.to_ulong
396 offs += dump.length
397 end
398 dumps.each do |dump|
399 raw += dump
400 end
401 raw
402 end
403
404 end
405
406 end
407 end
408 end