Skip to main content

read_fonts/
offset_array.rs

1//! Arrays of offsets with dynamic resolution
2//!
3//! This module provides a number of types that wrap arrays of offsets, dynamically
4//! resolving individual offsets as they are accessed.
5
6use crate::offset::ResolveNullableOffset;
7use font_types::{BigEndian, Nullable, Offset16, Scalar};
8
9use crate::{FontData, FontReadWithArgs, Offset, ReadArgs, ReadError, ResolveOffset};
10
11/// An array of offsets that can be resolved on access.
12///
13/// This bundles up the raw offsets with the data used to resolve them, along
14/// with any arguments needed to resolve those offsets; it provides a simple
15/// ergonomic interface that unburdens the user from needing to manually
16/// determine the appropriate input data and arguments for a raw offset.
17#[derive(Clone)]
18pub struct ArrayOfOffsets<'a, T: ReadArgs, O: Scalar = Offset16> {
19    offsets: &'a [BigEndian<O>],
20    data: FontData<'a>,
21    args: T::Args,
22}
23
24/// An array of nullable offsets that can be resolved on access.
25///
26/// This is identical to [`ArrayOfOffsets`], except that each offset is
27/// allowed to be null.
28#[derive(Clone)]
29pub struct ArrayOfNullableOffsets<'a, T: ReadArgs, O: Scalar = Offset16> {
30    offsets: &'a [BigEndian<Nullable<O>>],
31    data: FontData<'a>,
32    args: T::Args,
33}
34
35impl<'a, T, O> ArrayOfOffsets<'a, T, O>
36where
37    O: Scalar,
38    T: ReadArgs,
39{
40    pub(crate) fn new(offsets: &'a [BigEndian<O>], data: FontData<'a>, args: T::Args) -> Self {
41        Self {
42            offsets,
43            data,
44            args,
45        }
46    }
47}
48
49impl<'a, T, O> ArrayOfOffsets<'a, T, O>
50where
51    O: Scalar + Offset,
52    T: ReadArgs + FontReadWithArgs<'a>,
53    T::Args: Copy + 'static,
54{
55    /// The number of offsets in the array
56    pub fn len(&self) -> usize {
57        self.offsets.len()
58    }
59
60    /// `true` if the array is empty
61    pub fn is_empty(&self) -> bool {
62        self.offsets.is_empty()
63    }
64
65    /// Resolve the offset at the provided index.
66    ///
67    /// Note: if the index is invalid this will return the `InvalidCollectionIndex`
68    /// error variant instead of `None`.
69    pub fn get(&self, idx: usize) -> Result<T, ReadError> {
70        self.offsets
71            .get(idx)
72            .ok_or(ReadError::InvalidCollectionIndex(idx as _))
73            .and_then(|o| o.get().resolve_with_args(self.data, &self.args))
74    }
75
76    /// Iterate over all of the offset targets.
77    ///
78    /// Each offset will be resolved as it is encountered.
79    pub fn iter(&self) -> impl Iterator<Item = Result<T, ReadError>> + 'a {
80        let mut iter = self.offsets.iter();
81        let args = self.args;
82        let data = self.data;
83        std::iter::from_fn(move || {
84            iter.next()
85                .map(|off| off.get().resolve_with_args(data, &args))
86        })
87    }
88
89    /// Iterate over all of the offset targets.
90    ///
91    /// Offset is treated as nullable and each offset will be resolved as it is encountered.
92    pub(crate) fn iter_as_nullable(
93        &self,
94    ) -> impl Iterator<Item = Option<Result<T, ReadError>>> + 'a {
95        self.iter().map(|off| match off {
96            Err(ReadError::NullOffset) => None,
97            other => Some(other),
98        })
99    }
100}
101
102impl<'a, T, O> ArrayOfNullableOffsets<'a, T, O>
103where
104    O: Scalar + Offset,
105    T: ReadArgs,
106{
107    pub(crate) fn new(
108        offsets: &'a [BigEndian<Nullable<O>>],
109        data: FontData<'a>,
110        args: T::Args,
111    ) -> Self {
112        Self {
113            offsets,
114            data,
115            args,
116        }
117    }
118}
119
120impl<'a, T, O> ArrayOfNullableOffsets<'a, T, O>
121where
122    O: Scalar + Offset,
123    T: ReadArgs + FontReadWithArgs<'a>,
124    T::Args: Copy + 'static,
125{
126    /// The number of offsets in the array
127    pub fn len(&self) -> usize {
128        self.offsets.len()
129    }
130
131    /// `true` if the array is empty
132    pub fn is_empty(&self) -> bool {
133        self.offsets.is_empty()
134    }
135
136    /// Resolve the offset at the provided index.
137    ///
138    /// This will return `None` only if the offset *exists*, but is null. if the
139    /// provided index does not exist, this will return the `InvalidCollectionIndex`
140    /// error variant.
141    pub fn get(&self, idx: usize) -> Option<Result<T, ReadError>> {
142        let Some(offset) = self.offsets.get(idx) else {
143            return Some(Err(ReadError::InvalidCollectionIndex(idx as _)));
144        };
145        offset.get().resolve_with_args(self.data, &self.args)
146    }
147
148    /// Iterate over all of the offset targets.
149    ///
150    /// Each offset will be resolved as it is encountered.
151    pub fn iter(&self) -> impl Iterator<Item = Option<Result<T, ReadError>>> + 'a {
152        let mut iter = self.offsets.iter();
153        let args = self.args;
154        let data = self.data;
155        std::iter::from_fn(move || {
156            iter.next()
157                .map(|off| off.get().resolve_with_args(data, &args))
158        })
159    }
160}
161
162#[cfg(feature = "experimental_traverse")]
163impl<'a, T, O> crate::traversal::SomeArray<'a> for ArrayOfOffsets<'a, T, O>
164where
165    O: Scalar + Offset + Into<crate::traversal::OffsetType>,
166    T: ReadArgs + FontReadWithArgs<'a> + crate::traversal::SomeTable<'a> + 'a,
167    T::Args: Copy + 'static,
168{
169    fn type_name(&self) -> &str {
170        let full_name = std::any::type_name::<T>();
171        full_name.split("::").last().unwrap_or(full_name)
172    }
173
174    fn len(&self) -> usize {
175        self.offsets.len()
176    }
177
178    fn get(&self, idx: usize) -> Option<crate::traversal::FieldType<'a>> {
179        let off = self.offsets.get(idx)?;
180        let raw = off.get();
181        let result = raw.resolve_with_args::<T>(self.data, &self.args);
182        Some(crate::traversal::FieldType::offset(raw, result))
183    }
184}
185
186#[cfg(feature = "experimental_traverse")]
187impl<'a, T, O> crate::traversal::SomeArray<'a> for ArrayOfNullableOffsets<'a, T, O>
188where
189    O: Scalar + Offset + Into<crate::traversal::OffsetType> + Clone,
190    T: ReadArgs + FontReadWithArgs<'a> + crate::traversal::SomeTable<'a> + 'a,
191    T::Args: Copy + 'static,
192{
193    fn type_name(&self) -> &str {
194        let full_name = std::any::type_name::<T>();
195        full_name.split("::").last().unwrap_or(full_name)
196    }
197
198    fn len(&self) -> usize {
199        self.offsets.len()
200    }
201
202    fn get(&self, idx: usize) -> Option<crate::traversal::FieldType<'a>> {
203        let off = self.offsets.get(idx)?;
204        let raw = off.get();
205        let result = raw.resolve_with_args::<T>(self.data, &self.args);
206        Some(crate::traversal::FieldType::offset(raw, result))
207    }
208}