ttfunk 1.7.0 → 1.8.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 (88) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +74 -0
  4. data/README.md +17 -15
  5. data/lib/ttfunk/aggregate.rb +5 -0
  6. data/lib/ttfunk/bin_utils.rb +27 -8
  7. data/lib/ttfunk/bit_field.rb +25 -2
  8. data/lib/ttfunk/collection.rb +27 -3
  9. data/lib/ttfunk/directory.rb +7 -1
  10. data/lib/ttfunk/encoded_string.rb +58 -4
  11. data/lib/ttfunk/max.rb +14 -0
  12. data/lib/ttfunk/min.rb +14 -0
  13. data/lib/ttfunk/one_based_array.rb +20 -0
  14. data/lib/ttfunk/otf_encoder.rb +5 -14
  15. data/lib/ttfunk/placeholder.rb +15 -1
  16. data/lib/ttfunk/reader.rb +6 -4
  17. data/lib/ttfunk/resource_file.rb +29 -5
  18. data/lib/ttfunk/sci_form.rb +20 -3
  19. data/lib/ttfunk/sub_table.rb +29 -4
  20. data/lib/ttfunk/subset/base.rb +48 -0
  21. data/lib/ttfunk/subset/code_page.rb +49 -2
  22. data/lib/ttfunk/subset/mac_roman.rb +2 -0
  23. data/lib/ttfunk/subset/unicode.rb +32 -0
  24. data/lib/ttfunk/subset/unicode_8bit.rb +32 -0
  25. data/lib/ttfunk/subset/windows_1252.rb +2 -0
  26. data/lib/ttfunk/subset.rb +8 -0
  27. data/lib/ttfunk/subset_collection.rb +39 -14
  28. data/lib/ttfunk/sum.rb +13 -0
  29. data/lib/ttfunk/table/cff/charset.rb +96 -18
  30. data/lib/ttfunk/table/cff/charsets/expert.rb +3 -2
  31. data/lib/ttfunk/table/cff/charsets/expert_subset.rb +3 -2
  32. data/lib/ttfunk/table/cff/charsets/iso_adobe.rb +3 -2
  33. data/lib/ttfunk/table/cff/charsets/standard_strings.rb +3 -2
  34. data/lib/ttfunk/table/cff/charsets.rb +1 -0
  35. data/lib/ttfunk/table/cff/charstring.rb +33 -12
  36. data/lib/ttfunk/table/cff/charstrings_index.rb +17 -11
  37. data/lib/ttfunk/table/cff/dict.rb +53 -23
  38. data/lib/ttfunk/table/cff/encoding.rb +82 -24
  39. data/lib/ttfunk/table/cff/encodings/expert.rb +3 -2
  40. data/lib/ttfunk/table/cff/encodings/standard.rb +3 -2
  41. data/lib/ttfunk/table/cff/encodings.rb +1 -0
  42. data/lib/ttfunk/table/cff/fd_selector.rb +61 -21
  43. data/lib/ttfunk/table/cff/font_dict.rb +30 -18
  44. data/lib/ttfunk/table/cff/font_index.rb +22 -10
  45. data/lib/ttfunk/table/cff/header.rb +16 -3
  46. data/lib/ttfunk/table/cff/index.rb +97 -65
  47. data/lib/ttfunk/table/cff/one_based_index.rb +11 -1
  48. data/lib/ttfunk/table/cff/path.rb +43 -4
  49. data/lib/ttfunk/table/cff/private_dict.rb +31 -11
  50. data/lib/ttfunk/table/cff/subr_index.rb +7 -2
  51. data/lib/ttfunk/table/cff/top_dict.rb +82 -59
  52. data/lib/ttfunk/table/cff/top_index.rb +10 -6
  53. data/lib/ttfunk/table/cff.rb +41 -21
  54. data/lib/ttfunk/table/cmap/format00.rb +27 -6
  55. data/lib/ttfunk/table/cmap/format04.rb +34 -14
  56. data/lib/ttfunk/table/cmap/format06.rb +28 -1
  57. data/lib/ttfunk/table/cmap/format10.rb +29 -2
  58. data/lib/ttfunk/table/cmap/format12.rb +29 -2
  59. data/lib/ttfunk/table/cmap/subtable.rb +50 -6
  60. data/lib/ttfunk/table/cmap.rb +21 -0
  61. data/lib/ttfunk/table/dsig.rb +47 -6
  62. data/lib/ttfunk/table/glyf/compound.rb +73 -6
  63. data/lib/ttfunk/table/glyf/path_based.rb +40 -3
  64. data/lib/ttfunk/table/glyf/simple.rb +50 -5
  65. data/lib/ttfunk/table/glyf.rb +15 -7
  66. data/lib/ttfunk/table/head.rb +84 -6
  67. data/lib/ttfunk/table/hhea.rb +71 -10
  68. data/lib/ttfunk/table/hmtx.rb +32 -5
  69. data/lib/ttfunk/table/kern/format0.rb +25 -7
  70. data/lib/ttfunk/table/kern.rb +16 -4
  71. data/lib/ttfunk/table/loca.rb +21 -8
  72. data/lib/ttfunk/table/maxp.rb +195 -10
  73. data/lib/ttfunk/table/name.rb +126 -9
  74. data/lib/ttfunk/table/os2.rb +150 -26
  75. data/lib/ttfunk/table/post/format10.rb +7 -0
  76. data/lib/ttfunk/table/post/format20.rb +9 -0
  77. data/lib/ttfunk/table/post/format30.rb +6 -0
  78. data/lib/ttfunk/table/post/format40.rb +5 -0
  79. data/lib/ttfunk/table/post.rb +63 -7
  80. data/lib/ttfunk/table/sbix.rb +50 -14
  81. data/lib/ttfunk/table/simple.rb +5 -0
  82. data/lib/ttfunk/table/vorg.rb +31 -3
  83. data/lib/ttfunk/table.rb +20 -1
  84. data/lib/ttfunk/ttf_encoder.rb +39 -41
  85. data/lib/ttfunk.rb +154 -1
  86. data.tar.gz.sig +0 -0
  87. metadata +50 -28
  88. metadata.gz.sig +0 -0
