Skip to main content

read_fonts/tables/
gsub.rs

1//! the [GSUB] table
2//!
3//! [GSUB]: https://docs.microsoft.com/en-us/typography/opentype/spec/gsub
4
5pub use super::layout::{
6    ChainedSequenceContext, ClassDef, CoverageTable, Device, FeatureList, FeatureVariations,
7    Lookup, LookupList, ScriptList, SequenceContext,
8};
9use super::layout::{ExtensionLookup, LookupFlag, Subtables};
10
11#[cfg(feature = "std")]
12mod closure;
13#[cfg(test)]
14#[path = "../tests/test_gsub.rs"]
15mod tests;
16
17include!("../../generated/generated_gsub.rs");
18
19/// A typed GSUB [LookupList] table
20pub type SubstitutionLookupList<'a> = LookupList<'a, SubstitutionLookup<'a>>;
21
22/// A GSUB [SequenceContext]
23pub type SubstitutionSequenceContext<'a> = super::layout::SequenceContext<'a>;
24
25/// A GSUB [ChainedSequenceContext]
26pub type SubstitutionChainContext<'a> = super::layout::ChainedSequenceContext<'a>;
27
28impl<'a, T: FontRead<'a>> ExtensionLookup<'a, T> for ExtensionSubstFormat1<'a, T> {
29    fn extension(&self) -> Result<T, ReadError> {
30        self.extension()
31    }
32}
33
34type SubSubtables<'a, T> = Subtables<'a, T, ExtensionSubstFormat1<'a, T>>;
35
36/// The subtables from a GPOS lookup.
37///
38/// This type is a convenience that removes the need to dig into the
39/// [`SubstitutionLookup`] enum in order to access subtables, and it also abstracts
40/// away the distinction between extension and non-extension lookups.
41pub enum SubstitutionSubtables<'a> {
42    Single(SubSubtables<'a, SingleSubst<'a>>),
43    Multiple(SubSubtables<'a, MultipleSubstFormat1<'a>>),
44    Alternate(SubSubtables<'a, AlternateSubstFormat1<'a>>),
45    Ligature(SubSubtables<'a, LigatureSubstFormat1<'a>>),
46    Contextual(SubSubtables<'a, SubstitutionSequenceContext<'a>>),
47    ChainContextual(SubSubtables<'a, SubstitutionChainContext<'a>>),
48    Reverse(SubSubtables<'a, ReverseChainSingleSubstFormat1<'a>>),
49    /// An extension lookup did not have any subtables
50    EmptyExtension,
51}
52
53impl<'a> SubstitutionLookup<'a> {
54    pub fn lookup_flag(&self) -> LookupFlag {
55        self.of_unit_type().lookup_flag()
56    }
57
58    /// Different enumerations for GSUB and GPOS
59    pub fn lookup_type(&self) -> u16 {
60        self.of_unit_type().lookup_type()
61    }
62
63    pub fn mark_filtering_set(&self) -> Option<u16> {
64        self.of_unit_type().mark_filtering_set()
65    }
66
67    /// Return the subtables for this lookup.
68    ///
69    /// This method handles both extension and non-extension lookups, and saves
70    /// the caller needing to dig into the `SubstitutionLookup` enum itself.
71    pub fn subtables(&self) -> Result<SubstitutionSubtables<'a>, ReadError> {
72        let raw_lookup = self.of_unit_type();
73        let offsets = raw_lookup.subtable_offsets();
74        let data = raw_lookup.offset_data();
75        match raw_lookup.lookup_type() {
76            1 => Ok(SubstitutionSubtables::Single(Subtables::new(offsets, data))),
77            2 => Ok(SubstitutionSubtables::Multiple(Subtables::new(
78                offsets, data,
79            ))),
80            3 => Ok(SubstitutionSubtables::Alternate(Subtables::new(
81                offsets, data,
82            ))),
83            4 => Ok(SubstitutionSubtables::Ligature(Subtables::new(
84                offsets, data,
85            ))),
86            5 => Ok(SubstitutionSubtables::Contextual(Subtables::new(
87                offsets, data,
88            ))),
89            6 => Ok(SubstitutionSubtables::ChainContextual(Subtables::new(
90                offsets, data,
91            ))),
92            8 => Ok(SubstitutionSubtables::Reverse(Subtables::new(
93                offsets, data,
94            ))),
95            7 => {
96                // look through subtable offsets to try and find a lookup type.
97                // this is robust in the case where the first subtable offset is
98                // malformed, but a later one is okay.
99                let Some(lookup_type) = offsets.iter().find_map(|off| {
100                    off.get()
101                        .resolve::<ExtensionSubstFormat1<()>>(data)
102                        .ok()
103                        .map(|ext| ext.extension_lookup_type())
104                }) else {
105                    return Ok(SubstitutionSubtables::EmptyExtension);
106                };
107
108                match lookup_type {
109                    1 => Ok(SubstitutionSubtables::Single(Subtables::new_ext(
110                        offsets, data,
111                    ))),
112                    2 => Ok(SubstitutionSubtables::Multiple(Subtables::new_ext(
113                        offsets, data,
114                    ))),
115                    3 => Ok(SubstitutionSubtables::Alternate(Subtables::new_ext(
116                        offsets, data,
117                    ))),
118                    4 => Ok(SubstitutionSubtables::Ligature(Subtables::new_ext(
119                        offsets, data,
120                    ))),
121                    5 => Ok(SubstitutionSubtables::Contextual(Subtables::new_ext(
122                        offsets, data,
123                    ))),
124                    6 => Ok(SubstitutionSubtables::ChainContextual(Subtables::new_ext(
125                        offsets, data,
126                    ))),
127                    8 => Ok(SubstitutionSubtables::Reverse(Subtables::new_ext(
128                        offsets, data,
129                    ))),
130                    other => Err(ReadError::InvalidFormat(other as _)),
131                }
132            }
133            other => Err(ReadError::InvalidFormat(other as _)),
134        }
135    }
136}