1//! Custom array types
23#![deny(clippy::arithmetic_side_effects)]
45use bytemuck::AnyBitPattern;
6use font_types::FixedSize;
78use crate::read::{ComputeSize, FontReadWithArgs, ReadArgs, VarSize};
9use crate::{FontData, FontRead, ReadError};
1011/// An array whose items size is not known at compile time.
12///
13/// This requires the inner type to implement [`FontReadWithArgs`] as well as
14/// [`ComputeSize`].
15///
16/// At runtime, `Args` are provided which will be used to compute the size
17/// of each item; this size is then used to compute the positions of the items
18/// within the underlying data, from which they will be read lazily.
19#[derive(Clone)]
20pub struct ComputedArray<'a, T: ReadArgs> {
21// the length of each item
22item_len: usize,
23 len: usize,
24 data: FontData<'a>,
25 args: T::Args,
26}
2728impl<'a, T: ComputeSize> ComputedArray<'a, T> {
29pub fn new(data: FontData<'a>, args: T::Args) -> Result<Self, ReadError> {
30let item_len = T::compute_size(&args)?;
31let len = data.len().checked_div(item_len).unwrap_or(0);
32Ok(ComputedArray {
33 item_len,
34 len,
35 data,
36 args,
37 })
38 }
3940/// The number of items in the array
41pub fn len(&self) -> usize {
42self.len
43 }
4445pub fn is_empty(&self) -> bool {
46self.len == 0
47}
48}
4950impl<T: ReadArgs> ReadArgs for ComputedArray<'_, T> {
51type Args = T::Args;
52}
5354impl<'a, T> FontReadWithArgs<'a> for ComputedArray<'a, T>
55where
56T: ComputeSize + FontReadWithArgs<'a>,
57 T::Args: Copy,
58{
59fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result<Self, ReadError> {
60Self::new(data, *args)
61 }
62}
6364impl<'a, T> ComputedArray<'a, T>
65where
66T: FontReadWithArgs<'a>,
67 T::Args: Copy + 'static,
68{
69pub fn iter(&self) -> impl Iterator<Item = Result<T, ReadError>> + 'a {
70let mut i = 0;
71let data = self.data;
72let args = self.args;
73let item_len = self.item_len;
74let len = self.len;
7576 std::iter::from_fn(move || {
77if i == len {
78return None;
79 }
80let item_start = item_len.checked_mul(i)?;
81 i = i.checked_add(1)?;
82let data = data.split_off(item_start)?;
83Some(T::read_with_args(data, &args))
84 })
85 }
8687pub fn get(&self, idx: usize) -> Result<T, ReadError> {
88let item_start = idx
89 .checked_mul(self.item_len)
90 .ok_or(ReadError::OutOfBounds)?;
91self.data
92 .split_off(item_start)
93 .ok_or(ReadError::OutOfBounds)
94 .and_then(|data| T::read_with_args(data, &self.args))
95 }
96}
9798impl<T: ReadArgs> std::fmt::Debug for ComputedArray<'_, T> {
99fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
100 f.debug_struct("DynSizedArray")
101 .field("bytes", &self.data)
102 .finish()
103 }
104}
105106/// An array of items of non-uniform length.
107///
108/// Random access into this array cannot be especially efficient, since it requires
109/// a linear scan.
110pub struct VarLenArray<'a, T> {
111 data: FontData<'a>,
112 phantom: std::marker::PhantomData<*const T>,
113}
114115impl<'a, T: FontRead<'a> + VarSize> VarLenArray<'a, T> {
116/// Return the item at the provided index.
117 ///
118 /// # Performance
119 ///
120 /// Determining the position of an item in this collection requires looking
121 /// at all the preceding items; that is, it is `O(n)` instead of `O(1)` as
122 /// it would be for a `Vec`.
123 ///
124 /// As a consequence, calling this method in a loop could potentially be
125 /// very slow. If this is something you need to do, it will probably be
126 /// much faster to first collect all the items into a `Vec` beforehand,
127 /// and then fetch them from there.
128pub fn get(&self, idx: usize) -> Option<Result<T, ReadError>> {
129let mut pos = 0usize;
130for _ in 0..idx {
131 pos = pos.checked_add(T::read_len_at(self.data, pos)?)?;
132 }
133self.data.split_off(pos).map(T::read)
134 }
135136/// Return an iterator over this array's items.
137pub fn iter(&self) -> impl Iterator<Item = Result<T, ReadError>> + 'a {
138let mut data = self.data;
139 std::iter::from_fn(move || {
140if data.is_empty() {
141return None;
142 }
143144let item_len = T::read_len_at(data, 0)?;
145// If the length is 0 then then it's not useful to continue
146 // iteration. The subsequent read will probably fail but if
147 // the user is skipping malformed elements (which is common)
148 // this this iterator will continue forever.
149if item_len == 0 {
150return None;
151 }
152let item_data = data.slice(..item_len)?;
153let next = T::read(item_data);
154 data = data.split_off(item_len)?;
155Some(next)
156 })
157 }
158}
159160impl<'a, T> FontRead<'a> for VarLenArray<'a, T> {
161fn read(data: FontData<'a>) -> Result<Self, ReadError> {
162Ok(VarLenArray {
163 data,
164 phantom: core::marker::PhantomData,
165 })
166 }
167}
168169impl<T: AnyBitPattern> ReadArgs for &[T] {
170type Args = u16;
171}
172173impl<'a, T: AnyBitPattern + FixedSize> FontReadWithArgs<'a> for &'a [T] {
174fn read_with_args(data: FontData<'a>, args: &u16) -> Result<Self, ReadError> {
175let len = (*args as usize)
176 .checked_mul(T::RAW_BYTE_LEN)
177 .ok_or(ReadError::OutOfBounds)?;
178 data.read_array(0..len)
179 }
180}
181182#[cfg(test)]
183mod tests {
184use super::*;
185use crate::codegen_test::records::VarLenItem;
186use font_test_data::bebuffer::BeBuffer;
187188impl VarSize for VarLenItem<'_> {
189type Size = u32;
190191fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
192 data.read_at::<u32>(pos).ok().map(|len| len as usize)
193 }
194 }
195196/// HB/HarfRuzz test "shlana_9_006" has a morx table containing a chain
197 /// with a length of 0. This caused the VarLenArray iterator to loop
198 /// indefinitely.
199#[test]
200fn var_len_iter_with_zero_length_item() {
201// Create a buffer containing three elements where the last
202 // has zero length
203let mut buf = BeBuffer::new();
204 buf = buf.push(8u32).extend([0u8; 4]);
205 buf = buf.push(18u32).extend([0u8; 14]);
206 buf = buf.push(0u32);
207let arr: VarLenArray<VarLenItem> = VarLenArray::read(FontData::new(buf.data())).unwrap();
208// Ensure we don't iterate forever and only read two elements (the
209 // take() exists so that the test fails rather than hanging if the
210 // code regresses in the future)
211assert_eq!(arr.iter().take(10).count(), 2);
212 }
213}