fontisan 0.2.2 → 0.2.3

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f52b19a1d943c0e079127e9699e517b6832a30d46bab88454ae8471a398886d2
4
- data.tar.gz: 8395db7eef4bb2bed2169a08f54208e5119209f3336dcf17bf6d27b602e7fd5f
3
+ metadata.gz: d342cbc82376aa9327747fd679edf9199ab0e1fbed9a729d4385aae52d6c5de9
4
+ data.tar.gz: 8427c56d4e65ca035a5bff415ef7020e3f6e17c31f4c43d8b28b5ca8e41e4fe4
5
5
  SHA512:
6
- metadata.gz: 65003981ab35b7ff61c6f7524e9552278cfff918f0bbbfeb9a28271042e91033aa4623f8f301b66e1b6a171bea423ae30c644c32b4d111ce10a1d8bd6a82b61e
7
- data.tar.gz: 7ff760af226e26a619cce271e505c92c8c6a3f21e6ec9c3c6582571e8944352ae7ff6be448c9236e11c5f6f023550f492716db5945980ac0607eaee700c9318e
6
+ metadata.gz: e5c9b89e658113edcd96e98c4c56733538a4e47eb18220d49ca8e21c5117285795759a6e3dd5b4f2a0b29f7a4a66c0bf0bf25757780dd438045eb705feef97ef
7
+ data.tar.gz: 5ec8c48befb405a5d9911a6f00a2287a9cae461847b52094abe1367cd5f1fd5c451363182ed3c9a924c96d13299659869c6ffa2f192951301d1218eb840e0623
data/.rubocop_todo.yml CHANGED
@@ -1,6 +1,6 @@
1
1
  # This configuration was generated by
2
2
  # `rubocop --auto-gen-config`
3
- # on 2025-12-29 12:26:25 UTC using RuboCop version 1.81.7.
3
+ # on 2025-12-30 09:54:17 UTC using RuboCop version 1.81.7.
4
4
  # The point is for the user to remove these configuration records
5
5
  # one by one as the offenses are removed from the code base.
6
6
  # Note that changes in the inspected code, or installation of new
@@ -31,21 +31,13 @@ Layout/ExtraSpacing:
31
31
  Exclude:
32
32
  - 'lib/fontisan/hints/truetype_instruction_analyzer.rb'
33
33
 
34
- # Offense count: 1101
34
+ # Offense count: 1109
35
35
  # This cop supports safe autocorrection (--autocorrect).
36
36
  # Configuration parameters: Max, AllowHeredoc, AllowURI, AllowQualifiedName, URISchemes, IgnoreCopDirectives, AllowedPatterns, SplitStrings.
37
37
  # URISchemes: http, https
38
38
  Layout/LineLength:
39
39
  Enabled: false
40
40
 
41
- # Offense count: 1
42
- # This cop supports safe autocorrection (--autocorrect).
43
- # Configuration parameters: EnforcedStyle.
44
- # SupportedStyles: final_newline, final_blank_line
45
- Layout/TrailingEmptyLines:
46
- Exclude:
47
- - 'spec/fontisan_spec.rb'
48
-
49
41
  # Offense count: 26
50
42
  # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
51
43
  Lint/DuplicateBranch:
@@ -106,7 +98,7 @@ Lint/UselessAssignment:
106
98
  - 'lib/fontisan/hints/truetype_instruction_analyzer.rb'
107
99
  - 'spec/fontisan/hints/hint_round_trip_spec.rb'
108
100
 
109
- # Offense count: 431
101
+ # Offense count: 432
110
102
  # Configuration parameters: AllowedMethods, AllowedPatterns, CountRepeatedAttributes, Max.
111
103
  Metrics/AbcSize:
112
104
  Enabled: false
@@ -122,7 +114,7 @@ Metrics/BlockLength:
122
114
  Metrics/BlockNesting:
123
115
  Max: 5
124
116
 
125
- # Offense count: 220
117
+ # Offense count: 223
126
118
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
127
119
  Metrics/CyclomaticComplexity:
128
120
  Enabled: false
@@ -138,7 +130,7 @@ Metrics/ParameterLists:
138
130
  Max: 39
139
131
  MaxOptionalParameters: 4
140
132
 
141
- # Offense count: 154
133
+ # Offense count: 157
142
134
  # Configuration parameters: AllowedMethods, AllowedPatterns, Max.
