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.
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fontisan
4
- VERSION = "0.2.5"
4
+ VERSION = "0.2.6"
5
5
  end
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.5
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-03 00:00:00.000000000 Z
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