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