fontisan 0.2.4 → 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 (35) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +150 -30
  3. data/README.adoc +497 -242
  4. data/lib/fontisan/cli.rb +67 -6
  5. data/lib/fontisan/commands/validate_command.rb +107 -151
  6. data/lib/fontisan/converters/woff2_encoder.rb +7 -29
  7. data/lib/fontisan/models/validation_report.rb +227 -0
  8. data/lib/fontisan/pipeline/transformation_pipeline.rb +4 -8
  9. data/lib/fontisan/tables/cmap.rb +82 -2
  10. data/lib/fontisan/tables/glyf.rb +118 -0
  11. data/lib/fontisan/tables/head.rb +60 -0
  12. data/lib/fontisan/tables/hhea.rb +74 -0
  13. data/lib/fontisan/tables/maxp.rb +60 -0
  14. data/lib/fontisan/tables/name.rb +76 -0
  15. data/lib/fontisan/tables/os2.rb +113 -0
  16. data/lib/fontisan/tables/post.rb +57 -0
  17. data/lib/fontisan/validators/basic_validator.rb +85 -0
  18. data/lib/fontisan/validators/font_book_validator.rb +130 -0
  19. data/lib/fontisan/validators/opentype_validator.rb +112 -0
  20. data/lib/fontisan/validators/profile_loader.rb +139 -0
  21. data/lib/fontisan/validators/validator.rb +484 -0
  22. data/lib/fontisan/validators/web_font_validator.rb +102 -0
  23. data/lib/fontisan/version.rb +1 -1
  24. data/lib/fontisan.rb +78 -6
  25. metadata +7 -11
  26. data/lib/fontisan/config/validation_rules.yml +0 -149
  27. data/lib/fontisan/validation/checksum_validator.rb +0 -170
  28. data/lib/fontisan/validation/consistency_validator.rb +0 -197
  29. data/lib/fontisan/validation/structure_validator.rb +0 -198
  30. data/lib/fontisan/validation/table_validator.rb +0 -158
  31. data/lib/fontisan/validation/validator.rb +0 -152
  32. data/lib/fontisan/validation/variable_font_validator.rb +0 -218
  33. data/lib/fontisan/validation/woff2_header_validator.rb +0 -278
  34. data/lib/fontisan/validation/woff2_table_validator.rb +0 -270
  35. data/lib/fontisan/validation/woff2_validator.rb +0 -248
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "basic_validator"
4
+
5
+ module Fontisan
6
+ module Validators
7
+ # WebFontValidator provides web font optimization and embedding compatibility checks
8
+ #
9
+ # This validator extends BasicValidator with checks specific to web font use cases.
10
+ # Unlike FontBookValidator, it focuses on web embedding permissions, file size,
11
+ # and WOFF/WOFF2 conversion readiness rather than desktop installation.
12
+ #
13
+ # The validator inherits 8 checks from BasicValidator and adds 10 new checks:
14
+ # - Embedding permissions (OS/2 fsType)
15
+ # - File size and glyph complexity for web performance
16
+ # - Character coverage for web use
17
+ # - Glyph accessibility
18
+ # - WOFF/WOFF2 conversion readiness
19
+ #
20
+ # @example Using WebFontValidator
21
+ # validator = WebFontValidator.new
22
+ # report = validator.validate(font)
23
+ # puts "Font is web-ready" if report.valid?
24
+ class WebFontValidator < BasicValidator
25
+ private
26
+
27
+ # Define web font validation checks
28
+ #
29
+ # Calls super to inherit BasicValidator's 8 checks, then adds 10 new checks.
30
+ # All checks use helpers from Week 1 table implementations.
31
+ def define_checks
32
+ # Inherit BasicValidator checks (8 checks)
33
+ super
34
+
35
+ # Check 9: OS/2 embedding permissions must allow web use
36
+ check_table :embedding_permissions, 'OS/2', severity: :error do |table|
37
+ table.has_embedding_permissions?
38
+ end
39
+
40
+ # Check 10: OS/2 version should be present
41
+ check_table :os2_version_web, 'OS/2', severity: :warning do |table|
42
+ table.valid_version?
43
+ end
44
+
45
+ # Check 11: Glyph complexity should be reasonable for web
46
+ check_glyphs :no_complex_glyphs, severity: :warning do |font|
47
+ maxp = font.table('maxp')
48
+ next true unless maxp.version_1_0?
49
+
50
+ # Check max points and contours are reasonable for web rendering
51
+ maxp.max_points && maxp.max_points < 3000 &&
52
+ maxp.max_contours && maxp.max_contours < 500
53
+ end
54
+
55
+ # Check 12: Cmap must have Unicode mapping for web
56
+ check_table :character_coverage, 'cmap', severity: :error do |table|
57
+ table.has_unicode_mapping?
58
+ end
59
+
60
+ # Check 13: Cmap should have BMP coverage
61
+ check_table :cmap_bmp_web, 'cmap', severity: :warning do |table|
62
+ table.has_bmp_coverage?
63
+ end
64
+
65
+ # Check 14: Glyf glyphs must be accessible (web browsers need this)
66
+ check_glyphs :glyph_accessible_web, severity: :error do |font|
67
+ glyf = font.table('glyf')
68
+ next true unless glyf
69
+
70
+ loca = font.table('loca')
71
+ head = font.table('head')
72
+ maxp = font.table('maxp')
73
+ glyf.all_glyphs_accessible?(loca, head, maxp.num_glyphs)
74
+ end
75
+
76
+ # Check 15: Head table must have valid bounding box
77
+ check_table :head_bbox_web, 'head', severity: :error do |table|
78
+ table.valid_bounding_box?
79
+ end
80
+
81
+ # Check 16: Hhea metrics must be valid for web rendering
82
+ check_table :hhea_metrics_web, 'hhea', severity: :error do |table|
83
+ table.valid_ascent_descent? && table.valid_number_of_h_metrics?
84
+ end
85
+
86
+ # Check 17: WOFF conversion readiness
87
+ check_structure :woff_conversion_ready, severity: :info do |font|
88
+ # Check font can be converted to WOFF
89
+ # All required tables present
90
+ %w[name head maxp hhea].all? { |tag| font.table(tag) }
91
+ end
92
+
93
+ # Check 18: WOFF2 conversion readiness
94
+ check_structure :woff2_conversion_ready, severity: :info do |font|
95
+ # Check font can be converted to WOFF2
96
+ # Same requirements as WOFF
97
+ %w[name head maxp hhea].all? { |tag| font.table(tag) }
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Fontisan
4
- VERSION = "0.2.4"
4
+ VERSION = "0.2.5"
5
5
  end
