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    MetricIsMissing(Tag),
149    MalformedData(&'static str),
150}
151
152impl std::fmt::Display for ReadError {
153    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
154        match self {
155            ReadError::OutOfBounds => write!(f, "An offset was out of bounds"),
156            ReadError::InvalidFormat(x) => write!(f, "Invalid format '{x}'"),
157            ReadError::InvalidSfnt(ver) => write!(f, "Invalid sfnt version 0x{ver:08X}"),
158            ReadError::InvalidTtc(tag) => write!(f, "Invalid ttc tag {tag}"),
159            ReadError::InvalidCollectionIndex(ix) => {
160                write!(f, "Invalid index {ix} for font collection")
161            }
162            ReadError::InvalidArrayLen => {
163                write!(f, "Specified array length not a multiple of item size")
164            }
165            ReadError::ValidationError => write!(f, "A validation error occurred"),
166            ReadError::NullOffset => write!(f, "An offset was unexpectedly null"),
167            ReadError::TableIsMissing(tag) => write!(f, "the {tag} table is missing"),
168            ReadError::MetricIsMissing(tag) => write!(f, "the {tag} metric is missing"),
169            ReadError::MalformedData(msg) => write!(f, "Malformed data: '{msg}'"),
170        }
171    }
172}
173
174impl core::error::Error for ReadError {}
175
176#[cfg(test)]
177mod tests {
178    use font_test_data::bebuffer::BeBuffer;
179
180    use super::*;
181
182    struct DummyVarSize {}
183
184    impl VarSize for DummyVarSize {
185        type Size = u16;
186
187        fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
188            data.read_at::<u16>(pos).map(|v| v as usize).ok()
189        }
190    }
191
192    // Avoid fuzzer timeout when we have a VarSizeArray with a large count
193    // that contains a 0 length element.
194    // See <https://github.com/harfbuzz/harfrust/issues/203>
195    #[test]
196    fn total_var_size_with_zero_length_element() {
197        // Array that appears to have 4 var size elements totalling
198        // 26 bytes in length but the zero length 3rd element makes the
199        // final one inaccessible.
200        const PAYLOAD_NOT_SIZE: u16 = 1;
201        let buf = BeBuffer::new().extend([2u16, 4u16, PAYLOAD_NOT_SIZE, 0u16, 20u16]);
202        let total_len =
203            DummyVarSize::total_len_for_count(FontData::new(buf.data()), usize::MAX).unwrap();
204        assert_eq!(total_len, 6);
205    }
206}