|
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 |