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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +221 -49
  3. data/README.adoc +519 -5
  4. data/Rakefile +20 -7
  5. data/lib/fontisan/cli.rb +67 -6
  6. data/lib/fontisan/commands/base_command.rb +2 -19
  7. data/lib/fontisan/commands/convert_command.rb +16 -13
  8. data/lib/fontisan/commands/info_command.rb +88 -0
  9. data/lib/fontisan/commands/validate_command.rb +107 -151
  10. data/lib/fontisan/config/conversion_matrix.yml +58 -20
  11. data/lib/fontisan/converters/outline_converter.rb +6 -3
  12. data/lib/fontisan/converters/svg_generator.rb +45 -0
  13. data/lib/fontisan/converters/woff2_encoder.rb +84 -13
  14. data/lib/fontisan/models/bitmap_glyph.rb +123 -0
  15. data/lib/fontisan/models/bitmap_strike.rb +94 -0
  16. data/lib/fontisan/models/color_glyph.rb +57 -0
  17. data/lib/fontisan/models/color_layer.rb +53 -0
  18. data/lib/fontisan/models/color_palette.rb +60 -0
  19. data/lib/fontisan/models/font_info.rb +26 -0
  20. data/lib/fontisan/models/svg_glyph.rb +89 -0
  21. data/lib/fontisan/models/validation_report.rb +227 -0
  22. data/lib/fontisan/open_type_font.rb +6 -0
  23. data/lib/fontisan/optimizers/charstring_rewriter.rb +19 -8
  24. data/lib/fontisan/optimizers/pattern_analyzer.rb +4 -2
  25. data/lib/fontisan/optimizers/subroutine_builder.rb +6 -5
  26. data/lib/fontisan/optimizers/subroutine_optimizer.rb +5 -2
  27. data/lib/fontisan/pipeline/output_writer.rb +2 -2
  28. data/lib/fontisan/pipeline/transformation_pipeline.rb +4 -8
  29. data/lib/fontisan/tables/cbdt.rb +169 -0
  30. data/lib/fontisan/tables/cblc.rb +290 -0
  31. data/lib/fontisan/tables/cff.rb +6 -12
  32. data/lib/fontisan/tables/cmap.rb +82 -2
  33. data/lib/fontisan/tables/colr.rb +291 -0
  34. data/lib/fontisan/tables/cpal.rb +281 -0
  35. data/lib/fontisan/tables/glyf/glyph_builder.rb +5 -1
  36. data/lib/fontisan/tables/glyf.rb +118 -0
  37. data/lib/fontisan/tables/head.rb +60 -0
  38. data/lib/fontisan/tables/hhea.rb +74 -0
  39. data/lib/fontisan/tables/maxp.rb +60 -0
  40. data/lib/fontisan/tables/name.rb +76 -0
  41. data/lib/fontisan/tables/os2.rb +113 -0
  42. data/lib/fontisan/tables/post.rb +57 -0
  43. data/lib/fontisan/tables/sbix.rb +379 -0
  44. data/lib/fontisan/tables/svg.rb +301 -0
  45. data/lib/fontisan/true_type_font.rb +6 -0
  46. data/lib/fontisan/validators/basic_validator.rb +85 -0
  47. data/lib/fontisan/validators/font_book_validator.rb +130 -0
  48. data/lib/fontisan/validators/opentype_validator.rb +112 -0
  49. data/lib/fontisan/validators/profile_loader.rb +139 -0
  50. data/lib/fontisan/validators/validator.rb +484 -0
  51. data/lib/fontisan/validators/web_font_validator.rb +102 -0
  52. data/lib/fontisan/version.rb +1 -1
  53. data/lib/fontisan/woff2/directory.rb +40 -11
  54. data/lib/fontisan/woff2/table_transformer.rb +506 -73
  55. data/lib/fontisan/woff2_font.rb +29 -9
  56. data/lib/fontisan/woff_font.rb +17 -4
  57. data/lib/fontisan.rb +90 -6
  58. metadata +20 -9
  59. data/lib/fontisan/config/validation_rules.yml +0 -149
  60. data/lib/fontisan/validation/checksum_validator.rb +0 -170
  61. data/lib/fontisan/validation/consistency_validator.rb +0 -197
  62. data/lib/fontisan/validation/structure_validator.rb +0 -198
  63. data/lib/fontisan/validation/table_validator.rb +0 -158
  64. data/lib/fontisan/validation/validator.rb +0 -152
  65. data/lib/fontisan/validation/variable_font_validator.rb +0 -218
@@ -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
- require_relative "fontisan/validation/table_validator"
123
- require_relative "fontisan/validation/structure_validator"
124
- require_relative "fontisan/validation/consistency_validator"
125
- require_relative "fontisan/validation/checksum_validator"
126
- require_relative "fontisan/validation/validator"
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.3
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: 2025-12-30 00:00:00.000000000 Z
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/validation/checksum_validator.rb
314
- - lib/fontisan/validation/consistency_validator.rb
315
- - lib/fontisan/validation/structure_validator.rb
316
- - lib/fontisan/validation/table_validator.rb
317
- - lib/fontisan/validation/validator.rb
318
- - lib/fontisan/validation/variable_font_validator.rb
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