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, FontReadAt, ReadArgs, VarSize};
9use crate::{FontData, ReadError};
10use core::ops::Range;
11
12/// An array whose items size is not known at compile time.
13///
14/// This requires the inner type to implement [`FontReadAt`] as well as
15/// [`ComputeSize`].
16///
17/// At runtime, `Args` are provided which will be used to compute the size
18/// of each item; this size is then used to compute the positions of the items
19/// within the underlying data, from which they will be read lazily.
20///
21/// The data is the whole enclosing extent rather than a slice at the array, so
22/// an item that resolves offsets against the enclosing table still can.
23#[derive(Clone)]
24pub struct ComputedArray<'a, T: ReadArgs> {
25    // the length of each item
26    item_len: usize,
27    len: usize,
28    // the position of the first item within `data`
29    start: usize,
30    data: FontData<'a>,
31    args: T::Args,
32}
33
34impl<'a, T: ComputeSize> ComputedArray<'a, T> {
35    pub fn new(data: FontData<'a>, range: Range<usize>, args: T::Args) -> Result<Self, ReadError> {
36        let item_len = T::compute_size(args)?;
37        // the whole range must be present, as it was when this was built from
38        // data already sliced to it
39        let available = data
40            .as_bytes()
41            .get(range.clone())
42            .ok_or(ReadError::OutOfBounds)?
43            .len();
44        let len = available.checked_div(item_len).unwrap_or(0);
45        Ok(ComputedArray {
46            item_len,
47            len: representable_len(range.start, item_len, len),
48            start: range.start,
49            data,
50            args,
51        })
52    }
53
54    /// The number of items in the array
55    pub fn len(&self) -> usize {
56        self.len
57    }
58
59    pub fn is_empty(&self) -> bool {
60        self.len == 0
61    }
62}
63
64impl<T: ReadArgs> ReadArgs for ComputedArray<'_, T> {
65    type Args = T::Args;
66}
67
68impl<T> Default for ComputedArray<'_, T>
69where
70    T: ReadArgs,
71    T::Args: Default,
72{
73    fn default() -> Self {
74        Self {
75            item_len: 0,
76            len: 0,
77            start: 0,
78            data: Default::default(),
79            args: Default::default(),
80        }
81    }
82}
83
84impl<'a, T> ComputedArray<'a, T>
85where
86    T: FontReadAt<'a>,
87    T::Args: Copy + 'static,
88{
89    pub fn iter(&self) -> impl Iterator<Item = Result<T, ReadError>> + 'a + Clone {
90        let data = self.data;
91        let args = self.args;
92        let item_len = self.item_len;
93        let len = self.len;
94        // `new` bounded the length so that every offset below, including the
95        // one computed past the last item, is representable; each step is then
96        // a plain add.
97        let mut item_start = self.start;
98
99        (0..len).map(move |_| {
100            let item = T::read_at(data, item_start, args);
101            #[allow(clippy::arithmetic_side_effects)] // bounded by representable_len
102            {
103                item_start += item_len;
104            }
105            item
106        })
107    }
108
109    #[inline]
110    pub fn get(&self, idx: usize) -> Result<T, ReadError> {
111        if idx >= self.len {
112            return Err(ReadError::OutOfBounds);
113        }
114        let item_start = idx
115            .checked_mul(self.item_len)
116            .and_then(|start| start.checked_add(self.start))
117            .ok_or(ReadError::OutOfBounds)?;
118        T::read_at(self.data, item_start, self.args)
119    }
120}
121
122impl<T: ReadArgs> std::fmt::Debug for ComputedArray<'_, T> {
123    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
124        f.debug_struct("DynSizedArray")
125            .field("bytes", &self.data)
126            .finish()
127    }
128}
129
130/// The number of items whose offsets are representable.
131///
132/// Item `i` sits at `start + i * item_len`, and walking the array computes one
133/// offset past the last item, so this is the largest `len` for which
134/// `start + len * item_len` does not overflow. Establishing it once up front
135/// lets iteration be a plain add per step.
136///
137/// An array built by [`ComputedArray::new`] is already within this bound: its
138/// last offset is `range.end`, which had to be a valid index into `data`.
139fn representable_len(start: usize, item_len: usize, len: usize) -> usize {
140    if item_len == 0 {
141        return len;
142    }
143    // `start` is a usize, so this cannot underflow
144    #[allow(clippy::arithmetic_side_effects)]
145    let max_len = (usize::MAX - start) / item_len;
146    len.min(max_len)
147}
148
149/// Implements [`FontReadAt`] for a type that is read through [`FontRead`], by
150/// slicing the enclosing data to the item first.
151///
152/// Codegen emits this for the records it generates that are not positioned;
153/// this covers the types written by hand. A blanket impl is not possible,
154/// because a positioned type implements [`FontReadAt`] directly and the
155/// compiler cannot be told that no type does both.
156#[macro_export]
157macro_rules! impl_font_read_at {
158    ($typ:ty) => {
159        impl<'a> $crate::FontReadAt<'a> for $typ {
160            fn read_at(
161                data: $crate::FontData<'a>,
162                offset: usize,
163                args: <Self as $crate::ReadArgs>::Args,
164            ) -> Result<Self, $crate::ReadError> {
165                let len = <Self as $crate::ComputeSize>::compute_size(args)?;
166                let end = offset
167                    .checked_add(len)
168                    .ok_or($crate::ReadError::OutOfBounds)?;
169                let data = data
170                    .slice(offset..end)
171                    .ok_or($crate::ReadError::OutOfBounds)?;
172                <Self as $crate::FontRead<'a>>::read_with_args(data, args)
173            }
174        }
175    };
176}
177
178/// An array of items of non-uniform length.
179///
180/// Random access into this array cannot be especially efficient, since it requires
181/// a linear scan.
182pub struct VarLenArray<'a, T> {
183    data: FontData<'a>,
184    phantom: std::marker::PhantomData<*const T>,
185}
186
187impl<'a, T: FontRead<'a, Args = ()> + VarSize> VarLenArray<'a, T> {
188    /// Return the item at the provided index.
189    ///
190    /// # Performance
191    ///
192    /// Determining the position of an item in this collection requires looking
193    /// at all the preceding items; that is, it is `O(n)` instead of `O(1)` as
194    /// it would be for a `Vec`.
195    ///
196    /// As a consequence, calling this method in a loop could potentially be
197    /// very slow. If this is something you need to do, it will probably be
198    /// much faster to first collect all the items into a `Vec` beforehand,
199    /// and then fetch them from there.
200    pub fn get(&self, idx: usize) -> Option<Result<T, ReadError>> {
201        if self.data.is_empty() {
202            return None;
203        }
204        let mut pos = 0usize;
205        for _ in 0..idx {
206            pos = pos.checked_add(T::read_len_at(self.data, pos)?)?;
207        }
208        let len = T::read_len_at(self.data, pos)?;
209        let end = pos.checked_add(len)?;
210        self.data.slice(pos..end).map(T::read)
211    }
212
213    /// Return an iterator over this array's items.
214    pub fn iter(&self) -> impl Iterator<Item = Result<T, ReadError>> + 'a {
215        let mut data = self.data;
216        std::iter::from_fn(move || {
217            if data.is_empty() {
218                return None;
219            }
220
221            let item_len = T::read_len_at(data, 0)?;
222            // If the length is 0 then then it's not useful to continue
223            // iteration. The subsequent read will probably fail but if
224            // the user is skipping malformed elements (which is common)
225            // this this iterator will continue forever.
226            if item_len == 0 {
227                return None;
228            }
229            let item_data = data.slice(..item_len)?;
230            let next = T::read(item_data);
231            data = data.split_off(item_len)?;
232            Some(next)
233        })
234    }
235}
236
237impl<T> ReadArgs for VarLenArray<'_, T> {
238    type Args = ();
239}
240
241impl<'a, T> FontRead<'a> for VarLenArray<'a, T> {
242    fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
243        Ok(VarLenArray {
244            data,
245            phantom: core::marker::PhantomData,
246        })
247    }
248}
249
250impl<T> Default for VarLenArray<'_, T> {
251    fn default() -> Self {
252        Self {
253            data: Default::default(),
254            phantom: std::marker::PhantomData,
255        }
256    }
257}
258
259impl<T: AnyBitPattern> ReadArgs for &[T] {
260    type Args = u16;
261}
262
263impl<'a, T: AnyBitPattern + FixedSize> FontRead<'a> for &'a [T] {
264    fn read_with_args(data: FontData<'a>, args: u16) -> Result<Self, ReadError> {
265        let len = (args as usize)
266            .checked_mul(T::RAW_BYTE_LEN)
267            .ok_or(ReadError::OutOfBounds)?;
268        data.read_array(0..len)
269    }
270}
271
272/// Helper to retrieve a pair of items from a slice, returning an error
273/// if the second index overflows or if either index is out of bounds.
274pub(crate) fn get_pair<T>(slice: &[T], idx: usize) -> Result<&[T; 2], ReadError> {
275    slice
276        .get(idx..)
277        .ok_or(ReadError::OutOfBounds)?
278        .first_chunk()
279        .ok_or(ReadError::OutOfBounds)
280}
281
282#[cfg(test)]
283mod tests {
284    use super::*;
285    use crate::codegen_test::records::VarLenItem;
286    use font_test_data::bebuffer::BeBuffer;
287
288    /// The bound must admit every array that can actually be built, and must
289    /// keep `start + len * item_len` representable in the cases that cannot.
290    #[test]
291    fn representable_len_bound() {
292        // ordinary arrays are unaffected
293        assert_eq!(representable_len(0, 4, 10), 10);
294        assert_eq!(representable_len(100, 4, 10), 10);
295        // a zero stride has no offsets to overflow
296        assert_eq!(representable_len(usize::MAX, 0, 10), 10);
297
298        // exactly at the limit
299        assert_eq!(representable_len(0, 1, usize::MAX), usize::MAX);
300        assert_eq!(representable_len(2, 1, usize::MAX), usize::MAX - 2);
301        assert_eq!(representable_len(usize::MAX, 1, 5), 0);
302
303        // and the bound it promises actually holds
304        for (start, item_len, len) in [
305            (0usize, 4usize, 10usize),
306            (100, 4, 10),
307            (usize::MAX, 1, 5),
308            (usize::MAX - 8, 4, 100),
309            (usize::MAX / 2, 3, usize::MAX),
310        ] {
311            let bounded = representable_len(start, item_len, len);
312            assert!(bounded <= len);
313            assert!(
314                bounded
315                    .checked_mul(item_len)
316                    .and_then(|off| off.checked_add(start))
317                    .is_some(),
318                "start {start} item_len {item_len} len {len} -> {bounded}"
319            );
320        }
321    }
322
323    impl VarSize for VarLenItem<'_> {
324        type Size = u32;
325
326        fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
327            data.read_at::<u32>(pos).ok().map(|len| len as usize)
328        }
329    }
330
331    /// HB/HarfRuzz test "shlana_9_006" has a morx table containing a chain
332    /// with a length of 0. This caused the VarLenArray iterator to loop
333    /// indefinitely.
334    #[test]
335    fn var_len_iter_with_zero_length_item() {
336        // Create a buffer containing three elements where the last
337        // has zero length
338        let mut buf = BeBuffer::new();
339        buf = buf.push(8u32).extend([0u8; 4]);
340        buf = buf.push(18u32).extend([0u8; 14]);
341        buf = buf.push(0u32);
342        let arr: VarLenArray<VarLenItem> = VarLenArray::read(FontData::new(buf.data())).unwrap();
343        // Ensure we don't iterate forever and only read two elements (the
344        // take() exists so that the test fails rather than hanging if the
345        // code regresses in the future)
346        assert_eq!(arr.iter().take(10).count(), 2);
347    }
348
349    #[test]
350    fn var_len_iter_same_as_get() {
351        let mut buf = BeBuffer::new();
352        buf = buf.push(4u32).extend([1u8, 2, 3, 4]);
353        buf = buf.push(2u32).extend([5u8, 6]);
354        buf = buf.push(3u32).extend([7u8, 8, 9]);
355        let arr: VarLenArray<VarLenItem> = VarLenArray::read(FontData::new(buf.data())).unwrap();
356        let iter_items: Vec<_> = arr.iter().map(|x| x.unwrap()).collect();
357        let get_items: Vec<_> = (0..iter_items.len())
358            .map(|i| arr.get(i).unwrap().unwrap())
359            .collect();
360        assert_eq!(iter_items.len(), get_items.len());
361        for (a, b) in iter_items.iter().zip(get_items.iter()) {
362            assert_eq!(a.data(), b.data());
363        }
364    }
365}