ttfunk 1.5.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/CHANGELOG.md +60 -0
  5. data/README.md +2 -1
  6. data/lib/ttfunk.rb +45 -0
  7. data/lib/ttfunk/aggregate.rb +15 -0
  8. data/lib/ttfunk/bin_utils.rb +47 -0
  9. data/lib/ttfunk/bit_field.rb +31 -0
  10. data/lib/ttfunk/collection.rb +3 -1
  11. data/lib/ttfunk/directory.rb +6 -0
  12. data/lib/ttfunk/encoded_string.rb +97 -0
  13. data/lib/ttfunk/max.rb +25 -0
  14. data/lib/ttfunk/min.rb +25 -0
  15. data/lib/ttfunk/one_based_array.rb +36 -0
  16. data/lib/ttfunk/otf_encoder.rb +61 -0
  17. data/lib/ttfunk/placeholder.rb +13 -0
  18. data/lib/ttfunk/reader.rb +34 -32
  19. data/lib/ttfunk/resource_file.rb +7 -5
  20. data/lib/ttfunk/sci_form.rb +29 -0
  21. data/lib/ttfunk/sub_table.rb +38 -0
  22. data/lib/ttfunk/subset.rb +2 -0
  23. data/lib/ttfunk/subset/base.rb +61 -120
  24. data/lib/ttfunk/subset/code_page.rb +89 -0
  25. data/lib/ttfunk/subset/mac_roman.rb +5 -42
  26. data/lib/ttfunk/subset/unicode.rb +12 -6
  27. data/lib/ttfunk/subset/unicode_8bit.rb +14 -12
  28. data/lib/ttfunk/subset/windows_1252.rb +5 -47
  29. data/lib/ttfunk/subset_collection.rb +4 -0
  30. data/lib/ttfunk/sum.rb +20 -0
  31. data/lib/ttfunk/table.rb +4 -0
  32. data/lib/ttfunk/table/cff.rb +69 -0
  33. data/lib/ttfunk/table/cff/charset.rb +212 -0
  34. data/lib/ttfunk/table/cff/charsets.rb +14 -0
  35. data/lib/ttfunk/table/cff/charsets/expert.rb +189 -0
  36. data/lib/ttfunk/table/cff/charsets/expert_subset.rb +119 -0
  37. data/lib/ttfunk/table/cff/charsets/iso_adobe.rb +241 -0
  38. data/lib/ttfunk/table/cff/charsets/standard_strings.rb +404 -0
  39. data/lib/ttfunk/table/cff/charstring.rb +487 -0
  40. data/lib/ttfunk/table/cff/charstrings_index.rb +39 -0
  41. data/lib/ttfunk/table/cff/dict.rb +266 -0
  42. data/lib/ttfunk/table/cff/encoding.rb +220 -0
  43. data/lib/ttfunk/table/cff/encodings.rb +12 -0
  44. data/lib/ttfunk/table/cff/encodings/expert.rb +206 -0
  45. data/lib/ttfunk/table/cff/encodings/standard.rb +181 -0
  46. data/lib/ttfunk/table/cff/fd_selector.rb +150 -0
  47. data/lib/ttfunk/table/cff/font_dict.rb +79 -0
  48. data/lib/ttfunk/table/cff/font_index.rb +29 -0
  49. data/lib/ttfunk/table/cff/header.rb +33 -0
  50. data/lib/ttfunk/table/cff/index.rb +125 -0
  51. data/lib/ttfunk/table/cff/one_based_index.rb +31 -0
  52. data/lib/ttfunk/table/cff/path.rb +66 -0
  53. data/lib/ttfunk/table/cff/private_dict.rb +84 -0
  54. data/lib/ttfunk/table/cff/subr_index.rb +19 -0
  55. data/lib/ttfunk/table/cff/top_dict.rb +230 -0
  56. data/lib/ttfunk/table/cff/top_index.rb +16 -0
  57. data/lib/ttfunk/table/cmap.rb +4 -4
  58. data/lib/ttfunk/table/cmap/format00.rb +1 -2
  59. data/lib/ttfunk/table/cmap/format04.rb +11 -3
  60. data/lib/ttfunk/table/cmap/format06.rb +2 -0
  61. data/lib/ttfunk/table/cmap/format10.rb +2 -0
  62. data/lib/ttfunk/table/cmap/format12.rb +2 -0
  63. data/lib/ttfunk/table/cmap/subtable.rb +12 -8
  64. data/lib/ttfunk/table/dsig.rb +50 -0
  65. data/lib/ttfunk/table/glyf.rb +11 -9
  66. data/lib/ttfunk/table/glyf/compound.rb +14 -7
  67. data/lib/ttfunk/table/glyf/path_based.rb +47 -0
  68. data/lib/ttfunk/table/glyf/simple.rb +21 -15
  69. data/lib/ttfunk/table/head.rb +43 -5
  70. data/lib/ttfunk/table/hhea.rb +47 -4
  71. data/lib/ttfunk/table/hmtx.rb +11 -4
  72. data/lib/ttfunk/table/kern.rb +3 -0
  73. data/lib/ttfunk/table/kern/format0.rb +3 -0
  74. data/lib/ttfunk/table/loca.rb +2 -0
  75. data/lib/ttfunk/table/maxp.rb +144 -10
  76. data/lib/ttfunk/table/name.rb +75 -37
  77. data/lib/ttfunk/table/os2.rb +327 -4
  78. data/lib/ttfunk/table/post.rb +8 -1
  79. data/lib/ttfunk/table/post/format10.rb +2 -0
  80. data/lib/ttfunk/table/post/format20.rb +5 -1
  81. data/lib/ttfunk/table/post/format30.rb +2 -0
  82. data/lib/ttfunk/table/post/format40.rb +2 -0
  83. data/lib/ttfunk/table/sbix.rb +2 -0
  84. data/lib/ttfunk/table/simple.rb +2 -0
  85. data/lib/ttfunk/table/vorg.rb +54 -0
  86. data/lib/ttfunk/ttf_encoder.rb +220 -0
  87. metadata +88 -20
  88. metadata.gz.sig +0 -0
  89. data/lib/ttfunk/encoding/mac_roman.rb +0 -100
  90. data/lib/ttfunk/encoding/windows_1252.rb +0 -76
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Cff < TTFunk::Table
6
+ class SubrIndex < TTFunk::Table::Cff::Index
7
+ def bias
8
+ if count < 1240
9
+ 107
10
+ elsif count < 33_900
11
+ 1131
12
+ else
13
+ 32_768
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,230 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Cff < TTFunk::Table
6
+ class TopDict < TTFunk::Table::Cff::Dict
7
+ DEFAULT_CHARSTRING_TYPE = 2
8
+ POINTER_PLACEHOLDER_LENGTH = 5
9
+ PLACEHOLDER_LENGTH = 5
10
+
11
+ # operators whose values are offsets that point to other parts
12
+ # of the file
13
+ POINTER_OPERATORS = {
14
+ charset: 15,
15
+ encoding: 16,
16
+ charstrings_index: 17,
17
+ private: 18,
18
+ font_index: 1236,
19
+ font_dict_selector: 1237
20
+ }.freeze
21
+
22
+ # all the operators we currently care about
23
+ OPERATORS = {
24
+ **POINTER_OPERATORS,
25
+ ros: 1230,
26
+ charstring_type: 1206
27
+ }.freeze
28
+
29
+ OPERATOR_CODES = OPERATORS.invert
30
+
31
+ def encode(*)
32
+ EncodedString.new do |result|
33
+ each_with_index do |(operator, operands), _idx|
34
+ if operator == OPERATORS[:private]
35
+ result << encode_private
36
+ elsif pointer_operator?(operator)
37
+ result << Placeholder.new(
38
+ OPERATOR_CODES[operator],
39
+ length: POINTER_PLACEHOLDER_LENGTH
40
+ )
41
+ else
42
+ operands.each { |operand| result << encode_operand(operand) }
43
+ end
44
+
45
+ result << encode_operator(operator)
46
+ end
47
+ end
48
+ end
49
+
50
+ def finalize(new_cff_data, new_to_old, old_to_new)
51
+ if charset
52
+ finalize_subtable(
53
+ new_cff_data, :charset, charset.encode(new_to_old)
54
+ )
55
+ end
56
+
57
+ if encoding
58
+ finalize_subtable(
59
+ new_cff_data, :encoding, encoding.encode(new_to_old, old_to_new)
60
+ )
61
+ end
62
+
63
+ if charstrings_index
64
+ finalize_subtable(
65
+ new_cff_data,
66
+ :charstrings_index,
67
+ charstrings_index.encode(new_to_old, &:encode)
68
+ )
69
+ end
70
+
71
+ if font_index
72
+ finalize_subtable(
73
+ new_cff_data,
74
+ :font_index,
75
+ font_index.encode do |font_dict|
76
+ font_dict.encode(new_to_old)
77
+ end
78
+ )
79
+
80
+ font_index.finalize(new_cff_data, new_to_old)
81
+ end
82
+
83
+ if font_dict_selector
84
+ finalize_subtable(
85
+ new_cff_data,
86
+ :font_dict_selector,
87
+ font_dict_selector.encode(new_to_old)
88
+ )
89
+ end
90
+
91
+ if private_dict
92
+ encoded_private_dict = private_dict.encode(new_to_old)
93
+ encoded_offset = encode_integer32(new_cff_data.length)
94
+ encoded_length = encode_integer32(encoded_private_dict.length)
95
+
96
+ new_cff_data.resolve_placeholder(
97
+ :"private_length_#{@table_offset}", encoded_length
98
+ )
99
+
100
+ new_cff_data.resolve_placeholder(
101
+ :"private_offset_#{@table_offset}", encoded_offset
102
+ )
103
+
104
+ private_dict.finalize(encoded_private_dict)
105
+ new_cff_data << encoded_private_dict
106
+ end
107
+ end
108
+
109
+ def ros
110
+ self[OPERATORS[:ros]]
111
+ end
112
+
113
+ def ros?
114
+ !ros.nil?
115
+ end
116
+
117
+ alias is_cid_font? ros?
118
+
119
+ def charset
120
+ @charset ||=
121
+ if (charset_offset_or_id = self[OPERATORS[:charset]])
122
+ if charset_offset_or_id.empty?
123
+ Charset.new(self, file)
124
+ else
125
+ Charset.new(self, file, charset_offset_or_id.first)
126
+ end
127
+ end
128
+ end
129
+
130
+ def encoding
131
+ @encoding ||= begin
132
+ # PostScript type 1 fonts, i.e. CID fonts, i.e. some fonts that use
133
+ # the CFF table, don't specify an encoding, so this can be nil
134
+ if (encoding_offset_or_id = self[OPERATORS[:encoding]])
135
+ Encoding.new(self, file, encoding_offset_or_id.first)
136
+ end
137
+ end
138
+ end
139
+
140
+ # https://www.microsoft.com/typography/otspec/cff.htm
141
+ #
142
+ # "OpenType fonts with TrueType outlines use a glyph index to specify
143
+ # and access glyphs within a font; e.g., to index within the 'loca'
144
+ # table and thereby access glyph data in the 'glyf' table. This concept
145
+ # is retained in OpenType CFF fonts, except that glyph data is accessed
146
+ # through the CharStrings INDEX of the CFF table."
147
+ def charstrings_index
148
+ @charstrings_index ||=
149
+ if (charstrings_offset = self[OPERATORS[:charstrings_index]])
150
+ CharstringsIndex.new(
151
+ self, file, cff_offset + charstrings_offset.first
152
+ )
153
+ end
154
+ end
155
+
156
+ def charstring_type
157
+ @charstring_type =
158
+ self[OPERATORS[:charstring_type]] || DEFAULT_CHARSTRING_TYPE
159
+ end
160
+
161
+ def font_index
162
+ @font_index ||=
163
+ if (font_index_offset = self[OPERATORS[:font_index]])
164
+ FontIndex.new(self, file, cff_offset + font_index_offset.first)
165
+ end
166
+ end
167
+
168
+ def font_dict_selector
169
+ @font_dict_selector ||=
170
+ if (fd_select_offset = self[OPERATORS[:font_dict_selector]])
171
+ FdSelector.new(self, file, cff_offset + fd_select_offset.first)
172
+ end
173
+ end
174
+
175
+ def private_dict
176
+ @private_dict ||=
177
+ if (info = self[OPERATORS[:private]])
178
+ private_dict_length, private_dict_offset = info
179
+
180
+ PrivateDict.new(
181
+ file, cff_offset + private_dict_offset, private_dict_length
182
+ )
183
+ end
184
+ end
185
+
186
+ def cff
187
+ file.cff
188
+ end
189
+
190
+ def cff_offset
191
+ cff.offset
192
+ end
193
+
194
+ private
195
+
196
+ def encode_private
197
+ EncodedString.new do |result|
198
+ result << Placeholder.new(
199
+ :"private_length_#{@table_offset}",
200
+ length: PLACEHOLDER_LENGTH
201
+ )
202
+
203
+ result << Placeholder.new(
204
+ :"private_offset_#{@table_offset}",
205
+ length: PLACEHOLDER_LENGTH
206
+ )
207
+ end
208
+ end
209
+
210
+ def finalize_subtable(new_cff_data, name, table_data)
211
+ encoded = encode_integer32(new_cff_data.length)
212
+ new_cff_data.resolve_placeholder(name, encoded)
213
+ new_cff_data << table_data
214
+ end
215
+
216
+ def pointer_operator?(operator)
217
+ POINTER_OPERATORS.include?(OPERATOR_CODES[operator])
218
+ end
219
+
220
+ def encode_charstring_type(charstring_type)
221
+ if charstring_type == DEFAULT_CHARSTRING_TYPE
222
+ ''
223
+ else
224
+ encode_operand(charstring_type)
225
+ end
226
+ end
227
+ end
228
+ end
229
+ end
230
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Cff < TTFunk::Table
6
+ class TopIndex < TTFunk::Table::Cff::Index
7
+ def [](index)
8
+ entry_cache[index] ||= begin
9
+ start, finish = absolute_offsets_for(index)
10
+ TTFunk::Table::Cff::TopDict.new(file, start, (finish - start) + 1)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TTFunk
2
4
  class Table