@@ -4,38 +4,223 @@ require_relative '../table'
4
4
 
5
5
  module TTFunk
6
6
  class Table
7
+ # Maximum Profile (`maxp`) table
7
8
  class Maxp < Table
9
+ # Default maximum levels of recursion.
10
+ DEFAULT_MAX_COMPONENT_DEPTH = 1
11
+
12
+ # Size of full table version 1.
13
+ MAX_V1_TABLE_LENGTH = 32
14
+
15
+ # Table version.
16
+ # @return [Integer]
8
17
  attr_reader :version
18
+
19
+ # The number of glyphs in the font.
20
+ # @return [Integer]
9
21
  attr_reader :num_glyphs
22
+
23
+ # Maximum points in a non-composite glyph.
24
+ # @return [Integer]
10
25
  attr_reader :max_points
26
+
27
+ # Maximum contours in a non-composite glyph.
28
+ # @return [Integer]
11
29
  attr_reader :max_contours
30
+
31
+ # Maximum points in a composite glyph.
32
+ # @return [Integer]
12
33
  attr_reader :max_component_points
34
+
35
+ # Maximum contours in a composite glyph.
36
+ # @return [Integer]
13
37
  attr_reader :max_component_contours
38
+
39
+ # Maximum zones.
40
+ # * 1 if instructions do not use the twilight zone (Z0)
41
+ # * 2 if instructions do use Z0
42
+ # @return [Integer]
14
43
  attr_reader :max_zones
44
+
45
+ # Maximum points used in Z0.
46
+ # @return [Integer]
15
47
  attr_reader :max_twilight_points