data/lib/fontisan.rb CHANGED
@@ -124,18 +124,30 @@ require_relative "fontisan/models/color_layer"
124
124
  require_relative "fontisan/models/color_palette"
125
125
  require_relative "fontisan/models/svg_glyph"
126
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"
134
+
127
135
  # Export infrastructure
128
136
  require_relative "fontisan/export/table_serializer"
129
137
  require_relative "fontisan/export/ttx_generator"
130
138
  require_relative "fontisan/export/ttx_parser"
131
139
  require_relative "fontisan/export/exporter"
132
140
 
133
- # Validation infrastructure
134
- require_relative "fontisan/validation/table_validator"
135
- require_relative "fontisan/validation/structure_validator"
136
- require_relative "fontisan/validation/consistency_validator"
137
- require_relative "fontisan/validation/checksum_validator"
138
- 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"
139
151
 
140
152
  # Subsetting infrastructure
141
153
  require_relative "fontisan/subset/options"
@@ -261,4 +273,64 @@ module Fontisan
261
273
  def self.info(path, brief: false, font_index: 0)
262
274
  Commands::InfoCommand.new(path, brief: brief, font_index: font_index).run
263
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
264
336
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fontisan
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ribose Inc.
@@ -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
@@ -322,15 +321,12 @@ files:
322
321
  - lib/fontisan/utilities/brotli_wrapper.rb
323
322
  - lib/fontisan/utilities/checksum_calculator.rb
324
323
  - lib/fontisan/utils/thread_pool.rb
325
- - lib/fontisan/validation/checksum_validator.rb
326
- - lib/fontisan/validation/consistency_validator.rb
327
- - lib/fontisan/validation/structure_validator.rb
328
- - lib/fontisan/validation/table_validator.rb
329
- - lib/fontisan/validation/validator.rb
330
- - lib/fontisan/validation/variable_font_validator.rb
331
- - lib/fontisan/validation/woff2_header_validator.rb
332
- - lib/fontisan/validation/woff2_table_validator.rb
333
- - lib/fontisan/validation/woff2_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
334
330
  - lib/fontisan/variable/axis_normalizer.rb
335
331
  - lib/fontisan/variable/delta_applicator.rb
336
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