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}