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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/.rubocop_todo.yml +217 -0
- data/Gemfile +15 -0
- data/LICENSE +24 -0
- data/README.adoc +984 -0
- data/Rakefile +95 -0
- data/exe/fontisan +7 -0
- data/fontisan.gemspec +44 -0
- data/lib/fontisan/binary/base_record.rb +57 -0
- data/lib/fontisan/binary/structures.rb +84 -0
- data/lib/fontisan/cli.rb +192 -0
- data/lib/fontisan/commands/base_command.rb +82 -0
- data/lib/fontisan/commands/dump_table_command.rb +71 -0
- data/lib/fontisan/commands/features_command.rb +94 -0
- data/lib/fontisan/commands/glyphs_command.rb +50 -0
- data/lib/fontisan/commands/info_command.rb +120 -0
- data/lib/fontisan/commands/optical_size_command.rb +41 -0
- data/lib/fontisan/commands/scripts_command.rb +59 -0
- data/lib/fontisan/commands/tables_command.rb +52 -0
- data/lib/fontisan/commands/unicode_command.rb +76 -0
- data/lib/fontisan/commands/variable_command.rb +61 -0
- data/lib/fontisan/config/features.yml +143 -0
- data/lib/fontisan/config/scripts.yml +42 -0
- data/lib/fontisan/constants.rb +78 -0
- data/lib/fontisan/error.rb +15 -0
- data/lib/fontisan/font_loader.rb +109 -0
- data/lib/fontisan/formatters/text_formatter.rb +314 -0
- data/lib/fontisan/models/all_scripts_features_info.rb +21 -0
- data/lib/fontisan/models/features_info.rb +42 -0
- data/lib/fontisan/models/font_info.rb +99 -0
- data/lib/fontisan/models/glyph_info.rb +26 -0
- data/lib/fontisan/models/optical_size_info.rb +33 -0
- data/lib/fontisan/models/scripts_info.rb +39 -0
- data/lib/fontisan/models/table_info.rb +55 -0
- data/lib/fontisan/models/unicode_mappings.rb +42 -0
- data/lib/fontisan/models/variable_font_info.rb +82 -0
- data/lib/fontisan/open_type_collection.rb +97 -0
- data/lib/fontisan/open_type_font.rb +292 -0
- data/lib/fontisan/parsers/tag.rb +77 -0
- data/lib/fontisan/tables/cmap.rb +284 -0
- data/lib/fontisan/tables/fvar.rb +157 -0
- data/lib/fontisan/tables/gpos.rb +111 -0
- data/lib/fontisan/tables/gsub.rb +111 -0
- data/lib/fontisan/tables/head.rb +114 -0
- data/lib/fontisan/tables/layout_common.rb +73 -0
- data/lib/fontisan/tables/name.rb +188 -0
- data/lib/fontisan/tables/os2.rb +175 -0
- data/lib/fontisan/tables/post.rb +148 -0
- data/lib/fontisan/true_type_collection.rb +98 -0
- data/lib/fontisan/true_type_font.rb +313 -0
- data/lib/fontisan/utilities/checksum_calculator.rb +89 -0
- data/lib/fontisan/version.rb +5 -0
- data/lib/fontisan.rb +80 -0
- 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
|
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: []
|