Class: Fontisan::Audit::Checks::TableDirectoryCheck
- Inherits:
-
Fontisan::Audit::Check
- Object
- Fontisan::Audit::Check
- Fontisan::Audit::Checks::TableDirectoryCheck
- Defined in:
- lib/fontisan/audit/checks/table_directory_check.rb
Overview
Validates the SFNT table directory: checksums, 4-byte alignment, offset arithmetic, search-range/entry-selector/range-shift consistency.
These checks are the foundation of all font validation — a corrupt table directory makes every downstream parse unreliable. Replaces the table-directory portion of MS Font Validator + ots-sanitize.
Class Method Summary collapse
- .call(font) ⇒ Array<Models::ValidationReport::Issue>
- .code ⇒ Object
-
.validate_alignment(font) ⇒ Object
---------- 4-byte alignment ----------.
-
.validate_checksums(font) ⇒ Object
The 'head' table checksum has a special rule: its checkSumAdjustment field (offset 8) is set to make the whole-table checksum match the directory entry.
-
.validate_offsets(font) ⇒ Object
---------- offset arithmetic ----------.
-
.validate_search_fields(font) ⇒ Object
---------- search range / entry selector / range shift ----------.
Methods inherited from Fontisan::Audit::Check
Class Method Details
.call(font) ⇒ Array<Models::ValidationReport::Issue>
19 20 21 22 23 24 25 26 |
# File 'lib/fontisan/audit/checks/table_directory_check.rb', line 19 def self.call(font) issues = [] issues.concat(validate_search_fields(font)) issues.concat(validate_alignment(font)) issues.concat(validate_checksums(font)) issues.concat(validate_offsets(font)) issues end |
.code ⇒ Object
28 29 30 |
# File 'lib/fontisan/audit/checks/table_directory_check.rb', line 28 def self.code :table_directory end |
.validate_alignment(font) ⇒ Object
---------- 4-byte alignment ----------
74 75 76 77 78 79 80 81 82 83 |
# File 'lib/fontisan/audit/checks/table_directory_check.rb', line 74 def self.validate_alignment(font) font.tables.each_with_object([]) do |entry, issues| next if (entry.offset % 4).zero? issues << issue(severity: :warning, message: "Table '#{readable_tag(entry.tag)}' at offset " \ "#{entry.offset} is not 4-byte aligned", location: "tables.#{readable_tag(entry.tag)}.offset") end end |
.validate_checksums(font) ⇒ Object
The 'head' table checksum has a special rule: its checkSumAdjustment field (offset 8) is set to make the whole-table checksum match the directory entry. We skip the head table's own checksum verification and instead verify the adjustment is present.
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/fontisan/audit/checks/table_directory_check.rb', line 91 def self.validate_checksums(font) font.tables.each_with_object([]) do |entry, issues| tag = readable_tag(entry.tag) raw = font.table_data[tag] next unless raw next if tag == "head" # checkSumAdjustment complicates this actual = checksum(raw) next if actual == entry.checksum issues << issue(severity: :error, message: "Table '#{tag}' checksum mismatch: " \ "directory=0x#{entry.checksum.to_s(16).upcase} " \ "computed=0x#{actual.to_s(16).upcase}", location: "tables.#{tag}.checksum") end end |
.validate_offsets(font) ⇒ Object
---------- offset arithmetic ----------
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
# File 'lib/fontisan/audit/checks/table_directory_check.rb', line 123 def self.validate_offsets(font) dir_end = 12 + (font.header.num_tables * 16) font.tables.each_with_object([]) do |entry, issues| tag = readable_tag(entry.tag) if entry.offset < dir_end issues << issue(severity: :error, message: "Table '#{tag}' offset #{entry.offset} " \ "overlaps the table directory " \ "(directory ends at #{dir_end})", location: "tables.#{tag}.offset") end raw = font.table_data[tag] if raw && raw.bytesize != entry.table_length issues << issue(severity: :error, message: "Table '#{tag}' length mismatch: " \ "directory says #{entry.table_length} " \ "but actual data is #{raw.bytesize} bytes", location: "tables.#{tag}.table_length") end end end |
.validate_search_fields(font) ⇒ Object
---------- search range / entry selector / range shift ----------
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
# File 'lib/fontisan/audit/checks/table_directory_check.rb', line 34 def self.validate_search_fields(font) num_tables = font.header.num_tables expected_range = largest_power_of_two_le(num_tables) * 16 expected_selector = Math.log2(expected_range / 16).to_i expected_shift = (num_tables * 16) - expected_range issues = [] if font.header.search_range != expected_range issues << issue(severity: :warning, message: "searchRange is #{font.header.search_range} " \ "but should be #{expected_range} " \ "(numTables=#{num_tables})", location: "header.searchRange") end if font.header.entry_selector != expected_selector issues << issue(severity: :warning, message: "entrySelector is #{font.header.entry_selector} " \ "but should be #{expected_selector}", location: "header.entrySelector") end if font.header.range_shift != expected_shift issues << issue(severity: :warning, message: "rangeShift is #{font.header.range_shift} " \ "but should be #{expected_shift}", location: "header.rangeShift") end issues end |