fontisan 0.2.3 → 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 +221 -49
- data/README.adoc +519 -5
- data/Rakefile +20 -7
- data/lib/fontisan/cli.rb +67 -6
- data/lib/fontisan/commands/base_command.rb +2 -19
- data/lib/fontisan/commands/convert_command.rb +16 -13
- data/lib/fontisan/commands/info_command.rb +88 -0
- data/lib/fontisan/commands/validate_command.rb +107 -151
- data/lib/fontisan/config/conversion_matrix.yml +58 -20
- data/lib/fontisan/converters/outline_converter.rb +6 -3
- data/lib/fontisan/converters/svg_generator.rb +45 -0
- data/lib/fontisan/converters/woff2_encoder.rb +84 -13
- data/lib/fontisan/models/bitmap_glyph.rb +123 -0
- data/lib/fontisan/models/bitmap_strike.rb +94 -0
- data/lib/fontisan/models/color_glyph.rb +57 -0
- data/lib/fontisan/models/color_layer.rb +53 -0
- data/lib/fontisan/models/color_palette.rb +60 -0
- data/lib/fontisan/models/font_info.rb +26 -0
- data/lib/fontisan/models/svg_glyph.rb +89 -0
- data/lib/fontisan/models/validation_report.rb +227 -0
- data/lib/fontisan/open_type_font.rb +6 -0
- data/lib/fontisan/optimizers/charstring_rewriter.rb +19 -8
- data/lib/fontisan/optimizers/pattern_analyzer.rb +4 -2
- data/lib/fontisan/optimizers/subroutine_builder.rb +6 -5
- data/lib/fontisan/optimizers/subroutine_optimizer.rb +5 -2
- data/lib/fontisan/pipeline/output_writer.rb +2 -2
- data/lib/fontisan/pipeline/transformation_pipeline.rb +4 -8
- data/lib/fontisan/tables/cbdt.rb +169 -0
- data/lib/fontisan/tables/cblc.rb +290 -0
- data/lib/fontisan/tables/cff.rb +6 -12
- data/lib/fontisan/tables/cmap.rb +82 -2
- data/lib/fontisan/tables/colr.rb +291 -0
- data/lib/fontisan/tables/cpal.rb +281 -0
- data/lib/fontisan/tables/glyf/glyph_builder.rb +5 -1
- 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/tables/sbix.rb +379 -0
- data/lib/fontisan/tables/svg.rb +301 -0
- data/lib/fontisan/true_type_font.rb +6 -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/woff2/directory.rb +40 -11
- data/lib/fontisan/woff2/table_transformer.rb +506 -73
- data/lib/fontisan/woff2_font.rb +29 -9
- data/lib/fontisan/woff_font.rb +17 -4
- data/lib/fontisan.rb +90 -6
- metadata +20 -9
- 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
|
@@ -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
|