Skip to main content

read_fonts/
array.rs

1//! Custom array types
2
3#![deny(clippy::arithmetic_side_effects)]
4
5use bytemuck::AnyBitPattern;
6use font_types::FixedSize;
7
8use crate::read::{ComputeSize, FontRead, ReadArgs, VarSize};
9use crate::{FontData, ReadError};
10
11/// An array whose items size is not known at compile time.
12///
13/// This requires the inner type to implement [`FontRead`] 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
22    item_len: usize,
23    len: usize,
24    data: FontData<'a>,
25    args: T::Args,
26}
27
28impl<'a, T: ComputeSize> ComputedArray<'a, T> {
29    pub fn new(data: FontData<'a>, args: T::Args) -> Result<Self, ReadError> {
30        let item_len = T::compute_size(args)?;
31        let len = data.len().checked_div(item_len).unwrap_or(0);
32        Ok(ComputedArray {
33            item_len,
34            len,
35            data,
36            args,
37        })
38    }
39
40    /// The number of items in the array
41    pub fn len(&self) -> usize {
42        self.len
43    }
44
45    pub fn is_empty(&self) -> bool {
46        self.len == 0
47    }
48}
49
50impl<T: ReadArgs> ReadArgs for ComputedArray<'_, T> {
51    type Args = T::Args;
52}
53
54impl<'a, T> FontRead<'a> for ComputedArray<'a, T>
55where
56    T: ComputeSize + FontRead<'a>,
57    T::Args: Copy,
58{
59    fn read_with_args(data: FontData<'a>, args: Self::Args) -> Result<Self, ReadError> {
60        Self::new(data, args)
61    }
62}
63
64impl<T> Default for ComputedArray<'_, T>
65where
66    T: ReadArgs,
67    T::Args: Default,
68{
69    fn default() -> Self {
70        Self {
71            item_len: 0,
72            len: 0,
73            data: Default::default(),
74            args: Default::default(),
75        }
76    }
77}
78
79impl<'a, T> ComputedArray<'a, T>
80where
81    T: FontRead<'a>,
82    T::Args: Copy + 'static,
83{
84    pub fn iter(&self) -> impl Iterator<Item = Result<T, ReadError>> + 'a {
85        let mut i = 0;
86        let data = self.data;
87        let args = self.args;
88        let item_len = self.item_len;
89        let len = self.len;
90
91        std::iter::from_fn(move || {
92            if i == len {
93                return None;
94            }
95            let item_start = item_len.checked_mul(i)?;
96            i = i.checked_add(1)?;
97            let data = data.split_off(item_start)?;
98            Some(T::read_with_args(data, args))
99        })
100    }
101
102    #[inline]
103    pub fn get(&self, idx: usize) -> Result<T, ReadError> {
104        let item_start = idx
105            .checked_mul(self.item_len)
106            .ok_or(ReadError::OutOfBounds)?;
107        self.data
108            .split_off(item_start)
109            .ok_or(ReadError::OutOfBounds)
110            .and_then(|data| T::read_with_args(data, self.args))
111    }
112}
113
114impl<T: ReadArgs> std::fmt::Debug for ComputedArray<'_, T> {
115    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
116        f.debug_struct("DynSizedArray")
117            .field("bytes", &self.data)
118            .finish()
119    }
120}
121
122/// An array of items of non-uniform length.
123///
124/// Random access into this array cannot be especially efficient, since it requires
125/// a linear scan.
126pub struct VarLenArray<'a, T> {
127    data: FontData<'a>,
128    phantom: std::marker::PhantomData<*const T>,
129}
130
131impl<'a, T: FontRead<'a, Args = ()> + VarSize> VarLenArray<'a, T> {
132    /// Return the item at the provided index.
133    ///
134    /// # Performance
135    ///
136    /// Determining the position of an item in this collection requires looking
137    /// at all the preceding items; that is, it is `O(n)` instead of `O(1)` as
138    /// it would be for a `Vec`.
139    ///
140    /// As a consequence, calling this method in a loop could potentially be
141    /// very slow. If this is something you need to do, it will probably be
142    /// much faster to first collect all the items into a `Vec` beforehand,
143    /// and then fetch them from there.
144    pub fn get(&self, idx: usize) -> Option<Result<T, ReadError>> {
145        if self.data.is_empty() {
146            return None;
147        }
148
149        let mut pos = 0usize;
150        for _ in 0..idx {
151            pos = pos.checked_add(T::read_len_at(self.data, pos)?)?;
152        }
153        self.data.split_off(pos).map(T::read)
154    }
155
156    /// Return an iterator over this array's items.
157    pub fn iter(&self) -> impl Iterator<Item = Result<T, ReadError>> + 'a {
158        let mut data = self.data;
159        std::iter::from_fn(move || {
160            if data.is_empty() {
161                return None;
162            }
163
164            let item_len = T::read_len_at(data, 0)?;
165            // If the length is 0 then then it's not useful to continue
166            // iteration. The subsequent read will probably fail but if
167            // the user is skipping malformed elements (which is common)
168            // this this iterator will continue forever.
169            if item_len == 0 {
170                return None;
171            }
172            let item_data = data.slice(..item_len)?;
173            let next = T::read(item_data);
174            data = data.split_off(item_len)?;
175            Some(next)
176        })
177    }
178}
179
180impl<T> ReadArgs for VarLenArray<'_, T> {
181    type Args = ();
182}
183
184impl<'a, T> FontRead<'a> for VarLenArray<'a, T> {
185    fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
186        Ok(VarLenArray {
187            data,
188            phantom: core::marker::PhantomData,
189        })
190    }
191}
192
193impl<T> Default for VarLenArray<'_, T> {
194    fn default() -> Self {
195        Self {
196            data: Default::default(),
197            phantom: std::marker::PhantomData,
198        }
199    }
200}
201
202impl<T: AnyBitPattern> ReadArgs for &[T] {
203    type Args = u16;
204}
205
206impl<'a, T: AnyBitPattern + FixedSize> FontRead<'a> for &'a [T] {
207    fn read_with_args(data: FontData<'a>, args: u16) -> Result<Self, ReadError> {
208        let len = (args as usize)
209            .checked_mul(T::RAW_BYTE_LEN)
210            .ok_or(ReadError::OutOfBounds)?;
211        data.read_array(0..len)
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use crate::codegen_test::records::VarLenItem;
219    use font_test_data::bebuffer::BeBuffer;
220
221    impl VarSize for VarLenItem<'_> {
222        type Size = u32;
223
224        fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
225            data.read_at::<u32>(pos).ok().map(|len| len as usize)
226        }
227    }
228
229    /// HB/HarfRuzz test "shlana_9_006" has a morx table containing a chain
230    /// with a length of 0. This caused the VarLenArray iterator to loop
231    /// indefinitely.
232    #[test]
233    fn var_len_iter_with_zero_length_item() {
234        // Create a buffer containing three elements where the last
235        // has zero length
236        let mut buf = BeBuffer::new();
237        buf = buf.push(8u32).extend([0u8; 4]);
238        buf = buf.push(18u32).extend([0u8; 14]);
239        buf = buf.push(0u32);
240        let arr: VarLenArray<VarLenItem> = VarLenArray::read(FontData::new(buf.data())).unwrap();
241        // Ensure we don't iterate forever and only read two elements (the
242        // take() exists so that the test fails rather than hanging if the
243        // code regresses in the future)
244        assert_eq!(arr.iter().take(10).count(), 2);
245    }
246}