143
135
  Metrics/PerceivedComplexity:
144
136
  Enabled: false
@@ -192,10 +184,11 @@ Performance/CollectionLiteralInLoop:
192
184
  - 'spec/fontisan/commands/info_command_spec.rb'
193
185
  - 'spec/fontisan/commands/tables_command_spec.rb'
194
186
 
195
- # Offense count: 1
187
+ # Offense count: 3
196
188
  # This cop supports unsafe autocorrection (--autocorrect-all).
197
189
  Performance/TimesMap:
198
190
  Exclude:
191
+ - 'lib/fontisan/font_loader.rb'
199
192
  - 'lib/fontisan/tables/cff2.rb'
200
193
 
201
194
  # Offense count: 24
@@ -225,7 +218,7 @@ RSpec/DescribeMethod:
225
218
  Exclude:
226
219
  - 'spec/fontisan/collection/variable_font_builder_spec.rb'
227
220
 
228
- # Offense count: 1152
221
+ # Offense count: 1156
229
222
  # Configuration parameters: CountAsOne.
230
223
  RSpec/ExampleLength:
231
224
  Max: 66
@@ -295,11 +288,11 @@ RSpec/MultipleDescribes:
295
288
  - 'spec/fontisan/utils/thread_pool_spec.rb'
296
289
  - 'spec/fontisan/variation/cache_spec.rb'
297
290
 
298
- # Offense count: 1488
291
+ # Offense count: 1494
299
292
  RSpec/MultipleExpectations:
300
293
  Max: 22
301
294
 
302
- # Offense count: 132
295
+ # Offense count: 134
303
296
  # Configuration parameters: AllowSubject.
304
297
  RSpec/MultipleMemoizedHelpers:
305
298
  Max: 13
@@ -400,7 +393,7 @@ Style/CombinableLoops:
400
393
  Exclude:
401
394
  - 'lib/fontisan/woff2_font.rb'
402
395
 
403
- # Offense count: 1
396
+ # Offense count: 2
404
397
  # This cop supports safe autocorrection (--autocorrect).
405
398
  # Configuration parameters: EnforcedStyle, SingleLineConditionsOnly, IncludeTernaryExpressions.
406
399
  # SupportedStyles: assign_to_condition, assign_inside_condition
@@ -434,13 +427,14 @@ Style/HashLikeCase:
434
427
  - 'lib/fontisan/commands/unpack_command.rb'
435
428
  - 'lib/fontisan/models/validation_report.rb'
436
429
 
437
- # Offense count: 4
430
+ # Offense count: 6
438
431
  # This cop supports unsafe autocorrection (--autocorrect-all).
439
432
  # Configuration parameters: EnforcedStyle, AllowedMethods, AllowedPatterns.
440
433
  # SupportedStyles: predicate, comparison
441
434
  Style/NumericPredicate:
442
435
  Exclude:
443
436
  - 'spec/**/*'
437
+ - 'lib/fontisan/font_loader.rb'
444
438
  - 'lib/fontisan/hints/hint_converter.rb'
445
439
  - 'lib/fontisan/hints/hint_validator.rb'
446
440
  - 'lib/fontisan/hints/truetype_instruction_analyzer.rb'
data/README.adoc CHANGED
@@ -1043,6 +1043,37 @@ Fontisan provides comprehensive tools for managing TrueType Collections (TTC)
1043
1043
  and OpenType Collections (OTC). You can list fonts in a collection, extract
1044
1044
  individual fonts, unpack entire collections, and validate collection integrity.
1045
1045
 
