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