48
+
49
+ # Number of Storage Area locations.
50
+ # @return [Integer]
16
51
  attr_reader :max_storage
52
+
53
+ # Number of FDEFs.
54
+ # @return [Integer]
17
55
  attr_reader :max_function_defs
56
+
57
+ # Number of IDEFs.
58
+ # @return [Integer]
18
59
  attr_reader :max_instruction_defs
60
+
61
+ # Maximum stack depth across Font Program, CVT Program and all glyph
62
+ # instructions.
63
+ # @return [Integer]
19
64
  attr_reader :max_stack_elements
65
+
66
+ # Maximum byte count for glyph instructions.
67
+ # @return [Integer]
20
68
  attr_reader :max_size_of_instructions
69
+
70
+ # Maximum number of components referenced at "top level" for any composite
71
+ # glyph.
72
+ # @return [Integer]
21
73
  attr_reader :max_component_elements
74
+
75
+ # Maximum levels of recursion.
76
+ # @return [Integer]
22
77
  attr_reader :max_component_depth
23
78
 
24
- def self.encode(maxp, mapping)
25
- num_glyphs = mapping.length
26
- raw = maxp.raw
27
- raw[4, 2] = [num_glyphs].pack('n')
28
- raw
79
+ class << self
80
+ # Encode table.
81
+ #
82
+ # @param maxp [TTFunk::Table::Maxp]
83
+ # @param new2old_glyph [Hash{Integer => Integer}] keys are new glyph IDs, values
84
+ # are old glyph IDs.
85
+ # @return [String]
86
+ def encode(maxp, new2old_glyph)
87
+ ''.b.tap do |table|
88
+ num_glyphs = new2old_glyph.length
89
+ table << [maxp.version, num_glyphs].pack('Nn')
90
+
91
+ if maxp.version == 0x10000
92
+ stats = stats_for(maxp, glyphs_from_ids(maxp, new2old_glyph.values))
93
+
94
+ table << [
95
+ stats[:max_points],
96
+ stats[:max_contours],
97
+ stats[:max_component_points],
98
+ stats[:max_component_contours],
99
+ # these all come from the fpgm and cvt tables, which
100
+ # we don't support at the moment
101
+ maxp.max_zones,
102
+ maxp.max_twilight_points,
103
+ maxp.max_storage,
104
+ maxp.max_function_defs,
105
+ maxp.max_instruction_defs,
106
+ maxp.max_stack_elements,
107
+ stats[:max_size_of_instructions],
108
+ stats[:max_component_elements],
109
+ stats[:max_component_depth],
110
+ ].pack('n*')
111
+ end
112
+ end
113
+ end
114
+
115
+ private
116
+
117
+ def glyphs_from_ids(maxp, glyph_ids)
118
+ glyph_ids.each_with_object([]) do |glyph_id, ret|
119
+ if (glyph = maxp.file.glyph_outlines.for(glyph_id))
120
+ ret << glyph
121
+ end
122
+ end
123
+ end
124
+
125
+ def stats_for(maxp, glyphs)
126
+ stats_for_simple(maxp, glyphs)
127
+ .merge(stats_for_compound(maxp, glyphs))
128
+ .transform_values { |agg| agg.value_or(0) }
129
+ end
130
+
131
+ def stats_for_simple(_maxp, glyphs)
132
+ max_component_elements = Max.new
133
+ max_points = Max.new
134
+ max_contours = Max.new
135
+ max_size_of_instructions = Max.new
136
+
137
+ glyphs.each do |glyph|
138
+ if glyph.compound?
139
+ max_component_elements << glyph.glyph_ids.size
140
+ else
141
+ max_points << glyph.end_point_of_last_contour
142
+ max_contours << glyph.number_of_contours
143
+ max_size_of_instructions << glyph.instruction_length
144
+ end
145
+ end
146
+
147
+ {
148
+ max_component_elements: max_component_elements,
149
+ max_points: max_points,
150
+ max_contours: max_contours,
151
+ max_size_of_instructions: max_size_of_instructions,
152
+ }
153
+ end
154
+
155
+ def stats_for_compound(maxp, glyphs)
156
+ max_component_points = Max.new
157
+ max_component_depth = Max.new
158
+ max_component_contours = Max.new
159
+
160
+ glyphs.each do |glyph|
161
+ next unless glyph.compound?
162
+
163
+ stats = totals_for_compound(maxp, [glyph], 0)
164
+ max_component_points << stats[:total_points]
165
+ max_component_depth << stats[:max_depth]
166
+ max_component_contours << stats[:total_contours]
167
+ end
168
+
169
+ {
170
+ max_component_points: max_component_points,
171
+ max_component_depth: max_component_depth,
172
+ max_component_contours: max_component_contours,
173
+ }
174
+ end
175
+
176
+ def totals_for_compound(maxp, glyphs, depth)
177
+ total_points = Sum.new
178
+ total_contours = Sum.new
179
+ max_depth = Max.new(depth)
180
+
181
+ glyphs.each do |glyph|
182
+ if glyph.compound?
183
+ stats = totals_for_compound(maxp, glyphs_from_ids(maxp, glyph.glyph_ids), depth + 1)
184
+
185
+ total_points << stats[:total_points]
186
+ total_contours << stats[:total_contours]
187
+ max_depth << stats[:max_depth]
188
+ else
189
+ stats = stats_for_simple(maxp, [glyph])
190
+ total_points << stats[:max_points]
191
+ total_contours << stats[:max_contours]
192
+ end
193
+ end
194
+
195
+ {
196
+ total_points: total_points,
197
+ total_contours: total_contours,
198
+ max_depth: max_depth,
199
+ }
200
+ end
29
201
  end
