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 (0..count).try_fold(0usize, |current_pos, _i| {
103 Self::read_len_at(data, current_pos)
104 .and_then(|i_len| current_pos.checked_add(i_len))
105 .ok_or(ReadError::OutOfBounds)
106 })
107 }
108}
109
110/// An error that occurs when reading font data
111#[derive(Debug, Clone, PartialEq)]
112pub enum ReadError {
113 OutOfBounds,
114 // i64 is flexible enough to store any value we might encounter
115 InvalidFormat(i64),
116 InvalidSfnt(u32),
117 InvalidTtc(Tag),
118 InvalidCollectionIndex(u32),
119 InvalidArrayLen,
120 ValidationError,
121 NullOffset,
122 TableIsMissing(Tag),
123 MetricIsMissing(Tag),
124 MalformedData(&'static str),
125}
126
127impl std::fmt::Display for ReadError {
128 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
129 match self {
130 ReadError::OutOfBounds => write!(f, "An offset was out of bounds"),
131 ReadError::InvalidFormat(x) => write!(f, "Invalid format '{x}'"),
132 ReadError::InvalidSfnt(ver) => write!(f, "Invalid sfnt version 0x{ver:08X}"),
133 ReadError::InvalidTtc(tag) => write!(f, "Invalid ttc tag {tag}"),
134 ReadError::InvalidCollectionIndex(ix) => {
135 write!(f, "Invalid index {ix} for font collection")
136 }
137 ReadError::InvalidArrayLen => {
138 write!(f, "Specified array length not a multiple of item size")
139 }
140 ReadError::ValidationError => write!(f, "A validation error occurred"),
141 ReadError::NullOffset => write!(f, "An offset was unexpectedly null"),
142 ReadError::TableIsMissing(tag) => write!(f, "the {tag} table is missing"),
143 ReadError::MetricIsMissing(tag) => write!(f, "the {tag} metric is missing"),
144 ReadError::MalformedData(msg) => write!(f, "Malformed data: '{msg}'"),
145 }
146 }
147}
148
149#[cfg(feature = "std")]
150impl std::error::Error for ReadError {}