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}