#![deny(clippy::arithmetic_side_effects)]
use bytemuck::AnyBitPattern;
use font_types::FixedSize;
use crate::read::{ComputeSize, FontRead, FontReadAt, ReadArgs, VarSize};
use crate::{FontData, ReadError};
use core::ops::Range;
#[derive(Clone)]
pub struct ComputedArray<'a, T: ReadArgs> {
item_len: usize,
len: usize,
start: usize,
data: FontData<'a>,
args: T::Args,
}
impl<'a, T: ComputeSize> ComputedArray<'a, T> {
pub fn new(data: FontData<'a>, range: Range<usize>, args: T::Args) -> Result<Self, ReadError> {
let item_len = T::compute_size(args)?;
let available = data
.as_bytes()
.get(range.clone())
.ok_or(ReadError::OutOfBounds)?
.len();
let len = available.checked_div(item_len).unwrap_or(0);
Ok(ComputedArray {
item_len,
len: representable_len(range.start, item_len, len),
start: range.start,
data,
args,
})
}
pub fn len(&self) -> usize {
self.len
}
pub fn is_empty(&self) -> bool {
self.len == 0
}
}
impl<T: ReadArgs> ReadArgs for ComputedArray<'_, T> {
type Args = T::Args;
}
impl<T> Default for ComputedArray<'_, T>
where
T: ReadArgs,
T::Args: Default,
{
fn default() -> Self {
Self {
item_len: 0,
len: 0,
start: 0,
data: Default::default(),
args: Default::default(),
}
}
}
impl<'a, T> ComputedArray<'a, T>
where
T: FontReadAt<'a>,
T::Args: Copy + 'static,
{
pub fn iter(&self) -> impl Iterator<Item = Result<T, ReadError>> + 'a + Clone {
let data = self.data;
let args = self.args;
let item_len = self.item_len;
let len = self.len;
let mut item_start = self.start;
(0..len).map(move |_| {
let item = T::read_at(data, item_start, args);
#[allow(clippy::arithmetic_side_effects)] {
item_start += item_len;
}
item
})
}
#[inline]
pub fn get(&self, idx: usize) -> Result<T, ReadError> {
if idx >= self.len {
return Err(ReadError::OutOfBounds);
}
let item_start = idx
.checked_mul(self.item_len)
.and_then(|start| start.checked_add(self.start))
.ok_or(ReadError::OutOfBounds)?;
T::read_at(self.data, item_start, self.args)
}
}
impl<T: ReadArgs> 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()
}
}
fn representable_len(start: usize, item_len: usize, len: usize) -> usize {
if item_len == 0 {
return len;
}
#[allow(clippy::arithmetic_side_effects)]
let max_len = (usize::MAX - start) / item_len;
len.min(max_len)
}
#[macro_export]
macro_rules! impl_font_read_at {
($typ:ty) => {
impl<'a> $crate::FontReadAt<'a> for $typ {
fn read_at(
data: $crate::FontData<'a>,
offset: usize,
args: <Self as $crate::ReadArgs>::Args,
) -> Result<Self, $crate::ReadError> {
let len = <Self as $crate::ComputeSize>::compute_size(args)?;
let end = offset
.checked_add(len)
.ok_or($crate::ReadError::OutOfBounds)?;
let data = data
.slice(offset..end)
.ok_or($crate::ReadError::OutOfBounds)?;
<Self as $crate::FontRead<'a>>::read_with_args(data, args)
}
}
};
}
pub struct VarLenArray<'a, T> {
data: FontData<'a>,
phantom: std::marker::PhantomData<*const T>,
}
impl<'a, T: FontRead<'a, Args = ()> + VarSize> VarLenArray<'a, T> {
pub fn get(&self, idx: usize) -> Option<Result<T, ReadError>> {
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)?)?;
}
let len = T::read_len_at(self.data, pos)?;
let end = pos.checked_add(len)?;
self.data.slice(pos..end).map(T::read)
}
pub fn iter(&self) -> impl Iterator<Item = Result<T, ReadError>> + '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 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<T> ReadArgs for VarLenArray<'_, T> {
type Args = ();
}
impl<'a, T> FontRead<'a> for VarLenArray<'a, T> {
fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
Ok(VarLenArray {
data,
phantom: core::marker::PhantomData,
})
}
}
impl<T> Default for VarLenArray<'_, T> {
fn default() -> Self {
Self {
data: Default::default(),
phantom: std::marker::PhantomData,
}
}
}
impl<T: AnyBitPattern> ReadArgs for &[T] {
type Args = u16;
}
impl<'a, T: AnyBitPattern + FixedSize> FontRead<'a> for &'a [T] {
fn read_with_args(data: FontData<'a>, args: u16) -> Result<Self, ReadError> {
let len = (args as usize)
.checked_mul(T::RAW_BYTE_LEN)
.ok_or(ReadError::OutOfBounds)?;
data.read_array(0..len)
}
}
pub(crate) fn get_pair<T>(slice: &[T], idx: usize) -> Result<&[T; 2], ReadError> {
slice
.get(idx..)
.ok_or(ReadError::OutOfBounds)?
.first_chunk()
.ok_or(ReadError::OutOfBounds)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codegen_test::records::VarLenItem;
use font_test_data::bebuffer::BeBuffer;
#[test]
fn representable_len_bound() {
assert_eq!(representable_len(0, 4, 10), 10);
assert_eq!(representable_len(100, 4, 10), 10);
assert_eq!(representable_len(usize::MAX, 0, 10), 10);
assert_eq!(representable_len(0, 1, usize::MAX), usize::MAX);
assert_eq!(representable_len(2, 1, usize::MAX), usize::MAX - 2);
assert_eq!(representable_len(usize::MAX, 1, 5), 0);
for (start, item_len, len) in [
(0usize, 4usize, 10usize),
(100, 4, 10),
(usize::MAX, 1, 5),
(usize::MAX - 8, 4, 100),
(usize::MAX / 2, 3, usize::MAX),
] {
let bounded = representable_len(start, item_len, len);
assert!(bounded <= len);
assert!(
bounded
.checked_mul(item_len)
.and_then(|off| off.checked_add(start))
.is_some(),
"start {start} item_len {item_len} len {len} -> {bounded}"
);
}
}
impl VarSize for VarLenItem<'_> {
type Size = u32;
fn read_len_at(data: FontData, pos: usize) -> Option<usize> {
data.read_at::<u32>(pos).ok().map(|len| len as usize)
}
}
#[test]
fn var_len_iter_with_zero_length_item() {
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<VarLenItem> = VarLenArray::read(FontData::new(buf.data())).unwrap();
assert_eq!(arr.iter().take(10).count(), 2);
}
#[test]
fn var_len_iter_same_as_get() {
let mut buf = BeBuffer::new();
buf = buf.push(4u32).extend([1u8, 2, 3, 4]);
buf = buf.push(2u32).extend([5u8, 6]);
buf = buf.push(3u32).extend([7u8, 8, 9]);
let arr: VarLenArray<VarLenItem> = VarLenArray::read(FontData::new(buf.data())).unwrap();
let iter_items: Vec<_> = arr.iter().map(|x| x.unwrap()).collect();
let get_items: Vec<_> = (0..iter_items.len())
.map(|i| arr.get(i).unwrap().unwrap())
.collect();
assert_eq!(iter_items.len(), get_items.len());
for (a, b) in iter_items.iter().zip(get_items.iter()) {
assert_eq!(a.data(), b.data());
}
}
}