ttfunk 1.7.0 → 1.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/CHANGELOG.md +74 -0
- data/README.md +17 -15
- data/lib/ttfunk/aggregate.rb +5 -0
- data/lib/ttfunk/bin_utils.rb +27 -8
- data/lib/ttfunk/bit_field.rb +25 -2
- data/lib/ttfunk/collection.rb +27 -3
- data/lib/ttfunk/directory.rb +7 -1
- data/lib/ttfunk/encoded_string.rb +58 -4
- data/lib/ttfunk/max.rb +14 -0
- data/lib/ttfunk/min.rb +14 -0
- data/lib/ttfunk/one_based_array.rb +20 -0
- data/lib/ttfunk/otf_encoder.rb +5 -14
- data/lib/ttfunk/placeholder.rb +15 -1
- data/lib/ttfunk/reader.rb +6 -4
- data/lib/ttfunk/resource_file.rb +29 -5
- data/lib/ttfunk/sci_form.rb +20 -3
- data/lib/ttfunk/sub_table.rb +29 -4
- data/lib/ttfunk/subset/base.rb +48 -0
- data/lib/ttfunk/subset/code_page.rb +49 -2
- data/lib/ttfunk/subset/mac_roman.rb +2 -0
- data/lib/ttfunk/subset/unicode.rb +32 -0
- data/lib/ttfunk/subset/unicode_8bit.rb +32 -0
- data/lib/ttfunk/subset/windows_1252.rb +2 -0
- data/lib/ttfunk/subset.rb +8 -0
- data/lib/ttfunk/subset_collection.rb +39 -14
- data/lib/ttfunk/sum.rb +13 -0
- data/lib/ttfunk/table/cff/charset.rb +96 -18
- data/lib/ttfunk/table/cff/charsets/expert.rb +3 -2
- data/lib/ttfunk/table/cff/charsets/expert_subset.rb +3 -2
- data/lib/ttfunk/table/cff/charsets/iso_adobe.rb +3 -2
- data/lib/ttfunk/table/cff/charsets/standard_strings.rb +3 -2
- data/lib/ttfunk/table/cff/charsets.rb +1 -0
- data/lib/ttfunk/table/cff/charstring.rb +33 -12
- data/lib/ttfunk/table/cff/charstrings_index.rb +17 -11
- data/lib/ttfunk/table/cff/dict.rb +53 -23
- data/lib/ttfunk/table/cff/encoding.rb +82 -24
- data/lib/ttfunk/table/cff/encodings/expert.rb +3 -2
- data/lib/ttfunk/table/cff/encodings/standard.rb +3 -2
- data/lib/ttfunk/table/cff/encodings.rb +1 -0
- data/lib/ttfunk/table/cff/fd_selector.rb +61 -21
- data/lib/ttfunk/table/cff/font_dict.rb +30 -18
- data/lib/ttfunk/table/cff/font_index.rb +22 -10
- data/lib/ttfunk/table/cff/header.rb +16 -3
- data/lib/ttfunk/table/cff/index.rb +97 -65
- data/lib/ttfunk/table/cff/one_based_index.rb +11 -1
- data/lib/ttfunk/table/cff/path.rb +43 -4
- data/lib/ttfunk/table/cff/private_dict.rb +31 -11
- data/lib/ttfunk/table/cff/subr_index.rb +7 -2
- data/lib/ttfunk/table/cff/top_dict.rb +82 -59
- data/lib/ttfunk/table/cff/top_index.rb +10 -6
- data/lib/ttfunk/table/cff.rb +41 -21
- data/lib/ttfunk/table/cmap/format00.rb +27 -6
- data/lib/ttfunk/table/cmap/format04.rb +34 -14
- data/lib/ttfunk/table/cmap/format06.rb +28 -1
- data/lib/ttfunk/table/cmap/format10.rb +29 -2
- data/lib/ttfunk/table/cmap/format12.rb +29 -2
- data/lib/ttfunk/table/cmap/subtable.rb +50 -6
- data/lib/ttfunk/table/cmap.rb +21 -0
- data/lib/ttfunk/table/dsig.rb +47 -6
- data/lib/ttfunk/table/glyf/compound.rb +73 -6
- data/lib/ttfunk/table/glyf/path_based.rb +40 -3
- data/lib/ttfunk/table/glyf/simple.rb +50 -5
- data/lib/ttfunk/table/glyf.rb +15 -7
- data/lib/ttfunk/table/head.rb +84 -6
- data/lib/ttfunk/table/hhea.rb +71 -10
- data/lib/ttfunk/table/hmtx.rb +32 -5
- data/lib/ttfunk/table/kern/format0.rb +25 -7
- data/lib/ttfunk/table/kern.rb +16 -4
- data/lib/ttfunk/table/loca.rb +21 -8
- data/lib/ttfunk/table/maxp.rb +195 -10
- data/lib/ttfunk/table/name.rb +126 -9
- data/lib/ttfunk/table/os2.rb +150 -26
- data/lib/ttfunk/table/post/format10.rb +7 -0
- data/lib/ttfunk/table/post/format20.rb +9 -0
- data/lib/ttfunk/table/post/format30.rb +6 -0
- data/lib/ttfunk/table/post/format40.rb +5 -0
- data/lib/ttfunk/table/post.rb +63 -7
- data/lib/ttfunk/table/sbix.rb +50 -14
- data/lib/ttfunk/table/simple.rb +5 -0
- data/lib/ttfunk/table/vorg.rb +31 -3
- data/lib/ttfunk/table.rb +20 -1
- data/lib/ttfunk/ttf_encoder.rb +39 -41
- data/lib/ttfunk.rb +154 -1
- data.tar.gz.sig +0 -0
- metadata +50 -28
- metadata.gz.sig +0 -0
@@ -5,43 +5,74 @@ require 'bigdecimal'
|
|
5
5
|
module TTFunk
|
6
6
|
class Table
|
7
7
|
class Cff < TTFunk::Table
|
8
|
+
# CFF Dict.
|
8
9
|
class Dict < TTFunk::SubTable
|
10
|
+
# Indicates malformed operand.
|
9
11
|
class InvalidOperandError < StandardError; end
|
10
12
|
|
13
|
+
# Indicates too many operands.
|
11
14
|
class TooManyOperandsError < StandardError; end
|
12
15
|
|
13
|
-
#
|
16
|
+
# Single-byte operators.
|
14
17
|
OPERATOR_BZERO = (0..21).freeze
|
18
|
+
# Bytes indicating an operand.
|
15
19
|
OPERAND_BZERO = [28..30, 32..254].freeze
|
16
20
|
|
17
|
-
#
|
21
|
+
# Two-byte operator
|
18
22
|
WIDE_OPERATOR_BZERO = 12
|
23
|
+
# Two-byte operator adjustment. Used for encoding and decoding of wide
|
24
|
+
# operators.
|
19
25
|
WIDE_OPERATOR_ADJUSTMENT = 1200
|
20
26
|
|
21
|
-
#
|
27
|
+
# Maximum number of operands allowed per operator.
|
22
28
|
MAX_OPERANDS = 48
|
23
29
|
|
24
|
-
#
|
30
|
+
# Scientific notation operand significand validation regular
|
31
|
+
# experession.
|
25
32
|
VALID_SCI_SIGNIFICAND_RE = /\A-?(\.\d+|\d+|\d+\.\d+)\z/.freeze
|
33
|
+
|
34
|
+
# Scientific notation operand exponent validation regular experession.
|
26
35
|
VALID_SCI_EXPONENT_RE = /\A-?\d+\z/.freeze
|
27
36
|
|
28
37
|
include Enumerable
|
29
38
|
|
39
|
+
# Get dict value by operator.
|
40
|
+
#
|
41
|
+
# @param operator [Integer]
|
42
|
+
# @return [Array<Integer, TTFunk::SciForm>]
|
30
43
|
def [](operator)
|
31
44
|
@dict[operator]
|
32
45
|
end
|
33
46
|
|
47
|
+
# Add dict entry.
|
48
|
+
#
|
49
|
+
# @param operator [Integer] Entry operator. Must be in range 0..255. Wide operators must be in range 1200..1455.
|
50
|
+
# @param operands [Array<Integer, TTFunk::SciForm>]
|
51
|
+
def []=(operator, *operands)
|
52
|
+
@dict[operator] = Array(*operands)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Iterate over dict entries.
|
56
|
+
#
|
57
|
+
# @yieldparam key [Integer]
|
58
|
+
# @yieldparam value [Array<Integer, TTFunk::SciForm>]
|
59
|
+
# @return [void]
|
34
60
|
def each(&block)
|
35
61
|
@dict.each(&block)
|
36
62
|
end
|
37
63
|
|
38
64
|
alias each_pair each
|
39
65
|
|
66
|
+
# Encode dict.
|
67
|
+
#
|
68
|
+
# @return [String]
|
40
69
|
def encode
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
70
|
+
sort_by(&:first)
|
71
|
+
.map { |(operator, operands)|
|
72
|
+
operands.map { |operand| encode_operand(operand) }.join +
|
73
|
+
encode_operator(operator)
|
74
|
+
}
|
75
|
+
.join
|
45
76
|
end
|
46
77
|
|
47
78
|
private
|
@@ -50,7 +81,7 @@ module TTFunk
|
|
50
81
|
if operator >= WIDE_OPERATOR_ADJUSTMENT
|
51
82
|
[
|
52
83
|
WIDE_OPERATOR_BZERO,
|
53
|
-
operator - WIDE_OPERATOR_ADJUSTMENT
|
84
|
+
operator - WIDE_OPERATOR_ADJUSTMENT,
|
54
85
|
].pack('C*')
|
55
86
|
else
|
56
87
|
[operator].pack('C')
|
@@ -113,7 +144,7 @@ module TTFunk
|
|
113
144
|
sig.to_s.each_char.with_object([]) do |char, ret|
|
114
145
|
case char
|
115
146
|
when '0'..'9'
|
116
|
-
ret << char
|
147
|
+
ret << Integer(char)
|
117
148
|
when '.'
|
118
149
|
ret << 0xA
|
119
150
|
when '-'
|
@@ -130,7 +161,7 @@ module TTFunk
|
|
130
161
|
nibbles.each_slice(2).each do |(high_nb, low_nb)|
|
131
162
|
# low_nb can be nil if nibbles contains an odd number of elements
|
132
163
|
low_nb ||= 0xF
|
133
|
-
bytes << (high_nb << 4 | low_nb)
|
164
|
+
bytes << ((high_nb << 4) | low_nb)
|
134
165
|
end
|
135
166
|
|
136
167
|
bytes << 0xFF if nibbles.size.even?
|
@@ -156,8 +187,7 @@ module TTFunk
|
|
156
187
|
|
157
188
|
if operands.size > MAX_OPERANDS
|
158
189
|
raise TooManyOperandsError,
|
159
|
-
|
160
|
-
"position #{io.pos} in dict at position #{table_offset}"
|
190
|
+
"found one too many operands at position #{io.pos} in dict at position #{table_offset}"
|
161
191
|
end
|
162
192
|
else
|
163
193
|
raise Error, "dict byte value #{b_zero} is reserved"
|
@@ -210,15 +240,17 @@ module TTFunk
|
|
210
240
|
|
211
241
|
validate_sci!(significand, exponent)
|
212
242
|
|
213
|
-
|
243
|
+
exponent = 0 if exponent.empty?
|
244
|
+
|
245
|
+
SciForm.new(Float(significand), Integer(exponent))
|
214
246
|
end
|
215
247
|
|
216
248
|
def validate_sci!(significand, exponent)
|
217
249
|
unless valid_significand?(significand) && valid_exponent?(exponent)
|
218
250
|
raise InvalidOperandError,
|
219
|
-
'invalid scientific notation operand with significand '\
|
220
|
-
|
221
|
-
|
251
|
+
'invalid scientific notation operand with significand ' \
|
252
|
+
"'#{significand}' and exponent '#{exponent}' ending at " \
|
253
|
+
"position #{io.pos} in dict at position #{table_offset}"
|
222
254
|
end
|
223
255
|
end
|
224
256
|
|
@@ -242,24 +274,22 @@ module TTFunk
|
|
242
274
|
when 247..250
|
243
275
|
# 2 bytes
|
244
276
|
b_one = read(1, 'C').first
|
245
|
-
(b_zero - 247) * 256 + b_one + 108
|
277
|
+
((b_zero - 247) * 256) + b_one + 108
|
246
278
|
|
247
279
|
when 251..254
|
248
280
|
# 2 bytes
|
249
281
|
b_one = read(1, 'C').first
|
250
|
-
-(b_zero - 251) * 256 - b_one - 108
|
282
|
+
(-(b_zero - 251) * 256) - b_one - 108
|
251
283
|
|
252
284
|
when 28
|
253
285
|
# 2 bytes in number (3 total)
|
254
286
|
b_one, b_two = read(2, 'C*')
|
255
|
-
BinUtils.twos_comp_to_int(b_one << 8 | b_two, bit_width: 16)
|
287
|
+
BinUtils.twos_comp_to_int((b_one << 8) | b_two, bit_width: 16)
|
256
288
|
|
257
289
|
when 29
|
258
290
|
# 4 bytes in number (5 total)
|
259
291
|
b_one, b_two, b_three, b_four = read(4, 'C*')
|
260
|
-
BinUtils.twos_comp_to_int(
|
261
|
-
b_one << 24 | b_two << 16 | b_three << 8 | b_four, bit_width: 32
|
262
|
-
)
|
292
|
+
BinUtils.twos_comp_to_int((b_one << 24) | (b_two << 16) | (b_three << 8) | b_four, bit_width: 32)
|
263
293
|
end
|
264
294
|
end
|
265
295
|
end
|
@@ -3,15 +3,24 @@
|
|
3
3
|
module TTFunk
|
4
4
|
class Table
|
5
5
|
class Cff < TTFunk::Table
|
6
|
+
# CFF Encoding.
|
6
7
|
class Encoding < TTFunk::SubTable
|
7
8
|
include Enumerable
|
8
9
|
|
10
|
+
# Predefined Standard Encoding ID.
|
9
11
|
STANDARD_ENCODING_ID = 0
|
12
|
+
|
13
|
+
# Predefined Expert Encoding ID.
|
10
14
|
EXPERT_ENCODING_ID = 1
|
11
15
|
|
16
|
+
# Default encoding ID.
|
12
17
|
DEFAULT_ENCODING_ID = STANDARD_ENCODING_ID
|
13
18
|
|
14
19
|
class << self
|
20
|
+
# Get predefined encoding by ID.
|
21
|
+
#
|
22
|
+
# @param encoding_id [Integer]
|
23
|
+
# @return [TTFunk::OneBasedArray<Integer>]
|
15
24
|
def codes_for_encoding_id(encoding_id)
|
16
25
|
case encoding_id
|
17
26
|
when STANDARD_ENCODING_ID
|
@@ -22,26 +31,62 @@ module TTFunk
|
|
22
31
|
end
|
23
32
|
end
|
24
33
|
|
25
|
-
|
26
|
-
|
34
|
+
# Top dict.
|
35
|
+
# @return [TTFunk::Table::Cff::TopDict]
|
36
|
+
attr_reader :top_dict
|
37
|
+
|
38
|
+
# Encodign format.
|
39
|
+
# @return [Integer]
|
40
|
+
attr_reader :format
|
41
|
+
|
42
|
+
# Number of encoded items.
|
43
|
+
# @return [Integer]
|
44
|
+
attr_reader :items_count
|
45
|
+
|
46
|
+
# Offset or encoding ID.
|
47
|
+
# @return [Integer]
|
48
|
+
attr_reader :offset_or_id
|
49
|
+
|
50
|
+
# @overload initialize(top_dict, file, offset = nil, length = nil)
|
51
|
+
# @param top_dict [TTFunk::Table:Cff::TopDict]
|
52
|
+
# @param file [TTFunk::File]
|
53
|
+
# @param offset [Integer]
|
54
|
+
# @param length [Integer]
|
55
|
+
# @overload initialize(top_dict, file, charset_id)
|
56
|
+
# @param top_dict [TTFunk::Table:Cff::TopDict]
|
57
|
+
# @param file [TTFunk::File]
|
58
|
+
# @param encoding_id [Integer] 0, 1, or 2
|
27
59
|
def initialize(top_dict, file, offset_or_id = nil, length = nil)
|
28
60
|
@top_dict = top_dict
|
29
61
|
@offset_or_id = offset_or_id || DEFAULT_ENCODING_ID
|
30
62
|
|
31
63
|
if offset
|
32
64
|
super(file, offset, length)
|
65
|
+
@supplemental = format >> 7 == 1
|
33
66
|
else
|
34
|
-
@
|
67
|
+
@items_count = self.class.codes_for_encoding_id(offset_or_id).size
|
68
|
+
@supplemental = false
|
35
69
|
end
|
36
70
|
end
|
37
71
|
|
72
|
+
# Iterate over character codes.
|
73
|
+
#
|
74
|
+
# @overload each()
|
75
|
+
# @yieldparam code [Integer]
|
76
|
+
# @return [void]
|
77
|
+
# @overload each()
|
78
|
+
# @return [Enumerator]
|
38
79
|
def each
|
39
80
|
return to_enum(__method__) unless block_given?
|
40
81
|
|
41
82
|
# +1 adjusts for the implicit .notdef glyph
|
42
|
-
(
|
83
|
+
(items_count + 1).times { |i| yield(self[i]) }
|
43
84
|
end
|
44
85
|
|
86
|
+
# Get character code for glyph index.
|
87
|
+
#
|
88
|
+
# @param glyph_id [Integer]
|
89
|
+
# @return [Integer, nil]
|
45
90
|
def [](glyph_id)
|
46
91
|
return 0 if glyph_id.zero?
|
47
92
|
return code_for(glyph_id) if offset
|
@@ -49,6 +94,9 @@ module TTFunk
|
|
49
94
|
self.class.codes_for_encoding_id(offset_or_id)[glyph_id]
|
50
95
|
end
|
51
96
|
|
97
|
+
# Encoding offset in the file.
|
98
|
+
#
|
99
|
+
# @return [Integer, nil]
|
52
100
|
def offset
|
53
101
|
# Numbers from 0..1 mean encoding IDs instead of offsets. IDs are
|
54
102
|
# pre-defined, generic encodings that define the characters present
|
@@ -62,16 +110,25 @@ module TTFunk
|
|
62
110
|
end
|
63
111
|
end
|
64
112
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
113
|
+
# Encode encoding.
|
114
|
+
#
|
115
|
+
# @param charmap [Hash{Integer => Hash}] keys are the charac codes,
|
116
|
+
# values are hashes:
|
117
|
+
# * `:old` (<tt>Integer</tt>) - glyph ID in the original font.
|
118
|
+
# * `:new` (<tt>Integer</tt>) - glyph ID in the subset font.
|
119
|
+
# @return [String]
|
120
|
+
def encode(charmap)
|
121
|
+
# Any subset encoding is all but guaranteed to be different from the
|
122
|
+
# standard encoding so we don't even attempt to see if it matches. We
|
123
|
+
# assume it's different and just encode it anew.
|
124
|
+
|
125
|
+
return encode_supplemental(charmap) if supplemental?
|
70
126
|
|
71
127
|
codes =
|
72
|
-
|
73
|
-
|
74
|
-
|
128
|
+
charmap
|
129
|
+
.reject { |_code, mapping| mapping[:new].zero? }
|
130
|
+
.sort_by { |_code, mapping| mapping[:new] }
|
131
|
+
.map { |(code, _m)| code }
|
75
132
|
|
76
133
|
ranges = TTFunk::BinUtils.rangify(codes)
|
77
134
|
|
@@ -93,20 +150,21 @@ module TTFunk
|
|
93
150
|
end
|
94
151
|
end
|
95
152
|
|
153
|
+
# Is this a supplemental encoding?
|
154
|
+
#
|
155
|
+
# @return [Boolean]
|
96
156
|
def supplemental?
|
97
157
|
# high-order bit set to 1 indicates supplemental encoding
|
98
|
-
@
|
158
|
+
@supplemental
|
99
159
|
end
|
100
160
|
|
101
161
|
private
|
102
162
|
|
103
|
-
def encode_supplemental(
|
163
|
+
def encode_supplemental(charmap)
|
104
164
|
new_entries =
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
end
|
109
|
-
end
|
165
|
+
charmap
|
166
|
+
.reject { |_code, mapping| mapping[:new].zero? }
|
167
|
+
.transform_values { |mapping| mapping[:new] }
|
110
168
|
|
111
169
|
result = [format_int(:supplemental), new_entries.size].pack('CC')
|
112
170
|
fmt = element_format(:supplemental)
|
@@ -150,22 +208,22 @@ module TTFunk
|
|
150
208
|
|
151
209
|
case format_sym
|
152
210
|
when :array_format
|
153
|
-
@
|
211
|
+
@items_count = entry_count
|
154
212
|
@entries = OneBasedArray.new(read(length, 'C*'))
|
155
213
|
|
156
214
|
when :range_format
|
157
215
|
@entries = []
|
158
|
-
@
|
216
|
+
@items_count = 0
|
159
217
|
|
160
218
|
entry_count.times do
|
161
219
|
code, num_left = read(element_width, element_format)
|
162
220
|
@entries << (code..(code + num_left))
|
163
|
-
@
|
221
|
+
@items_count += num_left + 1
|
164
222
|
end
|
165
223
|
|
166
224
|
when :supplemental
|
167
225
|
@entries = {}
|
168
|
-
@
|
226
|
+
@items_count = entry_count
|
169
227
|
|
170
228
|
entry_count.times do
|
171
229
|
code, glyph = read(element_width, element_format)
|
@@ -178,7 +236,7 @@ module TTFunk
|
|
178
236
|
{
|
179
237
|
array_format: 'C',
|
180
238
|
range_format: 'CC',
|
181
|
-
supplemental: 'Cn'
|
239
|
+
supplemental: 'Cn',
|
182
240
|
}[fmt]
|
183
241
|
end
|
184
242
|
|
@@ -4,6 +4,7 @@ module TTFunk
|
|
4
4
|
class Table
|
5
5
|
class Cff < TTFunk::Table
|
6
6
|
module Encodings
|
7
|
+
# CFF predefined Expert encoding.
|
7
8
|
EXPERT = OneBasedArray.new(
|
8
9
|
[
|
9
10
|
*[0] * 31,
|
@@ -197,8 +198,8 @@ module TTFunk
|
|
197
198
|
375,
|
198
199
|
376,
|
199
200
|
377,
|
200
|
-
378
|
201
|
-
]
|
201
|
+
378,
|
202
|
+
],
|
202
203
|
).freeze
|
203
204
|
end
|
204
205
|
end
|
@@ -4,6 +4,7 @@ module TTFunk
|
|
4
4
|
class Table
|
5
5
|
class Cff < TTFunk::Table
|
6
6
|
module Encodings
|
7
|
+
# CFF predefined Standard encoding.
|
7
8
|
STANDARD = OneBasedArray.new(
|
8
9
|
[
|
9
10
|
*[0] * 31,
|
@@ -172,8 +173,8 @@ module TTFunk
|
|
172
173
|
147,
|
173
174
|
148,
|
174
175
|
149,
|
175
|
-
*[0] * 4
|
176
|
-
]
|
176
|
+
*[0] * 4,
|
177
|
+
],
|
177
178
|
).freeze
|
178
179
|
end
|
179
180
|
end
|
@@ -3,22 +3,51 @@
|
|
3
3
|
module TTFunk
|
4
4
|
class Table
|
5
5
|
class Cff < TTFunk::Table
|
6
|
+
# CFF FDSelect.
|
6
7
|
class FdSelector < TTFunk::SubTable
|
7
8
|
include Enumerable
|
8
9
|
|
10
|
+
# Array format.
|
9
11
|
ARRAY_FORMAT = 0
|
12
|
+
|
13
|
+
# Range format.
|
10
14
|
RANGE_FORMAT = 3
|
11
15
|
|
16
|
+
# Range entry size.
|
12
17
|
RANGE_ENTRY_SIZE = 3
|
18
|
+
|
19
|
+
# Array entry size.
|
13
20
|
ARRAY_ENTRY_SIZE = 1
|
14
21
|
|
15
|
-
|
22
|
+
# Top dict.
|
23
|
+
# @return [TTFunk::Table::Cff::TopDict]
|
24
|
+
attr_reader :top_dict
|
25
|
+
|
26
|
+
# Number of encoded items.
|
27
|
+
# @return [Integer]
|
28
|
+
attr_reader :items_count
|
29
|
+
|
30
|
+
# Number of entries.
|
31
|
+
# @return [Array<Integer>] if format is array.
|
32
|
+
# @return [Array<Array(Range, Integer)>] if format is range.
|
33
|
+
attr_reader :entries
|
34
|
+
|
35
|
+
# Number of glyphs.
|
36
|
+
# @return [Integer]
|
37
|
+
attr_reader :n_glyphs
|
16
38
|
|
39
|
+
# @param top_dict [TTFunk::Table:Cff::TopDict]
|
40
|
+
# @param file [TTFunk::File]
|
41
|
+
# @param offset [Integer]
|
42
|
+
# @param length [Integer]
|
17
43
|
def initialize(top_dict, file, offset, length = nil)
|
18
44
|
@top_dict = top_dict
|
19
45
|
super(file, offset, length)
|
20
46
|
end
|
21
47
|
|
48
|
+
# Get font dict index for glyph ID.
|
49
|
+
#
|
50
|
+
# @return [Integer]
|
22
51
|
def [](glyph_id)
|
23
52
|
case format_sym
|
24
53
|
when :array_format
|
@@ -30,34 +59,45 @@ module TTFunk
|
|
30
59
|
end
|
31
60
|
|
32
61
|
range, entry =
|
33
|
-
entries.bsearch
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
62
|
+
entries.bsearch { |rng, _|
|
63
|
+
if rng.cover?(glyph_id)
|
64
|
+
0
|
65
|
+
elsif glyph_id < rng.first
|
66
|
+
-1
|
67
|
+
else
|
68
|
+
1
|
69
|
+
end
|
70
|
+
}
|
42
71
|
|
43
72
|
range.each { |i| range_cache[i] = entry }
|
44
73
|
entry
|
45
74
|
end
|
46
75
|
end
|
47
76
|
|
77
|
+
# Iterate over font dicts for each glyph ID.
|
78
|
+
#
|
79
|
+
# @yieldparam [Integer] font dict index.
|
80
|
+
# @return [void]
|
48
81
|
def each
|
49
82
|
return to_enum(__method__) unless block_given?
|
50
83
|
|
51
|
-
|
84
|
+
items_count.times { |i| yield(self[i]) }
|
52
85
|
end
|
53
86
|
|
54
|
-
#
|
55
|
-
|
87
|
+
# Encode Font dict selector.
|
88
|
+
#
|
89
|
+
# @param charmap [Hash{Integer => Hash}] keys are the charac codes,
|
90
|
+
# values are hashes:
|
91
|
+
# * `:old` (<tt>Integer</tt>) - glyph ID in the original font.
|
92
|
+
# * `:new` (<tt>Integer</tt>) - glyph ID in the subset font.
|
93
|
+
# @return [String]
|
94
|
+
def encode(charmap)
|
56
95
|
# get list of [new_gid, fd_index] pairs
|
57
96
|
new_indices =
|
58
|
-
|
59
|
-
|
60
|
-
|
97
|
+
charmap
|
98
|
+
.reject { |code, mapping| mapping[:new].zero? && !code.zero? }
|
99
|
+
.sort_by { |_code, mapping| mapping[:new] }
|
100
|
+
.map { |(_code, mapping)| [mapping[:new], self[mapping[:old]]] }
|
61
101
|
|
62
102
|
ranges = rangify_gids(new_indices)
|
63
103
|
total_range_size = ranges.size * RANGE_ENTRY_SIZE
|
@@ -108,10 +148,10 @@ module TTFunk
|
|
108
148
|
|
109
149
|
case format_sym
|
110
150
|
when :array_format
|
111
|
-
@n_glyphs = top_dict.charstrings_index.
|
151
|
+
@n_glyphs = top_dict.charstrings_index.items_count
|
112
152
|
data = io.read(n_glyphs)
|
113
153
|
@length += data.bytesize
|
114
|
-
@
|
154
|
+
@items_count = data.bytesize
|
115
155
|
@entries = data.bytes
|
116
156
|
|
117
157
|
when :range_format
|
@@ -122,11 +162,11 @@ module TTFunk
|
|
122
162
|
ranges = Array.new(num_ranges) { read(RANGE_ENTRY_SIZE, 'nC') }
|
123
163
|
|
124
164
|
@entries =
|
125
|
-
ranges.each_cons(2).map
|
165
|
+
ranges.each_cons(2).map { |first, second|
|
126
166
|
first_gid, fd_index = first
|
127
167
|
second_gid, = second
|
128
168
|
[(first_gid...second_gid), fd_index]
|
129
|
-
|
169
|
+
}
|
130
170
|
|
131
171
|
# read the sentinel GID, otherwise known as the number of glyphs
|
132
172
|
# in the font
|
@@ -135,7 +175,7 @@ module TTFunk
|
|
135
175
|
last_start_gid, last_fd_index = ranges.last
|
136
176
|
@entries << [(last_start_gid...(n_glyphs + 1)), last_fd_index]
|
137
177
|
|
138
|
-
@
|
178
|
+
@items_count = entries.reduce(0) { |sum, entry| sum + entry.first.size }
|
139
179
|
end
|
140
180
|
end
|
141
181
|
|
@@ -3,19 +3,34 @@
|
|
3
3
|
module TTFunk
|
4
4
|
class Table
|
5
5
|
class Cff < TTFunk::Table
|
6
|
+
# CFF Font dict.
|
6
7
|
class FontDict < TTFunk::Table::Cff::Dict
|
8
|
+
# Length of placeholders.
|
7
9
|
PLACEHOLDER_LENGTH = 5
|
10
|
+
|
11
|
+
# Operators we care about in this dict.
|
8
12
|
OPERATORS = { private: 18 }.freeze
|
13
|
+
|
14
|
+
# Inverse operator mapping.
|
9
15
|
OPERATOR_CODES = OPERATORS.invert
|
10
16
|
|
17
|
+
# Top dict.
|
18
|
+
# @return [TTFunk::Table::Cff::TopDict]
|
11
19
|
attr_reader :top_dict
|
12
20
|
|
21
|
+
# @param top_dict [TTFunk::Table:Cff::TopDict]
|
22
|
+
# @param file [TTFunk::File]
|
23
|
+
# @param offset [Integer]
|
24
|
+
# @param length [Integer]
|
13
25
|
def initialize(top_dict, file, offset, length = nil)
|
14
26
|
@top_dict = top_dict
|
15
27
|
super(file, offset, length)
|
16
28
|
end
|
17
29
|
|
18
|
-
|
30
|
+
# Encode dict.
|
31
|
+
#
|
32
|
+
# @return [TTFunk::EncodedString]
|
33
|
+
def encode
|
19
34
|
EncodedString.new do |result|
|
20
35
|
each do |operator, operands|
|
21
36
|
case OPERATOR_CODES[operator]
|
@@ -30,23 +45,25 @@ module TTFunk
|
|
30
45
|
end
|
31
46
|
end
|
32
47
|
|
33
|
-
|
34
|
-
|
48
|
+
# Finalize dict.
|
49
|
+
#
|
50
|
+
# @param new_cff_data [TTFunk::EncodedString]
|
51
|
+
# @return [void]
|
52
|
+
def finalize(new_cff_data)
|
53
|
+
encoded_private_dict = private_dict.encode
|
35
54
|
encoded_offset = encode_integer32(new_cff_data.length)
|
36
55
|
encoded_length = encode_integer32(encoded_private_dict.length)
|
37
56
|
|
38
|
-
new_cff_data.resolve_placeholder(
|
39
|
-
|
40
|
-
)
|
41
|
-
|
42
|
-
new_cff_data.resolve_placeholder(
|
43
|
-
:"private_offset_#{@table_offset}", encoded_offset
|
44
|
-
)
|
57
|
+
new_cff_data.resolve_placeholder(:"private_length_#{@table_offset}", encoded_length)
|
58
|
+
new_cff_data.resolve_placeholder(:"private_offset_#{@table_offset}", encoded_offset)
|
45
59
|
|
46
60
|
private_dict.finalize(encoded_private_dict)
|
47
61
|
new_cff_data << encoded_private_dict
|
48
62
|
end
|
49
63
|
|
64
|
+
# Private dict.
|
65
|
+
#
|
66
|
+
# @return [TTFunk::Table::Cff::PrivateDict, nil]
|
50
67
|
def private_dict
|
51
68
|
@private_dict ||=
|
52
69
|
if (info = self[OPERATORS[:private]])
|
@@ -55,7 +72,7 @@ module TTFunk
|
|
55
72
|
PrivateDict.new(
|
56
73
|
file,
|
57
74
|
top_dict.cff_offset + private_dict_offset,
|
58
|
-
private_dict_length
|
75
|
+
private_dict_length,
|
59
76
|
)
|
60
77
|
end
|
61
78
|
end
|
@@ -64,13 +81,8 @@ module TTFunk
|
|
64
81
|
|
65
82
|
def encode_private
|
66
83
|
EncodedString.new do |result|
|
67
|
-
result << Placeholder.new(
|
68
|
-
|
69
|
-
)
|
70
|
-
|
71
|
-
result << Placeholder.new(
|
72
|
-
:"private_offset_#{@table_offset}", length: PLACEHOLDER_LENGTH
|
73
|
-
)
|
84
|
+
result << Placeholder.new(:"private_length_#{@table_offset}", length: PLACEHOLDER_LENGTH)
|
85
|
+
result << Placeholder.new(:"private_offset_#{@table_offset}", length: PLACEHOLDER_LENGTH)
|
74
86
|
end
|
75
87
|
end
|
76
88
|
end
|