Skip to main content

read_fonts/model/font/
tables.rs

1//! Table validation, caching and access.
2
3use super::{super::once::Once, FontBlob, FontSource};
4use crate::{
5    tables, types::Tag, FontRead, FontReadWithArgs, ReadError, TableProvider, TopLevelTable,
6};
7use alloc::{boxed::Box, sync::Arc};
8use core::sync::atomic::{AtomicU8, Ordering};
9
10include!("../../../data/generated/generated_tables.rs");
11
12/// A single table in a font blob.
13#[derive(Default)]
14struct BlobTableEntry {
15    /// Holds validation state.
16    flag: AtomicU8,
17    start: u32,
18    end: u32,
19}
20
21/// Blob and associated metadata for all tables.
22struct BlobTables {
23    blob: FontBlob,
24    tables: Box<PerTableData<BlobTableEntry>>,
25}
26
27impl<'a> TableDataProvider<'a> for &'a BlobTables {
28    type Entry = BlobTableEntry;
29
30    fn tables(&self) -> &'a PerTableData<Self::Entry> {
31        &self.tables
32    }
33
34    fn table_state(&self, _tag: Tag, entry: &'a Self::Entry) -> Option<TableState<'a>> {
35        self.blob
36            .get(entry.start as usize..entry.end as usize)
37            .map(|data| TableState {
38                flag: &entry.flag,
39                data,
40            })
41    }
42}
43
44/// Entry for a single table provided by a callback.
45type TableFunctionEntry = Once<Option<(AtomicU8, FontBlob)>>;
46
47/// Lazy, per table font data provided by a function.
48#[derive(Clone)]
49pub struct FontTableFunction {
50    table_fn: Arc<dyn Fn(Tag) -> Option<FontBlob> + Send + Sync>,
51    tables: Arc<PerTableData<TableFunctionEntry>>,
52}
53
54impl FontTableFunction {
55    /// Creates a new font table function with the given callback that should
56    /// return a blob containing the table data for the requested tag.
57    pub fn new(table_fn: Arc<dyn Fn(Tag) -> Option<FontBlob> + Send + Sync>) -> Self {
58        Self {
59            table_fn,
60            tables: Arc::new(PerTableData::default()),
61        }
62    }
63}
64
65impl<'a> TableDataProvider<'a> for &'a FontTableFunction {
66    type Entry = TableFunctionEntry;
67
68    fn tables(&self) -> &'a PerTableData<Self::Entry> {
69        &self.tables
70    }
71
72    fn table_state(&self, tag: Tag, entry: &'a Self::Entry) -> Option<TableState<'a>> {
73        entry
74            .get_or_init(|| (self.table_fn)(tag).map(|blob| (AtomicU8::new(0), blob)))
75            .as_ref()
76            .map(|(flag, data)| TableState {
77                flag,
78                data: data.as_ref(),
79            })
80    }
81}
82
83/// Source for a set of font tables.
84enum TableSource {
85    None,
86    Blob(BlobTables),
87    Function(FontTableFunction),
88}
89
90/// Reference to the validation flag and data for a table.
91#[derive(Copy, Clone)]
92struct TableState<'a> {
93    flag: &'a AtomicU8,
94    data: &'a [u8],
95}
96
97impl<'a> TableState<'a> {
98    const UNCHECKED: u8 = 0;
99    const VALID: u8 = 1;
100    const INVALID: u8 = 2;
101
102    fn try_load<T>(
103        &self,
104        sanitize_fn: impl Fn(&'a [u8]) -> Result<T, ReadError>,
105        load_fn: impl Fn(&'a [u8]) -> Result<T, ReadError>,
106    ) -> Result<T, ReadError> {
107        match self.flag.load(Ordering::Acquire) {
108            Self::UNCHECKED => {
109                if let Ok(table) = sanitize_fn(self.data) {
110                    self.flag.store(Self::VALID, Ordering::Release);
111                    Ok(table)
112                } else {
113                    self.flag.store(Self::INVALID, Ordering::Release);
114                    Err(ReadError::ValidationError)
115                }
116            }
117            Self::VALID => load_fn(self.data),
118            // Assume any other bit pattern is invalid
119            _ => Err(ReadError::ValidationError),
120        }
121    }
122}
123
124/// Individual font table access.
125pub struct FontTables(TableSource);
126
127impl FontTables {
128    /// Creates a new set of font tables for the given source.
129    pub fn new(source: impl Into<FontSource>, index: u32) -> Result<Self, ReadError> {
130        let source = source.into();
131        match source {
132            FontSource::Blob(blob) => {
133                let font_ref = crate::FontRef::from_index(&blob, index)?;
134                let mut tables = PerTableData::default();
135                let table_records = font_ref.table_directory().table_records();
136                tables.init_all(|tag, entry: &mut BlobTableEntry| {
137                    // Ensure missing entries report as a missing table rather
138                    // than returning an empty slice
139                    entry.start = u32::MAX;
140                    let Ok(idx) = table_records.binary_search_by_key(&tag, |rec| rec.tag()) else {
141                        return;
142                    };
143                    let record = &table_records[idx];
144                    let start = record.offset();
145                    let Some(end) = start.checked_add(record.length()) else {
146                        return;
147                    };
148                    if blob.get(start as usize..end as usize).is_none() {
149                        return;
150                    }
151                    *entry = BlobTableEntry {
152                        flag: AtomicU8::new(0),
153                        start,
154                        end,
155                    };
156                });
157                Ok(Self(TableSource::Blob(BlobTables {
158                    blob,
159                    tables: Box::new(tables),
160                })))
161            }
162            FontSource::TableFunction(func) => Ok(Self(TableSource::Function(func))),
163        }
164    }
165}
166
167impl FontTables {
168    fn load_table<'a, T: TopLevelTable + FontRead<'a>>(
169        &'a self,
170        state: Option<TableState<'a>>,
171    ) -> Result<T, ReadError> {
172        self.load_table_with_tag(state, T::TAG)
173    }
174
175    fn load_table_with_tag<'a, T: FontRead<'a>>(
176        &'a self,
177        state: Option<TableState<'a>>,
178        tag: Tag,
179    ) -> Result<T, ReadError> {
180        let state = state.ok_or(ReadError::TableIsMissing(tag))?;
181        state.try_load(
182            |data| FontRead::read(data.into()),
183            |data| FontRead::read(data.into()),
184        )
185    }
186
187    fn load_table_with_args<'a, T: TopLevelTable + FontReadWithArgs<'a>>(
188        &'a self,
189        state: Option<TableState<'a>>,
190        args: &T::Args,
191    ) -> Result<T, ReadError> {
192        let state = state.ok_or(ReadError::TableIsMissing(T::TAG))?;
193        state.try_load(
194            |data| FontReadWithArgs::read_with_args(data.into(), args),
195            |data| FontReadWithArgs::read_with_args(data.into(), args),
196        )
197    }
198}
199
200pub(super) static EMPTY_FONT_TABLES: FontTables = FontTables(TableSource::None);
201
202impl<'a> TableProvider<'a> for &'a FontTables {
203    fn data_for_tag(&self, _tag: Tag) -> Option<crate::FontData<'a>> {
204        None
205    }
206
207    fn head(&self) -> Result<tables::head::Head<'a>, ReadError> {
208        self.load_table(self.head_state())
209    }
210
211    fn name(&self) -> Result<tables::name::Name<'a>, ReadError> {
212        self.load_table(self.name_state())
213    }
214
215    fn hhea(&self) -> Result<tables::hhea::Hhea<'a>, ReadError> {
216        self.load_table(self.hhea_state())
217    }
218
219    fn vhea(&self) -> Result<tables::vhea::Vhea<'a>, ReadError> {
220        self.load_table(self.vhea_state())
221    }
222
223    fn hmtx(&self) -> Result<tables::hmtx::Hmtx<'a>, ReadError> {
224        //FIXME: should we make the user pass these in?
225        let number_of_h_metrics = self.hhea().map(|hhea| hhea.number_of_h_metrics())?;
226        self.load_table_with_args(self.hmtx_state(), &number_of_h_metrics)
227    }
228
229    fn hdmx(&self) -> Result<tables::hdmx::Hdmx<'a>, ReadError> {
230        let num_glyphs = self.maxp().map(|maxp| maxp.num_glyphs())?;
231        self.load_table_with_args(self.hdmx_state(), &num_glyphs)
232    }
233
234    fn vmtx(&self) -> Result<tables::vmtx::Vmtx<'a>, ReadError> {
235        //FIXME: should we make the user pass these in?
236        let number_of_v_metrics = self.vhea().map(|vhea| vhea.number_of_long_ver_metrics())?;
237        self.load_table_with_args(self.vmtx_state(), &number_of_v_metrics)
238    }
239
240    fn vorg(&self) -> Result<tables::vorg::Vorg<'a>, ReadError> {
241        self.load_table(self.vorg_state())
242    }
243
244    fn fvar(&self) -> Result<tables::fvar::Fvar<'a>, ReadError> {
245        self.load_table(self.fvar_state())
246    }
247
248    fn avar(&self) -> Result<tables::avar::Avar<'a>, ReadError> {
249        self.load_table(self.avar_state())
250    }
251
252    fn hvar(&self) -> Result<tables::hvar::Hvar<'a>, ReadError> {
253        self.load_table(self.hvar_state())
254    }
255
256    fn vvar(&self) -> Result<tables::vvar::Vvar<'a>, ReadError> {
257        self.load_table(self.vvar_state())
258    }
259
260    fn mvar(&self) -> Result<tables::mvar::Mvar<'a>, ReadError> {
261        self.load_table(self.mvar_state())
262    }
263
264    fn maxp(&self) -> Result<tables::maxp::Maxp<'a>, ReadError> {
265        self.load_table(self.maxp_state())
266    }
267
268    fn os2(&self) -> Result<tables::os2::Os2<'a>, ReadError> {
269        self.load_table(self.os2_state())
270    }
271
272    fn post(&self) -> Result<tables::post::Post<'a>, ReadError> {
273        self.load_table(self.post_state())
274    }
275
276    fn gasp(&self) -> Result<tables::gasp::Gasp<'a>, ReadError> {
277        self.load_table(self.gasp_state())
278    }
279
280    /// is_long can be optionally provided, if known, otherwise we look it up in head.
281    fn loca(&self, is_long: impl Into<Option<bool>>) -> Result<tables::loca::Loca<'a>, ReadError> {
282        let is_long = match is_long.into() {
283            Some(val) => val,
284            None => self.head()?.index_to_loc_format() == 1,
285        };
286        self.load_table_with_args(self.loca_state(), &is_long)
287    }
288
289    fn glyf(&self) -> Result<tables::glyf::Glyf<'a>, ReadError> {
290        self.load_table(self.glyf_state())
291    }
292
293    fn gvar(&self) -> Result<tables::gvar::Gvar<'a>, ReadError> {
294        self.load_table(self.gvar_state())
295    }
296
297    /// Returns the array of entries for the control value table which is used
298    /// for TrueType hinting.
299    fn cvt(&self) -> Result<&'a [types::BigEndian<i16>], ReadError> {
300        let table_data = crate::FontData::new(
301            self.cvt_data()
302                .ok_or(ReadError::TableIsMissing(Tag::new(b"cvt ")))?,
303        );
304        table_data.read_array(0..table_data.len())
305    }
306
307    fn cvar(&self) -> Result<tables::cvar::Cvar<'a>, ReadError> {
308        self.load_table(self.cvar_state())
309    }
310
311    fn cff(&self) -> Result<tables::cff::Cff<'a>, ReadError> {
312        self.load_table(self.cff_state())
313    }
314
315    fn cff2(&self) -> Result<tables::cff2::Cff2<'a>, ReadError> {
316        self.load_table(self.cff2_state())
317    }
318
319    fn cmap(&self) -> Result<tables::cmap::Cmap<'a>, ReadError> {
320        self.load_table(self.cmap_state())
321    }
322
323    fn gdef(&self) -> Result<tables::gdef::Gdef<'a>, ReadError> {
324        self.load_table(self.gdef_state())
325    }
326
327    fn gpos(&self) -> Result<tables::gpos::Gpos<'a>, ReadError> {
328        self.load_table(self.gpos_state())
329    }
330
331    fn gsub(&self) -> Result<tables::gsub::Gsub<'a>, ReadError> {
332        self.load_table(self.gsub_state())
333    }
334
335    fn feat(&self) -> Result<tables::feat::Feat<'a>, ReadError> {
336        self.load_table(self.feat_state())
337    }
338
339    fn ltag(&self) -> Result<tables::ltag::Ltag<'a>, ReadError> {
340        self.load_table(self.ltag_state())
341    }
342
343    fn ankr(&self) -> Result<tables::ankr::Ankr<'a>, ReadError> {
344        self.load_table(self.ankr_state())
345    }
346
347    fn trak(&self) -> Result<tables::trak::Trak<'a>, ReadError> {
348        self.load_table(self.trak_state())
349    }
350
351    fn morx(&self) -> Result<tables::morx::Morx<'a>, ReadError> {
352        self.load_table(self.morx_state())
353    }
354
355    fn kerx(&self) -> Result<tables::kerx::Kerx<'a>, ReadError> {
356        self.load_table(self.kerx_state())
357    }
358
359    fn kern(&self) -> Result<tables::kern::Kern<'a>, ReadError> {
360        self.load_table(self.kern_state())
361    }
362
363    fn colr(&self) -> Result<tables::colr::Colr<'a>, ReadError> {
364        self.load_table(self.colr_state())
365    }
366
367    fn cpal(&self) -> Result<tables::cpal::Cpal<'a>, ReadError> {
368        self.load_table(self.cpal_state())
369    }
370
371    fn cblc(&self) -> Result<tables::cblc::Cblc<'a>, ReadError> {
372        self.load_table(self.cblc_state())
373    }
374
375    fn cbdt(&self) -> Result<tables::cbdt::Cbdt<'a>, ReadError> {
376        self.load_table(self.cbdt_state())
377    }
378
379    fn eblc(&self) -> Result<tables::eblc::Eblc<'a>, ReadError> {
380        self.load_table(self.eblc_state())
381    }
382
383    fn ebdt(&self) -> Result<tables::ebdt::Ebdt<'a>, ReadError> {
384        self.load_table(self.ebdt_state())
385    }
386
387    fn sbix(&self) -> Result<tables::sbix::Sbix<'a>, ReadError> {
388        // should we make the user pass this in?
389        let num_glyphs = self.maxp().map(|maxp| maxp.num_glyphs())?;
390        self.load_table_with_args(self.sbix_state(), &num_glyphs)
391    }
392
393    fn stat(&self) -> Result<tables::stat::Stat<'a>, ReadError> {
394        self.load_table(self.stat_state())
395    }
396
397    fn svg(&self) -> Result<tables::svg::Svg<'a>, ReadError> {
398        self.load_table(self.svg_state())
399    }
400
401    fn varc(&self) -> Result<tables::varc::Varc<'a>, ReadError> {
402        self.load_table(self.varc_state())
403    }
404
405    #[cfg(feature = "ift")]
406    fn ift(&self) -> Result<tables::ift::Ift<'a>, ReadError> {
407        self.load_table_with_tag(self.ift_state(), Tag::new(b"IFT "))
408    }
409
410    #[cfg(feature = "ift")]
411    fn iftx(&self) -> Result<tables::ift::Ift<'a>, ReadError> {
412        self.load_table_with_tag(self.iftx_state(), Tag::new(b"IFTX"))
413    }
414
415    fn meta(&self) -> Result<tables::meta::Meta<'a>, ReadError> {
416        self.load_table(self.meta_state())
417    }
418
419    fn base(&self) -> Result<tables::base::Base<'a>, ReadError> {
420        self.load_table(self.base_state())
421    }
422
423    fn dsig(&self) -> Result<tables::dsig::Dsig<'a>, ReadError> {
424        self.load_table(self.dsig_state())
425    }
426}
427
428#[cfg(test)]
429mod tests {
430    use crate::FontRef;
431
432    use super::*;
433
434    #[test]
435    fn missing_tables_are_missing() {
436        let tables = &FontTables::new(font_test_data::AHEM, 0).unwrap();
437        assert!(matches!(tables.cbdt(), Err(ReadError::TableIsMissing(_))));
438        assert!(matches!(tables.cff(), Err(ReadError::TableIsMissing(_))));
439        assert!(matches!(tables.dsig(), Err(ReadError::TableIsMissing(_))));
440    }
441
442    #[test]
443    fn table_function_matches_font_ref() {
444        let font = FontRef::new(font_test_data::AHEM).unwrap();
445        let font_copy = font.clone();
446        let table_fn = FontTableFunction::new(Arc::new(move |tag| {
447            font_copy
448                .data_for_tag(tag)
449                .map(|data| Vec::from(data.as_bytes()).into())
450        }));
451        let tables = &FontTables::new(table_fn, 0).unwrap();
452        for (data, tag) in [
453            (tables.gasp_data(), b"gasp"),
454            (tables.glyf_data(), b"glyf"),
455            (tables.hmtx_data(), b"hmtx"),
456        ] {
457            let font_ref_data = font.data_for_tag(Tag::new(tag)).map(|d| d.as_bytes());
458            assert_eq!(data, font_ref_data);
459        }
460    }
461}