ttfunk 1.7.0 → 1.8.0

Sign up to get free protection for your applications and to get access to all the features.
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