read_fonts/tables/
gpos.rs

1//! the [GPOS] table
2//!
3//! [GPOS]: https://docs.microsoft.com/en-us/typography/opentype/spec/gpos
4
5#[path = "./value_record.rs"]
6mod value_record;
7
8#[cfg(feature = "std")]
9mod closure;
10
11use crate::array::ComputedArray;
12
13/// reexport stuff from layout that we use
14pub use super::layout::{
15    ClassDef, CoverageTable, Device, DeviceOrVariationIndex, FeatureList, FeatureVariations,
16    Lookup, ScriptList,
17};
18use super::layout::{ExtensionLookup, LookupFlag, Subtables};
19pub use value_record::ValueRecord;
20
21#[cfg(test)]
22#[path = "../tests/test_gpos.rs"]
23mod spec_tests;
24
25include!("../../generated/generated_gpos.rs");
26
27/// A typed GPOS [LookupList](super::layout::LookupList) table
28pub type PositionLookupList<'a> = super::layout::LookupList<'a, PositionLookup<'a>>;
29
30/// A GPOS [SequenceContext](super::layout::SequenceContext)
31pub type PositionSequenceContext<'a> = super::layout::SequenceContext<'a>;
32
33/// A GPOS [ChainedSequenceContext](super::layout::ChainedSequenceContext)
34pub type PositionChainContext<'a> = super::layout::ChainedSequenceContext<'a>;
35
36impl<'a> AnchorTable<'a> {
37    /// Attempt to resolve the `Device` or `VariationIndex` table for the
38    /// x_coordinate, if present
39    pub fn x_device(&self) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
40        match self {
41            AnchorTable::Format3(inner) => inner.x_device(),
42            _ => None,
43        }
44    }
45
46    /// Attempt to resolve the `Device` or `VariationIndex` table for the
47    /// y_coordinate, if present
48    pub fn y_device(&self) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
49        match self {
50            AnchorTable::Format3(inner) => inner.y_device(),
51            _ => None,
52        }
53    }
54}
55
56impl<'a, T: FontRead<'a>> ExtensionLookup<'a, T> for ExtensionPosFormat1<'a, T> {
57    fn extension(&self) -> Result<T, ReadError> {
58        self.extension()
59    }
60}
61
62type PosSubtables<'a, T> = Subtables<'a, T, ExtensionPosFormat1<'a, T>>;
63
64/// The subtables from a GPOS lookup.
65///
66/// This type is a convenience that removes the need to dig into the
67/// [`PositionLookup`] enum in order to access subtables, and it also abstracts
68/// away the distinction between extension and non-extension lookups.
69pub enum PositionSubtables<'a> {
70    Single(PosSubtables<'a, SinglePos<'a>>),
71    Pair(PosSubtables<'a, PairPos<'a>>),
72    Cursive(PosSubtables<'a, CursivePosFormat1<'a>>),
73    MarkToBase(PosSubtables<'a, MarkBasePosFormat1<'a>>),
74    MarkToLig(PosSubtables<'a, MarkLigPosFormat1<'a>>),
75    MarkToMark(PosSubtables<'a, MarkMarkPosFormat1<'a>>),
76    Contextual(PosSubtables<'a, PositionSequenceContext<'a>>),
77    ChainContextual(PosSubtables<'a, PositionChainContext<'a>>),
78}
79
80impl<'a> PositionLookup<'a> {
81    pub fn lookup_flag(&self) -> LookupFlag {
82        self.of_unit_type().lookup_flag()
83    }
84
85    /// Different enumerations for GSUB and GPOS
86    pub fn lookup_type(&self) -> u16 {
87        self.of_unit_type().lookup_type()
88    }
89
90    pub fn mark_filtering_set(&self) -> Option<u16> {
91        self.of_unit_type().mark_filtering_set()
92    }
93
94    /// Return the subtables for this lookup.
95    ///
96    /// This method handles both extension and non-extension lookups, and saves
97    /// the caller needing to dig into the `PositionLookup` enum itself.
98    pub fn subtables(&self) -> Result<PositionSubtables<'a>, ReadError> {
99        let raw_lookup = self.of_unit_type();
100        let offsets = raw_lookup.subtable_offsets();
101        let data = raw_lookup.offset_data();
102        match raw_lookup.lookup_type() {
103            1 => Ok(PositionSubtables::Single(Subtables::new(offsets, data))),
104            2 => Ok(PositionSubtables::Pair(Subtables::new(offsets, data))),
105            3 => Ok(PositionSubtables::Cursive(Subtables::new(offsets, data))),
106            4 => Ok(PositionSubtables::MarkToBase(Subtables::new(offsets, data))),
107            5 => Ok(PositionSubtables::MarkToLig(Subtables::new(offsets, data))),
108            6 => Ok(PositionSubtables::MarkToMark(Subtables::new(offsets, data))),
109            7 => Ok(PositionSubtables::Contextual(Subtables::new(offsets, data))),
110            8 => Ok(PositionSubtables::ChainContextual(Subtables::new(
111                offsets, data,
112            ))),
113            9 => {
114                let first = offsets.first().ok_or(ReadError::OutOfBounds)?.get();
115                let ext: ExtensionPosFormat1<()> = first.resolve(data)?;
116                match ext.extension_lookup_type() {
117                    1 => Ok(PositionSubtables::Single(Subtables::new_ext(offsets, data))),
118                    2 => Ok(PositionSubtables::Pair(Subtables::new_ext(offsets, data))),
119                    3 => Ok(PositionSubtables::Cursive(Subtables::new_ext(
120                        offsets, data,
121                    ))),
122                    4 => Ok(PositionSubtables::MarkToBase(Subtables::new_ext(
123                        offsets, data,
124                    ))),
125                    5 => Ok(PositionSubtables::MarkToLig(Subtables::new_ext(
126                        offsets, data,
127                    ))),
128                    6 => Ok(PositionSubtables::MarkToMark(Subtables::new_ext(
129                        offsets, data,
130                    ))),
131                    7 => Ok(PositionSubtables::Contextual(Subtables::new_ext(
132                        offsets, data,
133                    ))),
134                    8 => Ok(PositionSubtables::ChainContextual(Subtables::new_ext(
135                        offsets, data,
136                    ))),
137                    other => Err(ReadError::InvalidFormat(other as _)),
138                }
139            }
140            other => Err(ReadError::InvalidFormat(other as _)),
141        }
142    }
143}