fontisan 0.1.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 (56) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +13 -0
  4. data/.rubocop_todo.yml +217 -0
  5. data/Gemfile +15 -0
  6. data/LICENSE +24 -0
  7. data/README.adoc +984 -0
  8. data/Rakefile +95 -0
  9. data/exe/fontisan +7 -0
  10. data/fontisan.gemspec +44 -0
  11. data/lib/fontisan/binary/base_record.rb +57 -0
  12. data/lib/fontisan/binary/structures.rb +84 -0
  13. data/lib/fontisan/cli.rb +192 -0
  14. data/lib/fontisan/commands/base_command.rb +82 -0
  15. data/lib/fontisan/commands/dump_table_command.rb +71 -0
  16. data/lib/fontisan/commands/features_command.rb +94 -0
  17. data/lib/fontisan/commands/glyphs_command.rb +50 -0
  18. data/lib/fontisan/commands/info_command.rb +120 -0
  19. data/lib/fontisan/commands/optical_size_command.rb +41 -0
  20. data/lib/fontisan/commands/scripts_command.rb +59 -0
  21. data/lib/fontisan/commands/tables_command.rb +52 -0
  22. data/lib/fontisan/commands/unicode_command.rb +76 -0
  23. data/lib/fontisan/commands/variable_command.rb +61 -0
  24. data/lib/fontisan/config/features.yml +143 -0
  25. data/lib/fontisan/config/scripts.yml +42 -0
  26. data/lib/fontisan/constants.rb +78 -0
  27. data/lib/fontisan/error.rb +15 -0
  28. data/lib/fontisan/font_loader.rb +109 -0
  29. data/lib/fontisan/formatters/text_formatter.rb +314 -0
  30. data/lib/fontisan/models/all_scripts_features_info.rb +21 -0
  31. data/lib/fontisan/models/features_info.rb +42 -0
  32. data/lib/fontisan/models/font_info.rb +99 -0
  33. data/lib/fontisan/models/glyph_info.rb +26 -0
  34. data/lib/fontisan/models/optical_size_info.rb +33 -0
  35. data/lib/fontisan/models/scripts_info.rb +39 -0
  36. data/lib/fontisan/models/table_info.rb +55 -0
  37. data/lib/fontisan/models/unicode_mappings.rb +42 -0
  38. data/lib/fontisan/models/variable_font_info.rb +82 -0
  39. data/lib/fontisan/open_type_collection.rb +97 -0
  40. data/lib/fontisan/open_type_font.rb +292 -0
  41. data/lib/fontisan/parsers/tag.rb +77 -0
  42. data/lib/fontisan/tables/cmap.rb +284 -0
  43. data/lib/fontisan/tables/fvar.rb +157 -0
  44. data/lib/fontisan/tables/gpos.rb +111 -0
  45. data/lib/fontisan/tables/gsub.rb +111 -0
  46. data/lib/fontisan/tables/head.rb +114 -0
  47. data/lib/fontisan/tables/layout_common.rb +73 -0
  48. data/lib/fontisan/tables/name.rb +188 -0
  49. data/lib/fontisan/tables/os2.rb +175 -0
  50. data/lib/fontisan/tables/post.rb +148 -0
  51. data/lib/fontisan/true_type_collection.rb +98 -0
  52. data/lib/fontisan/true_type_font.rb +313 -0
  53. data/lib/fontisan/utilities/checksum_calculator.rb +89 -0
  54. data/lib/fontisan/version.rb +5 -0
  55. data/lib/fontisan.rb +80 -0
  56. metadata +150 -0
