fontisan 0.2.4 → 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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +168 -32
  3. data/README.adoc +673 -1091
  4. data/lib/fontisan/cli.rb +94 -13
  5. data/lib/fontisan/collection/dfont_builder.rb +315 -0
  6. data/lib/fontisan/commands/convert_command.rb +118 -7
  7. data/lib/fontisan/commands/pack_command.rb +129 -22
  8. data/lib/fontisan/commands/validate_command.rb +107 -151
  9. data/lib/fontisan/config/conversion_matrix.yml +175 -1
  10. data/lib/fontisan/constants.rb +8 -0
  11. data/lib/fontisan/converters/collection_converter.rb +438 -0
  12. data/lib/fontisan/converters/woff2_encoder.rb +7 -29
  13. data/lib/fontisan/dfont_collection.rb +185 -0
  14. data/lib/fontisan/font_loader.rb +91 -6
  15. data/lib/fontisan/models/validation_report.rb +227 -0
  16. data/lib/fontisan/parsers/dfont_parser.rb +192 -0
  17. data/lib/fontisan/pipeline/transformation_pipeline.rb +4 -8
  18. data/lib/fontisan/tables/cmap.rb +82 -2
  19. data/lib/fontisan/tables/glyf.rb +118 -0
  20. data/lib/fontisan/tables/head.rb +60 -0
  21. data/lib/fontisan/tables/hhea.rb +74 -0
  22. data/lib/fontisan/tables/maxp.rb +60 -0
  23. data/lib/fontisan/tables/name.rb +76 -0
  24. data/lib/fontisan/tables/os2.rb +113 -0
  25. data/lib/fontisan/tables/post.rb +57 -0
  26. data/lib/fontisan/true_type_font.rb +8 -46
  27. data/lib/fontisan/validation/collection_validator.rb +265 -0
  28. data/lib/fontisan/validators/basic_validator.rb +85 -0
  29. data/lib/fontisan/validators/font_book_validator.rb +130 -0
  30. data/lib/fontisan/validators/opentype_validator.rb +112 -0
  31. data/lib/fontisan/validators/profile_loader.rb +139 -0
  32. data/lib/fontisan/validators/validator.rb +484 -0
  33. data/lib/fontisan/validators/web_font_validator.rb +102 -0
  34. data/lib/fontisan/version.rb +1 -1
  35. data/lib/fontisan.rb +78 -6
  36. metadata +13 -12
  37. data/lib/fontisan/config/validation_rules.yml +0 -149
  38. data/lib/fontisan/validation/checksum_validator.rb +0 -170
  39. data/lib/fontisan/validation/consistency_validator.rb +0 -197
  40. data/lib/fontisan/validation/structure_validator.rb +0 -198
  41. data/lib/fontisan/validation/table_validator.rb +0 -158
  42. data/lib/fontisan/validation/validator.rb +0 -152
  43. data/lib/fontisan/validation/variable_font_validator.rb +0 -218
  44. data/lib/fontisan/validation/woff2_header_validator.rb +0 -278
  45. data/lib/fontisan/validation/woff2_table_validator.rb +0 -270
  46. data/lib/fontisan/validation/woff2_validator.rb +0 -248