3
5
  class Cmap < Table
@@ -24,10 +26,8 @@ module TTFunk
24
26
 
25
27
  def parse!
26
28
  @version, table_count = read(4, 'nn')
27
- @tables = []
28
-
29
- table_count.times do
30
- @tables << Cmap::Subtable.new(file, offset)
29
+ @tables = Array.new(table_count) do
30
+ Cmap::Subtable.new(file, offset)
31
31
  end
32
32
  end
33
33
  end
@@ -1,5 +1,4 @@
1
- require_relative '../../encoding/mac_roman'
2
- require_relative '../../encoding/windows_1252'
1
+ # frozen_string_literal: true
3
2
 
4
3
  module TTFunk
5
4
  class Table
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TTFunk
2
4
  class Table
3
5
  class Cmap
@@ -43,16 +45,21 @@ module TTFunk
43
45
  deltas = []
44
46
  range_offsets = []
45
47
  glyph_indices = []
46
-
47
48
  offset = 0
49
+
48
50
  start_codes.zip(end_codes).each_with_index do |(a, b), segment|
49
51
  if a == 0xFFFF
50
- deltas << 0
52
+ # We want the final 0xFFFF code to map to glyph 0.
53
+ # The glyph index is calculated as glyph = charcode + delta,
54
+ # which means that delta must be -0xFFFF to map character code
55
+ # 0xFFFF to glyph 0.
56
+ deltas << -0xFFFF
51
57
  range_offsets << 0
