//! Custom array types #![deny(clippy::arithmetic_side_effects)] use bytemuck::AnyBitPattern; use font_types::FixedSize; use crate::read::{ComputeSize, FontReadWithArgs, ReadArgs, VarSize}; use crate::{FontData, FontRead, ReadError}; /// An array whose items size is not known at compile time. /// /// This requires the inner type to implement [`FontReadWithArgs`] as well as /// [`ComputeSize`]. /// /// At runtime, `Args` are provided which will be used to compute the size /// of each item; this size is then used to compute the positions of the items /// within the underlying data, from which they will be read lazily. #[derive(Clone)] pub struct ComputedArray<'a, T: ReadArgs> { // the length of each item item_len: usize, len: usize, data: FontData<'a>, args: T::Args, } impl<'a, T: ComputeSize> ComputedArray<'a, T> { pub fn new(data: FontData<'a>, args: T::Args) -> Result { let item_len = T::compute_size(&args)?; let len = data.len().checked_div(item_len).unwrap_or(0); Ok(ComputedArray { item_len, len, data, args, }) } /// The number of items in the array pub fn len(&self) -> usize { self.len } pub fn is_empty(&self) -> bool { self.len == 0 } } impl ReadArgs for ComputedArray<'_, T> { type Args = T::Args; } impl<'a, T> FontReadWithArgs<'a> for ComputedArray<'a, T> where T: ComputeSize + FontReadWithArgs<'a>, T::Args: Copy, { fn read_with_args(data: FontData<'a>, args: &Self::Args) -> Result { Self::new(data, *args) } } impl Default for ComputedArray<'_, T> where T: ReadArgs, T::Args: Default, { fn default() -> Self { Self { item_len: 0, len: 0, data: Default::default(), args: Default::default(), } } } impl<'a, T> ComputedArray<'a, T> where T: FontReadWithArgs<'a>, T::Args: Copy + 'static, { pub fn iter(&self) -> impl Iterator> + 'a { let mut i = 0; let data = self.data; let args = self.args; let item_len = self.item_len; let len = self.len; std::iter::from_fn(move || { if i == len { return None; } let item_start = item_len.checked_mul(i)?; i = i.checked_add(1)?; let data = data.split_off(item_start)?; Some(T::read_with_args(data, &args)) }) } #[inline] pub fn get(&self, idx: usize) -> Result { let item_start = idx .checked_mul(self.item_len) .ok_or(ReadError::OutOfBounds)?; self.data .split_off(item_start) .ok_or(ReadError::OutOfBounds) .and_then(|data| T::read_with_args(data, &self.args)) } } impl std::fmt::Debug for ComputedArray<'_, T> { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { f.debug_struct("DynSizedArray") .field("bytes", &self.data) .finish() } } /// An array of items of non-uniform length. /// /// Random access into this array cannot be especially efficient, since it requires /// a linear scan. pub struct VarLenArray<'a, T> { data: FontData<'a>, phantom: std::marker::PhantomData<*const T>, } impl<'a, T: FontRead<'a> + VarSize> VarLenArray<'a, T> { /// Return the item at the provided index. /// /// # Performance /// /// Determining the position of an item in this collection requires looking /// at all the preceding items; that is, it is `O(n)` instead of `O(1)` as /// it would be for a `Vec`. /// /// As a consequence, calling this method in a loop could potentially be /// very slow. If this is something you need to do, it will probably be /// much faster to first collect all the items into a `Vec` beforehand, /// and then fetch them from there. pub fn get(&self, idx: usize) -> Option> { if self.data.is_empty() { return None; } let mut pos = 0usize; for _ in 0..idx { pos = pos.checked_add(T::read_len_at(self.data, pos)?)?; } self.data.split_off(pos).map(T::read) } /// Return an iterator over this array's items. pub fn iter(&self) -> impl Iterator> + 'a { let mut data = self.data; std::iter::from_fn(move || { if data.is_empty() { return None; } let item_len = T::read_len_at(data, 0)?; // If the length is 0 then then it's not useful to continue // iteration. The subsequent read will probably fail but if // the user is skipping malformed elements (which is common) // this this iterator will continue forever. if item_len == 0 { return None; } let item_data = data.slice(..item_len)?; let next = T::read(item_data); data = data.split_off(item_len)?; Some(next) }) } } impl<'a, T> FontRead<'a> for VarLenArray<'a, T> { fn read(data: FontData<'a>) -> Result { Ok(VarLenArray { data, phantom: core::marker::PhantomData, }) } } impl Default for VarLenArray<'_, T> { fn default() -> Self { Self { data: Default::default(), phantom: std::marker::PhantomData, } } } impl ReadArgs for &[T] { type Args = u16; } impl<'a, T: AnyBitPattern + FixedSize> FontReadWithArgs<'a> for &'a [T] { fn read_with_args(data: FontData<'a>, args: &u16) -> Result { let len = (*args as usize) .checked_mul(T::RAW_BYTE_LEN) .ok_or(ReadError::OutOfBounds)?; data.read_array(0..len) } } #[cfg(test)] mod tests { use super::*; use crate::codegen_test::records::VarLenItem; use font_test_data::bebuffer::BeBuffer; impl VarSize for VarLenItem<'_> { type Size = u32; fn read_len_at(data: FontData, pos: usize) -> Option { data.read_at::(pos).ok().map(|len| len as usize) } } /// HB/HarfRuzz test "shlana_9_006" has a morx table containing a chain /// with a length of 0. This caused the VarLenArray iterator to loop /// indefinitely. #[test] fn var_len_iter_with_zero_length_item() { // Create a buffer containing three elements where the last // has zero length let mut buf = BeBuffer::new(); buf = buf.push(8u32).extend([0u8; 4]); buf = buf.push(18u32).extend([0u8; 14]); buf = buf.push(0u32); let arr: VarLenArray = VarLenArray::read(FontData::new(buf.data())).unwrap(); // Ensure we don't iterate forever and only read two elements (the // take() exists so that the test fails rather than hanging if the // code regresses in the future) assert_eq!(arr.iter().take(10).count(), 2); } }