@@ -0,0 +1,313 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bindata"
4
+ require_relative "constants"
5
+ require_relative "utilities/checksum_calculator"
6
+
7
+ module Fontisan
8
+ # TTF Offset Table structure
9
+ class OffsetTable < BinData::Record
10
+ endian :big
11
+ uint32 :sfnt_version
12
+ uint16 :num_tables
13
+ uint16 :search_range
14
+ uint16 :entry_selector
15
+ uint16 :range_shift
16
+ end
17
+
18
+ # TTF Table Directory Entry structure
19
+ class TableDirectory < BinData::Record
20
+ endian :big
21
+ string :tag, length: 4
22
+ uint32 :checksum
23
+ uint32 :offset
24
+ uint32 :table_length
25
+ end
26
+
27
+ # TrueType Font domain object using BinData
28
+ #
29
+ # Represents a complete TrueType Font file using BinData's declarative
30
+ # DSL for binary structure definition. The structure definition IS the
31
+ # documentation, and BinData handles all low-level reading/writing.
32
+ #
33
+ # Extended from ExtractTTC to support table parsing for analysis.
34
+ #
35
+ # @example Reading and analyzing a font
36
+ # ttf = TrueTypeFont.from_file("font.ttf")
37
+ # puts ttf.header.num_tables # => 14
38
+ # name_table = ttf.table("name") # Fontisan extension
39
+ # puts name_table.english_name(Tables::Name::FAMILY)
40
+ #
41
+ # @example Writing a font
42
+ # ttf.to_file("output.ttf")
43
+ class TrueTypeFont < BinData::Record
44
+ endian :big
45
+
46
+ offset_table :header
47
+ array :tables, type: :table_directory, initial_length: lambda {
48
+ header.num_tables
49
+ }
50
+
51
+ # Table data is stored separately since it's at variable offsets
52
+ attr_accessor :table_data
53
+
54
+ # Parsed table instances cache (Fontisan extension)
55
+ attr_accessor :parsed_tables
56
+
57
+ # Read TrueType Font from a file
58
+ #
59
+ # @param path [String] Path to the TTF file
60
+ # @return [TrueTypeFont] A new instance
61
+ # @raise [ArgumentError] if path is nil or empty
62
+ # @raise [Errno::ENOENT] if file does not exist
63
+ # @raise [RuntimeError] if file format is invalid
64
+ def self.from_file(path)
65
+ if path.nil? || path.to_s.empty?
66
+ raise ArgumentError,
67
+ "path cannot be nil or empty"
68
+ end
69
+ raise Errno::ENOENT, "File not found: #{path}" unless File.exist?(path)
70
+
71
+ File.open(path, "rb") do |io|
72
+ font = read(io)
73
+ font.initialize_storage
74
+ font.read_table_data(io)
75
+ font
76
+ end
77
+ rescue BinData::ValidityError, EOFError => e
78
+ raise "Invalid TTF file: #{e.message}"
79
+ end
80
+
81
+ # Read TrueType Font from TTC at specific offset
82
+ #
83
+ # @param io [IO] Open file handle
84
+ # @param offset [Integer] Byte offset to the font
85
+ # @return [TrueTypeFont] A new instance
86
+ def self.from_ttc(io, offset)
87
+ io.seek(offset)
88
+ font = read(io)
89
+ font.initialize_storage
90
+ font.read_table_data(io)
91
+ font
92
+ end
93
+
94
+ # Initialize storage hashes (Fontisan extension)
95
+ #
96
+ # @return [void]
97
+ def initialize_storage
98
+ @table_data = {}
99
+ @parsed_tables = {}
100
+ end
101
+
102
+ # Read table data for all tables
103
+ #
104
+ # @param io [IO] Open file handle
105
+ # @return [void]
106
+ def read_table_data(io)
107
+ @table_data = {}
108
+ tables.each do |entry|
109
+ io.seek(entry.offset)
110
+ # Force UTF-8 encoding on tag for hash key consistency
111
+ tag_key = entry.tag.dup.force_encoding("UTF-8")
112
+ @table_data[tag_key] = io.read(entry.table_length)
113
+ end
114
+ end
115
+
116
+ # Write TrueType Font to a file
117
+ #
118
+ # Writes the complete TTF structure to disk, including proper checksum
119
+ # calculation and table alignment.
120
+ #
121
+ # @param path [String] Path where the TTF file will be written
122
+ # @return [Integer] Number of bytes written
123
+ # @raise [IOError] if writing fails
124
+ def to_file(path)
125
+ File.open(path, "wb") do |io|
126
+ # Write header and tables (directory)
127
+ write_structure(io)
128
+
129
+ # Write table data with updated offsets
130
+ write_table_data_with_offsets(io)
131
+
132
+ io.pos
133
+ end
134
+
135
+ # Update checksum adjustment in head table
136
+ update_checksum_adjustment_in_file(path) if head_table
137
+
138
+ File.size(path)
139
+ end
140
+
141
+ # Validate format correctness
142
+ #
143
+ # @return [Boolean] true if the TTF format is valid, false otherwise
144
+ def valid?
145
+ return false unless header
146
+ return false unless tables.respond_to?(:length)
147
+ return false unless @table_data.is_a?(Hash)
148
+ return false if tables.length != header.num_tables
149
+ return false unless head_table
150
+
151
+ true
152
+ end
153
+
154
+ # Check if font has a specific table
155
+ #
156
+ # @param tag [String] The table tag to check for
157
+ # @return [Boolean] true if table exists, false otherwise
158
+ def has_table?(tag)
159
+ tables.any? { |entry| entry.tag == tag }
160
+ end
161
+
162
+ # Find a table entry by tag
163
+ #
164
+ # @param tag [String] The table tag to find
165
+ # @return [TableDirectory, nil] The table entry or nil
166
+ def find_table_entry(tag)
167
+ tables.find { |entry| entry.tag == tag }
168
+ end
169
+
170
+ # Get the head table entry
171
+ #
172
+ # @return [TableDirectory, nil] The head table entry or nil
173
+ def head_table
174
+ find_table_entry(Constants::HEAD_TAG)
175
+ end
176
+
177
+ # Get list of all table tags (Fontisan extension)
178
+ #
179
+ # @return [Array<String>] Array of table tag strings
180
+ def table_names
181
+ tables.map(&:tag)
182
+ end
183
+
184
+ # Get parsed table instance (Fontisan extension)
185
+ #
186
+ # This method parses the raw table data into a structured table object
187
+ # and caches the result for subsequent calls.
188
+ #
189
+ # @param tag [String] The table tag to retrieve
190
+ # @return [Tables::*, nil] Parsed table object or nil if not found
191
+ def table(tag)
192
+ @parsed_tables[tag] ||= parse_table(tag)
193
+ end
194
+
195
+ # Get units per em from head table (Fontisan extension)
196
+ #
197
+ # @return [Integer, nil] Units per em value
198
+ def units_per_em
199
+ head = table(Constants::HEAD_TAG)
200
+ head&.units_per_em
201
+ end
202
+
203
+ private
204
+
205
+ # Parse a table from raw data (Fontisan extension)
206
+ #
207
+ # @param tag [String] The table tag to parse
208
+ # @return [Tables::*, nil] Parsed table object or nil
209
+ def parse_table(tag)
210
+ raw_data = @table_data[tag]
211
+ return nil unless raw_data
212
+
213
+ table_class = table_class_for(tag)
214
+ return nil unless table_class
215
+
216
+ table_class.read(raw_data)
217
+ end
218
+
219
+ # Map table tag to parser class (Fontisan extension)
220
+ #
221
+ # @param tag [String] The table tag
222
+ # @return [Class, nil] Table parser class or nil
223
+ def table_class_for(tag)
224
+ {
225
+ Constants::HEAD_TAG => Tables::Head,
226
+ Constants::NAME_TAG => Tables::Name,
227
+ Constants::OS2_TAG => Tables::Os2,
228
+ Constants::POST_TAG => Tables::Post,
229
+ Constants::CMAP_TAG => Tables::Cmap,
230
+ Constants::FVAR_TAG => Tables::Fvar,
231
+ Constants::GSUB_TAG => Tables::Gsub,
232
+ Constants::GPOS_TAG => Tables::Gpos,
233
+ }[tag]
234
+ end
235
+
236
+ # Write the structure (header + table directory) to IO
237
+ #
238
+ # @param io [IO] Open file handle
239
+ # @return [void]
240
+ def write_structure(io)
241
+ # Write header
242
+ header.write(io)
243
+
244
+ # Write table directory with placeholder offsets
245
+ tables.each do |entry|
246
+ io.write(entry.tag)
247
+ io.write([entry.checksum].pack("N"))
248
+ io.write([0].pack("N")) # Placeholder offset
249
+ io.write([entry.table_length].pack("N"))
250
+ end
251
+ end
252
+
253
+ # Write table data and update offsets in directory
254
+ #
255
+ # @param io [IO] Open file handle
256
+ # @return [void]
257
+ def write_table_data_with_offsets(io)
258
+ tables.each_with_index do |entry, index|
259
+ # Record current position
260
+ current_position = io.pos
261
+
262
+ # Write table data
263
+ data = @table_data[entry.tag]
264
+ raise IOError, "Missing table data for tag '#{entry.tag}'" if data.nil?
265
+
266
+ io.write(data)
267
+
268
+ # Add padding to align to 4-byte boundary
269
+ padding = (Constants::TABLE_ALIGNMENT - (io.pos % Constants::TABLE_ALIGNMENT)) % Constants::TABLE_ALIGNMENT
270
+ io.write("\x00" * padding) if padding.positive?
271
+
272
+ # Zero out checksumAdjustment field in head table
273
+ if entry.tag == Constants::HEAD_TAG
274
+ current_pos = io.pos
275
+ io.seek(current_position + 8)
276
+ io.write([0].pack("N"))
277
+ io.seek(current_pos)
278
+ end
279
+
280
+ # Update offset in table directory
281
+ # Table directory starts at byte 12, each entry is 16 bytes
282
+ # Offset field is at byte 8 within each entry
283
+ directory_offset_position = 12 + (index * 16) + 8
284
+ current_pos = io.pos
285
+ io.seek(directory_offset_position)
286
+ io.write([current_position].pack("N"))
287
+ io.seek(current_pos)
288
+ end
289
+ end
290
+
291
+ # Update checksumAdjustment field in head table
292
+ #
293
+ # @param path [String] Path to the TTF file
294
+ # @return [void]
295
+ def update_checksum_adjustment_in_file(path)
296
+ # Calculate file checksum
297
+ checksum = Utilities::ChecksumCalculator.calculate_file_checksum(path)
298
+
299
+ # Calculate adjustment
300
+ adjustment = Utilities::ChecksumCalculator.calculate_adjustment(checksum)
301
+
302
+ # Find head table position
303
+ head_entry = head_table
304
+ return unless head_entry
305
+
306
+ # Write adjustment to head table (offset 8 within head table)
307
+ File.open(path, "r+b") do |io|
308
+ io.seek(head_entry.offset + 8)
309
+ io.write([adjustment].pack("N"))
310
+ end
311
+ end
312
+ end
313
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../constants"
4
+
5
+ module Fontisan
6
+ module Utilities
7
+ # ChecksumCalculator provides stateless utility methods for calculating font file checksums.
8
+ #
9
+ # This class implements the TrueType/OpenType checksum algorithm which sums all uint32
10
+ # values in a file. The checksum is used to verify file integrity and calculate the
11
+ # checksumAdjustment value stored in the 'head' table.
12
+ #
13
+ # @example Calculate file checksum
14
+ # checksum = ChecksumCalculator.calculate_file_checksum("font.ttf")
15
+ # # => 2842116234
16
+ #
17
+ # @example Calculate checksum adjustment
18
+ # adjustment = ChecksumCalculator.calculate_adjustment(checksum)
19
+ # # => 1452851062
20
+ class ChecksumCalculator
21
+ # Calculate the checksum of an entire font file.
22
+ #
23
+ # The checksum is calculated by summing all uint32 (4-byte) values in the file.
24
+ # Files that are not multiples of 4 bytes are padded with zeros. The sum is
25
+ # masked to 32 bits to prevent overflow.
26
+ #
27
+ # @param file_path [String] path to the font file
28
+ # @return [Integer] the calculated uint32 checksum
29
+ # @raise [Errno::ENOENT] if the file does not exist
30
+ # @raise [Errno::EACCES] if the file cannot be read
31
+ #
32
+ # @example
33
+ # checksum = ChecksumCalculator.calculate_file_checksum("font.ttf")
34
+ # # => 2842116234
35
+ def self.calculate_file_checksum(file_path)
36
+ File.open(file_path, "rb") do |file|
37
+ calculate_checksum_from_io(file)
38
+ end
39
+ end
40
+
41
+ # Calculate the checksum adjustment value for the 'head' table.
42
+ #
43
+ # The checksum adjustment is stored at offset 8 in the 'head' table and is
44
+ # calculated as: CHECKSUM_ADJUSTMENT_MAGIC - file_checksum.
45
+ # This value ensures that the checksum of the entire font file equals the
46
+ # magic number.
47
+ #
48
+ # @param file_checksum [Integer] the calculated file checksum
49
+ # @return [Integer] the checksum adjustment value to write to the 'head' table
50
+ #
51
+ # @example
52
+ # adjustment = ChecksumCalculator.calculate_adjustment(2842116234)
53
+ # # => 1452851062
54
+ def self.calculate_adjustment(file_checksum)
55
+ (Constants::CHECKSUM_ADJUSTMENT_MAGIC - file_checksum) & 0xFFFFFFFF
56
+ end
57
+
58
+ # Calculate checksum from an IO object.
59
+ #
60
+ # Reads the IO stream in 4-byte chunks and calculates the uint32 checksum.
61
+ # This is the core checksum algorithm implementation.
62
+ #
63
+ # @param io [IO] the IO object to read from
64
+ # @return [Integer] the calculated uint32 checksum
65
+ # @api private
66
+ def self.calculate_checksum_from_io(io)
67
+ io.rewind
68
+ sum = 0
69
+
70
+ until io.eof?
71
+ # Read 4 bytes at a time
72
+ bytes = io.read(4)
73
+ break if bytes.nil? || bytes.empty?
74
+
75
+ # Pad with zeros if less than 4 bytes
76
+ bytes += "\0" * (4 - bytes.length) if bytes.length < 4
77
+
78
+ # Convert to uint32 (network byte order, big-endian)
79
+ value = bytes.unpack1("N")
80
+ sum = (sum + value) & 0xFFFFFFFF
81
+ end
82
+
83
+ sum
84
+ end
85
+
86
+ private_class_method :calculate_checksum_from_io
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fontisan
4
+ VERSION = "0.1.0"
5
+ end
data/lib/fontisan.rb ADDED
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+ require "lutaml/model"
5
+
6
+ # Core
7
+ require_relative "fontisan/version"
8
+ require_relative "fontisan/error"
9
+ require_relative "fontisan/constants"
10
+
11
+ # Binary structures and parsers
12
+ require_relative "fontisan/parsers/tag"
13
+ require_relative "fontisan/binary/base_record"
14
+
15
+ # Table parsers
16
+ require_relative "fontisan/tables/head"
17
+ require_relative "fontisan/tables/name"
18
+ require_relative "fontisan/tables/os2"
19
+ require_relative "fontisan/tables/post"
20
+ require_relative "fontisan/tables/cmap"
21
+ require_relative "fontisan/tables/fvar"
22
+ require_relative "fontisan/tables/layout_common"
23
+ require_relative "fontisan/tables/gsub"
24
+ require_relative "fontisan/tables/gpos"
25
+
26
+ # Domain objects (BinData::Record)
27
+ require_relative "fontisan/true_type_font"
28
+ require_relative "fontisan/open_type_font"
29
+ require_relative "fontisan/true_type_collection"
30
+ require_relative "fontisan/open_type_collection"
31
+
32
+ # Font loading
33
+ require_relative "fontisan/font_loader"
34
+
35
+ # Utilities
36
+ require_relative "fontisan/utilities/checksum_calculator"
37
+
38
+ # Information models (Lutaml::Model)
39
+ require_relative "fontisan/models/font_info"
40
+ require_relative "fontisan/models/table_info"
41
+ require_relative "fontisan/models/glyph_info"
42
+ require_relative "fontisan/models/unicode_mappings"
43
+ require_relative "fontisan/models/variable_font_info"
44
+ require_relative "fontisan/models/optical_size_info"
45
+ require_relative "fontisan/models/scripts_info"
46
+ require_relative "fontisan/models/features_info"
47
+ require_relative "fontisan/models/all_scripts_features_info"
48
+
49
+ # Commands
50
+ require_relative "fontisan/commands/base_command"
51
+ require_relative "fontisan/commands/info_command"
52
+ require_relative "fontisan/commands/tables_command"
53
+ require_relative "fontisan/commands/glyphs_command"
54
+ require_relative "fontisan/commands/unicode_command"
55
+ require_relative "fontisan/commands/variable_command"
56
+ require_relative "fontisan/commands/optical_size_command"
57
+ require_relative "fontisan/commands/scripts_command"
58
+ require_relative "fontisan/commands/features_command"
59
+ require_relative "fontisan/commands/dump_table_command"
60
+
61
+ # Formatters
62
+ require_relative "fontisan/formatters/text_formatter"
63
+
64
+ # CLI
65
+ require_relative "fontisan/cli"
66
+
67
+ module Fontisan
68
+ class << self
69
+ attr_accessor :logger
70
+
71
+ def configure
72
+ yield self if block_given?
73
+ end
74
+ end
75
+
76
+ # Set default logger
77
+ self.logger = Logger.new($stdout).tap do |log|
78
+ log.level = Logger::WARN
79
+ end
80
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fontisan
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Ribose Inc.
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-11-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bindata
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: lutaml-model
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.7'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: thor
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.4'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.4'
55
+ description: |
56
+ Fontisan provides font analysis tools and utilities. It is
57
+ designed as a pure Ruby implementation with full object-oriented architecture,
58
+ supporting extraction of information from OpenType and TrueType fonts (OTF, TTF, OTC, TTC).
59
+
60
+ The gem provides both a Ruby library API and a command-line interface,
61
+ with structured output formats (YAML, JSON, text).
62
+ email:
63
+ - open.source@ribose.com
64
+ executables:
65
+ - fontisan
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - ".rspec"
70
+ - ".rubocop.yml"
71
+ - ".rubocop_todo.yml"
72
+ - Gemfile
73
+ - LICENSE
74
+ - README.adoc
75
+ - Rakefile
76
+ - exe/fontisan
77
+ - fontisan.gemspec
78
+ - lib/fontisan.rb
79
+ - lib/fontisan/binary/base_record.rb
80
+ - lib/fontisan/binary/structures.rb
81
+ - lib/fontisan/cli.rb
82
+ - lib/fontisan/commands/base_command.rb
83
+ - lib/fontisan/commands/dump_table_command.rb
84
+ - lib/fontisan/commands/features_command.rb
85
+ - lib/fontisan/commands/glyphs_command.rb
86
+ - lib/fontisan/commands/info_command.rb
87
+ - lib/fontisan/commands/optical_size_command.rb
88
+ - lib/fontisan/commands/scripts_command.rb
89
+ - lib/fontisan/commands/tables_command.rb
90
+ - lib/fontisan/commands/unicode_command.rb
91
+ - lib/fontisan/commands/variable_command.rb
92
+ - lib/fontisan/config/features.yml
93
+ - lib/fontisan/config/scripts.yml
94
+ - lib/fontisan/constants.rb
95
+ - lib/fontisan/error.rb
96
+ - lib/fontisan/font_loader.rb
97
+ - lib/fontisan/formatters/text_formatter.rb
98
+ - lib/fontisan/models/all_scripts_features_info.rb
99
+ - lib/fontisan/models/features_info.rb
100
+ - lib/fontisan/models/font_info.rb
101
+ - lib/fontisan/models/glyph_info.rb
102
+ - lib/fontisan/models/optical_size_info.rb
103
+ - lib/fontisan/models/scripts_info.rb
104
+ - lib/fontisan/models/table_info.rb
105
+ - lib/fontisan/models/unicode_mappings.rb
106
+ - lib/fontisan/models/variable_font_info.rb
107
+ - lib/fontisan/open_type_collection.rb
108
+ - lib/fontisan/open_type_font.rb
109
+ - lib/fontisan/parsers/tag.rb
110
+ - lib/fontisan/tables/cmap.rb
111
+ - lib/fontisan/tables/fvar.rb
112
+ - lib/fontisan/tables/gpos.rb
113
+ - lib/fontisan/tables/gsub.rb
114
+ - lib/fontisan/tables/head.rb
115
+ - lib/fontisan/tables/layout_common.rb
116
+ - lib/fontisan/tables/name.rb
117
+ - lib/fontisan/tables/os2.rb
118
+ - lib/fontisan/tables/post.rb
119
+ - lib/fontisan/true_type_collection.rb
120
+ - lib/fontisan/true_type_font.rb
121
+ - lib/fontisan/utilities/checksum_calculator.rb
122
+ - lib/fontisan/version.rb
123
+ homepage: https://github.com/fontist/fontisan
124
+ licenses:
125
+ - BSD-2-Clause
126
+ metadata:
127
+ homepage_uri: https://github.com/fontist/fontisan
128
+ source_code_uri: https://github.com/fontist/fontisan
129
+ changelog_uri: https://github.com/fontist/fontisan/blob/main/CHANGELOG.md
130
+ rubygems_mfa_required: 'true'
131
+ post_install_message:
132
+ rdoc_options: []
133
+ require_paths:
134
+ - lib
135
+ required_ruby_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: 3.0.0
140
+ required_rubygems_version: !ruby/object:Gem::Requirement
141
+ requirements:
142
+ - - ">="
143
+ - !ruby/object:Gem::Version
144
+ version: '0'
145
+ requirements: []
146
+ rubygems_version: 3.5.22
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: Font analysis tools and utilities for OpenType fonts
150
+ test_files: []