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}