52
58
  break
53
59
  end
54
60
 
55
61
  start_glyph_id = new_map[a][:new]
62
+
56
63
  if a - start_glyph_id >= 0x8000
57
64
  deltas << 0
58
65
  range_offsets << 2 * (glyph_indices.length + segcount - segment)
@@ -61,6 +68,7 @@ module TTFunk
61
68
  deltas << -a + start_glyph_id
62
69
  range_offsets << 0
63
70
  end
71
+
64
72
  offset += 2
65
73
  end
66
74
 
@@ -116,7 +124,7 @@ module TTFunk
116
124
  else
117
125
  index = id_range_offset[i] / 2 +
118
126
  (code - start_code[i]) - (segcount - i)
119
- # Decause some TTF fonts are broken
127
+ # Because some TTF fonts are broken
120
128
  glyph_id = glyph_ids[index] || 0
121
129
  glyph_id += id_delta[i] if glyph_id != 0
122
130
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TTFunk
2
4
  class Table
3
5
  class Cmap
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TTFunk
2
4
  class Table
3
5
  class Cmap
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TTFunk
2
4
  class Table
3
5
  class Cmap
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../../reader'
2
4
 
3
5
  module TTFunk
@@ -34,14 +36,16 @@ module TTFunk
34
36
  mapping = ENCODING_MAPPINGS[encoding]
