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
|
@@ -1,218 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Fontisan
|
|
4
|
-
module Validation
|
|
5
|
-
# VariableFontValidator validates variable font structure
|
|
6
|
-
#
|
|
7
|
-
# Validates:
|
|
8
|
-
# - fvar table structure
|
|
9
|
-
# - Axis definitions and ranges
|
|
10
|
-
# - Instance definitions
|
|
11
|
-
# - Variation table consistency
|
|
12
|
-
# - Metrics variation tables
|
|
13
|
-
#
|
|
14
|
-
# @example Validate a variable font
|
|
15
|
-
# validator = VariableFontValidator.new(font)
|
|
16
|
-
# errors = validator.validate
|
|
17
|
-
# puts "Found #{errors.length} errors" if errors.any?
|
|
18
|
-
class VariableFontValidator
|
|
19
|
-
# Initialize validator with font
|
|
20
|
-
#
|
|
21
|
-
# @param font [TrueTypeFont, OpenTypeFont] Font to validate
|
|
22
|
-
def initialize(font)
|
|
23
|
-
@font = font
|
|
24
|
-
@errors = []
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# Validate variable font
|
|
28
|
-
#
|
|
29
|
-
# @return [Array<String>] Array of error messages
|
|
30
|
-
def validate
|
|
31
|
-
return [] unless @font.has_table?("fvar")
|
|
32
|
-
|
|
33
|
-
validate_fvar_structure
|
|
34
|
-
validate_axes
|
|
35
|
-
validate_instances
|
|
36
|
-
validate_variation_tables
|
|
37
|
-
validate_metrics_variation
|
|
38
|
-
|
|
39
|
-
@errors
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
private
|
|
43
|
-
|
|
44
|
-
# Validate fvar table structure
|
|
45
|
-
#
|
|
46
|
-
# @return [void]
|
|
47
|
-
def validate_fvar_structure
|
|
48
|
-
fvar = @font.table("fvar")
|
|
49
|
-
return unless fvar
|
|
50
|
-
|
|
51
|
-
if !fvar.respond_to?(:axes) || fvar.axes.nil? || fvar.axes.empty?
|
|
52
|
-
@errors << "fvar: No axes defined"
|
|
53
|
-
return
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
if fvar.respond_to?(:axis_count) && fvar.axis_count != fvar.axes.length
|
|
57
|
-
@errors << "fvar: Axis count mismatch (expected #{fvar.axis_count}, got #{fvar.axes.length})"
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
# Validate all axes
|
|
62
|
-
#
|
|
63
|
-
# @return [void]
|
|
64
|
-
def validate_axes
|
|
65
|
-
fvar = @font.table("fvar")
|
|
66
|
-
return unless fvar.respond_to?(:axes)
|
|
67
|
-
|
|
68
|
-
fvar.axes.each_with_index do |axis, index|
|
|
69
|
-
validate_axis_range(axis, index)
|
|
70
|
-
validate_axis_tag(axis, index)
|
|
71
|
-
end
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
# Validate axis range values
|
|
75
|
-
#
|
|
76
|
-
# @param axis [Object] Axis object
|
|
77
|
-
# @param index [Integer] Axis index
|
|
78
|
-
# @return [void]
|
|
79
|
-
def validate_axis_range(axis, index)
|
|
80
|
-
return unless axis.respond_to?(:min_value) && axis.respond_to?(:max_value)
|
|
81
|
-
|
|
82
|
-
if axis.min_value > axis.max_value
|
|
83
|
-
tag = axis.respond_to?(:axis_tag) ? axis.axis_tag : "axis #{index}"
|
|
84
|
-
@errors << "Axis #{tag}: min_value (#{axis.min_value}) > max_value (#{axis.max_value})"
|
|
85
|
-
end
|
|
86
|
-
|
|
87
|
-
if axis.respond_to?(:default_value) && (axis.default_value < axis.min_value || axis.default_value > axis.max_value)
|
|
88
|
-
tag = axis.respond_to?(:axis_tag) ? axis.axis_tag : "axis #{index}"
|
|
89
|
-
@errors << "Axis #{tag}: default_value (#{axis.default_value}) out of range [#{axis.min_value}, #{axis.max_value}]"
|
|
90
|
-
end
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# Validate axis tag format
|
|
94
|
-
#
|
|
95
|
-
# @param axis [Object] Axis object
|
|
96
|
-
# @param index [Integer] Axis index
|
|
97
|
-
# @return [void]
|
|
98
|
-
def validate_axis_tag(axis, index)
|
|
99
|
-
return unless axis.respond_to?(:axis_tag)
|
|
100
|
-
|
|
101
|
-
tag = axis.axis_tag
|
|
102
|
-
unless tag.is_a?(String) && tag.length == 4 && tag =~ /^[a-zA-Z]{4}$/
|
|
103
|
-
@errors << "Axis #{index}: invalid tag '#{tag}' (must be 4 ASCII letters)"
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Validate named instances
|
|
108
|
-
#
|
|
109
|
-
# @return [void]
|
|
110
|
-
def validate_instances
|
|
111
|
-
fvar = @font.table("fvar")
|
|
112
|
-
return unless fvar.respond_to?(:instances)
|
|
113
|
-
return unless fvar.instances
|
|
114
|
-
|
|
115
|
-
fvar.instances.each_with_index do |instance, idx|
|
|
116
|
-
validate_instance_coordinates(instance, idx, fvar)
|
|
117
|
-
end
|
|
118
|
-
end
|
|
119
|
-
|
|
120
|
-
# Validate instance coordinates
|
|
121
|
-
#
|
|
122
|
-
# @param instance [Object] Instance object
|
|
123
|
-
# @param idx [Integer] Instance index
|
|
124
|
-
# @param fvar [Object] fvar table
|
|
125
|
-
# @return [void]
|
|
126
|
-
def validate_instance_coordinates(instance, idx, fvar)
|
|
127
|
-
return unless instance.is_a?(Hash) && instance[:coordinates]
|
|
128
|
-
|
|
129
|
-
coords = instance[:coordinates]
|
|
130
|
-
axis_count = fvar.respond_to?(:axis_count) ? fvar.axis_count : fvar.axes.length
|
|
131
|
-
|
|
132
|
-
if coords.length != axis_count
|
|
133
|
-
@errors << "Instance #{idx}: coordinate count mismatch (expected #{axis_count}, got #{coords.length})"
|
|
134
|
-
end
|
|
135
|
-
|
|
136
|
-
coords.each_with_index do |value, axis_idx|
|
|
137
|
-
next if axis_idx >= fvar.axes.length
|
|
138
|
-
|
|
139
|
-
axis = fvar.axes[axis_idx]
|
|
140
|
-
next unless axis.respond_to?(:min_value) && axis.respond_to?(:max_value)
|
|
141
|
-
|
|
142
|
-
if value < axis.min_value || value > axis.max_value
|
|
143
|
-
tag = axis.respond_to?(:axis_tag) ? axis.axis_tag : "axis #{axis_idx}"
|
|
144
|
-
@errors << "Instance #{idx}: coordinate for #{tag} (#{value}) out of range [#{axis.min_value}, #{axis.max_value}]"
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
# Validate variation tables
|
|
150
|
-
#
|
|
151
|
-
# @return [void]
|
|
152
|
-
def validate_variation_tables
|
|
153
|
-
has_gvar = @font.has_table?("gvar")
|
|
154
|
-
has_cff2 = @font.has_table?("CFF2")
|
|
155
|
-
has_glyf = @font.has_table?("glyf")
|
|
156
|
-
has_cff = @font.has_table?("CFF ")
|
|
157
|
-
|
|
158
|
-
# TrueType variable fonts should have gvar
|
|
159
|
-
if has_glyf && !has_gvar
|
|
160
|
-
@errors << "TrueType variable font missing gvar table"
|
|
161
|
-
end
|
|
162
|
-
|
|
163
|
-
# CFF variable fonts should have CFF2
|
|
164
|
-
if has_cff && !has_cff2
|
|
165
|
-
@errors << "CFF variable font missing CFF2 table"
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
# Can't have both gvar and CFF2
|
|
169
|
-
if has_gvar && has_cff2
|
|
170
|
-
@errors << "Font has both gvar and CFF2 tables (incompatible)"
|
|
171
|
-
end
|
|
172
|
-
end
|
|
173
|
-
|
|
174
|
-
# Validate metrics variation tables
|
|
175
|
-
#
|
|
176
|
-
# @return [void]
|
|
177
|
-
def validate_metrics_variation
|
|
178
|
-
validate_hvar if @font.has_table?("HVAR")
|
|
179
|
-
validate_vvar if @font.has_table?("VVAR")
|
|
180
|
-
validate_mvar if @font.has_table?("MVAR")
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
# Validate HVAR table
|
|
184
|
-
#
|
|
185
|
-
# @return [void]
|
|
186
|
-
def validate_hvar
|
|
187
|
-
# HVAR validation would go here
|
|
188
|
-
# For now, just check it exists
|
|
189
|
-
hvar = @font.table_data["HVAR"]
|
|
190
|
-
if hvar.nil? || hvar.empty?
|
|
191
|
-
@errors << "HVAR table is empty"
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
# Validate VVAR table
|
|
196
|
-
#
|
|
197
|
-
# @return [void]
|
|
198
|
-
def validate_vvar
|
|
199
|
-
# VVAR validation would go here
|
|
200
|
-
vvar = @font.table_data["VVAR"]
|
|
201
|
-
if vvar.nil? || vvar.empty?
|
|
202
|
-
@errors << "VVAR table is empty"
|
|
203
|
-
end
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
# Validate MVAR table
|
|
207
|
-
#
|
|
208
|
-
# @return [void]
|
|
209
|
-
def validate_mvar
|
|
210
|
-
# MVAR validation would go here
|
|
211
|
-
mvar = @font.table_data["MVAR"]
|
|
212
|
-
if mvar.nil? || mvar.empty?
|
|
213
|
-
@errors << "MVAR table is empty"
|
|
214
|
-
end
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
end
|
|
218
|
-
end
|
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module Fontisan
|
|
4
|
-
module Validation
|
|
5
|
-
# Woff2HeaderValidator validates the WOFF2 header structure
|
|
6
|
-
#
|
|
7
|
-
# This validator checks the WOFF2 header for:
|
|
8
|
-
# - Valid signature (0x774F4632 'wOF2')
|
|
9
|
-
# - Valid flavor (TrueType or CFF)
|
|
10
|
-
# - Reserved field is zero
|
|
11
|
-
# - File length consistency
|
|
12
|
-
# - Valid table count
|
|
13
|
-
# - Valid compressed size
|
|
14
|
-
# - Metadata offset/length consistency
|
|
15
|
-
# - Private data offset/length consistency
|
|
16
|
-
#
|
|
17
|
-
# Single Responsibility: WOFF2 header validation
|
|
18
|
-
#
|
|
19
|
-
# @example Validating a WOFF2 header
|
|
20
|
-
# validator = Woff2HeaderValidator.new(rules)
|
|
21
|
-
# issues = validator.validate(woff2_font)
|
|
22
|
-
class Woff2HeaderValidator
|
|
23
|
-
# Valid WOFF2 flavors
|
|
24
|
-
VALID_FLAVORS = [
|
|
25
|
-
0x00010000, # TrueType
|
|
26
|
-
0x74727565, # 'true' (TrueType)
|
|
27
|
-
0x4F54544F, # 'OTTO' (CFF/OpenType)
|
|
28
|
-
].freeze
|
|
29
|
-
|
|
30
|
-
# Initialize WOFF2 header validator
|
|
31
|
-
#
|
|
32
|
-
# @param rules [Hash] Validation rules configuration
|
|
33
|
-
def initialize(rules)
|
|
34
|
-
@rules = rules
|
|
35
|
-
@woff2_config = rules["woff2_validation"] || {}
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Validate WOFF2 header
|
|
39
|
-
#
|
|
40
|
-
# @param woff2_font [Woff2Font] The WOFF2 font to validate
|
|
41
|
-
# @return [Array<Hash>] Array of validation issues
|
|
42
|
-
def validate(woff2_font)
|
|
43
|
-
issues = []
|
|
44
|
-
|
|
45
|
-
header = woff2_font.header
|
|
46
|
-
return issues unless header
|
|
47
|
-
|
|
48
|
-
# Check signature
|
|
49
|
-
issues.concat(check_signature(header))
|
|
50
|
-
|
|
51
|
-
# Check flavor
|
|
52
|
-
issues.concat(check_flavor(header))
|
|
53
|
-
|
|
54
|
-
# Check reserved field
|
|
55
|
-
issues.concat(check_reserved_field(header))
|
|
56
|
-
|
|
57
|
-
# Check table count
|
|
58
|
-
issues.concat(check_table_count(header, woff2_font))
|
|
59
|
-
|
|
60
|
-
# Check compressed size
|
|
61
|
-
issues.concat(check_compressed_size(header))
|
|
62
|
-
|
|
63
|
-
# Check metadata consistency
|
|
64
|
-
issues.concat(check_metadata(header))
|
|
65
|
-
|
|
66
|
-
# Check private data consistency
|
|
67
|
-
issues.concat(check_private_data(header))
|
|
68
|
-
|
|
69
|
-
issues
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
private
|
|
73
|
-
|
|
74
|
-
# Check signature validity
|
|
75
|
-
#
|
|
76
|
-
# @param header [Woff2::Woff2Header] The header
|
|
77
|
-
# @return [Array<Hash>] Array of signature issues
|
|
78
|
-
def check_signature(header)
|
|
79
|
-
issues = []
|
|
80
|
-
|
|
81
|
-
unless header.signature == Woff2::Woff2Header::SIGNATURE
|
|
82
|
-
issues << {
|
|
83
|
-
severity: "error",
|
|
84
|
-
category: "woff2_header",
|
|
85
|
-
message: "Invalid WOFF2 signature: expected 0x#{Woff2::Woff2Header::SIGNATURE.to_s(16)}, " \
|
|
86
|
-
"got 0x#{header.signature.to_s(16)}",
|
|
87
|
-
location: "header",
|
|
88
|
-
}
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
issues
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
# Check flavor validity
|
|
95
|
-
#
|
|
96
|
-
# @param header [Woff2::Woff2Header] The header
|
|
97
|
-
# @return [Array<Hash>] Array of flavor issues
|
|
98
|
-
def check_flavor(header)
|
|
99
|
-
issues = []
|
|
100
|
-
|
|
101
|
-
unless VALID_FLAVORS.include?(header.flavor)
|
|
102
|
-
issues << {
|
|
103
|
-
severity: "error",
|
|
104
|
-
category: "woff2_header",
|
|
105
|
-
message: "Invalid WOFF2 flavor: 0x#{header.flavor.to_s(16)} " \
|
|
106
|
-
"(expected TrueType 0x00010000 or CFF 0x4F54544F)",
|
|
107
|
-
location: "header",
|
|
108
|
-
}
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
issues
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Check reserved field
|
|
115
|
-
#
|
|
116
|
-
# @param header [Woff2::Woff2Header] The header
|
|
117
|
-
# @return [Array<Hash>] Array of reserved field issues
|
|
118
|
-
def check_reserved_field(header)
|
|
119
|
-
issues = []
|
|
120
|
-
|
|
121
|
-
if header.reserved != 0
|
|
122
|
-
issues << {
|
|
123
|
-
severity: "warning",
|
|
124
|
-
category: "woff2_header",
|
|
125
|
-
message: "Reserved field should be 0, got #{header.reserved}",
|
|
126
|
-
location: "header",
|
|
127
|
-
}
|
|
128
|
-
end
|
|
129
|
-
|
|
130
|
-
issues
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
# Check table count validity
|
|
134
|
-
#
|
|
135
|
-
# @param header [Woff2::Woff2Header] The header
|
|
136
|
-
# @param woff2_font [Woff2Font] The WOFF2 font
|
|
137
|
-
# @return [Array<Hash>] Array of table count issues
|
|
138
|
-
def check_table_count(header, woff2_font)
|
|
139
|
-
issues = []
|
|
140
|
-
|
|
141
|
-
if header.num_tables.zero?
|
|
142
|
-
issues << {
|
|
143
|
-
severity: "error",
|
|
144
|
-
category: "woff2_header",
|
|
145
|
-
message: "Number of tables cannot be zero",
|
|
146
|
-
location: "header",
|
|
147
|
-
}
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# Check consistency with actual table entries
|
|
151
|
-
actual_count = woff2_font.table_entries.length
|
|
152
|
-
if header.num_tables != actual_count
|
|
153
|
-
issues << {
|
|
154
|
-
severity: "error",
|
|
155
|
-
category: "woff2_header",
|
|
156
|
-
message: "Table count mismatch: header=#{header.num_tables}, actual=#{actual_count}",
|
|
157
|
-
location: "header",
|
|
158
|
-
}
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
issues
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
# Check compressed size validity
|
|
165
|
-
#
|
|
166
|
-
# @param header [Woff2::Woff2Header] The header
|
|
167
|
-
# @return [Array<Hash>] Array of compressed size issues
|
|
168
|
-
def check_compressed_size(header)
|
|
169
|
-
issues = []
|
|
170
|
-
|
|
171
|
-
if header.total_compressed_size.zero?
|
|
172
|
-
issues << {
|
|
173
|
-
severity: "error",
|
|
174
|
-
category: "woff2_header",
|
|
175
|
-
message: "Total compressed size cannot be zero",
|
|
176
|
-
location: "header",
|
|
177
|
-
}
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Check compression ratio
|
|
181
|
-
if header.total_sfnt_size.positive? && header.total_compressed_size.positive?
|
|
182
|
-
ratio = header.total_compressed_size.to_f / header.total_sfnt_size
|
|
183
|
-
min_ratio = @woff2_config["min_compression_ratio"] || 0.2
|
|
184
|
-
max_ratio = @woff2_config["max_compression_ratio"] || 0.95
|
|
185
|
-
|
|
186
|
-
if ratio < min_ratio
|
|
187
|
-
issues << {
|
|
188
|
-
severity: "warning",
|
|
189
|
-
category: "woff2_header",
|
|
190
|
-
message: "Compression ratio (#{(ratio * 100).round(2)}%) is unusually low",
|
|
191
|
-
location: "header",
|
|
192
|
-
}
|
|
193
|
-
elsif ratio > max_ratio
|
|
194
|
-
issues << {
|
|
195
|
-
severity: "warning",
|
|
196
|
-
category: "woff2_header",
|
|
197
|
-
message: "Compression ratio (#{(ratio * 100).round(2)}%) is unusually high",
|
|
198
|
-
location: "header",
|
|
199
|
-
}
|
|
200
|
-
end
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
issues
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
# Check metadata consistency
|
|
207
|
-
#
|
|
208
|
-
# @param header [Woff2::Woff2Header] The header
|
|
209
|
-
# @return [Array<Hash>] Array of metadata issues
|
|
210
|
-
def check_metadata(header)
|
|
211
|
-
issues = []
|
|
212
|
-
|
|
213
|
-
# If metadata offset is set, length must be positive
|
|
214
|
-
if header.meta_offset.positive? && header.meta_length.zero?
|
|
215
|
-
issues << {
|
|
216
|
-
severity: "warning",
|
|
217
|
-
category: "woff2_header",
|
|
218
|
-
message: "Metadata offset is set but length is zero",
|
|
219
|
-
location: "header",
|
|
220
|
-
}
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
# If metadata length is set, offset must be positive
|
|
224
|
-
if header.meta_length.positive? && header.meta_offset.zero?
|
|
225
|
-
issues << {
|
|
226
|
-
severity: "warning",
|
|
227
|
-
category: "woff2_header",
|
|
228
|
-
message: "Metadata length is set but offset is zero",
|
|
229
|
-
location: "header",
|
|
230
|
-
}
|
|
231
|
-
end
|
|
232
|
-
|
|
233
|
-
# Original length should be >= compressed length
|
|
234
|
-
if header.meta_orig_length.positive? && header.meta_length.positive? && (header.meta_orig_length < header.meta_length)
|
|
235
|
-
issues << {
|
|
236
|
-
severity: "error",
|
|
237
|
-
category: "woff2_header",
|
|
238
|
-
message: "Metadata original length (#{header.meta_orig_length}) " \
|
|
239
|
-
"is less than compressed length (#{header.meta_length})",
|
|
240
|
-
location: "header",
|
|
241
|
-
}
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
issues
|
|
245
|
-
end
|
|
246
|
-
|
|
247
|
-
# Check private data consistency
|
|
248
|
-
#
|
|
249
|
-
# @param header [Woff2::Woff2Header] The header
|
|
250
|
-
# @return [Array<Hash>] Array of private data issues
|
|
251
|
-
def check_private_data(header)
|
|
252
|
-
issues = []
|
|
253
|
-
|
|
254
|
-
# If private offset is set, length must be positive
|
|
255
|
-
if header.priv_offset.positive? && header.priv_length.zero?
|
|
256
|
-
issues << {
|
|
257
|
-
severity: "warning",
|
|
258
|
-
category: "woff2_header",
|
|
259
|
-
message: "Private data offset is set but length is zero",
|
|
260
|
-
location: "header",
|
|
261
|
-
}
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
# If private length is set, offset must be positive
|
|
265
|
-
if header.priv_length.positive? && header.priv_offset.zero?
|
|
266
|
-
issues << {
|
|
267
|
-
severity: "warning",
|
|
268
|
-
category: "woff2_header",
|
|
269
|
-
message: "Private data length is set but offset is zero",
|
|
270
|
-
location: "header",
|
|
271
|
-
}
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
issues
|
|
275
|
-
end
|
|
276
|
-
end
|
|
277
|
-
end
|
|
278
|
-
end
|