30
202
 
31
203
  private
32
204
 
33
205
  def parse!
34
- @version, @num_glyphs, @max_points, @max_contours,
35
- @max_component_points, @max_component_contours, @max_zones,
36
- @max_twilight_points, @max_storage, @max_function_defs,
37
- @max_instruction_defs, @max_stack_elements, @max_size_of_instructions,
38
- @max_component_elements, @max_component_depth = read(length, 'Nn*')
206
+ @version, @num_glyphs = read(6, 'Nn')
207
+
208
+ if @version == 0x10000
209
+ @max_points, @max_contours, @max_component_points,
210
+ @max_component_contours, @max_zones, @max_twilight_points,
211
+ @max_storage, @max_function_defs, @max_instruction_defs,
212
+ @max_stack_elements, @max_size_of_instructions,
213
+ @max_component_elements = read(24, 'n*')
214
+
215
+ # a number of fonts omit these last two bytes for some reason,
216
+ # so we have to supply a default here to prevent nils
217
+ @max_component_depth =
218
+ if length == MAX_V1_TABLE_LENGTH
219
+ read(2, 'n').first
220
+ else
221
+ DEFAULT_MAX_COMPONENT_DEPTH
222
+ end
223
+ end
39
224
  end
40
225
  end
41
226
  end
@@ -5,12 +5,26 @@ require 'digest/sha1'
5
5
 
6
6
  module TTFunk
7
7
  class Table
8
+ # Naming (`name`) table
8
9
  class Name < Table
10
+ # Name Record.
9
11
  class NameString < ::String
12
+ # Platform ID.
13
+ # @return [Integer]
10
14
  attr_reader :platform_id
15
+
16
+ # Platform-specific encoding ID.
17
+ # @return [Integer]
11
18
  attr_reader :encoding_id
19
+
20
+ # Language ID.
21
+ # @return [Integer]
12
22
  attr_reader :language_id
13
23
 
24
+ # @param text [String]
25
+ # @param platform_id [Integer]
26
+ # @param encoding_id [Integer]
27
+ # @param language_id [Integer]
14
28
  def initialize(text, platform_id, encoding_id, language_id)
