Skip to main content

ttf_view/tables/
directory.rs

1use crate::{
2    tables::{Table, TableError},
3    types::{Offset32, Tag, tags, uint16, uint32},
4    util::custom_iterator,
5};
6
7#[repr(C)]
8pub struct TableDirectory {
9    pub sfnt_version: uint32,
10    pub num_tables: uint16,
11    pub search_range: uint16,
12    pub entry_selector: uint16,
13    pub range_shift: uint16,
14    table_records: [TableRecordRaw; 0],
15}
16#[repr(C)]
17pub struct TableRecordRaw {
18    _exhaustive_but_dont_instantiate: (),
19    pub table_tag: Tag,
20    pub checksum: uint32,
21    pub offset: Offset32,
22    pub length: uint32,
23}
24
25impl TableDirectory {
26    pub fn new(bytes: &[u8]) -> Result<&Self, TableError> {
27        // Validate that table directory is in range
28        if bytes.len() < size_of::<TableDirectory>() {
29            return Err(TableError::InvalidLen);
30        }
31        let dir = unsafe { Self::new_unchecked(bytes) };
32
33        // Validate that all table records are in range
34        let required_len = size_of::<TableDirectory>()
35            + dir.num_tables.get() as usize * size_of::<TableRecordRaw>();
36        if bytes.len() < required_len {
37            return Err(TableError::InvalidLen);
38        }
39
40        // Validate that every table's data is in range
41        for table in dir.table_records_raw() {
42            let offset = table.offset.get() as usize + table.length.get() as usize;
43            if bytes.len() < offset {
44                return Err(TableError::InvalidLen);
45            }
46        }
47
48        Ok(dir)
49    }
50    pub const unsafe fn new_unchecked(bytes: &[u8]) -> &Self {
51        unsafe { &*bytes.as_ptr().cast() }
52    }
53
54    pub const fn directory_as_bytes(&self) -> &[u8] {
55        let start = std::ptr::from_ref(self).cast();
56        let end = self.table_records_raw().as_ptr_range().end.cast();
57        unsafe { std::slice::from_ptr_range(start..end) }
58    }
59
60    pub const fn table_records_raw(&self) -> &[TableRecordRaw] {
61        let len = self.num_tables.get() as usize;
62        unsafe { std::slice::from_raw_parts(self.table_records.as_ptr(), len) }
63    }
64    pub fn table_record_raw(&self, tag: Tag) -> Option<&TableRecordRaw> {
65        self.table_records_raw().iter().find(|x| x.table_tag == tag)
66    }
67
68    pub const fn table_records(&self) -> TableRecordsIter<'_> {
69        TableRecordsIter::new(self)
70    }
71    pub fn table_record(&self, tag: Tag) -> Option<TableRecord<'_>> {
72        Some(TableRecord(self, self.table_record_raw(tag)?))
73    }
74
75    pub fn table<'a, T: Table<'a>>(&'a self) -> Result<T, TableError> {
76        T::new_in(self)
77    }
78
79    // Note: see src/tables/mod.rs for specific table methods
80}
81
82#[derive(Copy)]
83#[derive_const(Clone)]
84pub struct TableRecord<'a>(&'a TableDirectory, &'a TableRecordRaw);
85
86const impl<'a> std::ops::Deref for TableRecord<'a> {
87    type Target = &'a TableRecordRaw;
88    fn deref(&self) -> &Self::Target {
89        &self.1
90    }
91}
92
93impl<'a> TableRecord<'a> {
94    pub const fn table_as_bytes(&self) -> &'a [u8] {
95        unsafe {
96            let start = std::ptr::from_ref(self.0).cast::<u8>().add(self.offset.get() as _);
97            std::slice::from_raw_parts(start, self.length.get() as _)
98        }
99    }
100    pub fn table_as<T: Table<'a>>(&self) -> Result<T, TableError> {
101        T::new_in(self.0)
102    }
103
104    pub(crate) fn raw_as<T>(&self) -> Option<&'a T> {
105        if self.length.get() >= size_of::<T>() as u32 {
106            Some(unsafe { &*self.table_as_bytes().as_ptr().cast() })
107        } else {
108            None
109        }
110    }
111
112    pub fn calculate_checksum(&self) -> u32 {
113        let (uint32s, unpadded) = self.table_as_bytes().as_chunks::<4>();
114        let mut sum: u32 = 0;
115
116        for chunk in uint32s {
117            sum = sum.wrapping_add(u32::from_be_bytes(*chunk));
118        }
119
120        if !unpadded.is_empty() {
121            let mut buf = [0; 4];
122            buf[..unpadded.len()].copy_from_slice(unpadded);
123            sum = sum.wrapping_add(u32::from_be_bytes(buf));
124        }
125
126        if self.table_tag == tags::head {
127            let checksum_adjustment = u32::from_be_bytes(uint32s[2]);
128            sum = sum.wrapping_sub(checksum_adjustment);
129        }
130
131        sum
132    }
133}
134
135// TODO: When std::slice::Iter's Clone is constified, make the derive const
136#[derive(Clone)]
137pub struct TableRecordsIter<'a> {
138    dir: &'a TableDirectory,
139    inner: std::slice::Iter<'a, TableRecordRaw>,
140}
141impl<'a> TableRecordsIter<'a> {
142    pub const fn new(dir: &'a TableDirectory) -> Self {
143        Self { dir, inner: dir.table_records_raw().iter() }
144    }
145    // TODO: When std::slice::Iter's as_slice() is constified, constify as_records()
146    pub fn as_records(&self) -> &'a [TableRecordRaw] {
147        self.inner.as_slice()
148    }
149}
150custom_iterator!(TableRecordsIter<'a> as this {
151    type Item = TableRecord<'a>;
152    map: |x| TableRecord(this.dir, x);
153});
154
155impl std::fmt::Debug for TableDirectory {
156    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
157        f.debug_struct("TableDirectory")
158            .field_with("sfnt_version", |f| write!(f, "{:#010X}", self.sfnt_version))
159            .field("num_tables", &self.num_tables.get())
160            .field("search_range", &self.search_range.get())
161            .field("entry_selector", &self.entry_selector.get())
162            .field("range_shift", &self.range_shift.get())
163            .field_with("table_records", |f| {
164                let mut list = f.debug_list();
165
166                for table in self.table_records() {
167                    list.entry_with(|f| {
168                        table.fmt(&mut f.with_options(*f.options().alternate(false)))
169                    });
170                }
171                list.finish()
172            })
173            .finish()
174    }
175}
176impl std::fmt::Debug for TableRecordRaw {
177    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
178        f.debug_struct("TableRecord")
179            .field("table_tag", &self.table_tag)
180            .field_with("checksum", |f| write!(f, "{:#010X}", self.checksum))
181            .field_with("offset", |f| write!(f, "{:#010X}", self.offset))
182            .field_with("length", |f| write!(f, "{:#010X}", self.length))
183            .finish()
184    }
185}
186impl std::fmt::Debug for TableRecord<'_> {
187    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
188        TableRecordRaw::fmt(self, f)
189    }
190}