@@ -0,0 +1,484 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../models/validation_report"
4
+
5
+ module Fontisan
6
+ module Validators
7
+ # Base class for all validators using block-based DSL
8
+ #
9
+ # This class provides a declarative DSL for defining validation checks
10
+ # and an execution engine that runs those checks against font files.
11
+ # Subclasses define their validation logic by implementing define_checks.
12
+ #
13
+ # @example Creating a custom validator
14
+ # class MyValidator < Validator
15
+ # private
16
+ #
17
+ # def define_checks
18
+ # check_table :name_table, 'name' do
19
+ # check_field :family_name, :family_name do |table, value|
20
+ # !value.nil? && !value.empty?
21
+ # end
22
+ # end
23
+ # end
24
+ # end
25
+ #
26
+ # @example Running validation
27
+ # validator = MyValidator.new
28
+ # report = validator.validate(font)
29
+ # puts report.valid?
30
+ class Validator
31
+ # Initialize validator and define checks
32
+ def initialize
33
+ @checks = []
34
+ @current_table_context = nil
35
+ define_checks
36
+ end
37
+
38
+ # Validate a font and return a ValidationReport
39
+ #
40
+ # @param font [TrueTypeFont, OpenTypeFont] Font object to validate
41
+ # @return [ValidationReport] Complete validation report
42
+ def validate(font)
43
+ start_time = Time.now
44
+ all_results = []
45
+
46
+ @checks.each do |check_def|
47
+ result = execute_check(font, check_def)
48
+ all_results << result
49
+ end
50
+
51
+ elapsed = Time.now - start_time
52
+ build_report(font, all_results, elapsed)
53
+ end
54
+
55
+ protected
56
+
57
+ # DSL method: Define a table-level check
58
+ #
59
+ # @param check_id [Symbol] Unique identifier for this check
60
+ # @param table_tag [String] OpenType table tag (e.g., 'name', 'head')
61
+ # @param severity [Symbol] Severity level (:info, :warning, :error, :fatal)
62
+ # @param block [Proc] Check logic that receives table as parameter
63
+ def check_table(check_id, table_tag, severity: :error, &block)
64
+ @checks << {
65
+ type: :table,
66
+ id: check_id,
67
+ table_tag: table_tag,
68
+ severity: severity,
69
+ block: block,
70
+ }
71
+ end
72
+
73
+ # DSL method: Define a field-level check
74
+ #
75
+ # Must be called within a check_table block to establish table context
76
+ #
77
+ # @param check_id [Symbol] Unique identifier for this check
78
+ # @param field_key [Symbol] Field name to check
79
+ # @param severity [Symbol] Severity level (:info, :warning, :error, :fatal)
80
+ # @param block [Proc] Check logic that receives (table, value) as parameters
81
+ def check_field(check_id, field_key, severity: :error, &block)
82
+ unless @current_table_context
83
+ raise ArgumentError, "check_field must be called within check_table block"
84
+ end
85
+
86
+ @checks << {
87
+ type: :field,
88
+ id: check_id,
89
+ table_tag: @current_table_context,
90
+ field: field_key,
91
+ severity: severity,
92
+ block: block,
93
+ }
94
+ end
95
+
96
+ # DSL method: Define a structural validation check
97
+ #
98
+ # Used for checks that validate font structure and relationships
99
+ #
100
+ # @param check_id [Symbol] Unique identifier for this check
101
+ # @param severity [Symbol] Severity level (:info, :warning, :error, :fatal)
102
+ # @param block [Proc] Check logic that receives font as parameter
103
+ def check_structure(check_id, severity: :error, &block)
104
+ @checks << {
105
+ type: :structure,
106
+ id: check_id,
107
+ severity: severity,
108
+ block: block,
109
+ }
110
+ end
111
+
112
+ # DSL method: Define a usability check
113
+ #
114
+ # Used for checks that validate font usability and best practices
115
+ #
116
+ # @param check_id [Symbol] Unique identifier for this check
117
+ # @param severity [Symbol] Severity level (:info, :warning, :error, :fatal)
118
+ # @param block [Proc] Check logic that receives font as parameter
119
+ def check_usability(check_id, severity: :warning, &block)
120
+ @checks << {
121
+ type: :usability,
122
+ id: check_id,
123
+ severity: severity,
124
+ block: block,
125
+ }
126
+ end
127
+
128
+ # DSL method: Define an instruction validation check
129
+ #
130
+ # Used for checks that validate TrueType instructions/hinting
131
+ #
132
+ # @param check_id [Symbol] Unique identifier for this check
133
+ # @param severity [Symbol] Severity level (:info, :warning, :error, :fatal)
134
+ # @param block [Proc] Check logic that receives font as parameter
135
+ def check_instructions(check_id, severity: :warning, &block)
136
+ @checks << {
137
+ type: :instructions,
138
+ id: check_id,
139
+ severity: severity,
140
+ block: block,
141
+ }
142
+ end
143
+
144
+ # DSL method: Define a glyph-level check
145
+ #
146
+ # Used for checks that validate individual glyphs
147
+ #
148
+ # @param check_id [Symbol] Unique identifier for this check
149
+ # @param severity [Symbol] Severity level (:info, :warning, :error, :fatal)
150
+ # @param block [Proc] Check logic that receives font as parameter
151
+ def check_glyphs(check_id, severity: :error, &block)
152
+ @checks << {
153
+ type: :glyphs,
154
+ id: check_id,
155
+ severity: severity,
156
+ block: block,
157
+ }
158
+ end
159
+
160
+ private
161
+
162
+ # Template method: Subclasses implement this to define their checks
163
+ #
164
+ # @return [void]
165
+ def define_checks
166
+ # Subclasses override this method
167
+ end
168
+
169
+ # Execute a single check using strategy pattern
170
+ #
171
+ # @param font [TrueTypeFont, OpenTypeFont] Font to validate
172
+ # @param check_def [Hash] Check definition
173
+ # @return [Hash] Check result with :passed, :severity, :messages, :issues
174
+ def execute_check(font, check_def)
175
+ case check_def[:type]
176
+ when :table
177
+ execute_table_check(font, check_def)
178
+ when :field
179
+ execute_field_check(font, check_def)
180
+ when :structure
181
+ execute_structure_check(font, check_def)
182
+ when :usability
183
+ execute_usability_check(font, check_def)
184
+ when :instructions
185
+ execute_instruction_check(font, check_def)
186
+ when :glyphs
187
+ execute_glyph_check(font, check_def)
188
+ else
189
+ {
190
+ check_id: check_def[:id],
191
+ passed: false,
192
+ severity: :fatal,
193
+ messages: ["Unknown check type: #{check_def[:type]}"],
194
+ issues: [],
195
+ }
196
+ end
197
+ rescue => e
198
+ {
199
+ check_id: check_def[:id],
200
+ passed: false,
201
+ severity: :fatal,
202
+ messages: ["Check execution failed: #{e.message}"],
203
+ issues: [{
204
+ severity: "fatal",
205
+ category: "check_execution",
206
+ message: "Exception during check execution: #{e.class} - #{e.message}",
207
+ }],
208
+ }
209
+ end
210
+
211
+ # Execute a table-level check
212
+ #
213
+ # @param font [TrueTypeFont, OpenTypeFont] Font to validate
214
+ # @param check_def [Hash] Check definition
215
+ # @return [Hash] Check result
216
+ def execute_table_check(font, check_def)
217
+ table_tag = check_def[:table_tag]
218
+ table = font.table(table_tag)
219
+
220
+ unless table
221
+ return {
222
+ check_id: check_def[:id],
223
+ passed: false,
224
+ severity: check_def[:severity].to_s,
225
+ messages: ["Table '#{table_tag}' not found in font"],
226
+ table: table_tag,
227
+ issues: [{
228
+ severity: check_def[:severity].to_s,
229
+ category: "table_presence",
230
+ table: table_tag,
231
+ message: "Required table '#{table_tag}' is missing",
232
+ }],
233
+ }
234
+ end
235
+
236
+ # Set context for nested field checks
237
+ old_context = @current_table_context
238
+ @current_table_context = table_tag
239
+
240
+ begin
241
+ result = check_def[:block].call(table)
242
+ passed = result != false && result != nil
243
+
244
+ {
245
+ check_id: check_def[:id],
246
+ passed: passed,
247
+ severity: check_def[:severity].to_s,
248
+ messages: passed ? [] : ["Table '#{table_tag}' validation failed"],
249
+ table: table_tag,
250
+ issues: passed ? [] : [{
251
+ severity: check_def[:severity].to_s,
252
+ category: "table_validation",
253
+ table: table_tag,
254
+ message: "Table '#{table_tag}' failed validation",
255
+ }],
256
+ }
257
+ ensure
258
+ @current_table_context = old_context
259
+ end
260
+ end
261
+
262
+ # Execute a field-level check
263
+ #
264
+ # @param font [TrueTypeFont, OpenTypeFont] Font to validate
265
+ # @param check_def [Hash] Check definition
266
+ # @return [Hash] Check result
267
+ def execute_field_check(font, check_def)
268
+ table_tag = check_def[:table_tag]
269
+ field_key = check_def[:field]
270
+ table = font.table(table_tag)
271
+
272
+ unless table
273
+ return {
274
+ check_id: check_def[:id],
275
+ passed: false,
276
+ severity: check_def[:severity].to_s,
277
+ messages: ["Table '#{table_tag}' not found"],
278
+ table: table_tag,
279
+ field: field_key.to_s,
280
+ issues: [{
281
+ severity: check_def[:severity].to_s,
282
+ category: "table_presence",
283
+ table: table_tag,
284
+ field: field_key.to_s,
285
+ message: "Cannot validate field '#{field_key}': table '#{table_tag}' missing",
286
+ }],
287
+ }
288
+ end
289
+
290
+ # Get field value
291
+ value = if table.respond_to?(field_key)
292
+ table.public_send(field_key)
293
+ else
294
+ nil
295
+ end
296
+
297
+ result = check_def[:block].call(table, value)
298
+ passed = result != false && result != nil
299
+
300
+ {
301
+ check_id: check_def[:id],
302
+ passed: passed,
303
+ severity: check_def[:severity].to_s,
304
+ messages: passed ? [] : ["Field '#{field_key}' validation failed"],
305
+ table: table_tag,
306
+ field: field_key.to_s,
307
+ issues: passed ? [] : [{
308
+ severity: check_def[:severity].to_s,
309
+ category: "field_validation",
310
+ table: table_tag,
311
+ field: field_key.to_s,
312
+ message: "Field '#{field_key}' in table '#{table_tag}' failed validation",
313
+ }],
314
+ }
315
+ end
316
+
317
+ # Execute a structure check
318
+ #
319
+ # @param font [TrueTypeFont, OpenTypeFont] Font to validate
320
+ # @param check_def [Hash] Check definition
321
+ # @return [Hash] Check result
322
+ def execute_structure_check(font, check_def)
323
+ result = check_def[:block].call(font)
324
+ passed = result != false && result != nil
325
+
326
+ {
327
+ check_id: check_def[:id],
328
+ passed: passed,
329
+ severity: check_def[:severity].to_s,
330
+ messages: passed ? [] : ["Structure validation failed"],
331
+ issues: passed ? [] : [{
332
+ severity: check_def[:severity].to_s,
333
+ category: "structure",
334
+ message: "Font structure validation failed for check '#{check_def[:id]}'",
335
+ }],
336
+ }
337
+ end
338
+
339
+ # Execute a usability check
340
+ #
341
+ # @param font [TrueTypeFont, OpenTypeFont] Font to validate
342
+ # @param check_def [Hash] Check definition
343
+ # @return [Hash] Check result
344
+ def execute_usability_check(font, check_def)
345
+ result = check_def[:block].call(font)
346
+ passed = result != false && result != nil
347
+
348
+ {
349
+ check_id: check_def[:id],
350
+ passed: passed,
351
+ severity: check_def[:severity].to_s,
352
+ messages: passed ? [] : ["Usability check failed"],
353
+ issues: passed ? [] : [{
354
+ severity: check_def[:severity].to_s,
355
+ category: "usability",
356
+ message: "Font usability check failed for '#{check_def[:id]}'",
357
+ }],
358
+ }
359
+ end
360
+
361
+ # Execute an instruction check
362
+ #
363
+ # @param font [TrueTypeFont, OpenTypeFont] Font to validate
364
+ # @param check_def [Hash] Check definition
365
+ # @return [Hash] Check result
366
+ def execute_instruction_check(font, check_def)
367
+ result = check_def[:block].call(font)
368
+ passed = result != false && result != nil
369
+
370
+ {
371
+ check_id: check_def[:id],
372
+ passed: passed,
373
+ severity: check_def[:severity].to_s,
374
+ messages: passed ? [] : ["Instruction validation failed"],
375
+ issues: passed ? [] : [{
376
+ severity: check_def[:severity].to_s,
377
+ category: "instructions",
378
+ message: "TrueType instruction check failed for '#{check_def[:id]}'",
379
+ }],
380
+ }
381
+ end
382
+
383
+ # Execute a glyph check
384
+ #
385
+ # @param font [TrueTypeFont, OpenTypeFont] Font to validate
386
+ # @param check_def [Hash] Check definition
387
+ # @return [Hash] Check result
388
+ def execute_glyph_check(font, check_def)
389
+ result = check_def[:block].call(font)
390
+ passed = result != false && result != nil
391
+
392
+ {
393
+ check_id: check_def[:id],
394
+ passed: passed,
395
+ severity: check_def[:severity].to_s,
396
+ messages: passed ? [] : ["Glyph validation failed"],
397
+ issues: passed ? [] : [{
398
+ severity: check_def[:severity].to_s,
399
+ category: "glyphs",
400
+ message: "Glyph validation failed for check '#{check_def[:id]}'",
401
+ }],
402
+ }
403
+ end
404
+
405
+ # Build ValidationReport from check results
406
+ #
407
+ # @param font [TrueTypeFont, OpenTypeFont] Font that was validated
408
+ # @param all_results [Array<Hash>] All check results
409
+ # @param elapsed [Float] Elapsed time in seconds
410
+ # @return [ValidationReport] Complete report
411
+ def build_report(font, all_results, elapsed)
412
+ # Extract font path from font object
413
+ font_path = if font.respond_to?(:path)
414
+ font.path
415
+ elsif font.respond_to?(:filename)
416
+ font.filename
417
+ elsif font.instance_variable_defined?(:@filename)
418
+ font.instance_variable_get(:@filename)
419
+ else
420
+ "unknown"
421
+ end
422
+
423
+ report = Models::ValidationReport.new(
424
+ font_path: font_path,
425
+ valid: true,
426
+ )
427
+
428
+ # Build CheckResult objects
429
+ all_results.each do |result|
430
+ check_result = Models::ValidationReport::CheckResult.new(
431
+ check_id: result[:check_id].to_s,
432
+ passed: result[:passed],
433
+ severity: result[:severity],
434
+ messages: result[:messages] || [],
435
+ table: result[:table],
436
+ field: result[:field],
437
+ )
438
+ report.check_results << check_result
439
+
440
+ # Add issues to main report
441
+ if result[:issues]
442
+ result[:issues].each do |issue_data|
443
+ case issue_data[:severity]
444
+ when "error", "fatal"
445
+ report.add_error(
446
+ issue_data[:category] || "validation",
447
+ issue_data[:message],
448
+ issue_data[:table] || issue_data[:field],
449
+ )
450
+ when "warning"
451
+ report.add_warning(
452
+ issue_data[:category] || "validation",
453
+ issue_data[:message],
454
+ issue_data[:table] || issue_data[:field],
455
+ )
456
+ when "info"
457
+ report.add_info(
458
+ issue_data[:category] || "validation",
459
+ issue_data[:message],
460
+ issue_data[:table] || issue_data[:field],
461
+ )
462
+ end
463
+ end
464
+ end
465
+ end
466
+
467
+ # Mark checks performed
468
+ report.checks_performed = all_results.map { |r| r[:check_id].to_s }
469
+
470
+ # Set status based on results
471
+ if report.has_errors?
472
+ report.status = "invalid"
473
+ report.valid = false
474
+ elsif report.has_warnings?
475
+ report.status = "valid_with_warnings"
476
+ else
477
+ report.status = "valid"
478
+ end
479
+
480
+ report
481
+ end
482
+ end
483
+ end
484
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "basic_validator"
4
+
5
+ module Fontisan
6
+ module Validators
7
+ # WebFontValidator provides web font optimization and embedding compatibility checks
8
+ #
9
+ # This validator extends BasicValidator with checks specific to web font use cases.
10
+ # Unlike FontBookValidator, it focuses on web embedding permissions, file size,
11
+ # and WOFF/WOFF2 conversion readiness rather than desktop installation.
12
+ #
13
+ # The validator inherits 8 checks from BasicValidator and adds 10 new checks:
14
+ # - Embedding permissions (OS/2 fsType)
15
+ # - File size and glyph complexity for web performance
16
+ # - Character coverage for web use
17
+ # - Glyph accessibility
18
+ # - WOFF/WOFF2 conversion readiness
19
+ #
20
+ # @example Using WebFontValidator
21
+ # validator = WebFontValidator.new
22
+ # report = validator.validate(font)
23
+ # puts "Font is web-ready" if report.valid?
24
+ class WebFontValidator < BasicValidator
25
+ private
26
+
27
+ # Define web font validation checks
28
+ #
29
+ # Calls super to inherit BasicValidator's 8 checks, then adds 10 new checks.
30
+ # All checks use helpers from Week 1 table implementations.
31
+ def define_checks
32
+ # Inherit BasicValidator checks (8 checks)
33
+ super
34
+
35
+ # Check 9: OS/2 embedding permissions must allow web use
36
+ check_table :embedding_permissions, 'OS/2', severity: :error do |table|
37
+ table.has_embedding_permissions?
38
+ end
39
+
40
+ # Check 10: OS/2 version should be present
41
+ check_table :os2_version_web, 'OS/2', severity: :warning do |table|
42
+ table.valid_version?
43
+ end
44
+
45
+ # Check 11: Glyph complexity should be reasonable for web
46
+ check_glyphs :no_complex_glyphs, severity: :warning do |font|
47
+ maxp = font.table('maxp')
48
+ next true unless maxp.version_1_0?
49
+
50
+ # Check max points and contours are reasonable for web rendering
51
+ maxp.max_points && maxp.max_points < 3000 &&
52
+ maxp.max_contours && maxp.max_contours < 500
53
+ end
54
+
55
+ # Check 12: Cmap must have Unicode mapping for web
56
+ check_table :character_coverage, 'cmap', severity: :error do |table|
57
+ table.has_unicode_mapping?
58
+ end
59
+
60
+ # Check 13: Cmap should have BMP coverage
61
+ check_table :cmap_bmp_web, 'cmap', severity: :warning do |table|
62
+ table.has_bmp_coverage?
63
+ end
64
+
65
+ # Check 14: Glyf glyphs must be accessible (web browsers need this)
66
+ check_glyphs :glyph_accessible_web, severity: :error do |font|
67
+ glyf = font.table('glyf')
68
+ next true unless glyf
69
+
70
+ loca = font.table('loca')
71
+ head = font.table('head')
72
+ maxp = font.table('maxp')
73
+ glyf.all_glyphs_accessible?(loca, head, maxp.num_glyphs)
74
+ end
75
+
76
+ # Check 15: Head table must have valid bounding box
77
+ check_table :head_bbox_web, 'head', severity: :error do |table|
78
+ table.valid_bounding_box?
79
+ end
80
+
81
+ # Check 16: Hhea metrics must be valid for web rendering
82
+ check_table :hhea_metrics_web, 'hhea', severity: :error do |table|
83
+ table.valid_ascent_descent? && table.valid_number_of_h_metrics?
84
+ end
85
+
86
+ # Check 17: WOFF conversion readiness
87
+ check_structure :woff_conversion_ready, severity: :info do |font|
88
+ # Check font can be converted to WOFF
89
+ # All required tables present
90
+ %w[name head maxp hhea].all? { |tag| font.table(tag) }
91
+ end
92
+
93
+ # Check 18: WOFF2 conversion readiness
94
+ check_structure :woff2_conversion_ready, severity: :info do |font|
95
+ # Check font can be converted to WOFF2
96
+ # Same requirements as WOFF
97
+ %w[name head maxp hhea].all? { |tag| font.table(tag) }
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fontisan
4
- VERSION = "0.2.4"
4
+ VERSION = "0.2.6"
5
5
  end
