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 type that can compute its size at runtime, based on some input.
71///
72/// For types with a constant size, see [`FixedSize`] and
73/// for types which store their size inline, see [`VarSize`].
74pub trait ComputeSize: ReadArgs {
75 /// Compute the number of bytes required to represent this type.
76 fn compute_size(args: &Self::Args) -> Result<usize, ReadError>;
77}
78
79/// A trait for types that have variable length.
80///
81/// As a rule, these types have an initial length field.
82///
83/// For types with a constant size, see [`FixedSize`] and
84/// for types which can pre-compute their size, see [`ComputeSize`].
85pub trait VarSize {
86 /// The type of the first (length) field of the item.
87 ///
88 /// When reading this type, we will read this value first, and use it to
89 /// determine the total length.
90 type Size: Scalar + Into<u32>;
91
92 #[doc(hidden)]
93 fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
94 let asu32 = data.read_at::<Self::Size>(pos).ok()?.into();
95 (asu32 as usize).checked_add(Self::Size::RAW_BYTE_LEN)
96 }
97
98 /// Determine the total length required to store `count` items of `Self` in
99 /// `data` starting from `start`.
100 #[doc(hidden)]
101 fn total_len_for_count(data: FontData, count: usize) -> Result<usize, ReadError> {
102 let mut current_pos = 0;
103 for _ in 0..count {
104 let len = Self::read_len_at(data, current_pos).ok_or(ReadError::OutOfBounds)?;
105 // If length is 0 then this will spin until we've completed
106 // `count` iterations so just bail out early.
107 // See <https://github.com/harfbuzz/harfrust/issues/203>
108 if len == 0 {
109 return Ok(current_pos);
110 }
111 current_pos = current_pos.checked_add(len).ok_or(ReadError::OutOfBounds)?;
112 }
113 Ok(current_pos)
114 }
115}
116
117/// An error that occurs when reading font data
118#[derive(Debug, Clone, PartialEq)]
119pub enum ReadError {
120 OutOfBounds,
121 // i64 is flexible enough to store any value we might encounter
122 InvalidFormat(i64),
123 InvalidSfnt(u32),
124 InvalidTtc(Tag),
125 InvalidCollectionIndex(u32),
126 InvalidArrayLen,
127 ValidationError,
128 NullOffset,
129 TableIsMissing(Tag),
130 MetricIsMissing(Tag),
131 MalformedData(&'static str),
132}
133
134impl std::fmt::Display for ReadError {
135 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
136 match self {
137 ReadError::OutOfBounds => write!(f, "An offset was out of bounds"),
138 ReadError::InvalidFormat(x) => write!(f, "Invalid format '{x}'"),
139 ReadError::InvalidSfnt(ver) => write!(f, "Invalid sfnt version 0x{ver:08X}"),
140 ReadError::InvalidTtc(tag) => write!(f, "Invalid ttc tag {tag}"),
141 ReadError::InvalidCollectionIndex(ix) => {
142 write!(f, "Invalid index {ix} for font collection")
143 }
144 ReadError::InvalidArrayLen => {
145 write!(f, "Specified array length not a multiple of item size")
146 }
147 ReadError::ValidationError => write!(f, "A validation error occurred"),
148 ReadError::NullOffset => write!(f, "An offset was unexpectedly null"),
149 ReadError::TableIsMissing(tag) => write!(f, "the {tag} table is missing"),
150 ReadError::MetricIsMissing(tag) => write!(f, "the {tag} metric is missing"),
151 ReadError::MalformedData(msg) => write!(f, "Malformed data: '{msg}'"),
152 }
153 }
154}
155
156#[cfg(feature = "std")]
157impl std::error::Error for ReadError {}
158
159#[cfg(test)]
160mod tests {
161 use font_test_data::bebuffer::BeBuffer;
162
163 use super::*;
164
165 struct DummyVarSize {}
166
167 impl VarSize for DummyVarSize {
168 type Size = u16;
169
170 fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
171 data.read_at::<u16>(pos).map(|v| v as usize).ok()
172 }
173 }
174
175 // Avoid fuzzer timeout when we have a VarSizeArray with a large count
176 // that contains a 0 length element.
177 // See <https://github.com/harfbuzz/harfrust/issues/203>
178 #[test]
179 fn total_var_size_with_zero_length_element() {
180 // Array that appears to have 4 var size elements totalling
181 // 26 bytes in length but the zero length 3rd element makes the
182 // final one inaccessible.
183 const PAYLOAD_NOT_SIZE: u16 = 1;
184 let buf = BeBuffer::new().extend([2u16, 4u16, PAYLOAD_NOT_SIZE, 0u16, 20u16]);
185 let total_len =
186 DummyVarSize::total_len_for_count(FontData::new(buf.data()), usize::MAX).unwrap();
187 assert_eq!(total_len, 6);
188 }
189}