fontisan 0.2.5 → 0.2.6
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 +36 -20
- data/README.adoc +184 -857
- data/lib/fontisan/cli.rb +27 -7
- data/lib/fontisan/collection/dfont_builder.rb +315 -0
- data/lib/fontisan/commands/convert_command.rb +118 -7
- data/lib/fontisan/commands/pack_command.rb +129 -22
- data/lib/fontisan/config/conversion_matrix.yml +175 -1
- data/lib/fontisan/constants.rb +8 -0
- data/lib/fontisan/converters/collection_converter.rb +438 -0
- data/lib/fontisan/dfont_collection.rb +185 -0
- data/lib/fontisan/font_loader.rb +91 -6
- data/lib/fontisan/parsers/dfont_parser.rb +192 -0
- data/lib/fontisan/true_type_font.rb +8 -46
- data/lib/fontisan/validation/collection_validator.rb +265 -0
- data/lib/fontisan/version.rb +1 -1
- metadata +7 -2
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../error"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Validation
|
|
7
|
+
# CollectionValidator validates font compatibility for collection formats
|
|
8
|
+
#
|
|
9
|
+
# Main responsibility: Enforce format-specific compatibility rules for
|
|
10
|
+
# TTC, OTC, and dfont collections according to OpenType spec and Apple standards.
|
|
11
|
+
#
|
|
12
|
+
# Rules:
|
|
13
|
+
# - TTC: TrueType fonts ONLY (per OpenType spec)
|
|
14
|
+
# - OTC: CFF fonts required, mixed TTF+OTF allowed (Fontisan extension)
|
|
15
|
+
# - dfont: Any SFNT fonts (TTF, OTF, or mixed)
|
|
16
|
+
# - All: Web fonts (WOFF/WOFF2) are NEVER allowed in collections
|
|
17
|
+
#
|
|
18
|
+
# @example Validate TTC compatibility
|
|
19
|
+
# validator = CollectionValidator.new
|
|
20
|
+
# validator.validate!([font1, font2], :ttc)
|
|
21
|
+
#
|
|
22
|
+
# @example Check compatibility without raising
|
|
23
|
+
# validator = CollectionValidator.new
|
|
24
|
+
# result = validator.compatible?([font1, font2], :otc)
|
|
25
|
+
class CollectionValidator
|
|
26
|
+
# Validate fonts are compatible with collection format
|
|
27
|
+
#
|
|
28
|
+
# @param fonts [Array<TrueTypeFont, OpenTypeFont>] Fonts to validate
|
|
29
|
+
# @param format [Symbol] Collection format (:ttc, :otc, or :dfont)
|
|
30
|
+
# @return [Boolean] true if valid
|
|
31
|
+
# @raise [Error] if validation fails
|
|
32
|
+
def validate!(fonts, format)
|
|
33
|
+
validate_not_empty!(fonts)
|
|
34
|
+
validate_format!(format)
|
|
35
|
+
|
|
36
|
+
case format
|
|
37
|
+
when :ttc
|
|
38
|
+
validate_ttc!(fonts)
|
|
39
|
+
when :otc
|
|
40
|
+
validate_otc!(fonts)
|
|
41
|
+
when :dfont
|
|
42
|
+
validate_dfont!(fonts)
|
|
43
|
+
else
|
|
44
|
+
raise Error, "Unknown collection format: #{format}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
true
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Check if fonts are compatible with format (without raising)
|
|
51
|
+
#
|
|
52
|
+
# @param fonts [Array] Fonts to check
|
|
53
|
+
# @param format [Symbol] Collection format
|
|
54
|
+
# @return [Boolean] true if compatible
|
|
55
|
+
def compatible?(fonts, format)
|
|
56
|
+
validate!(fonts, format)
|
|
57
|
+
true
|
|
58
|
+
rescue Error
|
|
59
|
+
false
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Get compatibility issues for fonts and format
|
|
63
|
+
#
|
|
64
|
+
# @param fonts [Array] Fonts to check
|
|
65
|
+
# @param format [Symbol] Collection format
|
|
66
|
+
# @return [Array<String>] Array of issue descriptions (empty if compatible)
|
|
67
|
+
def compatibility_issues(fonts, format)
|
|
68
|
+
issues = []
|
|
69
|
+
|
|
70
|
+
return ["Font array cannot be empty"] if fonts.nil? || fonts.empty?
|
|
71
|
+
return ["Invalid format: #{format}"] unless %i[ttc otc dfont].include?(format)
|
|
72
|
+
|
|
73
|
+
case format
|
|
74
|
+
when :ttc
|
|
75
|
+
issues.concat(ttc_issues(fonts))
|
|
76
|
+
when :otc
|
|
77
|
+
issues.concat(otc_issues(fonts))
|
|
78
|
+
when :dfont
|
|
79
|
+
issues.concat(dfont_issues(fonts))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
issues
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
# Validate fonts array is not empty
|
|
88
|
+
#
|
|
89
|
+
# @param fonts [Array] Fonts
|
|
90
|
+
# @raise [ArgumentError] if empty or nil
|
|
91
|
+
def validate_not_empty!(fonts)
|
|
92
|
+
if fonts.nil? || fonts.empty?
|
|
93
|
+
raise ArgumentError, "Font array cannot be empty"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Validate format is supported
|
|
98
|
+
#
|
|
99
|
+
# @param format [Symbol] Format
|
|
100
|
+
# @raise [ArgumentError] if invalid
|
|
101
|
+
def validate_format!(format)
|
|
102
|
+
unless %i[ttc otc dfont].include?(format)
|
|
103
|
+
raise ArgumentError, "Invalid format: #{format}. Must be :ttc, :otc, or :dfont"
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Validate TTC compatibility
|
|
108
|
+
#
|
|
109
|
+
# Per OpenType spec: TTC = TrueType outlines ONLY
|
|
110
|
+
# "CFF rasterizer does not currently support TTC files"
|
|
111
|
+
#
|
|
112
|
+
# @param fonts [Array] Fonts
|
|
113
|
+
# @raise [Error] if incompatible
|
|
114
|
+
def validate_ttc!(fonts)
|
|
115
|
+
fonts.each_with_index do |font, index|
|
|
116
|
+
# Check for web fonts
|
|
117
|
+
if web_font?(font)
|
|
118
|
+
raise Error,
|
|
119
|
+
"Font #{index} is a web font (WOFF/WOFF2). " \
|
|
120
|
+
"Web fonts cannot be packed into collections."
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Check for TrueType outline format
|
|
124
|
+
unless truetype_font?(font)
|
|
125
|
+
raise Error,
|
|
126
|
+
"Font #{index} is not TrueType. " \
|
|
127
|
+
"TTC requires TrueType fonts only (per OpenType spec)."
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Validate OTC compatibility
|
|
133
|
+
#
|
|
134
|
+
# Per OpenType 1.8: OTC for CFF collections
|
|
135
|
+
# Fontisan extension: Also allows mixed TTF+OTF for flexibility
|
|
136
|
+
#
|
|
137
|
+
# @param fonts [Array] Fonts
|
|
138
|
+
# @raise [Error] if incompatible
|
|
139
|
+
def validate_otc!(fonts)
|
|
140
|
+
has_cff = false
|
|
141
|
+
|
|
142
|
+
fonts.each_with_index do |font, index|
|
|
143
|
+
# Check for web fonts
|
|
144
|
+
if web_font?(font)
|
|
145
|
+
raise Error,
|
|
146
|
+
"Font #{index} is a web font (WOFF/WOFF2). " \
|
|
147
|
+
"Web fonts cannot be packed into collections."
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Track if any font has CFF
|
|
151
|
+
has_cff = true if cff_font?(font)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# OTC should have at least one CFF font
|
|
155
|
+
unless has_cff
|
|
156
|
+
raise Error,
|
|
157
|
+
"OTC requires at least one CFF/OpenType font. " \
|
|
158
|
+
"All fonts are TrueType - use TTC instead."
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Validate dfont compatibility
|
|
163
|
+
#
|
|
164
|
+
# Apple dfont suitcase: Any SFNT fonts OK (TTF, OTF, or mixed)
|
|
165
|
+
# dfont stores complete Mac resources (FOND, NFNT, sfnt)
|
|
166
|
+
#
|
|
167
|
+
# @param fonts [Array] Fonts
|
|
168
|
+
# @raise [Error] if incompatible
|
|
169
|
+
def validate_dfont!(fonts)
|
|
170
|
+
fonts.each_with_index do |font, index|
|
|
171
|
+
# Only check for web fonts - dfont accepts any SFNT
|
|
172
|
+
if web_font?(font)
|
|
173
|
+
raise Error,
|
|
174
|
+
"Font #{index} is a web font (WOFF/WOFF2). " \
|
|
175
|
+
"Web fonts cannot be packed into dfont."
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# Get TTC compatibility issues
|
|
181
|
+
#
|
|
182
|
+
# @param fonts [Array] Fonts
|
|
183
|
+
# @return [Array<String>] Issues
|
|
184
|
+
def ttc_issues(fonts)
|
|
185
|
+
issues = []
|
|
186
|
+
|
|
187
|
+
fonts.each_with_index do |font, index|
|
|
188
|
+
if web_font?(font)
|
|
189
|
+
issues << "Font #{index} is WOFF/WOFF2 (not allowed in collections)"
|
|
190
|
+
elsif !truetype_font?(font)
|
|
191
|
+
issues << "Font #{index} is not TrueType (TTC requires TrueType only)"
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
issues
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Get OTC compatibility issues
|
|
199
|
+
#
|
|
200
|
+
# @param fonts [Array] Fonts
|
|
201
|
+
# @return [Array<String>] Issues
|
|
202
|
+
def otc_issues(fonts)
|
|
203
|
+
issues = []
|
|
204
|
+
has_cff = false
|
|
205
|
+
|
|
206
|
+
fonts.each_with_index do |font, index|
|
|
207
|
+
if web_font?(font)
|
|
208
|
+
issues << "Font #{index} is WOFF/WOFF2 (not allowed in collections)"
|
|
209
|
+
end
|
|
210
|
+
has_cff = true if cff_font?(font)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
unless has_cff
|
|
214
|
+
issues << "OTC requires at least one CFF font (all fonts are TrueType)"
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
issues
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Get dfont compatibility issues
|
|
221
|
+
#
|
|
222
|
+
# @param fonts [Array] Fonts
|
|
223
|
+
# @return [Array<String>] Issues
|
|
224
|
+
def dfont_issues(fonts)
|
|
225
|
+
issues = []
|
|
226
|
+
|
|
227
|
+
fonts.each_with_index do |font, index|
|
|
228
|
+
if web_font?(font)
|
|
229
|
+
issues << "Font #{index} is WOFF/WOFF2 (not allowed in dfont)"
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
issues
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Check if font is a web font
|
|
237
|
+
#
|
|
238
|
+
# @param font [Object] Font object
|
|
239
|
+
# @return [Boolean] true if WOFF or WOFF2
|
|
240
|
+
def web_font?(font)
|
|
241
|
+
font.class.name.include?("Woff")
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# Check if font is TrueType
|
|
245
|
+
#
|
|
246
|
+
# @param font [Object] Font object
|
|
247
|
+
# @return [Boolean] true if TrueType
|
|
248
|
+
def truetype_font?(font)
|
|
249
|
+
return false unless font.respond_to?(:has_table?)
|
|
250
|
+
|
|
251
|
+
font.has_table?("glyf")
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
# Check if font is CFF/OpenType
|
|
255
|
+
#
|
|
256
|
+
# @param font [Object] Font object
|
|
257
|
+
# @return [Boolean] true if CFF
|
|
258
|
+
def cff_font?(font)
|
|
259
|
+
return false unless font.respond_to?(:has_table?)
|
|
260
|
+
|
|
261
|
+
font.has_table?("CFF ") || font.has_table?("CFF2")
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
end
|
|
265
|
+
end
|
data/lib/fontisan/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fontisan
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-01-
|
|
11
|
+
date: 2026-01-05 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -127,6 +127,7 @@ files:
|
|
|
127
127
|
- lib/fontisan/binary/structures.rb
|
|
128
128
|
- lib/fontisan/cli.rb
|
|
129
129
|
- lib/fontisan/collection/builder.rb
|
|
130
|
+
- lib/fontisan/collection/dfont_builder.rb
|
|
130
131
|
- lib/fontisan/collection/offset_calculator.rb
|
|
131
132
|
- lib/fontisan/collection/table_analyzer.rb
|
|
132
133
|
- lib/fontisan/collection/table_deduplicator.rb
|
|
@@ -159,6 +160,7 @@ files:
|
|
|
159
160
|
- lib/fontisan/config/variable_settings.yml
|
|
160
161
|
- lib/fontisan/config/woff2_settings.yml
|
|
161
162
|
- lib/fontisan/constants.rb
|
|
163
|
+
- lib/fontisan/converters/collection_converter.rb
|
|
162
164
|
- lib/fontisan/converters/conversion_strategy.rb
|
|
163
165
|
- lib/fontisan/converters/format_converter.rb
|
|
164
166
|
- lib/fontisan/converters/outline_converter.rb
|
|
@@ -166,6 +168,7 @@ files:
|
|
|
166
168
|
- lib/fontisan/converters/table_copier.rb
|
|
167
169
|
- lib/fontisan/converters/woff2_encoder.rb
|
|
168
170
|
- lib/fontisan/converters/woff_writer.rb
|
|
171
|
+
- lib/fontisan/dfont_collection.rb
|
|
169
172
|
- lib/fontisan/error.rb
|
|
170
173
|
- lib/fontisan/export/exporter.rb
|
|
171
174
|
- lib/fontisan/export/table_serializer.rb
|
|
@@ -237,6 +240,7 @@ files:
|
|
|
237
240
|
- lib/fontisan/optimizers/subroutine_generator.rb
|
|
238
241
|
- lib/fontisan/optimizers/subroutine_optimizer.rb
|
|
239
242
|
- lib/fontisan/outline_extractor.rb
|
|
243
|
+
- lib/fontisan/parsers/dfont_parser.rb
|
|
240
244
|
- lib/fontisan/parsers/tag.rb
|
|
241
245
|
- lib/fontisan/pipeline/format_detector.rb
|
|
242
246
|
- lib/fontisan/pipeline/output_writer.rb
|
|
@@ -321,6 +325,7 @@ files:
|
|
|
321
325
|
- lib/fontisan/utilities/brotli_wrapper.rb
|
|
322
326
|
- lib/fontisan/utilities/checksum_calculator.rb
|
|
323
327
|
- lib/fontisan/utils/thread_pool.rb
|
|
328
|
+
- lib/fontisan/validation/collection_validator.rb
|
|
324
329
|
- lib/fontisan/validators/basic_validator.rb
|
|
325
330
|
- lib/fontisan/validators/font_book_validator.rb
|
|
326
331
|
- lib/fontisan/validators/opentype_validator.rb
|