data/lib/fontisan.rb CHANGED
@@ -124,18 +124,30 @@ require_relative "fontisan/models/color_layer"
124
124
  require_relative "fontisan/models/color_palette"
125
125
  require_relative "fontisan/models/svg_glyph"
126
126
 
127
+ # Validators infrastructure (NEW - DSL-based framework from Week 2+)
128
+ require_relative "fontisan/validators/validator"
129
+ require_relative "fontisan/validators/basic_validator"
130
+ require_relative "fontisan/validators/font_book_validator"
131
+ require_relative "fontisan/validators/opentype_validator"
132
+ require_relative "fontisan/validators/web_font_validator"
133
+ require_relative "fontisan/validators/profile_loader"
134
+
127
135
  # Export infrastructure
128
136
  require_relative "fontisan/export/table_serializer"
129
137
  require_relative "fontisan/export/ttx_generator"
130
138
  require_relative "fontisan/export/ttx_parser"
131
139
  require_relative "fontisan/export/exporter"
132
140
 
133
- # Validation infrastructure
134
- require_relative "fontisan/validation/table_validator"
135
- require_relative "fontisan/validation/structure_validator"
136
- require_relative "fontisan/validation/consistency_validator"
137
- require_relative "fontisan/validation/checksum_validator"
138
- require_relative "fontisan/validation/validator"
141
+ # Validation infrastructure (OLD - commented out for new DSL framework)
142
+ # Week 1 deleted these, Week 2-5 building new DSL-based framework
143
+ # require_relative "fontisan/validation/checks/base_check"
144
+ # require_relative "fontisan/validation/check_registry"
145
+ # require_relative "fontisan/validation/profile"
146
+ # require_relative "fontisan/validation/table_validator"
147
+ # require_relative "fontisan/validation/structure_validator"
148
+ # require_relative "fontisan/validation/consistency_validator"
149
+ # require_relative "fontisan/validation/checksum_validator"
150
+ # require_relative "fontisan/validation/validator"
139
151
 
