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}