35
37
 
36
38
  # platform-id, encoding-id, offset
37
- result[:subtable] = [
38
- mapping[:platform_id],
39
- mapping[:encoding_id],
40
- 12,
41
- result[:subtable]
42
- ].pack('nnNA*')
43
-
44
- result
39
+ result.merge(
40
+ platform_id: mapping[:platform_id],
41
+ encoding_id: mapping[:encoding_id],
42
+ subtable: [
43
+ mapping[:platform_id],
44
+ mapping[:encoding_id],
45
+ 12,
46
+ result[:subtable]
47
+ ].pack('nnNA*')
48
+ )
45
49
  end
46
50
 
47
51
  def initialize(file, table_start)
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TTFunk
4
+ class Table
5
+ class Dsig < Table
6
+ class SignatureRecord
7
+ attr_reader :format, :length, :offset, :signature
8
+
9
+ def initialize(format, length, offset, signature)
10
+ @format = format
11
+ @length = length
12
+ @offset = offset
13
+ @signature = signature
14
+ end
15
+ end
16
+
17
+ attr_reader :version, :flags, :signatures
18
+
19
+ TAG = 'DSIG'
20
+
21
+ def self.encode(dsig)
22
+ return nil unless dsig
23
+
24
+ # Don't attempt to re-sign or anything - just use dummy values.
25
+ # Since we're subsetting that should be permissible.
26
+ [dsig.version, 0, 0].pack('Nnn')
27
+ end
28
+
29
+ def tag
30
+ TAG
31
+ end
32
+
33
+ private
34
+
35
+ def parse!
36
+ @version, num_signatures, @flags = read(8, 'Nnn')
37
+
38
+ @signatures = Array.new(num_signatures) do
39
+ format, length, sig_offset = read(12, 'N3')
40
+ signature = parse_from(offset + sig_offset) do
41
+ _, _, sig_length = read(8, 'nnN')
42
+ read(sig_length, 'C*')
43
+ end
44
+
45
+ SignatureRecord.new(format, length, sig_offset, signature)
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require_relative '../table'
2
4
 