15
29
  super(text)
16
30
  @platform_id = platform_id
@@ -18,6 +32,8 @@ module TTFunk
18
32
  @language_id = language_id
19
33
  end
20
34
 
35
+ # Removes chracter incompatible with PostScript.
36
+ # @return [String] PostScript-compatible version of this string.
21
37
  def strip_extended
22
38
  stripped = gsub(/[\x00-\x19\x80-\xff]/n, '')
23
39
  stripped = '[not-postscript]' if stripped.empty?
@@ -25,60 +41,159 @@ module TTFunk
25
41
  end
26
42
  end
27
43
 
44
+ # Name records.
45
+ # @return [Array<Hash>]
28
46
  attr_reader :entries
47
+
48
+ # Name strings.
49
+ # @return [Hash{Integer => NameString}]
29
50
  attr_reader :strings
30
51
 
52
+ # Copyright notice.
53
+ # @return [Array<NameString>]
31
54
  attr_reader :copyright
55
+
56
+ # Font Family names.
57
+ # @return [Array<NameString>]
32
58
  attr_reader :font_family
59
+
60
+ # Font Subfamily names.
61
+ # @return [Array<NameString>]
33
62
  attr_reader :font_subfamily
63
+
64
+ # Unique font identifiers.
65
+ # @return [Array<NameString>]
34
66
  attr_reader :unique_subfamily
67
+
68
+ # Full font names.
69
+ # @return [Array<NameString>]
35
70
  attr_reader :font_name
71
+
72
+ # Version strings.
73
+ # @return [Array<NameString>]
36
74
  attr_reader :version
75
+
76
+ # Trademarks.
77
+ # @return [Array<NameString>]
37
78
  attr_reader :trademark
79
+
80
+ # Manufacturer Names.
81
+ # @return [Array<NameString>]
38
82
  attr_reader :manufacturer
83
+
84
+ # Designers.
85
+ # @return [Array<NameString>]
39
86
  attr_reader :designer
87
+
88
+ # Descriptions.
89
+ # @return [Array<NameString>]
40
90
  attr_reader :description
91
+
92
+ # Vendor URLs.
93
+ # @return [Array<NameString>]
41
94
  attr_reader :vendor_url
95
+
96
+ # Designer URLs.
97
+ # @return [Array<NameString>]
42
98
  attr_reader :designer_url
99
+
100
+ # License Descriptions.
101
+ # @return [Array<NameString>]
43
102
  attr_reader :license
103
+
104
+ # License Info URLs.
105
+ # @return [Array<NameString>]
44
106
  attr_reader :license_url
107
+
108
+ # Typographic Family names.
109
+ # @return [Array<NameString>]
45
110
  attr_reader :preferred_family
111
+
112
+ # Typographic Subfamily names.
113
+ # @return [Array<NameString>]
46
114
  attr_reader :preferred_subfamily
115
+
116
+ # Compatible Full Names.
117
+ # @return [Array<NameString>]
47
118
  attr_reader :compatible_full
119
+
120
+ # Sample texts.
121
+ # @return [Array<NameString>]
48
122
  attr_reader :sample_text
49
123
 
124
+ # Copyright notice ID.
50
125
  COPYRIGHT_NAME_ID = 0
126
+
127
+ # Font Family name ID.
51
128
  FONT_FAMILY_NAME_ID = 1
129
+
130
+ # Font Subfamily name ID.
52
131
  FONT_SUBFAMILY_NAME_ID = 2
132
+
133
+ # Unique font identifier ID.
53
134
  UNIQUE_SUBFAMILY_NAME_ID = 3
135
+
136
+ # Full font name that reflects all family and relevant subfamily
137
+ # descriptors ID.
54
138
  FONT_NAME_NAME_ID = 4
139
+
140
+ # Version string ID.
55
141
  VERSION_NAME_ID = 5
