font_subset/
errors.rs

1use core::{fmt, ops};
2
3use crate::{
4    alloc::{format, vec, String, Vec},
5    TableTag,
6};
7
8/// Kind of a font [`ParseError`].
9#[derive(Debug)]
10#[non_exhaustive]
11pub enum ParseErrorKind {
12    /// Unexpected end of the font data.
13    UnexpectedEof,
14    /// Unexpected numerical value.
15    UnexpectedValue {
16        /// Name of the value parsed.
17        name: &'static str,
18        /// Description of the expected value.
19        expected: String,
20        /// Actual encountered value.
21        actual: u32,
22    },
23    /// Invalid Unicode char code. Unicode char codes must be in `0..=0xd7ff` or `0xe000..=0x10ffff`.
24    InvalidCharCode(u32),
25    /// Missing required font table (e.g., `head`).
26    MissingTable,
27    /// A font table is not aligned to a 4-byte boundary.
28    UnalignedTable,
29    /// No supported subtable in the `cmap` table.
30    NoSupportedCmap,
31    /// Offset inferred from the table data is out of bounds.
32    OffsetOutOfBounds(usize),
33    /// Range inferred from the table data is out of bounds.
34    RangeOutOfBounds {
35        /// Inferred range.
36        range: ops::Range<usize>,
37        /// Length of the indexed data.
38        len: usize,
39    },
40    /// Unexpected table length.
41    UnexpectedTableLen {
42        /// Expected length.
43        expected: usize,
44        /// Actual length.
45        actual: usize,
46    },
47    /// Checksum mismatch.
48    Checksum {
49        /// Expected checksum.
50        expected: u32,
51        /// Actual checksum read from the font data.
52        actual: u32,
53    },
54    /// UTF-16 decoding error.
55    Utf16,
56    /// Base-128 decoding error (used in WOFF2).
57    UintBase128,
58    /// Unsupported WOFF2 table tag.
59    UnsupportedWoff2Table {
60        /// Encountered tag.
61        tag: TableTag,
62        /// Encountered transform bits.
63        transform_bits: u8,
64    },
65    /// Font size is too large.
66    TooLargeFont(usize),
67    /// Brotli decompression error for WOFF2.
68    BrotliDecompression,
69}
70
71impl fmt::Display for ParseErrorKind {
72    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
73        match self {
74            Self::UnexpectedEof => formatter.write_str("unexpected end of the font data"),
75            Self::UnexpectedValue {
76                name,
77                expected,
78                actual,
79            } => {
80                write!(
81                    formatter,
82                    "unexpected value of `{name}`: expected {expected}, got {actual}"
83                )
84            }
85            Self::InvalidCharCode(val) => {
86                write!(formatter, "invalid Unicode char code: {val}")
87            }
88            Self::MissingTable => formatter.write_str("missing required font table"),
89            Self::UnalignedTable => {
90                formatter.write_str("font table is not aligned to a 4-byte boundary")
91            }
92            Self::NoSupportedCmap => {
93                formatter.write_str("no supported subtable in the `cmap` table")
94            }
95            Self::OffsetOutOfBounds(val) => {
96                write!(
97                    formatter,
98                    "offset ({val}) inferred from the table data is out of bounds"
99                )
100            }
101            Self::RangeOutOfBounds { range, len } => {
102                write!(
103                    formatter,
104                    "range ({range:?}) inferred from the table data is out of bounds (..{len})"
105                )
106            }
107            Self::UnexpectedTableLen { expected, actual } => {
108                write!(
109                    formatter,
110                    "unexpected table length: expected {expected}, got {actual}"
111                )
112            }
113            Self::Checksum { expected, actual } => {
114                write!(
115                    formatter,
116                    "unexpected checksum: expected {expected}, got {actual}"
117                )
118            }
119            Self::Utf16 => formatter.write_str("failed decoding UTF-16 string"),
120            Self::UintBase128 => formatter.write_str("failed decoding uint128 value"),
121            Self::UnsupportedWoff2Table {
122                tag,
123                transform_bits,
124            } => {
125                write!(
126                    formatter,
127                    "unsupported WOFF2 table tag ({tag}) with transform {transform_bits}"
128                )
129            }
130            Self::TooLargeFont(len) => {
131                write!(formatter, "font size ({len}) is too large to be processed")
132            }
133            Self::BrotliDecompression => formatter.write_str("Brotli decompression error"),
134        }
135    }
136}
137
138#[cfg(feature = "std")]
139impl std::error::Error for ParseErrorKind {}
140
141macro_rules! check_exact {
142    ($val:ident, $expected:expr) => {
143        if $val == $expected {
144            Ok(())
145        } else {
146            Err($crate::ParseErrorKind::UnexpectedValue {
147                name: ::core::stringify!($val),
148                expected: $crate::alloc::ToString::to_string(&$expected),
149                actual: u32::from($val),
150            })
151        }
152    };
153}
154
155/// Errors that can occur when parsing an OpenType [`Font`](crate::Font).
156#[derive(Debug)]
157pub struct ParseError {
158    pub(crate) kind: ParseErrorKind,
159    pub(crate) offset: usize,
160    pub(crate) table: Option<TableTag>,
161}
162
163impl fmt::Display for ParseError {
164    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
165        if let Some(table) = self.table {
166            write!(formatter, "[{table}] ")?;
167        }
168        if self.offset > 0 {
169            write!(formatter, "{}: ", self.offset)?;
170        }
171        fmt::Display::fmt(&self.kind, formatter)
172    }
173}
174
175#[cfg(feature = "std")]
176impl std::error::Error for ParseError {}
177
178impl ParseError {
179    pub(crate) fn missing_table(tag: TableTag) -> Self {
180        Self {
181            kind: ParseErrorKind::MissingTable,
182            offset: 0,
183            table: Some(tag),
184        }
185    }
186
187    /// Gets the error kind.
188    pub fn kind(&self) -> &ParseErrorKind {
189        &self.kind
190    }
191
192    /// Gets the table this error relates to.
193    pub fn table(&self) -> Option<TableTag> {
194        self.table
195    }
196
197    /// Gets the offset in the font data.
198    pub fn offset(&self) -> usize {
199        self.offset
200    }
201}
202
203/// Kind of a [`Warning`].
204#[derive(Debug)]
205#[non_exhaustive]
206pub enum WarningKind {
207    /// Mismatch between the computed and recorded value.
208    ValueMismatch {
209        /// Name of the value.
210        name: &'static str,
211        /// Computed value.
212        computed: String,
213        /// Actual encountered value.
214        recorded: String,
215    },
216}
217
218impl fmt::Display for WarningKind {
219    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
220        match self {
221            Self::ValueMismatch {
222                name,
223                computed,
224                recorded,
225            } => {
226                write!(
227                    formatter,
228                    "mismatch between computed ({computed}) and recorded ({recorded}) values of `{name}`"
229                )
230            }
231        }
232    }
233}
234
235/// Warning that can occur [validating a font](crate::Font::validate()).
236#[derive(Debug)]
237pub struct Warning {
238    kind: WarningKind,
239    table: Option<TableTag>,
240}
241
242impl fmt::Display for Warning {
243    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
244        if let Some(table) = self.table {
245            write!(formatter, "[{table}] ")?;
246        }
247        fmt::Display::fmt(&self.kind, formatter)
248    }
249}
250
251#[cfg(feature = "std")]
252impl std::error::Error for Warning {}
253
254impl Warning {
255    /// Returns the kind of this warning.
256    pub fn kind(&self) -> &WarningKind {
257        &self.kind
258    }
259
260    /// Gets the table this warning relates to.
261    pub fn table(&self) -> Option<TableTag> {
262        self.table
263    }
264}
265
266/// Set of [`Warning`]s.
267#[derive(Debug)]
268pub struct Warnings(Vec<Warning>);
269
270impl fmt::Display for Warnings {
271    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
272        for warn in &self.0 {
273            writeln!(formatter, "- {warn}")?;
274        }
275        Ok(())
276    }
277}
278
279impl IntoIterator for Warnings {
280    type Item = Warning;
281    type IntoIter = vec::IntoIter<Warning>;
282
283    fn into_iter(self) -> Self::IntoIter {
284        self.0.into_iter()
285    }
286}
287
288#[cfg(feature = "std")]
289impl std::error::Error for Warnings {}
290
291impl Warnings {
292    pub(crate) fn empty() -> Self {
293        Self(vec![])
294    }
295
296    /// Checks if there's at least one warning.
297    pub fn is_empty(&self) -> bool {
298        self.0.is_empty()
299    }
300
301    /// Returns the number of contained warnings.
302    pub fn len(&self) -> usize {
303        self.0.len()
304    }
305
306    /// Iterates over the contained warnings in no particular order.
307    pub fn iter(&self) -> impl ExactSizeIterator<Item = &Warning> + '_ {
308        self.0.iter()
309    }
310
311    pub(crate) fn for_table(&mut self, table: TableTag) -> TableWarnings<'_> {
312        TableWarnings { inner: self, table }
313    }
314
315    /// Converts these warnings into a `Result`. This is useful to treat warnings as errors.
316    ///
317    /// # Errors
318    ///
319    /// Returns `Err(_)` if there's at least one warning.
320    pub fn into_result(self) -> Result<(), Self> {
321        if self.0.is_empty() {
322            Ok(())
323        } else {
324            Err(self)
325        }
326    }
327}
328
329#[derive(Debug)]
330pub(crate) struct TableWarnings<'a> {
331    inner: &'a mut Warnings,
332    table: TableTag,
333}
334
335impl TableWarnings<'_> {
336    pub(crate) fn check_match<T: Copy + PartialEq + fmt::Debug>(
337        &mut self,
338        name: &'static str,
339        computed: T,
340        recorded: T,
341    ) {
342        if computed != recorded {
343            self.inner.0.push(Warning {
344                kind: WarningKind::ValueMismatch {
345                    name,
346                    computed: format!("{computed:?}"),
347                    recorded: format!("{recorded:?}"),
348                },
349                table: Some(self.table),
350            });
351        }
352    }
353}