ttfunk 1.5.1 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +0 -0
- data/CHANGELOG.md +60 -0
- data/README.md +2 -1
- data/lib/ttfunk.rb +45 -0
- data/lib/ttfunk/aggregate.rb +15 -0
- data/lib/ttfunk/bin_utils.rb +47 -0
- data/lib/ttfunk/bit_field.rb +31 -0
- data/lib/ttfunk/collection.rb +3 -1
- data/lib/ttfunk/directory.rb +6 -0
- data/lib/ttfunk/encoded_string.rb +97 -0
- data/lib/ttfunk/max.rb +25 -0
- data/lib/ttfunk/min.rb +25 -0
- data/lib/ttfunk/one_based_array.rb +36 -0
- data/lib/ttfunk/otf_encoder.rb +61 -0
- data/lib/ttfunk/placeholder.rb +13 -0
- data/lib/ttfunk/reader.rb +34 -32
- data/lib/ttfunk/resource_file.rb +7 -5
- data/lib/ttfunk/sci_form.rb +29 -0
- data/lib/ttfunk/sub_table.rb +38 -0
- data/lib/ttfunk/subset.rb +2 -0
- data/lib/ttfunk/subset/base.rb +61 -120
- data/lib/ttfunk/subset/code_page.rb +89 -0
- data/lib/ttfunk/subset/mac_roman.rb +5 -42
- data/lib/ttfunk/subset/unicode.rb +12 -6
- data/lib/ttfunk/subset/unicode_8bit.rb +14 -12
- data/lib/ttfunk/subset/windows_1252.rb +5 -47
- data/lib/ttfunk/subset_collection.rb +4 -0
- data/lib/ttfunk/sum.rb +20 -0
- data/lib/ttfunk/table.rb +4 -0
- data/lib/ttfunk/table/cff.rb +69 -0
- data/lib/ttfunk/table/cff/charset.rb +212 -0
- data/lib/ttfunk/table/cff/charsets.rb +14 -0
- data/lib/ttfunk/table/cff/charsets/expert.rb +189 -0
- data/lib/ttfunk/table/cff/charsets/expert_subset.rb +119 -0
- data/lib/ttfunk/table/cff/charsets/iso_adobe.rb +241 -0
- data/lib/ttfunk/table/cff/charsets/standard_strings.rb +404 -0
- data/lib/ttfunk/table/cff/charstring.rb +487 -0
- data/lib/ttfunk/table/cff/charstrings_index.rb +39 -0
- data/lib/ttfunk/table/cff/dict.rb +266 -0
- data/lib/ttfunk/table/cff/encoding.rb +220 -0
- data/lib/ttfunk/table/cff/encodings.rb +12 -0
- data/lib/ttfunk/table/cff/encodings/expert.rb +206 -0
- data/lib/ttfunk/table/cff/encodings/standard.rb +181 -0
- data/lib/ttfunk/table/cff/fd_selector.rb +150 -0
- data/lib/ttfunk/table/cff/font_dict.rb +79 -0
- data/lib/ttfunk/table/cff/font_index.rb +29 -0
- data/lib/ttfunk/table/cff/header.rb +33 -0
- data/lib/ttfunk/table/cff/index.rb +125 -0
- data/lib/ttfunk/table/cff/one_based_index.rb +31 -0
- data/lib/ttfunk/table/cff/path.rb +66 -0
- data/lib/ttfunk/table/cff/private_dict.rb +84 -0
- data/lib/ttfunk/table/cff/subr_index.rb +19 -0
- data/lib/ttfunk/table/cff/top_dict.rb +230 -0
- data/lib/ttfunk/table/cff/top_index.rb +16 -0
- data/lib/ttfunk/table/cmap.rb +4 -4
- data/lib/ttfunk/table/cmap/format00.rb +1 -2
- data/lib/ttfunk/table/cmap/format04.rb +11 -3
- data/lib/ttfunk/table/cmap/format06.rb +2 -0
- data/lib/ttfunk/table/cmap/format10.rb +2 -0
- data/lib/ttfunk/table/cmap/format12.rb +2 -0
- data/lib/ttfunk/table/cmap/subtable.rb +12 -8
- data/lib/ttfunk/table/dsig.rb +50 -0
- data/lib/ttfunk/table/glyf.rb +11 -9
- data/lib/ttfunk/table/glyf/compound.rb +14 -7
- data/lib/ttfunk/table/glyf/path_based.rb +47 -0
- data/lib/ttfunk/table/glyf/simple.rb +21 -15
- data/lib/ttfunk/table/head.rb +43 -5
- data/lib/ttfunk/table/hhea.rb +47 -4
- data/lib/ttfunk/table/hmtx.rb +11 -4
- data/lib/ttfunk/table/kern.rb +3 -0
- data/lib/ttfunk/table/kern/format0.rb +3 -0
- data/lib/ttfunk/table/loca.rb +2 -0
- data/lib/ttfunk/table/maxp.rb +144 -10
- data/lib/ttfunk/table/name.rb +75 -37
- data/lib/ttfunk/table/os2.rb +327 -4
- data/lib/ttfunk/table/post.rb +8 -1
- data/lib/ttfunk/table/post/format10.rb +2 -0
- data/lib/ttfunk/table/post/format20.rb +5 -1
- data/lib/ttfunk/table/post/format30.rb +2 -0
- data/lib/ttfunk/table/post/format40.rb +2 -0
- data/lib/ttfunk/table/sbix.rb +2 -0
- data/lib/ttfunk/table/simple.rb +2 -0
- data/lib/ttfunk/table/vorg.rb +54 -0
- data/lib/ttfunk/ttf_encoder.rb +220 -0
- metadata +88 -20
- metadata.gz.sig +0 -0
- data/lib/ttfunk/encoding/mac_roman.rb +0 -100
- data/lib/ttfunk/encoding/windows_1252.rb +0 -76
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTFunk
|
4
|
+
class Table
|
5
|
+
class Cff < TTFunk::Table
|
6
|
+
class CharstringsIndex < TTFunk::Table::Cff::Index
|
7
|
+
attr_reader :top_dict
|
8
|
+
|
9
|
+
def initialize(top_dict, *remaining_args)
|
10
|
+
super(*remaining_args)
|
11
|
+
@top_dict = top_dict
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](index)
|
15
|
+
entry_cache[index] ||= TTFunk::Table::Cff::Charstring.new(
|
16
|
+
index, top_dict, font_dict_for(index), super
|
17
|
+
)
|
18
|
+
end
|
19
|
+
|
20
|
+
# gets passed a mapping of new => old glyph ids
|
21
|
+
def encode(mapping)
|
22
|
+
super() do |_entry, index|
|
23
|
+
self[mapping[index]].encode if mapping.include?(index)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def font_dict_for(index)
|
30
|
+
# only CID-keyed fonts contain an FD selector and font dicts
|
31
|
+
if top_dict.is_cid_font?
|
32
|
+
fd_index = top_dict.font_dict_selector[index]
|
33
|
+
top_dict.font_index[fd_index]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,266 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bigdecimal'
|
4
|
+
|
5
|
+
module TTFunk
|
6
|
+
class Table
|
7
|
+
class Cff < TTFunk::Table
|
8
|
+
class Dict < TTFunk::SubTable
|
9
|
+
class InvalidOperandError < StandardError; end
|
10
|
+
class TooManyOperandsError < StandardError; end
|
11
|
+
|
12
|
+
# for regular single-byte operators
|
13
|
+
OPERATOR_BZERO = (0..21).freeze
|
14
|
+
OPERAND_BZERO = [28..30, 32..254].freeze
|
15
|
+
|
16
|
+
# for operators that are two bytes wide
|
17
|
+
WIDE_OPERATOR_BZERO = 12
|
18
|
+
WIDE_OPERATOR_ADJUSTMENT = 1200
|
19
|
+
|
20
|
+
# maximum number of operands allowed per operator
|
21
|
+
MAX_OPERANDS = 48
|
22
|
+
|
23
|
+
# used to validate operands expressed in scientific notation
|
24
|
+
VALID_SCI_SIGNIFICAND_RE = /\A-?(\.\d+|\d+|\d+\.\d+)\z/.freeze
|
25
|
+
VALID_SCI_EXPONENT_RE = /\A-?\d+\z/.freeze
|
26
|
+
|
27
|
+
include Enumerable
|
28
|
+
|
29
|
+
def [](operator)
|
30
|
+
@dict[operator]
|
31
|
+
end
|
32
|
+
|
33
|
+
def each(&block)
|
34
|
+
@dict.each(&block)
|
35
|
+
end
|
36
|
+
|
37
|
+
alias each_pair each
|
38
|
+
|
39
|
+
def encode
|
40
|
+
map do |(operator, operands)|
|
41
|
+
operands.map { |operand| encode_operand(operand) }.join +
|
42
|
+
encode_operator(operator)
|
43
|
+
end.join
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def encode_operator(operator)
|
49
|
+
if operator >= WIDE_OPERATOR_ADJUSTMENT
|
50
|
+
[
|
51
|
+
WIDE_OPERATOR_BZERO,
|
52
|
+
operator - WIDE_OPERATOR_ADJUSTMENT
|
53
|
+
].pack('C*')
|
54
|
+
else
|
55
|
+
[operator].pack('C')
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def encode_operand(operand)
|
60
|
+
case operand
|
61
|
+
when Integer
|
62
|
+
encode_integer(operand)
|
63
|
+
when Float, BigDecimal
|
64
|
+
encode_float(operand)
|
65
|
+
when SciForm
|
66
|
+
encode_sci(operand)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def encode_integer(int)
|
71
|
+
case int
|
72
|
+
when -107..107
|
73
|
+
[int + 139].pack('C')
|
74
|
+
|
75
|
+
when 108..1131
|
76
|
+
int -= 108
|
77
|
+
[(int >> 8) + 247, int & 0xFF].pack('C*')
|
78
|
+
|
79
|
+
when -1131..-108
|
80
|
+
int = -int - 108
|
81
|
+
[(int >> 8) + 251, int & 0xFF].pack('C*')
|
82
|
+
|
83
|
+
when -32_768..32_767
|
84
|
+
[28, (int >> 8) & 0xFF, int & 0xFF].pack('C*')
|
85
|
+
|
86
|
+
else
|
87
|
+
encode_integer32(int)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def encode_integer32(int)
|
92
|
+
[29, int].pack('CN')
|
93
|
+
end
|
94
|
+
|
95
|
+
def encode_float(float)
|
96
|
+
pack_decimal_nibbles(encode_significand(float))
|
97
|
+
end
|
98
|
+
|
99
|
+
def encode_sci(sci)
|
100
|
+
sig_bytes = encode_significand(sci.significand)
|
101
|
+
exp_bytes = encode_exponent(sci.exponent)
|
102
|
+
pack_decimal_nibbles(sig_bytes + exp_bytes)
|
103
|
+
end
|
104
|
+
|
105
|
+
def encode_exponent(exp)
|
106
|
+
return [] if exp == 0
|
107
|
+
|
108
|
+
[exp > 0 ? 0xB : 0xC, *encode_significand(exp.abs)]
|
109
|
+
end
|
110
|
+
|
111
|
+
def encode_significand(sig)
|
112
|
+
sig.to_s.each_char.with_object([]) do |char, ret|
|
113
|
+
case char
|
114
|
+
when '0'..'9'
|
115
|
+
ret << char.to_i
|
116
|
+
when '.'
|
117
|
+
ret << 0xA
|
118
|
+
when '-'
|
119
|
+
ret << 0xE
|
120
|
+
else
|
121
|
+
break ret
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def pack_decimal_nibbles(nibbles)
|
127
|
+
bytes = [30]
|
128
|
+
|
129
|
+
nibbles.each_slice(2).each do |(high_nb, low_nb)|
|
130
|
+
# low_nb can be nil if nibbles contains an odd number of elements
|
131
|
+
low_nb ||= 0xF
|
132
|
+
bytes << (high_nb << 4 | low_nb)
|
133
|
+
end
|
134
|
+
|
135
|
+
bytes << 0xFF if nibbles.size.even?
|
136
|
+
bytes.pack('C*')
|
137
|
+
end
|
138
|
+
|
139
|
+
def parse!
|
140
|
+
@dict = {}
|
141
|
+
operands = []
|
142
|
+
|
143
|
+
# @length must be set via the constructor
|
144
|
+
while io.pos < table_offset + length
|
145
|
+
case b_zero = read(1, 'C').first
|
146
|
+
when WIDE_OPERATOR_BZERO
|
147
|
+
operator = decode_wide_operator
|
148
|
+
@dict[operator] = operands
|
149
|
+
operands = []
|
150
|
+
when OPERATOR_BZERO
|
151
|
+
@dict[b_zero] = operands unless operands.empty?
|
152
|
+
operands = []
|
153
|
+
when *OPERAND_BZERO
|
154
|
+
operands << decode_operand(b_zero)
|
155
|
+
|
156
|
+
if operands.size > MAX_OPERANDS
|
157
|
+
raise TooManyOperandsError, 'found one too many operands at '\
|
158
|
+
"position #{io.pos} in dict at position #{table_offset}"
|
159
|
+
end
|
160
|
+
else
|
161
|
+
raise "dict byte value #{b_zero} is reserved"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
def decode_wide_operator
|
167
|
+
WIDE_OPERATOR_ADJUSTMENT + read(1, 'C').first
|
168
|
+
end
|
169
|
+
|
170
|
+
def decode_operand(b_zero)
|
171
|
+
case b_zero
|
172
|
+
when 30
|
173
|
+
decode_sci
|
174
|
+
else
|
175
|
+
decode_integer(b_zero)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def decode_sci
|
180
|
+
significand = ''.b
|
181
|
+
exponent = ''.b
|
182
|
+
|
183
|
+
loop do
|
184
|
+
current = read(1, 'C').first
|
185
|
+
break if current == 0xFF
|
186
|
+
|
187
|
+
high_nibble = current >> 4
|
188
|
+
low_nibble = current & 0x0F # 0b00001111
|
189
|
+
|
190
|
+
[high_nibble, low_nibble].each do |nibble|
|
191
|
+
case nibble
|
192
|
+
when 0..9
|
193
|
+
(exponent.empty? ? significand : exponent) << nibble.to_s
|
194
|
+
when 0xA
|
195
|
+
significand << '.'
|
196
|
+
when 0xB
|
197
|
+
# take advantage of Integer#to_i not caring about whitespace
|
198
|
+
exponent << ' '
|
199
|
+
when 0xC
|
200
|
+
exponent << '-'
|
201
|
+
when 0xE
|
202
|
+
significand << '-'
|
203
|
+
end
|
204
|
+
end
|
205
|
+
|
206
|
+
break if low_nibble == 0xF
|
207
|
+
end
|
208
|
+
|
209
|
+
validate_sci!(significand, exponent)
|
210
|
+
|
211
|
+
SciForm.new(significand.to_f, exponent.to_i)
|
212
|
+
end
|
213
|
+
|
214
|
+
def validate_sci!(significand, exponent)
|
215
|
+
unless valid_significand?(significand) && valid_exponent?(exponent)
|
216
|
+
raise InvalidOperandError,
|
217
|
+
'invalid scientific notation operand with significand '\
|
218
|
+
"'#{significand}' and exponent '#{exponent}' ending at "\
|
219
|
+
"position #{io.pos} in dict at position #{table_offset}"
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def valid_significand?(significand)
|
224
|
+
!(significand.strip =~ VALID_SCI_SIGNIFICAND_RE).nil?
|
225
|
+
end
|
226
|
+
|
227
|
+
def valid_exponent?(exponent)
|
228
|
+
exponent = exponent.strip
|
229
|
+
return true if exponent.empty?
|
230
|
+
|
231
|
+
!(exponent.strip =~ VALID_SCI_EXPONENT_RE).nil?
|
232
|
+
end
|
233
|
+
|
234
|
+
def decode_integer(b_zero)
|
235
|
+
case b_zero
|
236
|
+
when 32..246
|
237
|
+
# 1 byte
|
238
|
+
b_zero - 139
|
239
|
+
|
240
|
+
when 247..250
|
241
|
+
# 2 bytes
|
242
|
+
b_one = read(1, 'C').first
|
243
|
+
(b_zero - 247) * 256 + b_one + 108
|
244
|
+
|
245
|
+
when 251..254
|
246
|
+
# 2 bytes
|
247
|
+
b_one = read(1, 'C').first
|
248
|
+
-(b_zero - 251) * 256 - b_one - 108
|
249
|
+
|
250
|
+
when 28
|
251
|
+
# 2 bytes in number (3 total)
|
252
|
+
b_one, b_two = read(2, 'C*')
|
253
|
+
BinUtils.twos_comp_to_int(b_one << 8 | b_two, bit_width: 16)
|
254
|
+
|
255
|
+
when 29
|
256
|
+
# 4 bytes in number (5 total)
|
257
|
+
b_one, b_two, b_three, b_four = read(4, 'C*')
|
258
|
+
BinUtils.twos_comp_to_int(
|
259
|
+
b_one << 24 | b_two << 16 | b_three << 8 | b_four, bit_width: 32
|
260
|
+
)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TTFunk
|
4
|
+
class Table
|
5
|
+
class Cff < TTFunk::Table
|
6
|
+
class Encoding < TTFunk::SubTable
|
7
|
+
include Enumerable
|
8
|
+
|
9
|
+
STANDARD_ENCODING_ID = 0
|
10
|
+
EXPERT_ENCODING_ID = 1
|
11
|
+
|
12
|
+
DEFAULT_ENCODING_ID = STANDARD_ENCODING_ID
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def codes_for_encoding_id(encoding_id)
|
16
|
+
case encoding_id
|
17
|
+
when STANDARD_ENCODING_ID
|
18
|
+
Encodings::STANDARD
|
19
|
+
when EXPERT_ENCODING_ID
|
20
|
+
Encodings::EXPERT
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :top_dict, :format, :count, :offset_or_id
|
26
|
+
|
27
|
+
def initialize(top_dict, file, offset_or_id = nil, length = nil)
|
28
|
+
@top_dict = top_dict
|
29
|
+
@offset_or_id = offset_or_id || DEFAULT_ENCODING_ID
|
30
|
+
|
31
|
+
if offset
|
32
|
+
super(file, offset, length)
|
33
|
+
else
|
34
|
+
@count = self.class.codes_for_encoding_id(offset_or_id).size
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def each
|
39
|
+
return to_enum(__method__) unless block_given?
|
40
|
+
|
41
|
+
# +1 adjusts for the implicit .notdef glyph
|
42
|
+
(count + 1).times { |i| yield self[i] }
|
43
|
+
end
|
44
|
+
|
45
|
+
def [](glyph_id)
|
46
|
+
return 0 if glyph_id == 0
|
47
|
+
return code_for(glyph_id) if offset
|
48
|
+
|
49
|
+
self.class.codes_for_encoding_id(offset_or_id)[glyph_id]
|
50
|
+
end
|
51
|
+
|
52
|
+
def offset
|
53
|
+
# Numbers from 0..1 mean encoding IDs instead of offsets. IDs are
|
54
|
+
# pre-defined, generic encodings that define the characters present
|
55
|
+
# in the font.
|
56
|
+
#
|
57
|
+
# In the case of an offset, add the CFF table's offset since the
|
58
|
+
# charset offset is relative to the start of the CFF table. Otherwise
|
59
|
+
# return nil (no offset).
|
60
|
+
if offset_or_id > 1
|
61
|
+
offset_or_id + top_dict.cff_offset
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def encode(new_to_old, old_to_new)
|
66
|
+
# no offset means no encoding was specified (i.e. we're supposed to
|
67
|
+
# use a predefined encoding) so there's nothing to encode
|
68
|
+
return '' unless offset
|
69
|
+
return encode_supplemental(new_to_old, old_to_new) if supplemental?
|
70
|
+
|
71
|
+
codes = new_to_old.keys.sort.map do |new_gid|
|
72
|
+
code_for(new_to_old[new_gid])
|
73
|
+
end
|
74
|
+
|
75
|
+
ranges = TTFunk::BinUtils.rangify(codes)
|
76
|
+
|
77
|
+
# calculate whether storing the charset as a series of ranges is
|
78
|
+
# more efficient (i.e. takes up less space) vs storing it as an
|
79
|
+
# array of SID values
|
80
|
+
total_range_size = (2 * ranges.size) +
|
81
|
+
(element_width(:range_format) * ranges.size)
|
82
|
+
|
83
|
+
total_array_size = codes.size * element_width(:array_format)
|
84
|
+
|
85
|
+
if total_array_size <= total_range_size
|
86
|
+
([format_int(:array_format), codes.size] + codes).pack('C*')
|
87
|
+
else
|
88
|
+
element_fmt = element_format(:range_format)
|
89
|
+
result = [format_int(:range_format), ranges.size].pack('CC')
|
90
|
+
ranges.each { |range| result << range.pack(element_fmt) }
|
91
|
+
result
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def supplemental?
|
96
|
+
# high-order bit set to 1 indicates supplemental encoding
|
97
|
+
@format >> 7 == 1
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
def encode_supplemental(_new_to_old, old_to_new)
|
103
|
+
new_entries = @entries.each_with_object({}) do |(code, old_gid), ret|
|
104
|
+
if (new_gid = old_to_new[old_gid])
|
105
|
+
ret[code] = new_gid
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
result = [format_int(:supplemental), new_entries.size].pack('CC')
|
110
|
+
fmt = element_format(:supplemental)
|
111
|
+
|
112
|
+
new_entries.each do |code, new_gid|
|
113
|
+
result << [code, new_gid].pack(fmt)
|
114
|
+
end
|
115
|
+
|
116
|
+
result
|
117
|
+
end
|
118
|
+
|
119
|
+
def code_for(glyph_id)
|
120
|
+
return 0 if glyph_id == 0
|
121
|
+
|
122
|
+
# rather than validating the glyph as part of one of the predefined
|
123
|
+
# encodings, just pass it through
|
124
|
+
return glyph_id unless offset
|
125
|
+
|
126
|
+
case format_sym
|
127
|
+
when :array_format
|
128
|
+
@entries[glyph_id]
|
129
|
+
|
130
|
+
when :range_format
|
131
|
+
remaining = glyph_id
|
132
|
+
|
133
|
+
@entries.each do |range|
|
134
|
+
if range.size >= remaining
|
135
|
+
return (range.first + remaining) - 1
|
136
|
+
end
|
137
|
+
|
138
|
+
remaining -= range.size
|
139
|
+
end
|
140
|
+
|
141
|
+
0
|
142
|
+
|
143
|
+
when :supplemental
|
144
|
+
@entries[glyph_id]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
148
|
+
def parse!
|
149
|
+
@format, entry_count = read(2, 'C*')
|
150
|
+
@length = entry_count * element_width
|
151
|
+
|
152
|
+
case format_sym
|
153
|
+
when :array_format
|
154
|
+
@count = entry_count
|
155
|
+
@entries = OneBasedArray.new(read(length, 'C*'))
|
156
|
+
|
157
|
+
when :range_format
|
158
|
+
@entries = []
|
159
|
+
@count = 0
|
160
|
+
|
161
|
+
entry_count.times do
|
162
|
+
code, num_left = read(element_width, element_format)
|
163
|
+
@entries << (code..(code + num_left))
|
164
|
+
@count += num_left + 1
|
165
|
+
end
|
166
|
+
|
167
|
+
when :supplemental
|
168
|
+
@entries = {}
|
169
|
+
@count = entry_count
|
170
|
+
|
171
|
+
entry_count.times do
|
172
|
+
code, glyph = read(element_width, element_format)
|
173
|
+
@entries[code] = glyph
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def element_format(fmt = format_sym)
|
179
|
+
case fmt
|
180
|
+
when :array_format then 'C'
|
181
|
+
when :range_format then 'CC'
|
182
|
+
when :supplemental then 'Cn'
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# @TODO: handle supplemental encoding (necessary?)
|
187
|
+
def element_width(fmt = format_sym)
|
188
|
+
case fmt
|
189
|
+
when :array_format then 1
|
190
|
+
when :range_format then 2
|
191
|
+
when :supplemental then 3
|
192
|
+
else
|
193
|
+
raise "'#{fmt}' is an unsupported encoding format"
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def format_sym
|
198
|
+
return :supplemental if supplemental?
|
199
|
+
|
200
|
+
case @format
|
201
|
+
when 0 then :array_format
|
202
|
+
when 1 then :range_format
|
203
|
+
else
|
204
|
+
raise "unsupported charset format '#{fmt}'"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def format_int(sym = format_sym)
|
209
|
+
case sym
|
210
|
+
when :array_format then 0
|
211
|
+
when :range_format then 1
|
212
|
+
when :supplemental then 129
|
213
|
+
else
|
214
|
+
raise "unsupported charset format '#{sym}'"
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|