Skip to main content

font_types/
table_data.rs

1//! Table data with a fixed-size header at its start.
2
3use core::marker::PhantomData;
4
5/// The bytes of a table whose start holds a header of type `H`.
6///
7/// The length is checked once, when the reference is made, so reading the
8/// header's fields needs no further bounds checks.
9pub struct TableDataRef<'a, H> {
10    data: &'a [u8],
11    _header: PhantomData<&'a H>,
12}
13
14impl<'a, H: bytemuck::AnyBitPattern> TableDataRef<'a, H> {
15    const HEADER_ALIGN_IS_ONE: () = assert!(
16        core::mem::align_of::<H>() == 1,
17        "a table header must be readable at any address"
18    );
19
20    /// Creates a reference to the given data, or `None` if it is too short to
21    /// hold the header.
22    #[inline(always)]
23    pub fn new(data: &'a [u8]) -> Option<Self> {
24        #[allow(clippy::let_unit_value)]
25        let _ = Self::HEADER_ALIGN_IS_ONE;
26        (data.len() >= core::mem::size_of::<H>()).then_some(Self {
27            data,
28            _header: PhantomData,
29        })
30    }
31
32    /// Returns the header at the start of the data.
33    #[inline(always)]
34    pub fn header(&self) -> &'a H {
35        // SAFETY: `new` is the only constructor, and it checked that the data
36        // holds at least `size_of::<H>()` bytes. `H` has an alignment of one,
37        // and any bit pattern is a valid `H`.
38        unsafe { &*self.data.as_ptr().cast::<H>() }
39    }
40
41    /// Returns all of the data, including the header.
42    #[inline(always)]
43    pub fn data(&self) -> &'a [u8] {
44        self.data
45    }
46}
47
48impl<H> Clone for TableDataRef<'_, H> {
49    fn clone(&self) -> Self {
50        *self
51    }
52}
53
54impl<H> Copy for TableDataRef<'_, H> {}
55
56#[cfg(test)]
57mod tests {
58    use super::*;
59    use crate::BigEndian;
60
61    #[derive(Clone, Copy, bytemuck::AnyBitPattern)]
62    #[repr(C, packed)]
63    struct Header {
64        a: BigEndian<u16>,
65        b: BigEndian<u32>,
66    }
67
68    #[test]
69    fn reads_header() {
70        let data = [0, 1, 0, 0, 0, 2, 0xff];
71        let table = TableDataRef::<Header>::new(&data).unwrap();
72        assert_eq!(table.header().a.get(), 1);
73        assert_eq!(table.header().b.get(), 2);
74        assert_eq!(table.data().len(), 7);
75    }
76
77    #[test]
78    fn rejects_short_data() {
79        assert!(TableDataRef::<Header>::new(&[0, 1, 0, 0, 0]).is_none());
80        assert!(TableDataRef::<Header>::new(&[0, 1, 0, 0, 0, 2]).is_some());
81    }
82
83    #[test]
84    fn reads_unaligned_header() {
85        let data = [0xff, 0, 3, 0, 0, 0, 4];
86        let table = TableDataRef::<Header>::new(&data[1..]).unwrap();
87        assert_eq!(table.header().a.get(), 3);
88        assert_eq!(table.header().b.get(), 4);
89    }
90}