Class: Fontisan::Hints::HintValidator
- Inherits:
-
Object
- Object
- Fontisan::Hints::HintValidator
- Defined in:
- lib/fontisan/hints/hint_validator.rb
Overview
Validates hint data for correctness and compatibility
This validator ensures that hints are well-formed and compatible with their target format. It performs multiple levels of validation:
-
TrueType instruction bytecode validation
-
PostScript hint parameter validation
-
Stack neutrality verification
-
Hint-outline compatibility checking
Constant Summary collapse
- MAX_BLUE_VALUES =
Maximum allowed values for PostScript hint parameters
14- MAX_OTHER_BLUES =
7 pairs
10- MAX_STEM_SNAP =
5 pairs
12
Instance Method Summary collapse
-
#validate_postscript_hints(hints) ⇒ Hash
Validate PostScript hint parameters.
-
#validate_stack_neutrality(instructions) ⇒ Hash
Validate stack neutrality of instruction sequence.
-
#validate_truetype_instructions(instructions) ⇒ Hash
Validate TrueType instruction bytecode.
Instance Method Details
#validate_postscript_hints(hints) ⇒ Hash
Validate PostScript hint parameters
Checks for:
-
Valid parameter ranges
-
Proper pair counts for blue zones
-
Sensible stem width values
138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 |
# File 'lib/fontisan/hints/hint_validator.rb', line 138 def validate_postscript_hints(hints) errors = [] warnings = [] # Validate blue_values if hints[:blue_values] blue_values = hints[:blue_values] if blue_values.length > MAX_BLUE_VALUES errors << "blue_values exceeds maximum (#{MAX_BLUE_VALUES}): #{blue_values.length}" end if blue_values.length.odd? errors << "blue_values must be pairs (even count): #{blue_values.length}" end end # Validate other_blues if hints[:other_blues] other_blues = hints[:other_blues] if other_blues.length > MAX_OTHER_BLUES errors << "other_blues exceeds maximum (#{MAX_OTHER_BLUES}): #{other_blues.length}" end if other_blues.length.odd? errors << "other_blues must be pairs (even count): #{other_blues.length}" end end # Validate stem widths [:std_hw, :std_vw].each do |key| if hints[key] && hints[key] <= 0 errors << "#{key} must be positive: #{hints[key]}" end end # Validate stem snaps [:stem_snap_h, :stem_snap_v].each do |key| if hints[key] if hints[key].length > MAX_STEM_SNAP errors << "#{key} exceeds maximum (#{MAX_STEM_SNAP}): #{hints[key].length}" end if hints[key].any? { |v| v <= 0 } warnings << "#{key} contains non-positive values" end end end # Validate blue_scale if hints[:blue_scale] if hints[:blue_scale] <= 0 errors << "blue_scale must be positive: #{hints[:blue_scale]}" end if hints[:blue_scale] > 1.0 warnings << "blue_scale unusually large (>1.0): #{hints[:blue_scale]}" end end # Validate language_group if hints[:language_group] unless [0, 1].include?(hints[:language_group]) errors << "language_group must be 0 (Latin) or 1 (CJK): #{hints[:language_group]}" end end { valid: errors.empty?, errors: errors, warnings: warnings, } end |
#validate_stack_neutrality(instructions) ⇒ Hash
Validate stack neutrality of instruction sequence
Ensures the instruction sequence leaves the stack in the same state as it started (net stack change of zero).
214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 |
# File 'lib/fontisan/hints/hint_validator.rb', line 214 def validate_stack_neutrality(instructions) return { neutral: true, stack_depth: 0, errors: [] } if instructions.nil? || instructions.empty? errors = [] stack_depth = 0 bytes = instructions.bytes index = 0 begin while index < bytes.length opcode = bytes[index] index += 1 case opcode when 0x40 # NPUSHB count = bytes[index] index += 1 + count stack_depth += count when 0x41 # NPUSHW count = bytes[index] index += 1 + (count * 2) stack_depth += count when 0xB0..0xB7 # PUSHB[0-7] count = opcode - 0xB0 + 1 index += count stack_depth += count when 0xB8..0xBF # PUSHW[0-7] count = opcode - 0xB8 + 1 index += count * 2 stack_depth += count when 0x1D, 0x1E, 0x1F # SCVTCI, SSWCI, SSW stack_depth -= 1 when 0x44, 0x70 # WCVTP, WCVTF stack_depth -= 2 end end rescue StandardError => e errors << "Error analyzing stack: #{e.}" end { neutral: stack_depth == 0, stack_depth: stack_depth, errors: errors, } end |
#validate_truetype_instructions(instructions) ⇒ Hash
Validate TrueType instruction bytecode
Checks for:
-
Valid instruction opcodes
-
Correct parameter counts
-
Stack neutrality
42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/fontisan/hints/hint_validator.rb', line 42 def validate_truetype_instructions(instructions) return { valid: true, errors: [], warnings: [] } if instructions.nil? || instructions.empty? errors = [] warnings = [] begin bytes = instructions.bytes stack_depth = 0 index = 0 while index < bytes.length opcode = bytes[index] index += 1 case opcode when 0x40 # NPUSHB count = bytes[index] index += 1 if index + count > bytes.length errors << "NPUSHB: Not enough bytes (need #{count}, have #{bytes.length - index})" break end stack_depth += count index += count when 0x41 # NPUSHW count = bytes[index] index += 1 if index + (count * 2) > bytes.length errors << "NPUSHW: Not enough bytes (need #{count * 2}, have #{bytes.length - index})" break end stack_depth += count index += count * 2 when 0xB0..0xB7 # PUSHB[0-7] count = opcode - 0xB0 + 1 if index + count > bytes.length errors << "PUSHB[#{count - 1}]: Not enough bytes" break end stack_depth += count index += count when 0xB8..0xBF # PUSHW[0-7] count = opcode - 0xB8 + 1 if index + (count * 2) > bytes.length errors << "PUSHW[#{count - 1}]: Not enough bytes" break end stack_depth += count index += count * 2 when 0x1D, 0x1E, 0x1F # SCVTCI, SSWCI, SSW if stack_depth < 1 errors << "#{opcode_name(opcode)}: Stack underflow" end stack_depth -= 1 when 0x44, 0x70 # WCVTP, WCVTF if stack_depth < 2 errors << "#{opcode_name(opcode)}: Stack underflow (need 2 values)" end stack_depth -= 2 else warnings << "Unknown opcode: 0x#{opcode.to_s(16).upcase} at offset #{index - 1}" end end # Check stack neutrality if stack_depth != 0 warnings << "Stack not neutral: #{stack_depth} value(s) remaining" end rescue StandardError => e errors << "Exception during validation: #{e.}" end { valid: errors.empty?, errors: errors, warnings: warnings, } end |