1046
+ Both TTC and OTC files use the same `ttcf` tag in their binary format, but
1047
+ differ in the type of font data they contain:
1048
+
1049
+ TTC (TrueType Collection):: Supported since OpenType 1.4. Contains fonts with
1050
+ TrueType outlines (glyf table). Multiple fonts can share identical tables for
1051
+ efficient storage. File extension: `.ttc`
1052
+
1053
+ OTC (OpenType Collection):: Supported since OpenType 1.8. Contains fonts with
1054
+ CFF-format outlines (CFF table). Provides the same storage benefits and
1055
+ glyph-count advantages as TTC but for CFF fonts. File extension: `.otc`
1056
+
1057
+ The collection format allows:
1058
+
1059
+ Table sharing::
1060
+ Identical tables are stored once and referenced by multiple fonts
1061
+
1062
+ Gap mode::
1063
+ Overcomes the 65,535 glyph limit per font by distributing glyphs across multiple
1064
+ fonts in a single file
1065
+
1066
+ Efficient storage::
1067
+ Significant size reduction, especially for CJK fonts (e.g., Noto CJK OTC is ~10
1068
+ MB smaller than separate OTF files)
1069
+
1070
+ Fontist returns the appropriate collection type based on the font data:
1071
+
1072
+ * Examines font data within collection to determine type (TTC vs OTC)
1073
+ * TTC contains fonts with TrueType outlines (glyf table)
1074
+ * OTC contains fonts with CFF outlines (CFF table)
1075
+ * If ANY font in the collection has CFF outlines, use OpenTypeCollection
1076
+ * Only use TrueTypeCollection if ALL fonts have TrueType outlines
1046
1077
 
1047
1078
  === List fonts
1048
1079
 
