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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +168 -32
- data/README.adoc +673 -1091
- data/lib/fontisan/cli.rb +94 -13
- 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/commands/validate_command.rb +107 -151
- 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/converters/woff2_encoder.rb +7 -29
- data/lib/fontisan/dfont_collection.rb +185 -0
- data/lib/fontisan/font_loader.rb +91 -6
- data/lib/fontisan/models/validation_report.rb +227 -0
- data/lib/fontisan/parsers/dfont_parser.rb +192 -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/true_type_font.rb +8 -46
- data/lib/fontisan/validation/collection_validator.rb +265 -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 +13 -12
- 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
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "basic_validator"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Validators
|
|
7
|
+
# FontBookValidator provides macOS Font Book installation compatibility checks
|
|
8
|
+
#
|
|
9
|
+
# This validator extends BasicValidator with additional checks needed for
|
|
10
|
+
# fonts to be successfully installed and used in macOS Font Book. It ensures
|
|
11
|
+
# proper encoding combinations, OS/2 metrics, and other macOS-specific
|
|
12
|
+
# requirements.
|
|
13
|
+
#
|
|
14
|
+
# The validator inherits all 8 checks from BasicValidator and adds 12 new
|
|
15
|
+
# checks focusing on:
|
|
16
|
+
# - Name table encoding combinations (Windows and Mac)
|
|
17
|
+
# - OS/2 table metrics and metadata
|
|
18
|
+
# - Head table bounding box
|
|
19
|
+
# - Hhea table metrics
|
|
20
|
+
# - Post table metadata
|
|
21
|
+
# - Cmap subtables
|
|
22
|
+
#
|
|
23
|
+
# @example Using FontBookValidator
|
|
24
|
+
# validator = FontBookValidator.new
|
|
25
|
+
# report = validator.validate(font)
|
|
26
|
+
# puts "Font is Font Book compatible" if report.valid?
|
|
27
|
+
class FontBookValidator < BasicValidator
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
# Define Font Book compatibility checks
|
|
31
|
+
#
|
|
32
|
+
# Calls super to inherit BasicValidator's 8 checks, then adds 12 new checks.
|
|
33
|
+
# All checks use helpers from Week 1 table implementations.
|
|
34
|
+
def define_checks
|
|
35
|
+
# Inherit BasicValidator checks (8 checks)
|
|
36
|
+
super
|
|
37
|
+
|
|
38
|
+
# Check 9: Name table Windows Unicode English encoding
|
|
39
|
+
check_table :name_windows_encoding, 'name', severity: :error do |table|
|
|
40
|
+
table.has_valid_platform_combos?([3, 1, 0x0409]) # Windows Unicode English
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Check 10: Name table Mac Roman English encoding
|
|
44
|
+
check_table :name_mac_encoding, 'name', severity: :warning do |table|
|
|
45
|
+
table.has_valid_platform_combos?([1, 0, 0]) # Mac Roman English
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Check 11: OS/2 table version must be valid
|
|
49
|
+
check_table :os2_version, 'OS/2', severity: :error do |table|
|
|
50
|
+
table.valid_version?
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Check 12: OS/2 weight class must be valid (1-1000)
|
|
54
|
+
check_table :os2_weight_class, 'OS/2', severity: :error do |table|
|
|
55
|
+
table.valid_weight_class?
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Check 13: OS/2 width class must be valid (1-9)
|
|
59
|
+
check_table :os2_width_class, 'OS/2', severity: :error do |table|
|
|
60
|
+
table.valid_width_class?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Check 14: OS/2 vendor ID should be present
|
|
64
|
+
check_table :os2_vendor_id, 'OS/2', severity: :warning do |table|
|
|
65
|
+
table.has_vendor_id?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Check 15: OS/2 PANOSE classification should be present
|
|
69
|
+
check_table :os2_panose, 'OS/2', severity: :info do |table|
|
|
70
|
+
table.has_panose?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Check 16: OS/2 typographic metrics must be valid
|
|
74
|
+
check_table :os2_typo_metrics, 'OS/2', severity: :error do |table|
|
|
75
|
+
table.valid_typo_metrics?
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Check 17: OS/2 Windows metrics must be valid
|
|
79
|
+
check_table :os2_win_metrics, 'OS/2', severity: :error do |table|
|
|
80
|
+
table.valid_win_metrics?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Check 18: OS/2 Unicode ranges should be present
|
|
84
|
+
check_table :os2_unicode_ranges, 'OS/2', severity: :warning do |table|
|
|
85
|
+
table.has_unicode_ranges?
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Check 19: Head table bounding box must be valid
|
|
89
|
+
check_table :head_bounding_box, 'head', severity: :error do |table|
|
|
90
|
+
table.valid_bounding_box?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Check 20: Hhea ascent/descent must be valid
|
|
94
|
+
check_table :hhea_ascent_descent, 'hhea', severity: :error do |table|
|
|
95
|
+
table.valid_ascent_descent?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Check 21: Hhea line gap should be valid
|
|
99
|
+
check_table :hhea_line_gap, 'hhea', severity: :warning do |table|
|
|
100
|
+
table.valid_line_gap?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Check 22: Hhea horizontal metrics count must be valid
|
|
104
|
+
check_table :hhea_metrics_count, 'hhea', severity: :error do |table|
|
|
105
|
+
table.valid_number_of_h_metrics?
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Check 23: Post table version must be valid
|
|
109
|
+
check_table :post_version, 'post', severity: :error do |table|
|
|
110
|
+
table.valid_version?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Check 24: Post table italic angle should be valid
|
|
114
|
+
check_table :post_italic_angle, 'post', severity: :warning do |table|
|
|
115
|
+
table.valid_italic_angle?
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# Check 25: Post table underline metrics should be present
|
|
119
|
+
check_table :post_underline, 'post', severity: :info do |table|
|
|
120
|
+
table.has_underline_metrics?
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Check 26: Cmap table must have subtables
|
|
124
|
+
check_table :cmap_subtables, 'cmap', severity: :error do |table|
|
|
125
|
+
table.has_subtables?
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "font_book_validator"
|
|
4
|
+
|
|
5
|
+
module Fontisan
|
|
6
|
+
module Validators
|
|
7
|
+
# OpenTypeValidator provides comprehensive OpenType specification compliance checks
|
|
8
|
+
#
|
|
9
|
+
# This validator extends FontBookValidator with additional checks ensuring full
|
|
10
|
+
# OpenType specification compliance. It validates glyph data, character mappings,
|
|
11
|
+
# and cross-table consistency.
|
|
12
|
+
#
|
|
13
|
+
# The validator inherits all checks from FontBookValidator (18 checks from
|
|
14
|
+
# FontBookValidator + 8 from BasicValidator = 26 total) and adds 10 new checks:
|
|
15
|
+
# - Maxp TrueType metrics validation
|
|
16
|
+
# - Glyf table structure and accessibility
|
|
17
|
+
# - Cmap Unicode mapping and coverage
|
|
18
|
+
# - Cross-table consistency checks
|
|
19
|
+
#
|
|
20
|
+
# @example Using OpenTypeValidator
|
|
21
|
+
# validator = OpenTypeValidator.new
|
|
22
|
+
# report = validator.validate(font)
|
|
23
|
+
# puts "Font is OpenType compliant" if report.valid?
|
|
24
|
+
class OpenTypeValidator < FontBookValidator
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# Define OpenType specification compliance checks
|
|
28
|
+
#
|
|
29
|
+
# Calls super to inherit FontBookValidator's checks, then adds 10 new checks.
|
|
30
|
+
# All checks use helpers from Week 1 table implementations.
|
|
31
|
+
def define_checks
|
|
32
|
+
# Inherit FontBookValidator checks (26 checks total)
|
|
33
|
+
super
|
|
34
|
+
|
|
35
|
+
# Check 27: Maxp TrueType metrics (only for version 1.0)
|
|
36
|
+
check_table :maxp_truetype_metrics, 'maxp', severity: :warning do |table|
|
|
37
|
+
!table.version_1_0? || table.has_truetype_metrics?
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Check 28: Maxp max zones must be valid
|
|
41
|
+
check_table :maxp_zones, 'maxp', severity: :error do |table|
|
|
42
|
+
table.valid_max_zones?
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Check 29: Glyf glyphs must be accessible (TrueType fonts only)
|
|
46
|
+
check_glyphs :glyf_accessible, severity: :error do |font|
|
|
47
|
+
glyf = font.table('glyf')
|
|
48
|
+
next true unless glyf # Skip if CFF font
|
|
49
|
+
|
|
50
|
+
loca = font.table('loca')
|
|
51
|
+
head = font.table('head')
|
|
52
|
+
maxp = font.table('maxp')
|
|
53
|
+
glyf.all_glyphs_accessible?(loca, head, maxp.num_glyphs)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Check 30: Glyf glyphs should not be clipped
|
|
57
|
+
check_glyphs :glyf_no_clipping, severity: :warning do |font|
|
|
58
|
+
glyf = font.table('glyf')
|
|
59
|
+
next true unless glyf
|
|
60
|
+
|
|
61
|
+
loca = font.table('loca')
|
|
62
|
+
head = font.table('head')
|
|
63
|
+
maxp = font.table('maxp')
|
|
64
|
+
glyf.no_clipped_glyphs?(loca, head, maxp.num_glyphs)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Check 31: Glyf contour counts must be valid
|
|
68
|
+
check_glyphs :glyf_valid_contours, severity: :error do |font|
|
|
69
|
+
glyf = font.table('glyf')
|
|
70
|
+
next true unless glyf
|
|
71
|
+
|
|
72
|
+
loca = font.table('loca')
|
|
73
|
+
head = font.table('head')
|
|
74
|
+
maxp = font.table('maxp')
|
|
75
|
+
|
|
76
|
+
(0...maxp.num_glyphs).all? do |glyph_id|
|
|
77
|
+
glyf.valid_contour_count?(glyph_id, loca, head)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Check 32: Cmap must have Unicode mapping
|
|
82
|
+
check_table :cmap_unicode_mapping, 'cmap', severity: :error do |table|
|
|
83
|
+
table.has_unicode_mapping?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Check 33: Cmap should have BMP coverage
|
|
87
|
+
check_table :cmap_bmp_coverage, 'cmap', severity: :warning do |table|
|
|
88
|
+
table.has_bmp_coverage?
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Check 34: Cmap must have format 4 subtable
|
|
92
|
+
check_table :cmap_format4, 'cmap', severity: :error do |table|
|
|
93
|
+
table.has_format_4_subtable?
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Check 35: Cmap glyph indices must be valid
|
|
97
|
+
check_structure :cmap_glyph_indices, severity: :error do |font|
|
|
98
|
+
cmap = font.table('cmap')
|
|
99
|
+
maxp = font.table('maxp')
|
|
100
|
+
cmap.valid_glyph_indices?(maxp.num_glyphs)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Check 36: Table checksums (info level - many fonts have mismatches)
|
|
104
|
+
check_structure :checksum_valid, severity: :info do |font|
|
|
105
|
+
# Table checksum validation (info level - for reference)
|
|
106
|
+
# Most fonts have checksum mismatches, so we make it info not error
|
|
107
|
+
true # Placeholder - actual checksum validation if desired
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "basic_validator"
|
|
4
|
+
require_relative "font_book_validator"
|
|
5
|
+
require_relative "opentype_validator"
|
|
6
|
+
require_relative "web_font_validator"
|
|
7
|
+
|
|
8
|
+
module Fontisan
|
|
9
|
+
module Validators
|
|
10
|
+
# ProfileLoader manages validation profiles and loads appropriate validators
|
|
11
|
+
#
|
|
12
|
+
# This class provides a registry of validation profiles, each configured for
|
|
13
|
+
# specific use cases. Profiles define which validator to use, loading mode,
|
|
14
|
+
# and severity thresholds.
|
|
15
|
+
#
|
|
16
|
+
# Available profiles:
|
|
17
|
+
# - indexability: Fast validation for font discovery (BasicValidator)
|
|
18
|
+
# - usability: Basic usability for installation (FontBookValidator)
|
|
19
|
+
# - production: Comprehensive quality checks (OpenTypeValidator)
|
|
20
|
+
# - web: Web embedding and optimization (WebFontValidator)
|
|
21
|
+
# - spec_compliance: Full OpenType spec compliance (OpenTypeValidator)
|
|
22
|
+
# - default: Alias for production profile
|
|
23
|
+
#
|
|
24
|
+
# @example Loading a profile
|
|
25
|
+
# validator = ProfileLoader.load(:production)
|
|
26
|
+
# report = validator.validate(font)
|
|
27
|
+
#
|
|
28
|
+
# @example Getting profile info
|
|
29
|
+
# info = ProfileLoader.profile_info(:web)
|
|
30
|
+
# puts info[:description]
|
|
31
|
+
class ProfileLoader
|
|
32
|
+
# Profile definitions (hardcoded, no YAML)
|
|
33
|
+
PROFILES = {
|
|
34
|
+
indexability: {
|
|
35
|
+
name: "Font Indexability",
|
|
36
|
+
description: "Fast validation for font discovery and indexing",
|
|
37
|
+
validator: "BasicValidator",
|
|
38
|
+
loading_mode: "metadata",
|
|
39
|
+
severity_threshold: "error",
|
|
40
|
+
},
|
|
41
|
+
usability: {
|
|
42
|
+
name: "Font Usability",
|
|
43
|
+
description: "Basic usability for installation",
|
|
44
|
+
validator: "FontBookValidator",
|
|
45
|
+
loading_mode: "full",
|
|
46
|
+
severity_threshold: "warning",
|
|
47
|
+
},
|
|
48
|
+
production: {
|
|
49
|
+
name: "Production Quality",
|
|
50
|
+
description: "Comprehensive quality checks",
|
|
51
|
+
validator: "OpenTypeValidator",
|
|
52
|
+
loading_mode: "full",
|
|
53
|
+
severity_threshold: "warning",
|
|
54
|
+
},
|
|
55
|
+
web: {
|
|
56
|
+
name: "Web Font Readiness",
|
|
57
|
+
description: "Web embedding and optimization",
|
|
58
|
+
validator: "WebFontValidator",
|
|
59
|
+
loading_mode: "full",
|
|
60
|
+
severity_threshold: "warning",
|
|
61
|
+
},
|
|
62
|
+
spec_compliance: {
|
|
63
|
+
name: "OpenType Specification",
|
|
64
|
+
description: "Full OpenType spec compliance",
|
|
65
|
+
validator: "OpenTypeValidator",
|
|
66
|
+
loading_mode: "full",
|
|
67
|
+
severity_threshold: "info",
|
|
68
|
+
},
|
|
69
|
+
default: {
|
|
70
|
+
name: "Default Profile",
|
|
71
|
+
description: "Default validation profile (alias for production)",
|
|
72
|
+
validator: "OpenTypeValidator",
|
|
73
|
+
loading_mode: "full",
|
|
74
|
+
severity_threshold: "warning",
|
|
75
|
+
},
|
|
76
|
+
}.freeze
|
|
77
|
+
|
|
78
|
+
class << self
|
|
79
|
+
# Load a validator for the specified profile
|
|
80
|
+
#
|
|
81
|
+
# @param profile_name [Symbol, String] Profile name
|
|
82
|
+
# @return [Validator] Validator instance for the profile
|
|
83
|
+
# @raise [ArgumentError] if profile name is unknown
|
|
84
|
+
#
|
|
85
|
+
# @example Load production validator
|
|
86
|
+
# validator = ProfileLoader.load(:production)
|
|
87
|
+
def load(profile_name)
|
|
88
|
+
profile_name = profile_name.to_sym
|
|
89
|
+
profile_config = PROFILES[profile_name]
|
|
90
|
+
|
|
91
|
+
unless profile_config
|
|
92
|
+
raise ArgumentError,
|
|
93
|
+
"Unknown profile: #{profile_name}. " \
|
|
94
|
+
"Available profiles: #{available_profiles.join(', ')}"
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
validator_class_name = profile_config[:validator]
|
|
98
|
+
validator_class = Validators.const_get(validator_class_name)
|
|
99
|
+
validator_class.new
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Get list of available profile names
|
|
103
|
+
#
|
|
104
|
+
# @return [Array<Symbol>] Array of profile names
|
|
105
|
+
#
|
|
106
|
+
# @example List available profiles
|
|
107
|
+
# ProfileLoader.available_profiles
|
|
108
|
+
# # => [:indexability, :usability, :production, :web, :spec_compliance, :default]
|
|
109
|
+
def available_profiles
|
|
110
|
+
PROFILES.keys
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Get profile configuration
|
|
114
|
+
#
|
|
115
|
+
# @param profile_name [Symbol, String] Profile name
|
|
116
|
+
# @return [Hash, nil] Profile configuration or nil if not found
|
|
117
|
+
#
|
|
118
|
+
# @example Get profile info
|
|
119
|
+
# info = ProfileLoader.profile_info(:web)
|
|
120
|
+
# puts info[:description]
|
|
121
|
+
def profile_info(profile_name)
|
|
122
|
+
PROFILES[profile_name.to_sym]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Get all profiles with their configurations
|
|
126
|
+
#
|
|
127
|
+
# @return [Hash] All profile configurations
|
|
128
|
+
#
|
|
129
|
+
# @example Get all profiles
|
|
130
|
+
# ProfileLoader.all_profiles.each do |name, config|
|
|
131
|
+
# puts "#{name}: #{config[:description]}"
|
|
132
|
+
# end
|
|
133
|
+
def all_profiles
|
|
134
|
+
PROFILES
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|