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