@@ -0,0 +1,296 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bindata"
4
+ require_relative "constants"
5
+
6
+ module Fontisan
7
+ # Abstract base class for font collections (TTC/OTC)
8
+ #
9
+ # This class implements the shared logic for TrueTypeCollection and OpenTypeCollection
10
+ # using the Template Method pattern. Subclasses must implement the abstract methods
11
+ # to specify their font class and collection format.
12
+ #
13
+ # The BinData structure definition is shared between both collection types since
14
+ # both TTC and OTC files use the same "ttcf" tag and binary format. The only
15
+ # differences are:
16
+ # 1. The type of fonts contained (TrueType vs OpenType)
17
+ # 2. The format string used for display ("TTC" vs "OTC")
18
+ #
19
+ # @abstract Subclass and override {font_class} and {collection_format}
20
+ #
21
+ # @example Implementing a collection subclass
22
+ # class TrueTypeCollection < BaseCollection
23
+ # def self.font_class
24
+ # TrueTypeFont
25
+ # end
26
+ #
27
+ # def self.collection_format
28
+ # "TTC"
29
+ # end
30
+ # end
31
+ class BaseCollection < BinData::Record
32
+ endian :big
33
+
34
+ string :tag, length: 4, assert: "ttcf"
35
+ uint16 :major_version
36
+ uint16 :minor_version
37
+ uint32 :num_fonts
38
+ array :font_offsets, type: :uint32, initial_length: :num_fonts
39
+
40
+ # Abstract method: Get the font class for this collection type
41
+ #
42
+ # Subclasses must override this to return their specific font class
43
+ # (TrueTypeFont or OpenTypeFont).
44
+ #
45
+ # @return [Class] The font class (TrueTypeFont or OpenTypeFont)
46
+ # @raise [NotImplementedError] if not overridden by subclass
47
+ def self.font_class
48
+ raise NotImplementedError,
49
+ "#{name} must implement self.font_class"
50
+ end
51
+
52
+ # Abstract method: Get the collection format string
53
+ #
54
+ # Subclasses must override this to return "TTC" or "OTC".
55
+ #
56
+ # @return [String] Collection format ("TTC" or "OTC")
57
+ # @raise [NotImplementedError] if not overridden by subclass
58
+ def self.collection_format
59
+ raise NotImplementedError,
60
+ "#{name} must implement self.collection_format"
61
+ end
62
+
63
+ # Read collection from a file
64
+ #
65
+ # @param path [String] Path to the collection file
66
+ # @return [BaseCollection] A new instance
67
+ # @raise [ArgumentError] if path is nil or empty
68
+ # @raise [Errno::ENOENT] if file does not exist
69
+ # @raise [RuntimeError] if file format is invalid
70
+ def self.from_file(path)
71
+ if path.nil? || path.to_s.empty?
72
+ raise ArgumentError,
73
+ "path cannot be nil or empty"
74
+ end
75
+ raise Errno::ENOENT, "File not found: #{path}" unless File.exist?(path)
76
+
77
+ File.open(path, "rb") { |io| read(io) }
78
+ rescue BinData::ValidityError => e
79
+ raise "Invalid #{collection_format} file: #{e.message}"
80
+ rescue EOFError => e
81
+ raise "Invalid #{collection_format} file: unexpected end of file - #{e.message}"
82
+ end
83
+
84
+ # Extract fonts from the collection
85
+ #
86
+ # Reads each font from the collection file and returns them as font objects.
87
+ #
88
+ # @param io [IO] Open file handle to read fonts from
89
+ # @return [Array] Array of font objects (TrueTypeFont or OpenTypeFont)
90
+ def extract_fonts(io)
91
+ font_class = self.class.font_class
92
+
93
+ font_offsets.map do |offset|
94
+ font_class.from_collection(io, offset)
95
+ end
96
+ end
97
+
98
+ # Get a single font from the collection
99
+ #
100
+ # @param index [Integer] Index of the font (0-based)
101
+ # @param io [IO] Open file handle
102
+ # @param mode [Symbol] Loading mode (:metadata or :full, default: :full)
103
+ # @return [TrueTypeFont, OpenTypeFont, nil] Font object or nil if index out of range
104
+ def font(index, io, mode: LoadingModes::FULL)
105
+ return nil if index >= num_fonts
106
+
107
+ font_class = self.class.font_class
108
+ font_class.from_collection(io, font_offsets[index], mode: mode)
109
+ end
110
+
111
+ # Get font count
112
+ #
113
+ # @return [Integer] Number of fonts in collection
114
+ def font_count
115
+ num_fonts
116
+ end
117
+
118
+ # Validate format correctness
119
+ #
120
+ # @return [Boolean] true if the format is valid, false otherwise
121
+ def valid?
122
+ tag == Constants::TTC_TAG && num_fonts.positive? && font_offsets.length == num_fonts
123
+ rescue StandardError
124
+ false
125
+ end
126
+
127
+ # Get the collection version as a single integer
128
+ #
129
+ # @return [Integer] Version number (e.g., 0x00010000 for version 1.0)
130
+ def version
131
+ (major_version << 16) | minor_version
132
+ end
133
+
134
+ # Get the collection version as a string
135
+ #
136
+ # @return [String] Version string (e.g., "1.0")
137
+ def version_string
138
+ "#{major_version}.#{minor_version}"
139
+ end
140
+
141
+ # List all fonts in the collection with basic metadata
142
+ #
143
+ # Returns a CollectionListInfo model containing summaries of all fonts.
144
+ # This is the API method used by the `ls` command for collections.
145
+ #
146
+ # @param io [IO] Open file handle to read fonts from
147
+ # @return [CollectionListInfo] List of fonts with metadata
148
+ #
149
+ # @example List fonts in collection
150
+ # File.open("fonts.ttc", "rb") do |io|
151
+ # collection = TrueTypeCollection.read(io)
152
+ # list = collection.list_fonts(io)
153
+ # list.fonts.each { |f| puts "#{f.index}: #{f.family_name}" }
154
+ # end
155
+ def list_fonts(io)
156
+ require_relative "models/collection_list_info"
157
+ require_relative "models/collection_font_summary"
158
+ require_relative "tables/name"
159
+
160
+ font_class = self.class.font_class
161
+
162
+ fonts = font_offsets.map.with_index do |offset, index|
163
+ font = font_class.from_collection(io, offset)
164
+
165
+ # Extract basic font info
166
+ name_table = font.table("name")
167
+ post_table = font.table("post")
168
+
169
+ family_name = name_table&.english_name(Tables::Name::FAMILY) || "Unknown"
170
+ subfamily_name = name_table&.english_name(Tables::Name::SUBFAMILY) || "Regular"
171
+ postscript_name = name_table&.english_name(Tables::Name::POSTSCRIPT_NAME) || "Unknown"
172
+
173
+ # Determine font format
174
+ sfnt = font.header.sfnt_version
175
+ font_format = case sfnt
176
+ when 0x00010000, 0x74727565 # 0x74727565 = 'true'
177
+ "TrueType"
178
+ when 0x4F54544F # 'OTTO'
179
+ "OpenType"
180
+ else
181
+ "Unknown"
182
+ end
183
+
184
+ num_glyphs = post_table&.glyph_names&.length || 0
185
+ num_tables = font.table_names.length
186
+
187
+ Models::CollectionFontSummary.new(
188
+ index: index,
189
+ family_name: family_name,
190
+ subfamily_name: subfamily_name,
191
+ postscript_name: postscript_name,
192
+ font_format: font_format,
193
+ num_glyphs: num_glyphs,
194
+ num_tables: num_tables,
195
+ )
196
+ end
197
+
198
+ Models::CollectionListInfo.new(
199
+ collection_path: nil, # Will be set by command
200
+ num_fonts: num_fonts,
201
+ fonts: fonts,
202
+ )
203
+ end
204
+
205
+ # Get comprehensive collection metadata
206
+ #
207
+ # Returns a CollectionInfo model with header information, offsets,
208
+ # and table sharing statistics.
209
+ # This is the API method used by the `info` command for collections.
210
+ #
211
+ # @param io [IO] Open file handle to read fonts from
212
+ # @param path [String] Collection file path (for file size)
213
+ # @return [CollectionInfo] Collection metadata
214
+ #
215
+ # @example Get collection info
216
+ # File.open("fonts.ttc", "rb") do |io|
217
+ # collection = TrueTypeCollection.read(io)
218
+ # info = collection.collection_info(io, "fonts.ttc")
219
+ # puts "Version: #{info.version_string}"
220
+ # end
221
+ def collection_info(io, path)
222
+ require_relative "models/collection_info"
223
+ require_relative "models/table_sharing_info"
224
+
225
+ # Calculate table sharing statistics
226
+ table_sharing = calculate_table_sharing(io)
227
+
228
+ # Get file size
229
+ file_size = path ? File.size(path) : 0
230
+
231
+ Models::CollectionInfo.new(
232
+ collection_path: path,
233
+ collection_format: self.class.collection_format,
234
+ ttc_tag: tag,
235
+ major_version: major_version,
236
+ minor_version: minor_version,
237
+ num_fonts: num_fonts,
238
+ font_offsets: font_offsets.to_a,
239
+ file_size_bytes: file_size,
240
+ table_sharing: table_sharing,
241
+ )
242
+ end
243
+
244
+ private
245
+
246
+ # Calculate table sharing statistics
247
+ #
248
+ # Analyzes which tables are shared between fonts and calculates
249
+ # space savings from deduplication.
250
+ #
251
+ # @param io [IO] Open file handle
252
+ # @return [TableSharingInfo] Sharing statistics
253
+ def calculate_table_sharing(io)
254
+ require_relative "models/table_sharing_info"
255
+
256
+ font_class = self.class.font_class
257
+
258
+ # Extract all fonts
259
+ fonts = font_offsets.map do |offset|
260
+ font_class.from_collection(io, offset)
261
+ end
262
+
263
+ # Build table hash map (checksum -> size)
264
+ table_map = {}
265
+ total_table_size = 0
266
+
267
+ fonts.each do |font|
268
+ font.tables.each do |entry|
269
+ key = entry.checksum
270
+ size = entry.table_length
271
+ table_map[key] ||= size
272
+ total_table_size += size
273
+ end
274
+ end
275
+
276
+ # Count unique vs shared
277
+ unique_tables = table_map.size
278
+ total_tables = fonts.sum { |f| f.tables.length }
279
+ shared_tables = total_tables - unique_tables
280
+
281
+ # Calculate space saved
282
+ unique_size = table_map.values.sum
283
+ space_saved = total_table_size - unique_size
284
+
285
+ # Calculate sharing percentage
286
+ sharing_pct = total_tables.positive? ? (shared_tables.to_f / total_tables * 100).round(2) : 0.0
287
+
288
+ Models::TableSharingInfo.new(
289
+ shared_tables: shared_tables,
290
+ unique_tables: unique_tables,
291
+ sharing_percentage: sharing_pct,
292
+ space_saved_bytes: space_saved,
293
+ )
294
+ end
295
+ end
296
+ end
@@ -50,62 +50,80 @@ module Fontisan
50
50
  # Brief mode: load each font and populate brief info
