fontisan 0.2.4 → 0.2.5
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 +150 -30
- data/README.adoc +497 -242
- data/lib/fontisan/cli.rb +67 -6
- data/lib/fontisan/commands/validate_command.rb +107 -151
- data/lib/fontisan/converters/woff2_encoder.rb +7 -29
- data/lib/fontisan/models/validation_report.rb +227 -0
- data/lib/fontisan/pipeline/transformation_pipeline.rb +4 -8
- data/lib/fontisan/tables/cmap.rb +82 -2
- data/lib/fontisan/tables/glyf.rb +118 -0
- data/lib/fontisan/tables/head.rb +60 -0
- data/lib/fontisan/tables/hhea.rb +74 -0
- data/lib/fontisan/tables/maxp.rb +60 -0
- data/lib/fontisan/tables/name.rb +76 -0
- data/lib/fontisan/tables/os2.rb +113 -0
- data/lib/fontisan/tables/post.rb +57 -0
- data/lib/fontisan/validators/basic_validator.rb +85 -0
- data/lib/fontisan/validators/font_book_validator.rb +130 -0
- data/lib/fontisan/validators/opentype_validator.rb +112 -0
- data/lib/fontisan/validators/profile_loader.rb +139 -0
- data/lib/fontisan/validators/validator.rb +484 -0
- data/lib/fontisan/validators/web_font_validator.rb +102 -0
- data/lib/fontisan/version.rb +1 -1
- data/lib/fontisan.rb +78 -6
- metadata +7 -11
- data/lib/fontisan/config/validation_rules.yml +0 -149
- data/lib/fontisan/validation/checksum_validator.rb +0 -170
- data/lib/fontisan/validation/consistency_validator.rb +0 -197
- data/lib/fontisan/validation/structure_validator.rb +0 -198
- data/lib/fontisan/validation/table_validator.rb +0 -158
- data/lib/fontisan/validation/validator.rb +0 -152
- data/lib/fontisan/validation/variable_font_validator.rb +0 -218
- data/lib/fontisan/validation/woff2_header_validator.rb +0 -278
- data/lib/fontisan/validation/woff2_table_validator.rb +0 -270
- data/lib/fontisan/validation/woff2_validator.rb +0 -248
data/lib/fontisan/cli.rb
CHANGED
|
@@ -350,13 +350,62 @@ module Fontisan
|
|
|
350
350
|
handle_error(e)
|
|
351
351
|
end
|
|
352
352
|
|
|
353
|
-
desc "validate FONT_FILE", "Validate font file
|
|
354
|
-
|
|
355
|
-
|
|
353
|
+
desc "validate FONT_FILE", "Validate font file"
|
|
354
|
+
long_desc <<-DESC
|
|
355
|
+
Validate font file against quality checks and standards.
|
|
356
|
+
|
|
357
|
+
Test lists (-t/--test-list):
|
|
358
|
+
indexability - Fast indexing validation
|
|
359
|
+
usability - Installation compatibility
|
|
360
|
+
production - Comprehensive quality (default)
|
|
361
|
+
web - Web font readiness
|
|
362
|
+
spec_compliance - OpenType spec compliance
|
|
363
|
+
default - Production profile (alias)
|
|
364
|
+
|
|
365
|
+
Return values (with -R/--return-value-results):
|
|
366
|
+
0 No results
|
|
367
|
+
1 Execution errors
|
|
368
|
+
2 Fatal errors found
|
|
369
|
+
3 Major errors found
|
|
370
|
+
4 Minor errors found
|
|
371
|
+
5 Spec violations found
|
|
372
|
+
DESC
|
|
373
|
+
|
|
374
|
+
option :exclude, aliases: "-e", type: :array, desc: "Tests to exclude"
|
|
375
|
+
option :list, aliases: "-l", type: :boolean, desc: "List available tests"
|
|
376
|
+
option :output, aliases: "-o", type: :string, desc: "Output file"
|
|
377
|
+
option :full_report, aliases: "-r", type: :boolean, desc: "Full report"
|
|
378
|
+
option :return_value_results, aliases: "-R", type: :boolean, desc: "Use return value for results"
|
|
379
|
+
option :summary_report, aliases: "-S", type: :boolean, desc: "Summary report"
|
|
380
|
+
option :test_list, aliases: "-t", type: :string, default: "default", desc: "Tests to execute"
|
|
381
|
+
option :table_report, aliases: "-T", type: :boolean, desc: "Tabular report"
|
|
382
|
+
option :verbose, aliases: "-v", type: :boolean, desc: "Verbose output"
|
|
383
|
+
option :suppress_warnings, aliases: "-W", type: :boolean, desc: "Suppress warnings"
|
|
384
|
+
|
|
356
385
|
def validate(font_file)
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
386
|
+
if options[:list]
|
|
387
|
+
list_available_tests
|
|
388
|
+
return
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
cmd = Commands::ValidateCommand.new(
|
|
392
|
+
input: font_file,
|
|
393
|
+
profile: options[:test_list],
|
|
394
|
+
exclude: options[:exclude] || [],
|
|
395
|
+
output: options[:output],
|
|
396
|
+
format: options[:format].to_sym,
|
|
397
|
+
full_report: options[:full_report],
|
|
398
|
+
summary_report: options[:summary_report],
|
|
399
|
+
table_report: options[:table_report],
|
|
400
|
+
verbose: options[:verbose],
|
|
401
|
+
suppress_warnings: options[:suppress_warnings],
|
|
402
|
+
return_value_results: options[:return_value_results]
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
exit cmd.run
|
|
406
|
+
rescue => e
|
|
407
|
+
error "Validation failed: #{e.message}"
|
|
408
|
+
exit 1
|
|
360
409
|
end
|
|
361
410
|
|
|
362
411
|
desc "export FONT_FILE", "Export font to TTX/YAML/JSON format"
|
|
@@ -555,5 +604,17 @@ module Fontisan
|
|
|
555
604
|
warn message unless options[:quiet]
|
|
556
605
|
exit 1
|
|
557
606
|
end
|
|
607
|
+
|
|
608
|
+
# List available validation tests/profiles
|
|
609
|
+
#
|
|
610
|
+
# @return [void]
|
|
611
|
+
def list_available_tests
|
|
612
|
+
require_relative "validators/profile_loader"
|
|
613
|
+
profiles = Validators::ProfileLoader.all_profiles
|
|
614
|
+
puts "Available validation profiles:"
|
|
615
|
+
profiles.each do |profile_name, config|
|
|
616
|
+
puts " #{profile_name.to_s.ljust(20)} - #{config[:description]}"
|
|
617
|
+
end
|
|
618
|
+
end
|
|
558
619
|
end
|
|
559
620
|
end
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "base_command"
|
|
4
|
-
require_relative "../
|
|
5
|
-
require_relative "../validation/variable_font_validator"
|
|
4
|
+
require_relative "../validators/profile_loader"
|
|
6
5
|
require_relative "../font_loader"
|
|
7
6
|
|
|
8
7
|
module Fontisan
|
|
@@ -11,195 +10,152 @@ module Fontisan
|
|
|
11
10
|
#
|
|
12
11
|
# This command validates fonts against quality checks, structural integrity,
|
|
13
12
|
# and OpenType specification compliance. It supports different validation
|
|
14
|
-
#
|
|
13
|
+
# profiles and output formats, with ftxvalidator-compatible options.
|
|
15
14
|
#
|
|
16
|
-
# @example Validating a font
|
|
15
|
+
# @example Validating a font with default profile
|
|
16
|
+
# command = ValidateCommand.new(input: "font.ttf")
|
|
17
|
+
# exit_code = command.run
|
|
18
|
+
#
|
|
19
|
+
# @example Validating with specific profile
|
|
17
20
|
# command = ValidateCommand.new(
|
|
18
21
|
# input: "font.ttf",
|
|
19
|
-
#
|
|
20
|
-
# format: :
|
|
22
|
+
# profile: :web,
|
|
23
|
+
# format: :json
|
|
21
24
|
# )
|
|
22
25
|
# exit_code = command.run
|
|
23
26
|
class ValidateCommand < BaseCommand
|
|
24
27
|
# Initialize validate command
|
|
25
28
|
#
|
|
26
29
|
# @param input [String] Path to font file
|
|
27
|
-
# @param
|
|
30
|
+
# @param profile [Symbol, String, nil] Validation profile (default: :default)
|
|
31
|
+
# @param exclude [Array<String>] Tests to exclude
|
|
32
|
+
# @param output [String, nil] Output file path
|
|
28
33
|
# @param format [Symbol] Output format (:text, :yaml, :json)
|
|
29
|
-
# @param
|
|
30
|
-
# @param
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
# @param full_report [Boolean] Generate full detailed report
|
|
35
|
+
# @param summary_report [Boolean] Generate brief summary report
|
|
36
|
+
# @param table_report [Boolean] Generate tabular format report
|
|
37
|
+
# @param verbose [Boolean] Show verbose output
|
|
38
|
+
# @param suppress_warnings [Boolean] Suppress warning output
|
|
39
|
+
# @param return_value_results [Boolean] Use return values to indicate results
|
|
40
|
+
def initialize(
|
|
41
|
+
input:,
|
|
42
|
+
profile: nil,
|
|
43
|
+
exclude: [],
|
|
44
|
+
output: nil,
|
|
45
|
+
format: :text,
|
|
46
|
+
full_report: false,
|
|
47
|
+
summary_report: false,
|
|
48
|
+
table_report: false,
|
|
49
|
+
verbose: false,
|
|
50
|
+
suppress_warnings: false,
|
|
51
|
+
return_value_results: false
|
|
52
|
+
)
|
|
34
53
|
@input = input
|
|
35
|
-
@
|
|
36
|
-
@
|
|
54
|
+
@profile = profile || :default
|
|
55
|
+
@exclude = exclude
|
|
56
|
+
@output = output
|
|
57
|
+
@format = format
|
|
58
|
+
@full_report = full_report
|
|
59
|
+
@summary_report = summary_report
|
|
60
|
+
@table_report = table_report
|
|
37
61
|
@verbose = verbose
|
|
38
|
-
@
|
|
62
|
+
@suppress_warnings = suppress_warnings
|
|
63
|
+
@return_value_results = return_value_results
|
|
39
64
|
end
|
|
40
65
|
|
|
41
66
|
# Run the validation command
|
|
42
67
|
#
|
|
43
|
-
# @return [Integer] Exit code (0 = valid,
|
|
68
|
+
# @return [Integer] Exit code (0 = valid, 2 = fatal, 3 = errors, 4 = warnings, 5 = info)
|
|
44
69
|
def run
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
# Create validator
|
|
52
|
-
validator = Validation::Validator.new(level: @level)
|
|
53
|
-
|
|
54
|
-
# Run validation
|
|
55
|
-
report = validator.validate(font, @input)
|
|
70
|
+
# Load font with appropriate mode
|
|
71
|
+
profile_config = Validators::ProfileLoader.profile_info(@profile)
|
|
72
|
+
unless profile_config
|
|
73
|
+
puts "Error: Unknown profile '#{@profile}'" unless @suppress_warnings
|
|
74
|
+
return 1
|
|
75
|
+
end
|
|
56
76
|
|
|
57
|
-
|
|
58
|
-
validate_variable_font(font, report) if font.has_table?("fvar")
|
|
77
|
+
mode = profile_config[:loading_mode].to_sym
|
|
59
78
|
|
|
60
|
-
|
|
61
|
-
output_report(report) unless @quiet
|
|
79
|
+
font = FontLoader.load(@input, mode: mode)
|
|
62
80
|
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
rescue StandardError => e
|
|
66
|
-
puts "Error: #{e.message}" unless @quiet
|
|
67
|
-
puts e.backtrace.join("\n") if @verbose && !@quiet
|
|
68
|
-
1
|
|
69
|
-
end
|
|
81
|
+
# Select validator
|
|
82
|
+
validator = Validators::ProfileLoader.load(@profile)
|
|
70
83
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
# Validate variable font structure
|
|
74
|
-
#
|
|
75
|
-
# @param font [TrueTypeFont, OpenTypeFont] The font to validate
|
|
76
|
-
# @param report [Models::ValidationReport] The validation report to update
|
|
77
|
-
# @return [void]
|
|
78
|
-
def validate_variable_font(font, report)
|
|
79
|
-
var_validator = Validation::VariableFontValidator.new(font)
|
|
80
|
-
errors = var_validator.validate
|
|
84
|
+
# Run validation
|
|
85
|
+
report = validator.validate(font)
|
|
81
86
|
|
|
82
|
-
if
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
puts " ERROR: #{error}" if @verbose && !@quiet
|
|
86
|
-
# Add to report if report supports adding errors
|
|
87
|
-
if report.respond_to?(:errors)
|
|
88
|
-
report.errors << { message: error,
|
|
89
|
-
category: "variable_font" }
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
elsif @verbose && !@quiet
|
|
93
|
-
puts "\n✓ Variable font structure valid"
|
|
87
|
+
# Filter excluded checks if specified
|
|
88
|
+
if @exclude.any?
|
|
89
|
+
report.check_results.reject! { |cr| @exclude.include?(cr.check_id) }
|
|
94
90
|
end
|
|
95
|
-
end
|
|
96
91
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
# @raise [ArgumentError] if parameters are invalid
|
|
100
|
-
# @return [void]
|
|
101
|
-
def validate_params!
|
|
102
|
-
if @input.nil? || @input.empty?
|
|
103
|
-
raise ArgumentError,
|
|
104
|
-
"Input file is required"
|
|
105
|
-
end
|
|
106
|
-
unless File.exist?(@input)
|
|
107
|
-
raise ArgumentError,
|
|
108
|
-
"Input file does not exist: #{@input}"
|
|
109
|
-
end
|
|
92
|
+
# Generate output
|
|
93
|
+
output = generate_output(report)
|
|
110
94
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
valid_formats = %i[text yaml json]
|
|
118
|
-
unless valid_formats.include?(@format)
|
|
119
|
-
raise ArgumentError,
|
|
120
|
-
"Invalid format: #{@format}. Must be one of: #{valid_formats.join(', ')}"
|
|
95
|
+
# Write to file or stdout
|
|
96
|
+
if @output
|
|
97
|
+
File.write(@output, output)
|
|
98
|
+
puts "Validation report written to #{@output}" if @verbose && !@suppress_warnings
|
|
99
|
+
else
|
|
100
|
+
puts output unless @suppress_warnings
|
|
121
101
|
end
|
|
122
|
-
end
|
|
123
102
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
puts "Error loading font: #{e.message}" unless @quiet
|
|
131
|
-
nil
|
|
103
|
+
# Return exit code
|
|
104
|
+
exit_code(report)
|
|
105
|
+
rescue => e
|
|
106
|
+
puts "Error: #{e.message}" unless @suppress_warnings
|
|
107
|
+
puts e.backtrace.join("\n") if @verbose && !@suppress_warnings
|
|
108
|
+
1
|
|
132
109
|
end
|
|
133
110
|
|
|
134
|
-
|
|
135
|
-
#
|
|
136
|
-
# @param report [Models::ValidationReport] The validation report
|
|
137
|
-
# @return [void]
|
|
138
|
-
def output_report(report)
|
|
139
|
-
case @format
|
|
140
|
-
when :text
|
|
141
|
-
output_text(report)
|
|
142
|
-
when :yaml
|
|
143
|
-
output_yaml(report)
|
|
144
|
-
when :json
|
|
145
|
-
output_json(report)
|
|
146
|
-
end
|
|
147
|
-
end
|
|
111
|
+
private
|
|
148
112
|
|
|
149
|
-
#
|
|
113
|
+
# Generate output based on requested format
|
|
150
114
|
#
|
|
151
|
-
# @param report [
|
|
152
|
-
# @return [
|
|
153
|
-
def
|
|
154
|
-
if @
|
|
155
|
-
|
|
115
|
+
# @param report [ValidationReport] The validation report
|
|
116
|
+
# @return [String] Formatted output
|
|
117
|
+
def generate_output(report)
|
|
118
|
+
if @table_report
|
|
119
|
+
report.to_table_format
|
|
120
|
+
elsif @summary_report
|
|
121
|
+
report.to_summary
|
|
122
|
+
elsif @full_report
|
|
123
|
+
report.to_text_report
|
|
156
124
|
else
|
|
157
|
-
#
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
125
|
+
# Default: format-specific output
|
|
126
|
+
case @format
|
|
127
|
+
when :yaml
|
|
128
|
+
require "yaml"
|
|
129
|
+
report.to_yaml
|
|
130
|
+
when :json
|
|
131
|
+
require "json"
|
|
132
|
+
report.to_json
|
|
133
|
+
else
|
|
134
|
+
report.text_summary
|
|
164
135
|
end
|
|
165
136
|
end
|
|
166
137
|
end
|
|
167
138
|
|
|
168
|
-
# Output report in YAML format
|
|
169
|
-
#
|
|
170
|
-
# @param report [Models::ValidationReport] The validation report
|
|
171
|
-
# @return [void]
|
|
172
|
-
def output_yaml(report)
|
|
173
|
-
require "yaml"
|
|
174
|
-
puts report.to_yaml
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Output report in JSON format
|
|
178
|
-
#
|
|
179
|
-
# @param report [Models::ValidationReport] The validation report
|
|
180
|
-
# @return [void]
|
|
181
|
-
def output_json(report)
|
|
182
|
-
require "json"
|
|
183
|
-
puts report.to_json
|
|
184
|
-
end
|
|
185
|
-
|
|
186
139
|
# Determine exit code based on validation results
|
|
187
140
|
#
|
|
188
|
-
# Exit codes:
|
|
189
|
-
#
|
|
190
|
-
#
|
|
191
|
-
#
|
|
141
|
+
# Exit codes (ftxvalidator compatible):
|
|
142
|
+
# 0 = No issues found
|
|
143
|
+
# 1 = Execution errors
|
|
144
|
+
# 2 = Fatal errors found
|
|
145
|
+
# 3 = Major errors found
|
|
146
|
+
# 4 = Minor errors (warnings) found
|
|
147
|
+
# 5 = Spec violations (info) found
|
|
192
148
|
#
|
|
193
|
-
# @param report [
|
|
149
|
+
# @param report [ValidationReport] The validation report
|
|
194
150
|
# @return [Integer] Exit code
|
|
195
|
-
def
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
151
|
+
def exit_code(report)
|
|
152
|
+
return 0 unless @return_value_results
|
|
153
|
+
|
|
154
|
+
return 2 if report.fatal_errors.any?
|
|
155
|
+
return 3 if report.errors_only.any?
|
|
156
|
+
return 4 if report.warnings_only.any?
|
|
157
|
+
return 5 if report.info_only.any?
|
|
158
|
+
0
|
|
203
159
|
end
|
|
204
160
|
end
|
|
205
161
|
end
|
|
@@ -6,7 +6,8 @@ require_relative "../woff2/directory"
|
|
|
6
6
|
require_relative "../woff2/table_transformer"
|
|
7
7
|
require_relative "../utilities/brotli_wrapper"
|
|
8
8
|
require_relative "../utilities/checksum_calculator"
|
|
9
|
-
|
|
9
|
+
# Validation temporarily disabled - will be reimplemented with new DSL framework in Week 3+
|
|
10
|
+
# require_relative "../validation/woff2_validator"
|
|
10
11
|
require "yaml"
|
|
11
12
|
require "stringio"
|
|
12
13
|
|
|
@@ -111,10 +112,11 @@ module Fontisan
|
|
|
111
112
|
result = { woff2_binary: woff2_binary }
|
|
112
113
|
|
|
113
114
|
# Optional validation
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
115
|
+
# Temporarily disabled - will be reimplemented with new DSL framework
|
|
116
|
+
# if options[:validate]
|
|
117
|
+
# validation_report = validate_encoding(woff2_binary, options)
|
|
118
|
+
# result[:validation_report] = validation_report
|
|
119
|
+
# end
|
|
118
120
|
|
|
119
121
|
result
|
|
120
122
|
end
|
|
@@ -162,30 +164,6 @@ module Fontisan
|
|
|
162
164
|
|
|
163
165
|
private
|
|
164
166
|
|
|
165
|
-
# Validate encoded WOFF2 binary
|
|
166
|
-
#
|
|
167
|
-
# @param woff2_binary [String] Encoded WOFF2 data
|
|
168
|
-
# @param options [Hash] Validation options
|
|
169
|
-
# @return [Models::ValidationReport] Validation report
|
|
170
|
-
def validate_encoding(woff2_binary, options)
|
|
171
|
-
# Load the encoded WOFF2 from memory
|
|
172
|
-
io = StringIO.new(woff2_binary)
|
|
173
|
-
woff2_font = Woff2Font.from_file_io(io, "encoded.woff2")
|
|
174
|
-
|
|
175
|
-
# Run validation
|
|
176
|
-
validation_level = options[:validation_level] || :standard
|
|
177
|
-
validator = Validation::Woff2Validator.new(level: validation_level)
|
|
178
|
-
validator.validate(woff2_font, "encoded.woff2")
|
|
179
|
-
rescue StandardError => e
|
|
180
|
-
# If validation fails, create a report with the error
|
|
181
|
-
report = Models::ValidationReport.new(
|
|
182
|
-
font_path: "encoded.woff2",
|
|
183
|
-
valid: false,
|
|
184
|
-
)
|
|
185
|
-
report.add_error("woff2_validation", "Validation failed: #{e.message}", nil)
|
|
186
|
-
report
|
|
187
|
-
end
|
|
188
|
-
|
|
189
167
|
# Helper method to load WOFF2 from StringIO
|
|
190
168
|
#
|
|
191
169
|
# This is added to Woff2Font to support in-memory validation
|