142
+
143
+ # PostScript name for the font ID.
56
144
  POSTSCRIPT_NAME_NAME_ID = 6
145
+
146
+ # Trademark ID.
57
147
  TRADEMARK_NAME_ID = 7
148
+
149
+ # Manufacturer Name ID.
58
150
  MANUFACTURER_NAME_ID = 8
151
+
152
+ # Designer ID.
59
153
  DESIGNER_NAME_ID = 9
154
+
155
+ # Description ID.
60
156
  DESCRIPTION_NAME_ID = 10
157
+
158
+ # Vendor URL ID.
61
159
  VENDOR_URL_NAME_ID = 11
160
+
161
+ # Designer URL ID.
62
162
  DESIGNER_URL_NAME_ID = 12
163
+
164
+ # License Description ID.
63
165
  LICENSE_NAME_ID = 13
166
+
167
+ # License Info URL ID.
64
168
  LICENSE_URL_NAME_ID = 14
169
+
170
+ # Typographic Family name ID.
65
171
  PREFERRED_FAMILY_NAME_ID = 16
172
+
173
+ # Typographic Subfamily name ID.
66
174
  PREFERRED_SUBFAMILY_NAME_ID = 17
175
+
176
+ # Compatible Full ID.
67
177
  COMPATIBLE_FULL_NAME_ID = 18
178
+
179
+ # Sample text ID.
68
180
  SAMPLE_TEXT_NAME_ID = 19
69
181
 
182
+ # Encode table.
183
+ #
184
+ # @param names [TTFunk::Table::Name]
185
+ # @param key [String]
186
+ # @return [String]
70
187
  def self.encode(names, key = '')
71
188
  tag = Digest::SHA1.hexdigest(key)[0, 6]
72
189
 
73
- postscript_name = NameString.new(
74
- "#{tag}+#{names.postscript_name}", 1, 0, 0
75
- )
190
+ postscript_name = NameString.new("#{tag}+#{names.postscript_name}", 1, 0, 0)
76
191
 
77
192
  strings = names.strings.dup
78
193
  strings[6] = [postscript_name]
79
194
  str_count = strings.reduce(0) { |sum, (_, list)| sum + list.length }
80
195
 
81
- table = [0, str_count, 6 + 12 * str_count].pack('n*')
196
+ table = [0, str_count, 6 + (12 * str_count)].pack('n*')
82
197
  strtable = +''
83
198
 
84
199
  items = []
@@ -88,13 +203,13 @@ module TTFunk
88
203
  end
89
204
  end
90
205
  items =
91
- items.sort_by do |id, string|
206
+ items.sort_by { |id, string|
92
207
  [string.platform_id, string.encoding_id, string.language_id, id]
93
- end
208
+ }
94
209
  items.each do |id, string|
95
210
  table << [
96
211
  string.platform_id, string.encoding_id, string.language_id, id,
97
- string.length, strtable.length
212
+ string.length, strtable.length,
98
213
  ].pack('n*')
99
214
  strtable << string
100
215
  end
@@ -102,6 +217,8 @@ module TTFunk
102
217
  table << strtable
103
218
  end
104
219
 
220
+ # PostScript name for the font.
221
+ # @return [String]
105
222
  def postscript_name
106
223
  return @postscript_name if @postscript_name
107
224
 
@@ -124,7 +241,7 @@ module TTFunk
124
241
  name_id: id,
125
242
  length: length,
126
243
  offset: offset + string_offset + start_offset,
127
- text: nil
244
+ text: nil,
128
245
  }
129
246
  end
130
247
 
@@ -137,7 +254,7 @@ module TTFunk
137
254
  @entries[i][:text] || '',
138
255
  @entries[i][:platform_id],
139
256
  @entries[i][:encoding_id],
140
- @entries[i][:language_id]
257
+ @entries[i][:language_id],
141
258
  )
142
259
  end
143
260