use super::{DecodeError, Opcode, Program};
use crate::types::GlyphId;
#[derive(Clone, PartialEq, Debug)]
pub enum HintErrorKind {
UnexpectedEndOfBytecode,
UnhandledOpcode(Opcode),
DefinitionInGlyphProgram,
NestedDefinition,
DefinitionTooLarge,
TooManyDefinitions,
InvalidDefinition(usize),
ValueStackOverflow,
ValueStackUnderflow,
CallStackOverflow,
CallStackUnderflow,
InvalidStackValue(i32),
InvalidPointIndex(usize),
InvalidPointRange(usize, usize),
InvalidContourIndex(usize),
InvalidCvtIndex(usize),
InvalidStorageIndex(usize),
DivideByZero,
InvalidZoneIndex(i32),
NegativeLoopCounter,
InvalidJump,
ExceededExecutionBudget,
}
impl core::fmt::Display for HintErrorKind {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::UnexpectedEndOfBytecode => write!(f, "unexpected end of bytecode"),
Self::UnhandledOpcode(opcode) => write!(f, "unhandled instruction opcode {opcode}"),
Self::DefinitionInGlyphProgram => write!(
f,
"function or instruction definition present in glyph program"
),
Self::NestedDefinition => write!(f, "nested function or instruction definition"),
Self::DefinitionTooLarge => write!(
f,
"function or instruction definition exceeded the maximum size of 64k"
),
Self::TooManyDefinitions => write!(f, "too many function or instruction definitions"),
Self::InvalidDefinition(key) => {
write!(f, "function or instruction definition {key} not found")
}
Self::ValueStackOverflow => write!(f, "value stack overflow"),
Self::ValueStackUnderflow => write!(f, "value stack underflow"),
Self::CallStackOverflow => write!(f, "call stack overflow"),
Self::CallStackUnderflow => write!(f, "call stack underflow"),
Self::InvalidStackValue(value) => write!(
f,
"stack value {value} was invalid for the current operation"
),
Self::InvalidPointIndex(index) => write!(f, "point index {index} was out of bounds"),
Self::InvalidPointRange(start, end) => {
write!(f, "point range {start}..{end} was out of bounds")
}
Self::InvalidContourIndex(index) => {
write!(f, "contour index {index} was out of bounds")
}
Self::InvalidCvtIndex(index) => write!(f, "cvt index {index} was out of bounds"),
Self::InvalidStorageIndex(index) => {
write!(f, "storage area index {index} was out of bounds")
}
Self::DivideByZero => write!(f, "attempt to divide by 0"),
Self::InvalidZoneIndex(index) => write!(
f,
"zone index {index} was invalid (only 0 or 1 are permitted)"
),
Self::NegativeLoopCounter => {
write!(f, "attempt to set the loop counter to a negative value")
}
Self::InvalidJump => write!(f, "the target of a jump instruction was invalid"),
Self::ExceededExecutionBudget => write!(f, "too many instructions executed"),
}
}
}
impl From<DecodeError> for HintErrorKind {
fn from(_: DecodeError) -> Self {
Self::UnexpectedEndOfBytecode
}
}
#[derive(Clone, PartialEq, Debug)]
pub struct HintError {
pub program: Program,
pub glyph_id: Option<GlyphId>,
pub pc: usize,
pub opcode: Option<Opcode>,
pub kind: HintErrorKind,
}
impl core::fmt::Display for HintError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(match self.program {
Program::Font => "fpgm",
Program::ControlValue => "prep",
Program::Glyph => "glyf",
})?;
if let Some(glyph_id) = self.glyph_id {
write!(f, "[{}]", glyph_id.to_u32())?;
}
let (opcode, colon) = match self.opcode {
Some(opcode) => (opcode.name(), ":"),
_ => ("", ""),
};
write!(f, "@{}:{opcode}{colon} {}", self.pc, self.kind)
}
}
impl core::error::Error for HintError {}
impl From<HintError> for crate::model::Error {
fn from(_: HintError) -> Self {
Self::InvalidOperation
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::string::ToString;
#[test]
fn error_message_carries_its_context() {
let error = HintError {
program: Program::Glyph,
glyph_id: Some(GlyphId::new(42)),
pc: 17,
opcode: Some(Opcode::SVTCA0),
kind: HintErrorKind::DivideByZero,
};
assert_eq!(
error.to_string(),
"glyf[42]@17:SVTCA[y]: attempt to divide by 0"
);
}
#[test]
fn error_message_omits_what_it_does_not_have() {
let error = HintError {
program: Program::ControlValue,
glyph_id: None,
pc: 0,
opcode: None,
kind: HintErrorKind::ValueStackUnderflow,
};
assert_eq!(error.to_string(), "prep@0: value stack underflow");
}
#[test]
fn decode_errors_become_unexpected_end() {
assert_eq!(
HintErrorKind::from(DecodeError),
HintErrorKind::UnexpectedEndOfBytecode
);
}
}