Skip to main content

read_fonts/tables/glyf/bytecode/
error.rs

1//! What can go wrong while executing TrueType bytecode.
2//!
3//! Nothing in this crate executes bytecode. These live here because the set
4//! is closed by the specification rather than by any one interpreter, so an
5//! interpreter reports what happened in terms its caller already has, and two
6//! of them describe the same failure the same way.
7
8use super::{DecodeError, Opcode, Program};
9use crate::types::GlyphId;
10
11/// What went wrong, without the context of where.
12#[derive(Clone, PartialEq, Debug)]
13pub enum HintErrorKind {
14    /// An instruction's operands ran past the end of the bytecode.
15    UnexpectedEndOfBytecode,
16    /// The interpreter does not implement this instruction.
17    UnhandledOpcode(Opcode),
18    /// A glyph program tried to define a function or instruction, which only
19    /// the font program may do.
20    DefinitionInGlyphProgram,
21    /// A function or instruction definition began inside another.
22    NestedDefinition,
23    /// A definition exceeded the maximum size of 64k.
24    DefinitionTooLarge,
25    /// More definitions than the interpreter has room for.
26    TooManyDefinitions,
27    /// A call referenced a definition that was never made.
28    InvalidDefinition(usize),
29    /// A push exceeded the value stack.
30    ValueStackOverflow,
31    /// A pop found the value stack empty.
32    ValueStackUnderflow,
33    /// Calls nested deeper than the interpreter allows.
34    CallStackOverflow,
35    /// A return found the call stack empty.
36    CallStackUnderflow,
37    /// The value on the stack made no sense for the instruction reading it.
38    InvalidStackValue(i32),
39    /// A point index was past the end of the zone.
40    InvalidPointIndex(usize),
41    /// A point range ran past the end of the zone.
42    InvalidPointRange(usize, usize),
43    /// A contour index was past the end of the glyph.
44    InvalidContourIndex(usize),
45    /// A control value table index was out of bounds.
46    InvalidCvtIndex(usize),
47    /// A storage area index was out of bounds.
48    InvalidStorageIndex(usize),
49    /// A division had a zero divisor.
50    DivideByZero,
51    /// A zone index other than 0 (twilight) or 1 (glyph).
52    InvalidZoneIndex(i32),
53    /// An attempt to set the loop counter to a negative value.
54    NegativeLoopCounter,
55    /// A jump left the bounds of the program.
56    InvalidJump,
57    /// The program ran longer than the interpreter's budget allows.
58    ExceededExecutionBudget,
59}
60
61impl core::fmt::Display for HintErrorKind {
62    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
63        match self {
64            Self::UnexpectedEndOfBytecode => write!(f, "unexpected end of bytecode"),
65            Self::UnhandledOpcode(opcode) => write!(f, "unhandled instruction opcode {opcode}"),
66            Self::DefinitionInGlyphProgram => write!(
67                f,
68                "function or instruction definition present in glyph program"
69            ),
70            Self::NestedDefinition => write!(f, "nested function or instruction definition"),
71            Self::DefinitionTooLarge => write!(
72                f,
73                "function or instruction definition exceeded the maximum size of 64k"
74            ),
75            Self::TooManyDefinitions => write!(f, "too many function or instruction definitions"),
76            Self::InvalidDefinition(key) => {
77                write!(f, "function or instruction definition {key} not found")
78            }
79            Self::ValueStackOverflow => write!(f, "value stack overflow"),
80            Self::ValueStackUnderflow => write!(f, "value stack underflow"),
81            Self::CallStackOverflow => write!(f, "call stack overflow"),
82            Self::CallStackUnderflow => write!(f, "call stack underflow"),
83            Self::InvalidStackValue(value) => write!(
84                f,
85                "stack value {value} was invalid for the current operation"
86            ),
87            Self::InvalidPointIndex(index) => write!(f, "point index {index} was out of bounds"),
88            Self::InvalidPointRange(start, end) => {
89                write!(f, "point range {start}..{end} was out of bounds")
90            }
91            Self::InvalidContourIndex(index) => {
92                write!(f, "contour index {index} was out of bounds")
93            }
94            Self::InvalidCvtIndex(index) => write!(f, "cvt index {index} was out of bounds"),
95            Self::InvalidStorageIndex(index) => {
96                write!(f, "storage area index {index} was out of bounds")
97            }
98            Self::DivideByZero => write!(f, "attempt to divide by 0"),
99            Self::InvalidZoneIndex(index) => write!(
100                f,
101                "zone index {index} was invalid (only 0 or 1 are permitted)"
102            ),
103            Self::NegativeLoopCounter => {
104                write!(f, "attempt to set the loop counter to a negative value")
105            }
106            Self::InvalidJump => write!(f, "the target of a jump instruction was invalid"),
107            Self::ExceededExecutionBudget => write!(f, "too many instructions executed"),
108        }
109    }
110}
111
112impl From<DecodeError> for HintErrorKind {
113    fn from(_: DecodeError) -> Self {
114        Self::UnexpectedEndOfBytecode
115    }
116}
117
118/// What went wrong, and where.
119#[derive(Clone, PartialEq, Debug)]
120pub struct HintError {
121    /// The program that was running.
122    pub program: Program,
123    /// The glyph being hinted, if this was a glyph program.
124    pub glyph_id: Option<GlyphId>,
125    /// Offset of the instruction within its program.
126    pub pc: usize,
127    /// The instruction being executed, if the failure had reached one.
128    pub opcode: Option<Opcode>,
129    /// What went wrong.
130    pub kind: HintErrorKind,
131}
132
133impl core::fmt::Display for HintError {
134    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
135        f.write_str(match self.program {
136            Program::Font => "fpgm",
137            Program::ControlValue => "prep",
138            Program::Glyph => "glyf",
139        })?;
140        if let Some(glyph_id) = self.glyph_id {
141            write!(f, "[{}]", glyph_id.to_u32())?;
142        }
143        let (opcode, colon) = match self.opcode {
144            Some(opcode) => (opcode.name(), ":"),
145            _ => ("", ""),
146        };
147        write!(f, "@{}:{opcode}{colon} {}", self.pc, self.kind)
148    }
149}
150
151impl core::error::Error for HintError {}
152
153impl From<HintError> for crate::model::Error {
154    fn from(_: HintError) -> Self {
155        Self::InvalidOperation
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162    use alloc::string::ToString;
163
164    /// The message names the program, the glyph, the offset and the opcode.
165    #[test]
166    fn error_message_carries_its_context() {
167        let error = HintError {
168            program: Program::Glyph,
169            glyph_id: Some(GlyphId::new(42)),
170            pc: 17,
171            opcode: Some(Opcode::SVTCA0),
172            kind: HintErrorKind::DivideByZero,
173        };
174        assert_eq!(
175            error.to_string(),
176            "glyf[42]@17:SVTCA[y]: attempt to divide by 0"
177        );
178    }
179
180    /// Without a glyph or an opcode there is nothing in their place.
181    #[test]
182    fn error_message_omits_what_it_does_not_have() {
183        let error = HintError {
184            program: Program::ControlValue,
185            glyph_id: None,
186            pc: 0,
187            opcode: None,
188            kind: HintErrorKind::ValueStackUnderflow,
189        };
190        assert_eq!(error.to_string(), "prep@0: value stack underflow");
191    }
192
193    /// A truncated instruction reads as running out of bytecode.
194    #[test]
195    fn decode_errors_become_unexpected_end() {
196        assert_eq!(
197            HintErrorKind::from(DecodeError),
198            HintErrorKind::UnexpectedEndOfBytecode
199        );
200    }
201}