Skip to main content

read_fonts/model/font/
tables.rs

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