51
51
  brief_info = Models::CollectionBriefInfo.new
52
52
  brief_info.collection_path = @font_path
53
+ brief_info.collection_type = collection.class.collection_format
54
+ brief_info.collection_version = collection.version_string
53
55
  brief_info.num_fonts = collection.num_fonts
54
- brief_info.fonts = []
55
-
56
- collection.num_fonts.times do |index|
57
- # Load individual font from collection
58
- font = FontLoader.load(@font_path, font_index: index, mode: LoadingModes::METADATA)
59
-
60
- # Populate brief info for this font
61
- info = Models::FontInfo.new
62
-
63
- # Font format and variable status
64
- info.font_format = case font
65
- when TrueTypeFont
66
- "truetype"
67
- when OpenTypeFont
68
- "cff"
69
- else
70
- "unknown"
71
- end
72
- info.is_variable = font.has_table?(Constants::FVAR_TAG)
73
-
74
- # Collection offset (only populated for fonts in collections)
75
- info.collection_offset = collection.font_offsets[index]
76
-
77
- # Essential names
78
- if font.has_table?(Constants::NAME_TAG)
79
- name_table = font.table(Constants::NAME_TAG)
80
- info.family_name = name_table.english_name(Tables::Name::FAMILY)
81
- info.subfamily_name = name_table.english_name(Tables::Name::SUBFAMILY)
82
- info.full_name = name_table.english_name(Tables::Name::FULL_NAME)
83
- info.postscript_name = name_table.english_name(Tables::Name::POSTSCRIPT_NAME)
84
- info.version = name_table.english_name(Tables::Name::VERSION)
85
- end
86
-
87
- # Essential metrics
88
- if font.has_table?(Constants::HEAD_TAG)
89
- head = font.table(Constants::HEAD_TAG)
90
- info.font_revision = head.font_revision
91
- info.units_per_em = head.units_per_em
92
- end
93
-
94
- # Vendor ID
95
- if font.has_table?(Constants::OS2_TAG)
96
- os2_table = font.table(Constants::OS2_TAG)
97
- info.vendor_id = os2_table.vendor_id
98
- end
99
-
100
- brief_info.fonts << info
101
- end
56
+ brief_info.fonts = load_collection_fonts(collection, @font_path)
102
57
 