3
5
  module TTFunk
@@ -11,13 +13,13 @@ module TTFunk
11
13
  # * :table - a string representing the encoded 'glyf' table containing
12
14
  # the given glyphs.
13
15
  # * :offsets - an array of offsets for each glyph
14
- def self.encode(glyphs, new2old, old2new)
15
- result = { table: '', offsets: [] }
16
+ def self.encode(glyphs, new_to_old, old_to_new)
17
+ result = { table: +'', offsets: [] }
16
18
 
17
- new2old.keys.sort.each do |new_id|
18
- glyph = glyphs[new2old[new_id]]
19
+ new_to_old.keys.sort.each do |new_id|
20
+ glyph = glyphs[new_to_old[new_id]]
19
21
  result[:offsets] << result[:table].length
20
- result[:table] << glyph.recode(old2new) if glyph
22
+ result[:table] << glyph.recode(old_to_new) if glyph
21
23
  end
22
24
 
23
25
  # include an offset at the end of the table, for use in computing the
@@ -39,14 +41,13 @@ module TTFunk
39
41
 
40
42
  parse_from(offset + index) do
41
43
  raw = io.read(size)
42
- number_of_contours, x_min, y_min, x_max, y_max =
43
- raw.unpack('n5').map { |i| to_signed(i) }
44
+ number_of_contours = to_signed(raw.unpack1('n'))
44
45
 
45
46
  @cache[glyph_id] =
46
47
  if number_of_contours == -1
47
- Compound.new(raw, x_min, y_min, x_max, y_max)
48
+ Compound.new(glyph_id, raw)
48
49
  else
49
- Simple.new(raw, number_of_contours, x_min, y_min, x_max, y_max)
50
+ Simple.new(glyph_id, raw)
50
51
  end
51
52
  end
52
53
  end
@@ -63,4 +64,5 @@ module TTFunk
63
64
  end
64
65
 
65
66
  require_relative 'glyf/compound'
67
+ require_relative 'glyf/path_based'
66
68
  require_relative 'glyf/simple'