140
152
  # Subsetting infrastructure
141
153
  require_relative "fontisan/subset/options"
@@ -261,4 +273,64 @@ module Fontisan
261
273
  def self.info(path, brief: false, font_index: 0)
262
274
  Commands::InfoCommand.new(path, brief: brief, font_index: font_index).run
263
275
  end
276
+
277
+ # Validate a font file using specified profile
278
+ #
279
+ # Validates fonts against quality checks, structural integrity, and OpenType
280
+ # specification compliance using the new DSL-based validation framework.
281
+ #
282
+ # @param path [String] Path to font file
283
+ # @param profile [Symbol, String] Validation profile (default: :default)
284
+ # Available profiles:
285
+ # - :indexability - Fast validation for font discovery
286
+ # - :usability - Basic usability for installation
287
+ # - :production - Comprehensive quality checks (default)
288
+ # - :web - Web embedding and optimization
289
+ # - :spec_compliance - Full OpenType spec compliance
290
+ # - :default - Alias for production profile
291
+ # @param options [Hash] Additional validation options
292
+ # @return [Models::ValidationReport] Validation report with issues and status
293
+ #
294
+ # @example Validate with default profile
295
+ # report = Fontisan.validate("font.ttf")
296
+ # puts "Valid: #{report.valid?}"
297
+ #
298
+ # @example Validate for web use
299
+ # report = Fontisan.validate("font.ttf", profile: :web)
300
+ # puts "Errors: #{report.summary.errors}"
301
+ #
302
+ # @example Validate and get detailed report
303
+ # report = Fontisan.validate("font.ttf", profile: :production)
304
+ # puts report.to_yaml
305
+ def self.validate(path, profile: :default, options: {})
306
+ # Get profile configuration
307
+ profile_config = Validators::ProfileLoader.profile_info(profile)
308
+ raise ArgumentError, "Unknown profile: #{profile}" unless profile_config
309
+
310
+ # Load font with appropriate mode
311
+ mode = profile_config[:loading_mode].to_sym
312
+ font = FontLoader.load(path, mode: mode)
313
+
314
+ # Load validator for profile
315
+ validator = Validators::ProfileLoader.load(profile)
316
+
317
+ # Run validation
318
+ validator.validate(font)
319
+ end
320
+
321
+ class << self
322
+ private
323
+
324
+ # Get loading mode for validation profile
325
+ #
326
+ # Temporarily disabled - will be reimplemented with new DSL framework
327
+ #
328
+ # @param profile [Symbol] Validation profile
329
+ # @return [Symbol] Loading mode (:metadata or :full)
330
+ # def profile_loading_mode(profile)
331
+ # Validation::Profile.load(profile).loading_mode.to_sym
332
+ # rescue
333
+ # :full
334
+ # end
335
+ end
264
336
  end