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
data/lib/fontisan/woff_font.rb
CHANGED
|
@@ -78,11 +78,13 @@ module Fontisan
|
|
|
78
78
|
# Read WOFF font from a file
|
|
79
79
|
#
|
|
80
80
|
# @param path [String] Path to the WOFF file
|
|
81
|
+
# @param mode [Symbol] Loading mode (:metadata or :full) - currently ignored, loads all tables
|
|
82
|
+
# @param lazy [Boolean] Lazy loading flag - currently ignored, always eager
|
|
81
83
|
# @return [WoffFont] A new instance
|
|
82
84
|
# @raise [ArgumentError] if path is nil or empty
|
|
83
85
|
# @raise [Errno::ENOENT] if file does not exist
|
|
84
86
|
# @raise [InvalidFontError] if file format is invalid
|
|
85
|
-
def self.from_file(path)
|
|
87
|
+
def self.from_file(path, mode: LoadingModes::FULL, lazy: false)
|
|
86
88
|
if path.nil? || path.to_s.empty?
|
|
87
89
|
raise ArgumentError,
|
|
88
90
|
"path cannot be nil or empty"
|
|
@@ -158,9 +160,20 @@ module Fontisan
|
|
|
158
160
|
#
|
|
159
161
|
# Decompresses table data on first access and caches result
|
|
160
162
|
#
|
|
161
|
-
# @param tag [String] The table tag
|
|
162
|
-
# @return [String, nil] Decompressed table data or nil if not found
|
|
163
|
-
def table_data(tag)
|
|
163
|
+
# @param tag [String, nil] The table tag (optional)
|
|
164
|
+
# @return [String, Hash, nil] Decompressed table data, hash of all tables, or nil if not found
|
|
165
|
+
def table_data(tag = nil)
|
|
166
|
+
# If no tag provided, return all tables
|
|
167
|
+
if tag.nil?
|
|
168
|
+
# Decompress all tables and return as hash
|
|
169
|
+
result = {}
|
|
170
|
+
@compressed_table_data.each_key do |table_tag|
|
|
171
|
+
result[table_tag] = table_data(table_tag)
|
|
172
|
+
end
|
|
173
|
+
return result
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
# Tag provided - return specific table
|
|
164
177
|
return @decompressed_tables[tag] if @decompressed_tables.key?(tag)
|
|
165
178
|
|
|
166
179
|
compressed_data = @compressed_table_data[tag]
|
data/lib/fontisan.rb
CHANGED
|
@@ -69,6 +69,12 @@ require_relative "fontisan/tables/cff"
|
|
|
69
69
|
require_relative "fontisan/tables/layout_common"
|
|
70
70
|
require_relative "fontisan/tables/gsub"
|
|
71
71
|
require_relative "fontisan/tables/gpos"
|
|
72
|
+
require_relative "fontisan/tables/colr"
|
|
73
|
+
require_relative "fontisan/tables/cpal"
|
|
74
|
+
require_relative "fontisan/tables/svg"
|
|
75
|
+
require_relative "fontisan/tables/sbix"
|
|
76
|
+
require_relative "fontisan/tables/cblc"
|
|
77
|
+
require_relative "fontisan/tables/cbdt"
|
|
72
78
|
|
|
73
79
|
# Domain objects (BinData::Record)
|
|
74
80
|
require_relative "fontisan/true_type_font"
|
|
@@ -93,6 +99,8 @@ require_relative "fontisan/utilities/checksum_calculator"
|
|
|
93
99
|
require_relative "fontisan/font_writer"
|
|
94
100
|
|
|
95
101
|
# Information models (Lutaml::Model)
|
|
102
|
+
require_relative "fontisan/models/bitmap_strike"
|
|
103
|
+
require_relative "fontisan/models/bitmap_glyph"
|
|
96
104
|
require_relative "fontisan/models/font_info"
|
|
97
105
|
require_relative "fontisan/models/table_info"
|
|
98
106
|
require_relative "fontisan/models/glyph_info"
|
|
@@ -111,6 +119,18 @@ require_relative "fontisan/models/collection_brief_info"
|
|
|
111
119
|
require_relative "fontisan/models/collection_list_info"
|
|
112
120
|
require_relative "fontisan/models/font_summary"
|
|
113
121
|
require_relative "fontisan/models/table_sharing_info"
|
|
122
|
+
require_relative "fontisan/models/color_glyph"
|
|
123
|
+
require_relative "fontisan/models/color_layer"
|
|
124
|
+
require_relative "fontisan/models/color_palette"
|
|
125
|
+
require_relative "fontisan/models/svg_glyph"
|
|
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"
|
|
114
134
|
|
|
115
135
|
# Export infrastructure
|
|
116
136
|
require_relative "fontisan/export/table_serializer"
|
|
@@ -118,12 +138,16 @@ require_relative "fontisan/export/ttx_generator"
|
|
|
118
138
|
require_relative "fontisan/export/ttx_parser"
|
|
119
139
|
require_relative "fontisan/export/exporter"
|
|
120
140
|
|
|
121
|
-
# Validation infrastructure
|
|
122
|
-
|
|
123
|
-
require_relative "fontisan/validation/
|
|
124
|
-
require_relative "fontisan/validation/
|
|
125
|
-
require_relative "fontisan/validation/
|
|
126
|
-
require_relative "fontisan/validation/
|
|
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"
|
|
127
151
|
|
|
128
152
|
# Subsetting infrastructure
|
|
129
153
|
require_relative "fontisan/subset/options"
|
|
@@ -249,4 +273,64 @@ module Fontisan
|
|
|
249
273
|
def self.info(path, brief: false, font_index: 0)
|
|
250
274
|
Commands::InfoCommand.new(path, brief: brief, font_index: font_index).run
|
|
251
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
|
|
252
336
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fontisan
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.5
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ribose Inc.
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date:
|
|
11
|
+
date: 2026-01-03 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: base64
|
|
@@ -156,7 +156,6 @@ files:
|
|
|
156
156
|
- lib/fontisan/config/scripts.yml
|
|
157
157
|
- lib/fontisan/config/subset_profiles.yml
|
|
158
158
|
- lib/fontisan/config/svg_settings.yml
|
|
159
|
-
- lib/fontisan/config/validation_rules.yml
|
|
160
159
|
- lib/fontisan/config/variable_settings.yml
|
|
161
160
|
- lib/fontisan/config/woff2_settings.yml
|
|
162
161
|
- lib/fontisan/constants.rb
|
|
@@ -194,10 +193,15 @@ files:
|
|
|
194
193
|
- lib/fontisan/loading_modes.rb
|
|
195
194
|
- lib/fontisan/metrics_calculator.rb
|
|
196
195
|
- lib/fontisan/models/all_scripts_features_info.rb
|
|
196
|
+
- lib/fontisan/models/bitmap_glyph.rb
|
|
197
|
+
- lib/fontisan/models/bitmap_strike.rb
|
|
197
198
|
- lib/fontisan/models/collection_brief_info.rb
|
|
198
199
|
- lib/fontisan/models/collection_font_summary.rb
|
|
199
200
|
- lib/fontisan/models/collection_info.rb
|
|
200
201
|
- lib/fontisan/models/collection_list_info.rb
|
|
202
|
+
- lib/fontisan/models/color_glyph.rb
|
|
203
|
+
- lib/fontisan/models/color_layer.rb
|
|
204
|
+
- lib/fontisan/models/color_palette.rb
|
|
201
205
|
- lib/fontisan/models/features_info.rb
|
|
202
206
|
- lib/fontisan/models/font_export.rb
|
|
203
207
|
- lib/fontisan/models/font_info.rb
|
|
@@ -208,6 +212,7 @@ files:
|
|
|
208
212
|
- lib/fontisan/models/optical_size_info.rb
|
|
209
213
|
- lib/fontisan/models/outline.rb
|
|
210
214
|
- lib/fontisan/models/scripts_info.rb
|
|
215
|
+
- lib/fontisan/models/svg_glyph.rb
|
|
211
216
|
- lib/fontisan/models/table_info.rb
|
|
212
217
|
- lib/fontisan/models/table_sharing_info.rb
|
|
213
218
|
- lib/fontisan/models/ttx/glyph_order.rb
|
|
@@ -250,6 +255,8 @@ files:
|
|
|
250
255
|
- lib/fontisan/svg/font_generator.rb
|
|
251
256
|
- lib/fontisan/svg/glyph_generator.rb
|
|
252
257
|
- lib/fontisan/svg/view_box_calculator.rb
|
|
258
|
+
- lib/fontisan/tables/cbdt.rb
|
|
259
|
+
- lib/fontisan/tables/cblc.rb
|
|
253
260
|
- lib/fontisan/tables/cff.rb
|
|
254
261
|
- lib/fontisan/tables/cff/cff_glyph.rb
|
|
255
262
|
- lib/fontisan/tables/cff/charset.rb
|
|
@@ -280,6 +287,8 @@ files:
|
|
|
280
287
|
- lib/fontisan/tables/cff2/table_reader.rb
|
|
281
288
|
- lib/fontisan/tables/cff2/variation_data_extractor.rb
|
|
282
289
|
- lib/fontisan/tables/cmap.rb
|
|
290
|
+
- lib/fontisan/tables/colr.rb
|
|
291
|
+
- lib/fontisan/tables/cpal.rb
|
|
283
292
|
- lib/fontisan/tables/cvar.rb
|
|
284
293
|
- lib/fontisan/tables/fvar.rb
|
|
285
294
|
- lib/fontisan/tables/glyf.rb
|
|
@@ -302,6 +311,8 @@ files:
|
|
|
302
311
|
- lib/fontisan/tables/name.rb
|
|
303
312
|
- lib/fontisan/tables/os2.rb
|
|
304
313
|
- lib/fontisan/tables/post.rb
|
|
314
|
+
- lib/fontisan/tables/sbix.rb
|
|
315
|
+
- lib/fontisan/tables/svg.rb
|
|
305
316
|
- lib/fontisan/tables/variation_common.rb
|
|
306
317
|
- lib/fontisan/tables/vvar.rb
|
|
307
318
|
- lib/fontisan/true_type_collection.rb
|
|
@@ -310,12 +321,12 @@ files:
|
|
|
310
321
|
- lib/fontisan/utilities/brotli_wrapper.rb
|
|
311
322
|
- lib/fontisan/utilities/checksum_calculator.rb
|
|
312
323
|
- lib/fontisan/utils/thread_pool.rb
|
|
313
|
-
- lib/fontisan/
|
|
314
|
-
- lib/fontisan/
|
|
315
|
-
- lib/fontisan/
|
|
316
|
-
- lib/fontisan/
|
|
317
|
-
- lib/fontisan/
|
|
318
|
-
- lib/fontisan/
|
|
324
|
+
- lib/fontisan/validators/basic_validator.rb
|
|
325
|
+
- lib/fontisan/validators/font_book_validator.rb
|
|
326
|
+
- lib/fontisan/validators/opentype_validator.rb
|
|
327
|
+
- lib/fontisan/validators/profile_loader.rb
|
|
328
|
+
- lib/fontisan/validators/validator.rb
|
|
329
|
+
- lib/fontisan/validators/web_font_validator.rb
|
|
319
330
|
- lib/fontisan/variable/axis_normalizer.rb
|
|
320
331
|
- lib/fontisan/variable/delta_applicator.rb
|
|
321
332
|
- lib/fontisan/variable/glyph_delta_processor.rb
|
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
# Font Validation Rules Configuration
|
|
2
|
-
#
|
|
3
|
-
# This file defines validation rules for different font types and validation levels.
|
|
4
|
-
# The rules determine which checks are performed and their severity thresholds.
|
|
5
|
-
|
|
6
|
-
# Required tables for different font types
|
|
7
|
-
required_tables:
|
|
8
|
-
# Tables required for all fonts
|
|
9
|
-
all:
|
|
10
|
-
- head
|
|
11
|
-
- name
|
|
12
|
-
- cmap
|
|
13
|
-
- maxp
|
|
14
|
-
- hhea
|
|
15
|
-
- hmtx
|
|
16
|
-
- post
|
|
17
|
-
- OS/2
|
|
18
|
-
|
|
19
|
-
# Additional tables required for TrueType fonts (.ttf)
|
|
20
|
-
truetype:
|
|
21
|
-
- glyf
|
|
22
|
-
- loca
|
|
23
|
-
|
|
24
|
-
# Additional tables required for OpenType/CFF fonts (.otf)
|
|
25
|
-
opentype_cff:
|
|
26
|
-
- "CFF " # or CFF2 for CFF2 format (note: CFF has trailing space per OpenType spec)
|
|
27
|
-
|
|
28
|
-
# Additional tables required for variable fonts
|
|
29
|
-
variable:
|
|
30
|
-
- fvar
|
|
31
|
-
- gvar # For TrueType variable fonts
|
|
32
|
-
- HVAR # Horizontal metrics variations (optional but recommended)
|
|
33
|
-
- MVAR # Metrics variations (optional)
|
|
34
|
-
|
|
35
|
-
# Validation levels define which checks to perform
|
|
36
|
-
validation_levels:
|
|
37
|
-
# Strict: All checks must pass, no warnings allowed
|
|
38
|
-
strict:
|
|
39
|
-
check_required_tables: true
|
|
40
|
-
check_table_checksums: true
|
|
41
|
-
check_head_checksum_adjustment: true
|
|
42
|
-
check_glyph_consistency: true
|
|
43
|
-
check_cmap_references: true
|
|
44
|
-
check_hmtx_consistency: true
|
|
45
|
-
check_name_consistency: true
|
|
46
|
-
check_variable_consistency: true
|
|
47
|
-
check_table_offsets: true
|
|
48
|
-
check_table_ordering: false # Not critical for correctness
|
|
49
|
-
allow_warnings: false
|
|
50
|
-
allow_info: true
|
|
51
|
-
|
|
52
|
-
# Standard: Most checks enabled, warnings allowed
|
|
53
|
-
standard:
|
|
54
|
-
check_required_tables: true
|
|
55
|
-
check_table_checksums: true
|
|
56
|
-
check_head_checksum_adjustment: true
|
|
57
|
-
check_glyph_consistency: true
|
|
58
|
-
check_cmap_references: true
|
|
59
|
-
check_hmtx_consistency: true
|
|
60
|
-
check_name_consistency: true
|
|
61
|
-
check_variable_consistency: true
|
|
62
|
-
check_table_offsets: true
|
|
63
|
-
check_table_ordering: false
|
|
64
|
-
allow_warnings: true
|
|
65
|
-
allow_info: true
|
|
66
|
-
|
|
67
|
-
# Lenient: Basic checks only, many warnings allowed
|
|
68
|
-
lenient:
|
|
69
|
-
check_required_tables: true
|
|
70
|
-
check_table_checksums: false # Skip checksum validation
|
|
71
|
-
check_head_checksum_adjustment: false
|
|
72
|
-
check_glyph_consistency: true
|
|
73
|
-
check_cmap_references: false # Skip cmap validation
|
|
74
|
-
check_hmtx_consistency: true
|
|
75
|
-
check_name_consistency: false
|
|
76
|
-
check_variable_consistency: false
|
|
77
|
-
check_table_offsets: true
|
|
78
|
-
check_table_ordering: false
|
|
79
|
-
allow_warnings: true
|
|
80
|
-
allow_info: true
|
|
81
|
-
|
|
82
|
-
# Error messages templates
|
|
83
|
-
error_messages:
|
|
84
|
-
missing_table: "Missing required table: %{table}"
|
|
85
|
-
invalid_table_checksum: "Table '%{table}' checksum mismatch (expected: %{expected}, got: %{actual})"
|
|
86
|
-
invalid_head_checksum: "Invalid head table checksum adjustment"
|
|
87
|
-
glyph_count_mismatch: "Glyph count mismatch: maxp=%{maxp}, actual=%{actual}"
|
|
88
|
-
invalid_cmap_reference: "cmap references non-existent glyph ID %{glyph_id}"
|
|
89
|
-
hmtx_count_mismatch: "hmtx entries (%{hmtx}) don't match glyph count (%{glyph_count})"
|
|
90
|
-
invalid_table_offset: "Table '%{table}' has invalid offset: %{offset}"
|
|
91
|
-
variable_table_missing: "Variable font missing required table: %{table}"
|
|
92
|
-
fvar_gvar_mismatch: "fvar axis count (%{fvar}) doesn't match gvar axis count (%{gvar})"
|
|
93
|
-
|
|
94
|
-
# Warning messages templates
|
|
95
|
-
warning_messages:
|
|
96
|
-
table_checksum_mismatch: "Table '%{table}' checksum mismatch"
|
|
97
|
-
suboptimal_table_order: "Tables not in optimal order for performance"
|
|
98
|
-
missing_optional_table: "Optional table '%{table}' not present (recommended for %{reason})"
|
|
99
|
-
name_inconsistency: "Name table inconsistency: %{issue}"
|
|
100
|
-
large_font_size: "Font file size (%{size} bytes) is unusually large"
|
|
101
|
-
|
|
102
|
-
# Info messages templates
|
|
103
|
-
info_messages:
|
|
104
|
-
optimization_opportunity: "Font could benefit from %{optimization}"
|
|
105
|
-
subsetting_recommended: "Font contains %{glyph_count} glyphs; subsetting may reduce size"
|
|
106
|
-
compression_recommended: "Font could benefit from WOFF2 compression"
|
|
107
|
-
|
|
108
|
-
# Checksum validation settings
|
|
109
|
-
checksum_validation:
|
|
110
|
-
# Magic number for checksum adjustment calculation
|
|
111
|
-
magic: 0xB1B0AFBA
|
|
112
|
-
|
|
113
|
-
# Tables exempt from checksum validation (some tables have dynamic content)
|
|
114
|
-
skip_tables:
|
|
115
|
-
- DSIG # Digital signature table (changes with signing)
|
|
116
|
-
|
|
117
|
-
# Consistency checks configuration
|
|
118
|
-
consistency_checks:
|
|
119
|
-
# Maximum acceptable glyph ID for cmap validation
|
|
120
|
-
max_glyph_id_multiplier: 2.0 # Allow up to 2x maxp.numGlyphs for safety
|
|
121
|
-
|
|
122
|
-
# Minimum acceptable glyph count
|
|
123
|
-
min_glyph_count: 1
|
|
124
|
-
|
|
125
|
-
# Maximum acceptable glyph count (sanity check)
|
|
126
|
-
max_glyph_count: 65536
|
|
127
|
-
|
|
128
|
-
# Structure validation settings
|
|
129
|
-
structure_validation:
|
|
130
|
-
# Minimum valid offset (after header and directory)
|
|
131
|
-
min_table_offset: 12
|
|
132
|
-
|
|
133
|
-
# Table alignment requirement (4-byte alignment)
|
|
134
|
-
table_alignment: 4
|
|
135
|
-
|
|
136
|
-
# Maximum reasonable table size (100MB)
|
|
137
|
-
max_table_size: 104857600
|
|
138
|
-
|
|
139
|
-
# Variable font specific validation
|
|
140
|
-
variable_validation:
|
|
141
|
-
# Check that all required variation tables are consistent
|
|
142
|
-
check_axis_consistency: true
|
|
143
|
-
|
|
144
|
-
# Check that variation regions are valid
|
|
145
|
-
check_region_validity: true
|
|
146
|
-
|
|
147
|
-
# Check that deltas are within reasonable bounds
|
|
148
|
-
max_delta_value: 32767
|
|
149
|
-
min_delta_value: -32768
|
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require_relative "../utilities/checksum_calculator"
|
|
4
|
-
|
|
5
|
-
module Fontisan
|
|
6
|
-
module Validation
|
|
7
|
-
# ChecksumValidator validates font file and table checksums
|
|
8
|
-
#
|
|
9
|
-
# This validator checks that the head table checksum adjustment is correct
|
|
10
|
-
# and validates individual table checksums to ensure file integrity.
|
|
11
|
-
#
|
|
12
|
-
# Single Responsibility: Checksum validation and file integrity
|
|
13
|
-
#
|
|
14
|
-
# @example Validating checksums
|
|
15
|
-
# validator = ChecksumValidator.new(rules)
|
|
16
|
-
# issues = validator.validate(font, font_path)
|
|
17
|
-
class ChecksumValidator
|
|
18
|
-
# Initialize checksum validator
|
|
19
|
-
#
|
|
20
|
-
# @param rules [Hash] Validation rules configuration
|
|
21
|
-
def initialize(rules)
|
|
22
|
-
@rules = rules
|
|
23
|
-
@checksum_config = rules["checksum_validation"] || {}
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
# Validate font checksums
|
|
27
|
-
#
|
|
28
|
-
# @param font [TrueTypeFont, OpenTypeFont] The font to validate
|
|
29
|
-
# @param font_path [String] Path to the font file
|
|
30
|
-
# @return [Array<Hash>] Array of validation issues
|
|
31
|
-
def validate(font, font_path)
|
|
32
|
-
issues = []
|
|
33
|
-
|
|
34
|
-
# Check head table checksum adjustment if enabled
|
|
35
|
-
if should_check?("check_head_checksum_adjustment")
|
|
36
|
-
issues.concat(check_head_checksum_adjustment(font, font_path))
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# Check individual table checksums if enabled
|
|
40
|
-
if should_check?("check_table_checksums")
|
|
41
|
-
issues.concat(check_table_checksums(font))
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
issues
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
private
|
|
48
|
-
|
|
49
|
-
# Check if a validation should be performed
|
|
50
|
-
#
|
|
51
|
-
# @param check_name [String] The check name
|
|
52
|
-
# @return [Boolean] true if check should be performed
|
|
53
|
-
def should_check?(check_name)
|
|
54
|
-
@rules.dig("validation_levels", "standard", check_name)
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Check head table checksum adjustment
|
|
58
|
-
#
|
|
59
|
-
# @param font [TrueTypeFont, OpenTypeFont] The font
|
|
60
|
-
# @param font_path [String] Path to the font file
|
|
61
|
-
# @return [Array<Hash>] Array of checksum issues
|
|
62
|
-
def check_head_checksum_adjustment(font, font_path)
|
|
63
|
-
issues = []
|
|
64
|
-
|
|
65
|
-
head_entry = font.head_table
|
|
66
|
-
return issues unless head_entry
|
|
67
|
-
|
|
68
|
-
# Calculate the checksum of the entire font file
|
|
69
|
-
begin
|
|
70
|
-
file_checksum = Utilities::ChecksumCalculator.calculate_file_checksum(font_path)
|
|
71
|
-
magic = @checksum_config["magic"] || Constants::CHECKSUM_ADJUSTMENT_MAGIC
|
|
72
|
-
|
|
73
|
-
# Read the actual checksum adjustment from head table
|
|
74
|
-
head_data = font.table_data[Constants::HEAD_TAG]
|
|
75
|
-
return issues unless head_data && head_data.bytesize >= 12
|
|
76
|
-
|
|
77
|
-
actual_adjustment = head_data.byteslice(8, 4).unpack1("N")
|
|
78
|
-
|
|
79
|
-
# The actual adjustment should be 0 when we calculate, since we zero it out
|
|
80
|
-
# So we need to check if the file checksum with zeroed adjustment equals magic
|
|
81
|
-
if file_checksum != magic
|
|
82
|
-
# Calculate what the adjustment should be
|
|
83
|
-
temp_checksum = (file_checksum - actual_adjustment) & 0xFFFFFFFF
|
|
84
|
-
correct_adjustment = (magic - temp_checksum) & 0xFFFFFFFF
|
|
85
|
-
|
|
86
|
-
if actual_adjustment != correct_adjustment
|
|
87
|
-
issues << {
|
|
88
|
-
severity: "error",
|
|
89
|
-
category: "checksum",
|
|
90
|
-
message: "Invalid head table checksum adjustment (expected: 0x#{correct_adjustment.to_s(16)}, got: 0x#{actual_adjustment.to_s(16)})",
|
|
91
|
-
location: "head table",
|
|
92
|
-
}
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
rescue StandardError => e
|
|
96
|
-
issues << {
|
|
97
|
-
severity: "error",
|
|
98
|
-
category: "checksum",
|
|
99
|
-
message: "Failed to validate head checksum adjustment: #{e.message}",
|
|
100
|
-
location: "head table",
|
|
101
|
-
}
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
issues
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
# Check individual table checksums
|
|
108
|
-
#
|
|
109
|
-
# @param font [TrueTypeFont, OpenTypeFont] The font
|
|
110
|
-
# @return [Array<Hash>] Array of table checksum issues
|
|
111
|
-
def check_table_checksums(font)
|
|
112
|
-
issues = []
|
|
113
|
-
|
|
114
|
-
skip_tables = @checksum_config["skip_tables"] || []
|
|
115
|
-
|
|
116
|
-
font.tables.each do |table_entry|
|
|
117
|
-
tag = table_entry.tag.to_s # Convert BinData field to string
|
|
118
|
-
|
|
119
|
-
# Skip tables that are exempt from checksum validation
|
|
120
|
-
next if skip_tables.include?(tag)
|
|
121
|
-
|
|
122
|
-
# Get table data
|
|
123
|
-
table_data = font.table_data[tag]
|
|
124
|
-
next unless table_data
|
|
125
|
-
|
|
126
|
-
# Calculate checksum for the table
|
|
127
|
-
calculated_checksum = calculate_table_checksum(table_data)
|
|
128
|
-
declared_checksum = table_entry.checksum.to_i # Convert BinData field to integer
|
|
129
|
-
|
|
130
|
-
# Special handling for head table (checksum adjustment field should be 0)
|
|
131
|
-
if tag == Constants::HEAD_TAG
|
|
132
|
-
# Zero out checksum adjustment field for calculation
|
|
133
|
-
modified_data = table_data.dup
|
|
134
|
-
modified_data[8, 4] = "\x00\x00\x00\x00"
|
|
135
|
-
calculated_checksum = calculate_table_checksum(modified_data)
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
if calculated_checksum != declared_checksum
|
|
139
|
-
issues << {
|
|
140
|
-
severity: "warning",
|
|
141
|
-
category: "checksum",
|
|
142
|
-
message: "Table '#{tag}' checksum mismatch (expected: 0x#{declared_checksum.to_s(16)}, got: 0x#{calculated_checksum.to_s(16)})",
|
|
143
|
-
location: "#{tag} table",
|
|
144
|
-
}
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
issues
|
|
149
|
-
end
|
|
150
|
-
|
|
151
|
-
# Calculate checksum for table data
|
|
152
|
-
#
|
|
153
|
-
# @param data [String] The table data
|
|
154
|
-
# @return [Integer] The calculated checksum
|
|
155
|
-
def calculate_table_checksum(data)
|
|
156
|
-
sum = 0
|
|
157
|
-
# Pad to 4-byte boundary
|
|
158
|
-
padded_data = data + ("\x00" * ((4 - (data.bytesize % 4)) % 4))
|
|
159
|
-
|
|
160
|
-
# Sum all 32-bit values
|
|
161
|
-
(0...padded_data.bytesize).step(4) do |i|
|
|
162
|
-
value = padded_data.byteslice(i, 4).unpack1("N")
|
|
163
|
-
sum = (sum + value) & 0xFFFFFFFF
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
sum
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
|
-
end
|
|
170
|
-
end
|