Skip to main content

read_fonts/
read.rs

1//! Traits for interpreting font data
2
3#![deny(clippy::arithmetic_side_effects)]
4
5use types::{FixedSize, Scalar, Tag};
6
7use crate::font_data::FontData;
8
9/// A type that can be read from raw table data.
10///
11/// Some types require external state in order to be read; this is passed to
12/// [`read_with_args`], and its type is determined by the [`ReadArgs`]
13/// supertrait. Types that require no external state use `()` as their args,
14/// and get the argument-less [`read`] constructor for free.
15///
16/// [`read`]: Self::read
17/// [`read_with_args`]: Self::read_with_args
18pub trait FontRead<'a>: Sized + ReadArgs {
19    /// Read an item, performing validation.
20    ///
21    /// In the case of a table, this method is responsible for ensuring the input
22    /// data is consistent: this means ensuring that any versioned fields are
23    /// present as required by the version, and that any array lengths are not
24    /// out-of-bounds.
25    ///
26    /// If a type requires multiple arguments, they will be passed as a tuple.
27    ///
28    /// You should not generally need to call this directly; it is intended to
29    /// be used from generated code. Any type that requires external arguments
30    /// also has a custom `read` constructor where you can pass those arguments
31    /// like normal.
32    fn read_with_args(data: FontData<'a>, args: Self::Args) -> Result<Self, ReadError>;
33
34    /// Read an instance of `Self` from the provided data, performing validation.
35    ///
36    /// This is only available for types that require no external state
37    /// (`Args = ()`).
38    fn read(data: FontData<'a>) -> Result<Self, ReadError>
39    where
40        Self: FontRead<'a, Args = ()>,
41    {
42        Self::read_with_args(data, ())
43    }
44}
45
46/// A trait for a type that needs additional arguments to be read.
47///
48/// Types that do not require any external state use `()` as their args.
49///
50/// This is separate from [`FontRead`] so that it can also be a supertrait of
51/// [`ComputeSize`], which does not need a lifetime.
52pub trait ReadArgs {
53    type Args: Copy;
54}
55
56/// A trait for tables that have multiple possible formats.
57pub trait Format<T> {
58    /// The format value for this table.
59    const FORMAT: T;
60}
61
62/// A trait for tables that contain offsets to subtables of heterogeneous types.
63///
64/// The type of the subtable is determiend by an inline discriminant; this trait
65/// reads that discriminant.
66pub trait Discriminant {
67    /// Read the discriminant for this table.
68    // Currently these are always u16, we can switch to an associated type if needed
69    fn read_discriminant(data: FontData<'_>) -> Result<u16, ReadError>;
70}
71
72/// A type that can compute its size at runtime, based on some input.
73///
74/// For types with a constant size, see [`FixedSize`] and
75/// for types which store their size inline, see [`VarSize`].
76pub trait ComputeSize: ReadArgs {
77    /// Compute the number of bytes required to represent this type.
78    fn compute_size(args: Self::Args) -> Result<usize, ReadError>;
79}
80
81/// A trait for types that have variable length.
82///
83/// As a rule, these types have an initial length field.
84///
85/// For types with a constant size, see [`FixedSize`] and
86/// for types which can pre-compute their size, see [`ComputeSize`].
87pub trait VarSize {
88    /// The type of the first (length) field of the item.
89    ///
90    /// When reading this type, we will read this value first, and use it to
91    /// determine the total length.
92    type Size: Scalar + Into<u32>;
93
94    #[doc(hidden)]
95    fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
96        let asu32 = data.read_at::<Self::Size>(pos).ok()?.into();
97        (asu32 as usize).checked_add(Self::Size::RAW_BYTE_LEN)
98    }
99
100    /// Determine the total length required to store `count` items of `Self` in
101    /// `data` starting from `start`.
102    #[doc(hidden)]
103    fn total_len_for_count(data: FontData, count: usize) -> Result<usize, ReadError> {
104        let mut current_pos = 0;
105        for _ in 0..count {
106            let len = Self::read_len_at(data, current_pos).ok_or(ReadError::OutOfBounds)?;
107            // If length is 0 then this will spin until we've completed
108            // `count` iterations so just bail out early.
109            // See <https://github.com/harfbuzz/harfrust/issues/203>
110            if len == 0 {
111                return Ok(current_pos);
112            }
113            current_pos = current_pos.checked_add(len).ok_or(ReadError::OutOfBounds)?;
114        }
115        Ok(current_pos)
116    }
117}
118
119/// An error that occurs when reading font data
120#[derive(Debug, Clone, PartialEq)]
121pub enum ReadError {
122    OutOfBounds,
123    // i64 is flexible enough to store any value we might encounter
124    InvalidFormat(i64),
125    InvalidSfnt(u32),
126    InvalidTtc(Tag),
127    InvalidCollectionIndex(u32),
128    InvalidArrayLen,
129    ValidationError,
130    NullOffset,
131    TableIsMissing(Tag),
132    MetricIsMissing(Tag),
133    MalformedData(&'static str),
134}
135
136impl std::fmt::Display for ReadError {
137    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
138        match self {
139            ReadError::OutOfBounds => write!(f, "An offset was out of bounds"),
140            ReadError::InvalidFormat(x) => write!(f, "Invalid format '{x}'"),
141            ReadError::InvalidSfnt(ver) => write!(f, "Invalid sfnt version 0x{ver:08X}"),
142            ReadError::InvalidTtc(tag) => write!(f, "Invalid ttc tag {tag}"),
143            ReadError::InvalidCollectionIndex(ix) => {
144                write!(f, "Invalid index {ix} for font collection")
145            }
146            ReadError::InvalidArrayLen => {
147                write!(f, "Specified array length not a multiple of item size")
148            }
149            ReadError::ValidationError => write!(f, "A validation error occurred"),
150            ReadError::NullOffset => write!(f, "An offset was unexpectedly null"),
151            ReadError::TableIsMissing(tag) => write!(f, "the {tag} table is missing"),
152            ReadError::MetricIsMissing(tag) => write!(f, "the {tag} metric is missing"),
153            ReadError::MalformedData(msg) => write!(f, "Malformed data: '{msg}'"),
154        }
155    }
156}
157
158impl core::error::Error for ReadError {}
159
160#[cfg(test)]
161mod tests {
162    use font_test_data::bebuffer::BeBuffer;
163
164    use super::*;
165
166    struct DummyVarSize {}
167
168    impl VarSize for DummyVarSize {
169        type Size = u16;
170
171        fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
172            data.read_at::<u16>(pos).map(|v| v as usize).ok()
173        }
174    }
175
176    // Avoid fuzzer timeout when we have a VarSizeArray with a large count
177    // that contains a 0 length element.
178    // See <https://github.com/harfbuzz/harfrust/issues/203>
179    #[test]
180    fn total_var_size_with_zero_length_element() {
181        // Array that appears to have 4 var size elements totalling
182        // 26 bytes in length but the zero length 3rd element makes the
183        // final one inaccessible.
184        const PAYLOAD_NOT_SIZE: u16 = 1;
185        let buf = BeBuffer::new().extend([2u16, 4u16, PAYLOAD_NOT_SIZE, 0u16, 20u16]);
186        let total_len =
187            DummyVarSize::total_len_for_count(FontData::new(buf.data()), usize::MAX).unwrap();
188        assert_eq!(total_len, 6);
189    }
190}