fontisan 0.2.2 → 0.2.4
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 +4 -4
- data/.rubocop_todo.yml +94 -48
- data/README.adoc +293 -3
- data/Rakefile +20 -7
- data/lib/fontisan/base_collection.rb +296 -0
- data/lib/fontisan/commands/base_command.rb +2 -19
- data/lib/fontisan/commands/convert_command.rb +16 -13
- data/lib/fontisan/commands/info_command.rb +156 -50
- data/lib/fontisan/config/conversion_matrix.yml +58 -20
- data/lib/fontisan/converters/outline_converter.rb +6 -3
- data/lib/fontisan/converters/svg_generator.rb +45 -0
- data/lib/fontisan/converters/woff2_encoder.rb +106 -13
- data/lib/fontisan/font_loader.rb +109 -26
- data/lib/fontisan/formatters/text_formatter.rb +72 -19
- data/lib/fontisan/models/bitmap_glyph.rb +123 -0
- data/lib/fontisan/models/bitmap_strike.rb +94 -0
- data/lib/fontisan/models/collection_brief_info.rb +6 -0
- data/lib/fontisan/models/collection_info.rb +6 -1
- data/lib/fontisan/models/color_glyph.rb +57 -0
- data/lib/fontisan/models/color_layer.rb +53 -0
- data/lib/fontisan/models/color_palette.rb +60 -0
- data/lib/fontisan/models/font_info.rb +26 -0
- data/lib/fontisan/models/svg_glyph.rb +89 -0
- data/lib/fontisan/open_type_collection.rb +17 -220
- data/lib/fontisan/open_type_font.rb +6 -0
- data/lib/fontisan/optimizers/charstring_rewriter.rb +19 -8
- data/lib/fontisan/optimizers/pattern_analyzer.rb +4 -2
- data/lib/fontisan/optimizers/subroutine_builder.rb +6 -5
- data/lib/fontisan/optimizers/subroutine_optimizer.rb +5 -2
- data/lib/fontisan/pipeline/output_writer.rb +2 -2
- data/lib/fontisan/tables/cbdt.rb +169 -0
- data/lib/fontisan/tables/cblc.rb +290 -0
- data/lib/fontisan/tables/cff.rb +6 -12
- data/lib/fontisan/tables/colr.rb +291 -0
- data/lib/fontisan/tables/cpal.rb +281 -0
- data/lib/fontisan/tables/glyf/glyph_builder.rb +5 -1
- data/lib/fontisan/tables/sbix.rb +379 -0
- data/lib/fontisan/tables/svg.rb +301 -0
- data/lib/fontisan/true_type_collection.rb +29 -113
- data/lib/fontisan/true_type_font.rb +6 -0
- data/lib/fontisan/validation/woff2_header_validator.rb +278 -0
- data/lib/fontisan/validation/woff2_table_validator.rb +270 -0
- data/lib/fontisan/validation/woff2_validator.rb +248 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan/woff2/directory.rb +40 -11
- data/lib/fontisan/woff2/table_transformer.rb +506 -73
- data/lib/fontisan/woff2_font.rb +29 -9
- data/lib/fontisan/woff_font.rb +17 -4
- data/lib/fontisan.rb +12 -0
- metadata +18 -2
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "stringio"
|
|
4
|
+
require "zlib"
|
|
5
|
+
require_relative "../binary/base_record"
|
|
6
|
+
|
|
7
|
+
module Fontisan
|
|
8
|
+
module Tables
|
|
9
|
+
# SVG (Scalable Vector Graphics) table parser
|
|
10
|
+
#
|
|
11
|
+
# The SVG table contains embedded SVG documents for glyphs, typically used
|
|
12
|
+
# for color emoji or graphic elements. Each document can cover a range of
|
|
13
|
+
# glyph IDs and may be compressed with gzip.
|
|
14
|
+
#
|
|
15
|
+
# SVG Table Structure:
|
|
16
|
+
# ```
|
|
17
|
+
# SVG Table = Header (10 bytes)
|
|
18
|
+
# + Document Index
|
|
19
|
+
# + SVG Documents
|
|
20
|
+
# ```
|
|
21
|
+
#
|
|
22
|
+
# Header (10 bytes):
|
|
23
|
+
# - version (uint16): Table version (0)
|
|
24
|
+
# - svgDocumentListOffset (uint32): Offset to SVG Document Index
|
|
25
|
+
# - reserved (uint32): Reserved, set to 0
|
|
26
|
+
#
|
|
27
|
+
# Document Index:
|
|
28
|
+
# - numEntries (uint16): Number of SVG Document Index Entries
|
|
29
|
+
# - entries[numEntries]: Array of SVG Document Index Entries
|
|
30
|
+
#
|
|
31
|
+
# SVG Document Index Entry (12 bytes):
|
|
32
|
+
# - startGlyphID (uint16): First glyph ID
|
|
33
|
+
# - endGlyphID (uint16): Last glyph ID (inclusive)
|
|
34
|
+
# - svgDocOffset (uint32): Offset to SVG document
|
|
35
|
+
# - svgDocLength (uint32): Length of SVG document
|
|
36
|
+
#
|
|
37
|
+
# SVG documents may be compressed with gzip (identified by magic bytes 0x1f 0x8b).
|
|
38
|
+
#
|
|
39
|
+
# Reference: OpenType SVG specification
|
|
40
|
+
# https://docs.microsoft.com/en-us/typography/opentype/spec/svg
|
|
41
|
+
#
|
|
42
|
+
# @example Reading an SVG table
|
|
43
|
+
# data = font.table_data['SVG ']
|
|
44
|
+
# svg = Fontisan::Tables::Svg.read(data)
|
|
45
|
+
# svg_content = svg.svg_for_glyph(42)
|
|
46
|
+
# puts "Glyph 42 SVG: #{svg_content}"
|
|
47
|
+
class Svg < Binary::BaseRecord
|
|
48
|
+
# OpenType table tag for SVG (note: includes trailing space)
|
|
49
|
+
TAG = "SVG "
|
|
50
|
+
|
|
51
|
+
# SVG Document Index Entry structure
|
|
52
|
+
#
|
|
53
|
+
# Each entry associates a glyph range with an SVG document.
|
|
54
|
+
# Structure (12 bytes): start_glyph_id, end_glyph_id, svg_doc_offset, svg_doc_length
|
|
55
|
+
class SvgDocumentRecord < Binary::BaseRecord
|
|
56
|
+
endian :big
|
|
57
|
+
uint16 :start_glyph_id
|
|
58
|
+
uint16 :end_glyph_id
|
|
59
|
+
uint32 :svg_doc_offset
|
|
60
|
+
uint32 :svg_doc_length
|
|
61
|
+
|
|
62
|
+
# Check if this record includes a specific glyph ID
|
|
63
|
+
#
|
|
64
|
+
# @param glyph_id [Integer] Glyph ID to check
|
|
65
|
+
# @return [Boolean] True if glyph is in range
|
|
66
|
+
def includes_glyph?(glyph_id)
|
|
67
|
+
glyph_id >= start_glyph_id && glyph_id <= end_glyph_id
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Get the glyph range for this record
|
|
71
|
+
#
|
|
72
|
+
# @return [Range] Range of glyph IDs
|
|
73
|
+
def glyph_range
|
|
74
|
+
start_glyph_id..end_glyph_id
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# @return [Integer] SVG table version (0)
|
|
79
|
+
attr_reader :version
|
|
80
|
+
|
|
81
|
+
# @return [Integer] Offset to SVG Document Index
|
|
82
|
+
attr_reader :svg_document_list_offset
|
|
83
|
+
|
|
84
|
+
# @return [Integer] Number of SVG document entries
|
|
85
|
+
attr_reader :num_entries
|
|
86
|
+
|
|
87
|
+
# @return [Array<SvgDocumentRecord>] Parsed document records
|
|
88
|
+
attr_reader :document_records
|
|
89
|
+
|
|
90
|
+
# @return [String] Raw binary data for the entire SVG table
|
|
91
|
+
attr_reader :raw_data
|
|
92
|
+
|
|
93
|
+
# Override read to parse SVG structure
|
|
94
|
+
#
|
|
95
|
+
# @param io [IO, String] Binary data to read
|
|
96
|
+
# @return [Svg] Parsed SVG table
|
|
97
|
+
def self.read(io)
|
|
98
|
+
svg = new
|
|
99
|
+
return svg if io.nil?
|
|
100
|
+
|
|
101
|
+
data = io.is_a?(String) ? io : io.read
|
|
102
|
+
svg.parse!(data)
|
|
103
|
+
svg
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Parse the SVG table structure
|
|
107
|
+
#
|
|
108
|
+
# @param data [String] Binary data for the SVG table
|
|
109
|
+
# @raise [CorruptedTableError] If SVG structure is invalid
|
|
110
|
+
def parse!(data)
|
|
111
|
+
@raw_data = data
|
|
112
|
+
io = StringIO.new(data)
|
|
113
|
+
|
|
114
|
+
# Parse SVG header (10 bytes)
|
|
115
|
+
parse_header(io)
|
|
116
|
+
validate_header!
|
|
117
|
+
|
|
118
|
+
# Parse document index
|
|
119
|
+
parse_document_index(io)
|
|
120
|
+
rescue StandardError => e
|
|
121
|
+
raise CorruptedTableError, "Failed to parse SVG table: #{e.message}"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Get SVG document for a specific glyph ID
|
|
125
|
+
#
|
|
126
|
+
# Returns the SVG XML content for the specified glyph.
|
|
127
|
+
# Automatically decompresses gzipped content.
|
|
128
|
+
# Returns nil if glyph has no SVG data.
|
|
129
|
+
#
|
|
130
|
+
# @param glyph_id [Integer] Glyph ID to look up
|
|
131
|
+
# @return [String, nil] SVG XML content or nil
|
|
132
|
+
def svg_for_glyph(glyph_id)
|
|
133
|
+
record = find_document_record(glyph_id)
|
|
134
|
+
return nil unless record
|
|
135
|
+
|
|
136
|
+
extract_svg_document(record)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Check if glyph has SVG data
|
|
140
|
+
#
|
|
141
|
+
# @param glyph_id [Integer] Glyph ID to check
|
|
142
|
+
# @return [Boolean] True if glyph has SVG
|
|
143
|
+
def has_svg_for_glyph?(glyph_id)
|
|
144
|
+
!find_document_record(glyph_id).nil?
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Get all glyph IDs that have SVG data
|
|
148
|
+
#
|
|
149
|
+
# @return [Array<Integer>] Array of glyph IDs with SVG
|
|
150
|
+
def glyph_ids_with_svg
|
|
151
|
+
document_records.flat_map do |record|
|
|
152
|
+
record.glyph_range.to_a
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Get the number of SVG documents in this table
|
|
157
|
+
#
|
|
158
|
+
# @return [Integer] Number of SVG documents
|
|
159
|
+
def num_svg_documents
|
|
160
|
+
num_entries
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Validate the SVG table structure
|
|
164
|
+
#
|
|
165
|
+
# @return [Boolean] True if valid
|
|
166
|
+
def valid?
|
|
167
|
+
return false if version.nil?
|
|
168
|
+
return false if version != 0 # Only version 0 supported
|
|
169
|
+
return false if num_entries.nil? || num_entries.negative?
|
|
170
|
+
return false unless document_records
|
|
171
|
+
|
|
172
|
+
true
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private
|
|
176
|
+
|
|
177
|
+
# Parse SVG header (10 bytes)
|
|
178
|
+
#
|
|
179
|
+
# @param io [StringIO] Input stream
|
|
180
|
+
def parse_header(io)
|
|
181
|
+
@version = io.read(2).unpack1("n")
|
|
182
|
+
@svg_document_list_offset = io.read(4).unpack1("N")
|
|
183
|
+
@reserved = io.read(4).unpack1("N")
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Validate header values
|
|
187
|
+
#
|
|
188
|
+
# @raise [CorruptedTableError] If validation fails
|
|
189
|
+
def validate_header!
|
|
190
|
+
unless version.zero?
|
|
191
|
+
raise CorruptedTableError,
|
|
192
|
+
"Unsupported SVG version: #{version} (only version 0 supported)"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
if svg_document_list_offset > raw_data.length
|
|
196
|
+
raise CorruptedTableError,
|
|
197
|
+
"Invalid svgDocumentListOffset: #{svg_document_list_offset}"
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
# Parse document index
|
|
202
|
+
#
|
|
203
|
+
# @param io [StringIO] Input stream
|
|
204
|
+
def parse_document_index(io)
|
|
205
|
+
# Seek to document index
|
|
206
|
+
io.seek(svg_document_list_offset)
|
|
207
|
+
|
|
208
|
+
# Check if there's enough data to read num_entries
|
|
209
|
+
return if io.eof?
|
|
210
|
+
|
|
211
|
+
# Parse number of entries
|
|
212
|
+
num_entries_data = io.read(2)
|
|
213
|
+
return if num_entries_data.nil? || num_entries_data.length < 2
|
|
214
|
+
|
|
215
|
+
@num_entries = num_entries_data.unpack1("n")
|
|
216
|
+
@document_records = []
|
|
217
|
+
|
|
218
|
+
return if num_entries.zero?
|
|
219
|
+
|
|
220
|
+
# Parse each document record (12 bytes each)
|
|
221
|
+
num_entries.times do
|
|
222
|
+
record_data = io.read(12)
|
|
223
|
+
record = SvgDocumentRecord.read(record_data)
|
|
224
|
+
@document_records << record
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
# Find document record for a specific glyph ID
|
|
229
|
+
#
|
|
230
|
+
# Uses binary search since document records should be sorted by glyph ID.
|
|
231
|
+
#
|
|
232
|
+
# @param glyph_id [Integer] Glyph ID to find
|
|
233
|
+
# @return [SvgDocumentRecord, nil] Document record or nil if not found
|
|
234
|
+
def find_document_record(glyph_id)
|
|
235
|
+
# Binary search through document records
|
|
236
|
+
left = 0
|
|
237
|
+
right = document_records.length - 1
|
|
238
|
+
|
|
239
|
+
while left <= right
|
|
240
|
+
mid = (left + right) / 2
|
|
241
|
+
record = document_records[mid]
|
|
242
|
+
|
|
243
|
+
if record.includes_glyph?(glyph_id)
|
|
244
|
+
return record
|
|
245
|
+
elsif glyph_id < record.start_glyph_id
|
|
246
|
+
right = mid - 1
|
|
247
|
+
else
|
|
248
|
+
left = mid + 1
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
nil
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
# Extract SVG document from record
|
|
256
|
+
#
|
|
257
|
+
# Calculates absolute offset and extracts SVG data.
|
|
258
|
+
# Automatically decompresses gzipped content.
|
|
259
|
+
#
|
|
260
|
+
# @param record [SvgDocumentRecord] Document record
|
|
261
|
+
# @return [String] SVG XML content
|
|
262
|
+
def extract_svg_document(record)
|
|
263
|
+
# Calculate absolute offset
|
|
264
|
+
# Offset is relative to start of SVG Document List
|
|
265
|
+
# Document List = numEntries (2 bytes) + entries array + documents
|
|
266
|
+
documents_offset = svg_document_list_offset + 2 + (num_entries * 12)
|
|
267
|
+
absolute_offset = documents_offset + record.svg_doc_offset
|
|
268
|
+
|
|
269
|
+
# Extract SVG data
|
|
270
|
+
svg_data = raw_data[absolute_offset, record.svg_doc_length]
|
|
271
|
+
|
|
272
|
+
# Check if compressed (gzip magic bytes: 0x1f 0x8b)
|
|
273
|
+
if gzipped?(svg_data)
|
|
274
|
+
decompress_gzip(svg_data)
|
|
275
|
+
else
|
|
276
|
+
svg_data
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Check if data is gzipped
|
|
281
|
+
#
|
|
282
|
+
# @param data [String] Binary data
|
|
283
|
+
# @return [Boolean] True if gzipped
|
|
284
|
+
def gzipped?(data)
|
|
285
|
+
return false if data.nil? || data.length < 2
|
|
286
|
+
|
|
287
|
+
data[0..1].unpack("C*") == [0x1f, 0x8b]
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# Decompress gzipped data
|
|
291
|
+
#
|
|
292
|
+
# @param data [String] Gzipped binary data
|
|
293
|
+
# @return [String] Decompressed data
|
|
294
|
+
def decompress_gzip(data)
|
|
295
|
+
Zlib::GzipReader.new(StringIO.new(data)).read
|
|
296
|
+
rescue Zlib::Error => e
|
|
297
|
+
raise CorruptedTableError, "Failed to decompress SVG data: #{e.message}"
|
|
298
|
+
end
|
|
299
|
+
end
|
|
300
|
+
end
|
|
301
|
+
end
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
require_relative "constants"
|
|
3
|
+
require_relative "base_collection"
|
|
5
4
|
|
|
6
5
|
module Fontisan
|
|
7
|
-
# TrueType Collection domain object
|
|
6
|
+
# TrueType Collection domain object
|
|
8
7
|
#
|
|
9
|
-
# Represents a complete TrueType Collection file
|
|
10
|
-
#
|
|
11
|
-
# documentation, and BinData handles all low-level reading/writing.
|
|
8
|
+
# Represents a complete TrueType Collection file. Inherits all shared
|
|
9
|
+
# functionality from BaseCollection and implements TTC-specific behavior.
|
|
12
10
|
#
|
|
13
11
|
# @example Reading and extracting fonts
|
|
14
12
|
# File.open("Helvetica.ttc", "rb") do |io|
|
|
@@ -16,51 +14,25 @@ module Fontisan
|
|
|
16
14
|
# puts ttc.num_fonts # => 6
|
|
17
15
|
# fonts = ttc.extract_fonts(io) # => [TrueTypeFont, TrueTypeFont, ...]
|
|
18
16
|
# end
|
|
19
|
-
class TrueTypeCollection <
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
string :tag, length: 4, assert: "ttcf"
|
|
23
|
-
uint16 :major_version
|
|
24
|
-
uint16 :minor_version
|
|
25
|
-
uint32 :num_fonts
|
|
26
|
-
array :font_offsets, type: :uint32, initial_length: :num_fonts
|
|
27
|
-
|
|
28
|
-
# Read TrueType Collection from a file
|
|
17
|
+
class TrueTypeCollection < BaseCollection
|
|
18
|
+
# Get the font class for TrueType collections
|
|
29
19
|
#
|
|
30
|
-
# @
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
# @raise [RuntimeError] if file format is invalid
|
|
35
|
-
def self.from_file(path)
|
|
36
|
-
if path.nil? || path.to_s.empty?
|
|
37
|
-
raise ArgumentError,
|
|
38
|
-
"path cannot be nil or empty"
|
|
39
|
-
end
|
|
40
|
-
raise Errno::ENOENT, "File not found: #{path}" unless File.exist?(path)
|
|
41
|
-
|
|
42
|
-
File.open(path, "rb") { |io| read(io) }
|
|
43
|
-
rescue BinData::ValidityError => e
|
|
44
|
-
raise "Invalid TTC file: #{e.message}"
|
|
45
|
-
rescue EOFError => e
|
|
46
|
-
raise "Invalid TTC file: unexpected end of file - #{e.message}"
|
|
20
|
+
# @return [Class] TrueTypeFont class
|
|
21
|
+
def self.font_class
|
|
22
|
+
require_relative "true_type_font"
|
|
23
|
+
TrueTypeFont
|
|
47
24
|
end
|
|
48
25
|
|
|
49
|
-
#
|
|
26
|
+
# Get the collection format identifier
|
|
50
27
|
#
|
|
51
|
-
#
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
# @return [Array<TrueTypeFont>] Array of font objects
|
|
55
|
-
def extract_fonts(io)
|
|
56
|
-
require_relative "true_type_font"
|
|
57
|
-
|
|
58
|
-
font_offsets.map do |offset|
|
|
59
|
-
TrueTypeFont.from_ttc(io, offset)
|
|
60
|
-
end
|
|
28
|
+
# @return [String] "TTC" for TrueType Collection
|
|
29
|
+
def self.collection_format
|
|
30
|
+
"TTC"
|
|
61
31
|
end
|
|
62
32
|
|
|
63
|
-
# Get a single font from the collection
|
|
33
|
+
# Get a single font from the collection
|
|
34
|
+
#
|
|
35
|
+
# Overrides BaseCollection to use TrueType-specific from_ttc method.
|
|
64
36
|
#
|
|
65
37
|
# @param index [Integer] Index of the font (0-based)
|
|
66
38
|
# @param io [IO] Open file handle
|
|
@@ -73,43 +45,27 @@ module Fontisan
|
|
|
73
45
|
TrueTypeFont.from_ttc(io, font_offsets[index], mode: mode)
|
|
74
46
|
end
|
|
75
47
|
|
|
76
|
-
#
|
|
48
|
+
# Extract fonts as TrueTypeFont objects
|
|
77
49
|
#
|
|
78
|
-
#
|
|
79
|
-
|
|
80
|
-
num_fonts
|
|
81
|
-
end
|
|
82
|
-
|
|
83
|
-
# Validate format correctness
|
|
50
|
+
# Reads each font from the TTC file and returns them as TrueTypeFont objects.
|
|
51
|
+
# This method uses the TTC-specific from_ttc method.
|
|
84
52
|
#
|
|
85
|
-
# @
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
false
|
|
90
|
-
end
|
|
53
|
+
# @param io [IO] Open file handle to read fonts from
|
|
54
|
+
# @return [Array<TrueTypeFont>] Array of font objects
|
|
55
|
+
def extract_fonts(io)
|
|
56
|
+
require_relative "true_type_font"
|
|
91
57
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
def version
|
|
96
|
-
(major_version << 16) | minor_version
|
|
58
|
+
font_offsets.map do |offset|
|
|
59
|
+
TrueTypeFont.from_ttc(io, offset)
|
|
60
|
+
end
|
|
97
61
|
end
|
|
98
62
|
|
|
99
63
|
# List all fonts in the collection with basic metadata
|
|
100
64
|
#
|
|
101
|
-
#
|
|
102
|
-
# This is the API method used by the `ls` command for collections.
|
|
65
|
+
# Overrides BaseCollection to use TrueType-specific from_ttc method.
|
|
103
66
|
#
|
|
104
67
|
# @param io [IO] Open file handle to read fonts from
|
|
105
68
|
# @return [CollectionListInfo] List of fonts with metadata
|
|
106
|
-
#
|
|
107
|
-
# @example List fonts in collection
|
|
108
|
-
# File.open("fonts.ttc", "rb") do |io|
|
|
109
|
-
# ttc = TrueTypeCollection.read(io)
|
|
110
|
-
# list = ttc.list_fonts(io)
|
|
111
|
-
# list.fonts.each { |f| puts "#{f.index}: #{f.family_name}" }
|
|
112
|
-
# end
|
|
113
69
|
def list_fonts(io)
|
|
114
70
|
require_relative "models/collection_list_info"
|
|
115
71
|
require_relative "models/collection_font_summary"
|
|
@@ -159,51 +115,11 @@ module Fontisan
|
|
|
159
115
|
)
|
|
160
116
|
end
|
|
161
117
|
|
|
162
|
-
# Get comprehensive collection metadata
|
|
163
|
-
#
|
|
164
|
-
# Returns a CollectionInfo model with header information, offsets,
|
|
165
|
-
# and table sharing statistics.
|
|
166
|
-
# This is the API method used by the `info` command for collections.
|
|
167
|
-
#
|
|
168
|
-
# @param io [IO] Open file handle to read fonts from
|
|
169
|
-
# @param path [String] Collection file path (for file size)
|
|
170
|
-
# @return [CollectionInfo] Collection metadata
|
|
171
|
-
#
|
|
172
|
-
# @example Get collection info
|
|
173
|
-
# File.open("fonts.ttc", "rb") do |io|
|
|
174
|
-
# ttc = TrueTypeCollection.read(io)
|
|
175
|
-
# info = ttc.collection_info(io, "fonts.ttc")
|
|
176
|
-
# puts "Version: #{info.version_string}"
|
|
177
|
-
# end
|
|
178
|
-
def collection_info(io, path)
|
|
179
|
-
require_relative "models/collection_info"
|
|
180
|
-
require_relative "models/table_sharing_info"
|
|
181
|
-
|
|
182
|
-
# Calculate table sharing statistics
|
|
183
|
-
table_sharing = calculate_table_sharing(io)
|
|
184
|
-
|
|
185
|
-
# Get file size
|
|
186
|
-
file_size = path ? File.size(path) : 0
|
|
187
|
-
|
|
188
|
-
Models::CollectionInfo.new(
|
|
189
|
-
collection_path: path,
|
|
190
|
-
collection_format: "TTC",
|
|
191
|
-
ttc_tag: tag,
|
|
192
|
-
major_version: major_version,
|
|
193
|
-
minor_version: minor_version,
|
|
194
|
-
num_fonts: num_fonts,
|
|
195
|
-
font_offsets: font_offsets.to_a,
|
|
196
|
-
file_size_bytes: file_size,
|
|
197
|
-
table_sharing: table_sharing,
|
|
198
|
-
)
|
|
199
|
-
end
|
|
200
|
-
|
|
201
118
|
private
|
|
202
119
|
|
|
203
120
|
# Calculate table sharing statistics
|
|
204
121
|
#
|
|
205
|
-
#
|
|
206
|
-
# space savings from deduplication.
|
|
122
|
+
# Overrides BaseCollection to use TrueType-specific from_ttc method.
|
|
207
123
|
#
|
|
208
124
|
# @param io [IO] Open file handle
|
|
209
125
|
# @return [TableSharingInfo] Sharing statistics
|
|
@@ -532,6 +532,12 @@ module Fontisan
|
|
|
532
532
|
Constants::GPOS_TAG => Tables::Gpos,
|
|
533
533
|
Constants::GLYF_TAG => Tables::Glyf,
|
|
534
534
|
Constants::LOCA_TAG => Tables::Loca,
|
|
535
|
+
"SVG " => Tables::Svg,
|
|
536
|
+
"COLR" => Tables::Colr,
|
|
537
|
+
"CPAL" => Tables::Cpal,
|
|
538
|
+
"CBDT" => Tables::Cbdt,
|
|
539
|
+
"CBLC" => Tables::Cblc,
|
|
540
|
+
"sbix" => Tables::Sbix,
|
|
535
541
|
}[tag]
|
|
536
542
|
end
|
|
537
543
|
|