103
58
  brief_info
104
59
  else
105
- # Full mode: show detailed sharing statistics
106
- collection.collection_info(io, @font_path)
60
+ # Full mode: show detailed sharing statistics AND font information
61
+ full_info = collection.collection_info(io, @font_path)
62
+
63
+ # Add font information to full mode
64
+ full_info.fonts = load_collection_fonts(collection, @font_path)
65
+
66
+ full_info
67
+ end
68
+ end
69
+ end
70
+
71
+ # Load font information for all fonts in a collection
72
+ #
73
+ # @param collection [TrueTypeCollection, OpenTypeCollection] The collection
74
+ # @param collection_path [String] Path to the collection file
75
+ # @return [Array<Models::FontInfo>] Array of font info objects
76
+ def load_collection_fonts(collection, collection_path)
77
+ fonts = []
78
+
79
+ collection.num_fonts.times do |index|
80
+ # Load individual font from collection
81
+ font = FontLoader.load(collection_path, font_index: index, mode: LoadingModes::METADATA)
82
+
83
+ # Populate font info
84
+ info = Models::FontInfo.new
85
+
86
+ # Font format and variable status
87
+ info.font_format = case font
88
+ when TrueTypeFont
89
+ "truetype"
90
+ when OpenTypeFont
91
+ "cff"
92
+ else
93
+ "unknown"
94
+ end
95
+ info.is_variable = font.has_table?(Constants::FVAR_TAG)
96
+
97
+ # Collection offset (only populated for fonts in collections)
98
+ info.collection_offset = collection.font_offsets[index]
99
+
100
+ # Essential names
101
+ if font.has_table?(Constants::NAME_TAG)
102
+ name_table = font.table(Constants::NAME_TAG)
103
+ info.family_name = name_table.english_name(Tables::Name::FAMILY)
104
+ info.subfamily_name = name_table.english_name(Tables::Name::SUBFAMILY)
105
+ info.full_name = name_table.english_name(Tables::Name::FULL_NAME)
106
+ info.postscript_name = name_table.english_name(Tables::Name::POSTSCRIPT_NAME)
107
+ info.version = name_table.english_name(Tables::Name::VERSION)
108
+ end
109
+
110
+ # Essential metrics
111
+ if font.has_table?(Constants::HEAD_TAG)
112
+ head = font.table(Constants::HEAD_TAG)
113
+ info.font_revision = head.font_revision
114
+ info.units_per_em = head.units_per_em
107
115
  end
116
+
117
+ # Vendor ID
118
+ if font.has_table?(Constants::OS2_TAG)
119
+ os2_table = font.table(Constants::OS2_TAG)
120
+ info.vendor_id = os2_table.vendor_id
121
+ end
122
+
123
+ fonts << info
108
124
  end
125
+
126
+ fonts
109
127
  end
110
128
 
111
129
  # Get individual font information