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 determined 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 are read at a position within enclosing data.
82///
83/// [`FontRead`] receives data already sliced to the start of the item, which
84/// discards everything before it. That is the wrong shape for an item holding
85/// offsets that resolve relative to the enclosing table rather than to the item
86/// itself: a GPOS value record's device offsets are measured from the start of
87/// the table containing the record. Such a type is given the enclosing data and
88/// its own position within it, and keeps both.
89///
90/// You should not generally need to call this directly; it is intended to be
91/// used from generated code.
92pub trait FontReadAt<'a>: Sized + ReadArgs {
93    /// Read an item positioned at `offset` bytes into `data`.
94    fn read_at(data: FontData<'a>, offset: usize, args: Self::Args) -> Result<Self, ReadError>;
95}
96
97/// A trait for types that have variable length.
98///
99/// As a rule, these types have an initial length field.
100///
101/// For types with a constant size, see [`FixedSize`] and
102/// for types which can pre-compute their size, see [`ComputeSize`].
103pub trait VarSize {
104    /// The type of the first (length) field of the item.
105    ///
106    /// When reading this type, we will read this value first, and use it to
107    /// determine the total length.
108    type Size: Scalar + Into<u32>;
109
110    #[doc(hidden)]
111    fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
112        let asu32 = data.read_at::<Self::Size>(pos).ok()?.into();
113        (asu32 as usize).checked_add(Self::Size::RAW_BYTE_LEN)
114    }
115
116    /// Determine the total length required to store `count` items of `Self` in
117    /// `data` starting from `start`.
118    #[doc(hidden)]
119    fn total_len_for_count(data: FontData, count: usize) -> Result<usize, ReadError> {
120        let mut current_pos = 0;
121        for _ in 0..count {
122            let len = Self::read_len_at(data, current_pos).ok_or(ReadError::OutOfBounds)?;
123            // If length is 0 then this will spin until we've completed
124            // `count` iterations so just bail out early.
125            // See <https://github.com/harfbuzz/harfrust/issues/203>
126            if len == 0 {
127                return Ok(current_pos);
128            }
129            current_pos = current_pos.checked_add(len).ok_or(ReadError::OutOfBounds)?;
130        }
131        Ok(current_pos)
132    }
133}
134
135/// An error that occurs when reading font data
136#[derive(Debug, Clone, PartialEq)]
137pub enum ReadError {
138    OutOfBounds,
139    // i64 is flexible enough to store any value we might encounter
140    InvalidFormat(i64),
141    InvalidSfnt(u32),
142    InvalidTtc(Tag),
143    InvalidCollectionIndex(u32),
144    InvalidArrayLen,
145    ValidationError,
146    NullOffset,
147    TableIsMissing(Tag),
148    MalformedData(&'static str),
149}
150
151impl std::fmt::Display for ReadError {
152    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
153        match self {
154            ReadError::OutOfBounds => write!(f, "An offset was out of bounds"),
155            ReadError::InvalidFormat(x) => write!(f, "Invalid format '{x}'"),
156            ReadError::InvalidSfnt(ver) => write!(f, "Invalid sfnt version 0x{ver:08X}"),
157            ReadError::InvalidTtc(tag) => write!(f, "Invalid ttc tag {tag}"),
158            ReadError::InvalidCollectionIndex(ix) => {
159                write!(f, "Invalid index {ix} for font collection")
160            }
161            ReadError::InvalidArrayLen => {
162                write!(f, "Specified array length not a multiple of item size")
163            }
164            ReadError::ValidationError => write!(f, "A validation error occurred"),
165            ReadError::NullOffset => write!(f, "An offset was unexpectedly null"),
166            ReadError::TableIsMissing(tag) => write!(f, "the {tag} table is missing"),
167            ReadError::MalformedData(msg) => write!(f, "Malformed data: '{msg}'"),
168        }
169    }
170}
171
172impl core::error::Error for ReadError {}
173
174#[cfg(test)]
175mod tests {
176    use font_test_data::bebuffer::BeBuffer;
177
178    use super::*;
179
180    struct DummyVarSize {}
181
182    impl VarSize for DummyVarSize {
183        type Size = u16;
184
185        fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
186            data.read_at::<u16>(pos).map(|v| v as usize).ok()
187        }
188    }
189
190    // Avoid fuzzer timeout when we have a VarSizeArray with a large count
191    // that contains a 0 length element.
192    // See <https://github.com/harfbuzz/harfrust/issues/203>
193    #[test]
194    fn total_var_size_with_zero_length_element() {
195        // Array that appears to have 4 var size elements totalling
196        // 26 bytes in length but the zero length 3rd element makes the
197        // final one inaccessible.
198        const PAYLOAD_NOT_SIZE: u16 = 1;
199        let buf = BeBuffer::new().extend([2u16, 4u16, PAYLOAD_NOT_SIZE, 0u16, 20u16]);
200        let total_len =
201            DummyVarSize::total_len_for_count(FontData::new(buf.data()), usize::MAX).unwrap();
202        assert_eq!(total_len, 6);
203    }
204}