Skip to main content

skrifa/scaler/
error.rs

1//! The error from scaling a glyph.
2
3use core::fmt;
4use raw::{model::Error, tables::glyf::bytecode::HintError, types::GlyphId};
5
6use crate::outline::DrawError;
7
8/// An error from drawing a glyph with a [`ScalerFont`](super::ScalerFont).
9///
10/// Each variant names the step that failed and carries what that step knows.
11/// [`kind`](Self::kind) gives the same failure as a read-fonts [`Error`], for
12/// a caller that only sorts errors.
13#[non_exhaustive]
14#[derive(Clone, PartialEq, Debug)]
15pub enum ScaleError {
16    /// The font has no outline for the glyph.
17    GlyphNotFound(GlyphId),
18    /// The memory supplied for the draw is smaller than the glyph needs.
19    InsufficientMemory {
20        /// The size in bytes the glyph needs, as
21        /// [`ScalerOutline::required_memory_size`](super::ScalerOutline::required_memory_size)
22        /// reports it.
23        required: usize,
24        /// The size in bytes that was supplied.
25        supplied: usize,
26    },
27    /// Reading or evaluating the outline failed.
28    Outline(Error),
29    /// A TrueType program failed.
30    ///
31    /// This is the font or control value program when the scaler was
32    /// configured, or a glyph program when hinting is pedantic.
33    Hinting(HintError),
34}
35
36impl ScaleError {
37    /// Returns the kind of failure.
38    pub fn kind(&self) -> Error {
39        match self {
40            Self::GlyphNotFound(_) => Error::MissingResource,
41            Self::InsufficientMemory { .. } => Error::InsufficientMemory,
42            Self::Outline(error) => *error,
43            Self::Hinting(_) => Error::InvalidOperation,
44        }
45    }
46
47    /// Converts an error from drawing an outline into `supplied` bytes of a
48    /// `required` size.
49    pub(super) fn from_outline(error: Error, required: usize, supplied: usize) -> Self {
50        match error {
51            Error::InsufficientMemory => Self::InsufficientMemory { required, supplied },
52            error => Self::Outline(error),
53        }
54    }
55
56    /// Converts an error from skrifa's automatic hinter, which drew into
57    /// `supplied` bytes of a `required` size.
58    pub(super) fn from_draw_error(error: DrawError, required: usize, supplied: usize) -> Self {
59        match error {
60            DrawError::GlyphNotFound(glyph) => Self::GlyphNotFound(glyph),
61            DrawError::HintingFailed(error) => Self::Hinting(error),
62            DrawError::PostScript(error) => Self::Outline(error),
63            DrawError::InsufficientMemory => Self::InsufficientMemory { required, supplied },
64            DrawError::RecursionLimitExceeded(_) | DrawError::TooManyPoints(_) => {
65                Self::Outline(Error::LimitExceeded)
66            }
67            DrawError::NoSources => Self::Outline(Error::MissingResource),
68            _ => Self::Outline(Error::Malformed),
69        }
70    }
71}
72
73impl From<Error> for ScaleError {
74    fn from(error: Error) -> Self {
75        Self::Outline(error)
76    }
77}
78
79impl From<HintError> for ScaleError {
80    fn from(error: HintError) -> Self {
81        Self::Hinting(error)
82    }
83}
84
85impl fmt::Display for ScaleError {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        match self {
88            Self::GlyphNotFound(glyph) => write!(f, "glyph {glyph} has no outline"),
89            Self::InsufficientMemory { required, supplied } => write!(
90                f,
91                "the glyph needs {required} bytes of memory but {supplied} were supplied"
92            ),
93            Self::Outline(error) => write!(f, "{error}"),
94            Self::Hinting(error) => write!(f, "{error}"),
95        }
96    }
97}
98
99impl core::error::Error for ScaleError {}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use raw::tables::glyf::bytecode::{HintErrorKind, Program};
105
106    #[test]
107    fn each_variant_has_a_kind() {
108        let hinting = HintError {
109            program: Program::Font,
110            glyph_id: None,
111            pc: 0,
112            opcode: None,
113            kind: HintErrorKind::ValueStackUnderflow,
114        };
115        for (error, kind) in [
116            (
117                ScaleError::GlyphNotFound(GlyphId::new(3)),
118                Error::MissingResource,
119            ),
120            (
121                ScaleError::InsufficientMemory {
122                    required: 8,
123                    supplied: 4,
124                },
125                Error::InsufficientMemory,
126            ),
127            (
128                ScaleError::Outline(Error::LimitExceeded),
129                Error::LimitExceeded,
130            ),
131            (ScaleError::Hinting(hinting), Error::InvalidOperation),
132        ] {
133            assert_eq!(error.kind(), kind, "{error:?}");
134        }
135    }
136
137    // The automatic hinter's memory error is the documented variant.
138    #[test]
139    fn autohint_memory_errors_gain_the_memory_sizes() {
140        assert_eq!(
141            ScaleError::from_draw_error(DrawError::InsufficientMemory, 0, 4),
142            ScaleError::InsufficientMemory {
143                required: 0,
144                supplied: 4
145            }
146        );
147    }
148
149    // A short buffer reports the sizes, and anything else keeps its kind.
150    #[test]
151    fn outline_errors_gain_the_memory_sizes() {
152        assert_eq!(
153            ScaleError::from_outline(Error::InsufficientMemory, 64, 16),
154            ScaleError::InsufficientMemory {
155                required: 64,
156                supplied: 16,
157            }
158        );
159        assert_eq!(
160            ScaleError::from_outline(Error::Malformed, 64, 16),
161            ScaleError::Outline(Error::Malformed)
162        );
163    }
164}