//! Arrays of offsets with dynamic resolution
//!
//! This module provides a number of types that wrap arrays of offsets, dynamically
//! resolving individual offsets as they are accessed.
use crate::offset::ResolveNullableOffset;
use font_types::{BigEndian, Nullable, Offset16, Scalar};
use crate::{FontData, FontReadWithArgs, Offset, ReadArgs, ReadError, ResolveOffset};
/// An array of offsets that can be resolved on access.
///
/// This bundles up the raw offsets with the data used to resolve them, along
/// with any arguments needed to resolve those offsets; it provides a simple
/// ergonomic interface that unburdens the user from needing to manually
/// determine the appropriate input data and arguments for a raw offset.
#[derive(Clone)]
pub struct ArrayOfOffsets<'a, T: ReadArgs, O: Scalar = Offset16> {
offsets: &'a [BigEndian<O>],
data: FontData<'a>,
args: T::Args,
}
/// An array of nullable offsets that can be resolved on access.
///
/// This is identical to [`ArrayOfOffsets`], except that each offset is
/// allowed to be null.
#[derive(Clone)]
pub struct ArrayOfNullableOffsets<'a, T: ReadArgs, O: Scalar = Offset16> {
offsets: &'a [BigEndian<Nullable<O>>],
data: FontData<'a>,
args: T::Args,
}
impl<'a, T, O> ArrayOfOffsets<'a, T, O>
where
O: Scalar,
T: ReadArgs,
{
pub(crate) fn new(offsets: &'a [BigEndian<O>], data: FontData<'a>, args: T::Args) -> Self {
Self {
offsets,
data,
args,
}
}
}
impl<'a, T, O> ArrayOfOffsets<'a, T, O>
where
O: Scalar + Offset,
T: ReadArgs + FontReadWithArgs<'a>,
T::Args: Copy + 'static,
{
/// The number of offsets in the array
pub fn len(&self) -> usize {
self.offsets.len()
}
/// `true` if the array is empty
pub fn is_empty(&self) -> bool {
self.offsets.is_empty()
}
/// Resolve the offset at the provided index.
///
/// Note: if the index is invalid this will return the `InvalidCollectionIndex`
/// error variant instead of `None`.
pub fn get(&self, idx: usize) -> Result<T, ReadError> {
self.offsets
.get(idx)
.ok_or(ReadError::InvalidCollectionIndex(idx as _))
.and_then(|o| o.get().resolve_with_args(self.data, &self.args))
}
/// Iterate over all of the offset targets.
///
/// Each offset will be resolved as it is encountered.
pub fn iter(&self) -> impl Iterator<Item = Result<T, ReadError>> + 'a {
let mut iter = self.offsets.iter();
let args = self.args;
let data = self.data;
std::iter::from_fn(move || {
iter.next()
.map(|off| off.get().resolve_with_args(data, &args))
})
}
}
impl<'a, T, O> ArrayOfNullableOffsets<'a, T, O>
where
O: Scalar + Offset,
T: ReadArgs,
{
pub(crate) fn new(
offsets: &'a [BigEndian<Nullable<O>>],
data: FontData<'a>,
args: T::Args,
) -> Self {
Self {
offsets,
data,
args,
}
}
}
impl<'a, T, O> ArrayOfNullableOffsets<'a, T, O>
where
O: Scalar + Offset,
T: ReadArgs + FontReadWithArgs<'a>,
T::Args: Copy + 'static,
{
/// The number of offsets in the array
pub fn len(&self) -> usize {
self.offsets.len()
}
/// `true` if the array is empty
pub fn is_empty(&self) -> bool {
self.offsets.is_empty()
}
/// Resolve the offset at the provided index.
///
/// This will return `None` only if the offset *exists*, but is null. if the
/// provided index does not exist, this will return the `InvalidCollectionIndex`
/// error variant.
pub fn get(&self, idx: usize) -> Option<Result<T, ReadError>> {
let Some(offset) = self.offsets.get(idx) else {
return Some(Err(ReadError::InvalidCollectionIndex(idx as _)));
};
offset.get().resolve_with_args(self.data, &self.args)
}
/// Iterate over all of the offset targets.
///
/// Each offset will be resolved as it is encountered.
pub fn iter(&self) -> impl Iterator<Item = Option<Result<T, ReadError>>> + 'a {
let mut iter = self.offsets.iter();
let args = self.args;
let data = self.data;
std::iter::from_fn(move || {
iter.next()
.map(|off| off.get().resolve_with_args(data, &args))
})
}
}