Skip to main content

read_fonts/
font_data.rs

1//! raw font bytes
2
3#![deny(clippy::arithmetic_side_effects)]
4use std::ops::{Range, RangeBounds};
5
6use bytemuck::AnyBitPattern;
7use types::{BigEndian, FixedSize, Scalar};
8
9use crate::array::ComputedArray;
10use crate::read::{ComputeSize, FontRead, FontReadAt, ReadArgs, ReadError};
11
12/// A reference to raw binary font data.
13///
14/// This is a wrapper around a byte slice, that provides convenience methods
15/// for parsing and validating that data.
16#[derive(Debug, Default, Clone, Copy)]
17pub struct FontData<'a> {
18    bytes: &'a [u8],
19}
20
21/// A cursor for validating bytes during parsing.
22///
23/// This type improves the ergonomics of validation blah blah
24///
25/// # Note
26///
27/// call `finish` when you're done to ensure you're in bounds
28#[derive(Debug, Default, Clone, Copy)]
29pub struct Cursor<'a> {
30    pos: usize,
31    data: FontData<'a>,
32}
33
34// we reuse a single buffer for all tables, but it gets padded with a u16
35// to accurately represent format-1 tables
36const ARR_LEN: usize = FontData::NULL_POOL_SIZE + u16::RAW_BYTE_LEN;
37
38/// This is [0, 1] ('1' in u16be) followed by NULL_POOL_SIZE zeros.
39///
40/// - this same array is reused both for format-1 tables (which need a leading 1)
41///   as well as all other tables, which don't.
42static EMPTY_TABLE_BYTES: [u8; ARR_LEN] = {
43    let mut arr = [0u8; ARR_LEN];
44    arr[1] = 1;
45    arr
46};
47
48impl FontData<'static> {
49    // https://github.com/harfbuzz/harfbuzz/blob/aba63bb5/src/hb-null.hh#L40
50    /// The number of bytes required to represent the largest table we have.
51    ///
52    /// This is checked by an assert at compile time, and can be increased as needed.
53    const NULL_POOL_SIZE: usize = 262;
54
55    // this is only used in const eval contexts, which are not visible to the
56    // dead_code lint https://github.com/rust-lang/rust/issues/101532
57    #[allow(dead_code)]
58    /// Return `true` if our default data can represent a table `n_bytes` long
59    pub(crate) const fn default_data_long_enough(n_bytes: usize) -> bool {
60        n_bytes <= Self::NULL_POOL_SIZE
61    }
62
63    /// Return all zeroes suitable for the default impl of a table.
64    pub(crate) fn default_table_data() -> Self {
65        FontData::new(&EMPTY_TABLE_BYTES[2..])
66    }
67
68    /// Return a [0x0, 0x01] byte pair (u16be) and then all zeros, to represent
69    /// the default impl of a format 1 table with u16 format.
70    pub(crate) fn default_format_1_u16_table_data() -> Self {
71        FontData::new(&EMPTY_TABLE_BYTES)
72    }
73
74    /// Return a single 0x01 and then all zeros, to represent the default impl
75    /// of a format 1 table with u8 format.
76    pub(crate) fn default_format_1_u8_table_data() -> Self {
77        FontData::new(&EMPTY_TABLE_BYTES[1..])
78    }
79}
80
81impl<'a> FontData<'a> {
82    /// Empty data, useful for some tests and examples
83    pub const EMPTY: FontData<'static> = FontData { bytes: &[] };
84
85    /// Create a new `FontData` with these bytes.
86    ///
87    /// You generally don't need to do this? It is handled for you when loading
88    /// data from disk, but may be useful in tests.
89    pub const fn new(bytes: &'a [u8]) -> Self {
90        FontData { bytes }
91    }
92
93    /// The length of the data, in bytes
94    pub fn len(&self) -> usize {
95        self.bytes.len()
96    }
97
98    /// `true` if the data has a length of zero bytes.
99    pub fn is_empty(&self) -> bool {
100        self.bytes.is_empty()
101    }
102
103    /// Returns self[pos..]
104    pub fn split_off(&self, pos: usize) -> Option<FontData<'a>> {
105        self.bytes.get(pos..).map(|bytes| FontData { bytes })
106    }
107
108    /// returns self[..pos], and updates self to = self[pos..];
109    pub fn take_up_to(&mut self, pos: usize) -> Option<FontData<'a>> {
110        if pos > self.len() {
111            return None;
112        }
113        let (head, tail) = self.bytes.split_at(pos);
114        self.bytes = tail;
115        Some(FontData { bytes: head })
116    }
117
118    pub fn slice(&self, range: impl RangeBounds<usize>) -> Option<FontData<'a>> {
119        let bounds = (range.start_bound().cloned(), range.end_bound().cloned());
120        self.bytes.get(bounds).map(|bytes| FontData { bytes })
121    }
122
123    /// Read a scalar at the provided location in the data.
124    pub fn read_at<T: Scalar>(&self, offset: usize) -> Result<T, ReadError> {
125        let end = offset
126            .checked_add(T::RAW_BYTE_LEN)
127            .ok_or(ReadError::OutOfBounds)?;
128        self.bytes
129            .get(offset..end)
130            .and_then(T::read)
131            .ok_or(ReadError::OutOfBounds)
132    }
133
134    /// Read a big-endian value at the provided location in the data.
135    pub fn read_be_at<T: Scalar>(&self, offset: usize) -> Result<BigEndian<T>, ReadError> {
136        let end = offset
137            .checked_add(T::RAW_BYTE_LEN)
138            .ok_or(ReadError::OutOfBounds)?;
139        self.bytes
140            .get(offset..end)
141            .and_then(BigEndian::from_slice)
142            .ok_or(ReadError::OutOfBounds)
143    }
144
145    pub fn read_with_args<T>(&self, range: Range<usize>, args: T::Args) -> Result<T, ReadError>
146    where
147        T: FontRead<'a>,
148    {
149        self.slice(range)
150            .ok_or(ReadError::OutOfBounds)
151            .and_then(|data| T::read_with_args(data, args))
152    }
153
154    fn check_in_bounds(&self, offset: usize) -> Result<(), ReadError> {
155        self.bytes
156            .get(..offset)
157            .ok_or(ReadError::OutOfBounds)
158            .map(|_| ())
159    }
160
161    /// Interpret the bytes at the provided offset as a reference to `T`.
162    ///
163    /// Returns an error if the slice `offset..` is shorter than `T::RAW_BYTE_LEN`.
164    ///
165    /// This is a wrapper around [`read_ref_unchecked`][], which panics if
166    /// the type does not uphold the required invariants.
167    ///
168    /// # Panics
169    ///
170    /// This function will panic if `T` is zero-sized, has an alignment
171    /// other than one, or has any internal padding.
172    ///
173    /// [`read_ref_unchecked`]: [Self::read_ref_unchecked]
174    pub fn read_ref_at<T: AnyBitPattern + FixedSize>(
175        &self,
176        offset: usize,
177    ) -> Result<&'a T, ReadError> {
178        let end = offset
179            .checked_add(T::RAW_BYTE_LEN)
180            .ok_or(ReadError::OutOfBounds)?;
181        self.bytes
182            .get(offset..end)
183            .ok_or(ReadError::OutOfBounds)
184            .map(bytemuck::from_bytes)
185    }
186
187    /// Interpret the bytes at the provided offset as a slice of `T`.
188    ///
189    /// Returns an error if `range` is out of bounds for the underlying data,
190    /// or if the length of the range is not a multiple of `T::RAW_BYTE_LEN`.
191    ///
192    /// This is a wrapper around [`read_array_unchecked`][], which panics if
193    /// the type does not uphold the required invariants.
194    ///
195    /// # Panics
196    ///
197    /// This function will panic if `T` is zero-sized, has an alignment
198    /// other than one, or has any internal padding.
199    ///
200    /// [`read_array_unchecked`]: [Self::read_array_unchecked]
201    pub fn read_array<T: AnyBitPattern + FixedSize>(
202        &self,
203        range: Range<usize>,
204    ) -> Result<&'a [T], ReadError> {
205        let bytes = self
206            .bytes
207            .get(range.clone())
208            .ok_or(ReadError::OutOfBounds)?;
209        if bytes
210            .len()
211            .checked_rem(std::mem::size_of::<T>())
212            .unwrap_or(1) // definitely != 0
213            != 0
214        {
215            return Err(ReadError::InvalidArrayLen);
216        };
217        Ok(bytemuck::cast_slice(bytes))
218    }
219
220    pub(crate) fn cursor(&self) -> Cursor<'a> {
221        Cursor {
222            pos: 0,
223            data: *self,
224        }
225    }
226
227    /// Return the data as a byte slice
228    pub fn as_bytes(&self) -> &'a [u8] {
229        self.bytes
230    }
231}
232
233impl<'a> Cursor<'a> {
234    pub(crate) fn advance<T: Scalar>(&mut self) {
235        self.pos = self.pos.saturating_add(T::RAW_BYTE_LEN);
236    }
237
238    pub(crate) fn advance_by(&mut self, n_bytes: usize) {
239        self.pos = self.pos.saturating_add(n_bytes);
240    }
241
242    /// Read a variable length u32 and advance the cursor
243    pub(crate) fn read_u32_var(&mut self) -> Result<u32, ReadError> {
244        let mut next = || self.read::<u8>().map(|v| v as u32);
245        let b0 = next()?;
246        // TODO this feels possible to simplify, e.g. compute length, loop taking one and shifting and or'ing
247        #[allow(clippy::arithmetic_side_effects)] // these are all checked
248        let result = match b0 {
249            _ if b0 < 0x80 => b0,
250            _ if b0 < 0xC0 => ((b0 - 0x80) << 8) | next()?,
251            _ if b0 < 0xE0 => ((b0 - 0xC0) << 16) | (next()? << 8) | next()?,
252            _ if b0 < 0xF0 => ((b0 - 0xE0) << 24) | (next()? << 16) | (next()? << 8) | next()?,
253            _ => {
254                // 0xF0 is a dedicated 5-byte prefix; high bits are carried entirely
255                // by the following 4 bytes.
256                (next()? << 24) | (next()? << 16) | (next()? << 8) | next()?
257            }
258        };
259
260        Ok(result)
261    }
262
263    /// Read a scalar and advance the cursor.
264    pub(crate) fn read<T: Scalar>(&mut self) -> Result<T, ReadError> {
265        let temp = self.data.read_at(self.pos);
266        self.advance::<T>();
267        temp
268    }
269
270    /// Read a big-endian value and advance the cursor.
271    pub(crate) fn read_be<T: Scalar>(&mut self) -> Result<BigEndian<T>, ReadError> {
272        let temp = self.data.read_be_at(self.pos);
273        self.advance::<T>();
274        temp
275    }
276
277    /// Read an item positioned at the cursor and advance past it.
278    ///
279    /// The item is given the cursor's whole data plus its position,
280    /// rather than data sliced to it, so it can resolve offsets
281    /// relative to the enclosing table.
282    pub(crate) fn read_at_with_args<T>(&mut self, args: T::Args) -> Result<T, ReadError>
283    where
284        T: FontReadAt<'a> + ComputeSize,
285    {
286        let len = T::compute_size(args)?;
287        // check the item actually fits before handing out a handle to it
288        let range_end = self.pos.checked_add(len).ok_or(ReadError::OutOfBounds)?;
289        self.data.check_in_bounds(range_end)?;
290        let temp = T::read_at(self.data, self.pos, args);
291        self.advance_by(len);
292        temp
293    }
294
295    // only used in records that contain arrays :/
296    pub(crate) fn read_computed_array<T>(
297        &mut self,
298        len: usize,
299        args: T::Args,
300    ) -> Result<ComputedArray<'a, T>, ReadError>
301    where
302        T: FontReadAt<'a> + ComputeSize,
303    {
304        let byte_len = len
305            .checked_mul(T::compute_size(args)?)
306            .ok_or(ReadError::OutOfBounds)?;
307        let range_end = self
308            .pos
309            .checked_add(byte_len)
310            .ok_or(ReadError::OutOfBounds)?;
311        let temp = ComputedArray::new(self.data, self.pos..range_end, args);
312        self.advance_by(byte_len);
313        temp
314    }
315    pub(crate) fn read_array<T: AnyBitPattern + FixedSize>(
316        &mut self,
317        n_elem: usize,
318    ) -> Result<&'a [T], ReadError> {
319        let len = n_elem
320            .checked_mul(T::RAW_BYTE_LEN)
321            .ok_or(ReadError::OutOfBounds)?;
322        let end = self.pos.checked_add(len).ok_or(ReadError::OutOfBounds)?;
323        let temp = self.data.read_array(self.pos..end);
324        self.advance_by(len);
325        temp
326    }
327
328    /// return the current position, or an error if we are out of bounds
329    pub(crate) fn position(&self) -> Result<usize, ReadError> {
330        self.data.check_in_bounds(self.pos).map(|_| self.pos)
331    }
332
333    // used when handling fields with an implicit length, which must be at the
334    // end of a table.
335    pub(crate) fn remaining_bytes(&self) -> usize {
336        self.data.len().saturating_sub(self.pos)
337    }
338
339    pub(crate) fn remaining(self) -> Option<FontData<'a>> {
340        self.data.split_off(self.pos)
341    }
342
343    pub fn is_empty(&self) -> bool {
344        self.pos >= self.data.len()
345    }
346}
347
348// useful so we can have offsets that are just to data
349impl ReadArgs for FontData<'_> {
350    type Args = ();
351}
352
353impl<'a> FontRead<'a> for FontData<'a> {
354    fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
355        Ok(data)
356    }
357}
358
359impl AsRef<[u8]> for FontData<'_> {
360    fn as_ref(&self) -> &[u8] {
361        self.bytes
362    }
363}
364
365impl<'a> From<&'a [u8]> for FontData<'a> {
366    fn from(src: &'a [u8]) -> FontData<'a> {
367        FontData::new(src)
368    }
369}
370
371//kind of ugly, but makes FontData work with FontBuilder. If FontBuilder stops using
372//Cow in its API, we can probably get rid of this?
373#[cfg(feature = "std")]
374impl<'a> From<FontData<'a>> for std::borrow::Cow<'a, [u8]> {
375    fn from(src: FontData<'a>) -> Self {
376        src.bytes.into()
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::*;
383    #[test]
384    fn how_does_big_endian_work_again() {
385        let data = FontData::default_format_1_u16_table_data();
386        assert_eq!(data.read_at(0), Ok(1u16));
387
388        assert_eq!(
389            FontData::default_format_1_u8_table_data().read_at(0),
390            Ok(1u8)
391        );
392    }
393}