1#[path = "./value_record.rs"]
6mod value_record;
7
8#[cfg(feature = "std")]
9mod closure;
10
11use crate::array::ComputedArray;
12
13pub use super::layout::{
15 ClassDef, CoverageTable, Device, DeviceOrVariationIndex, FeatureList, FeatureVariations,
16 Lookup, ScriptList,
17};
18use super::layout::{ExtensionLookup, LookupFlag, Subtables};
19pub use value_record::{Value, ValueContext, ValueRecord};
20
21#[cfg(test)]
22#[path = "../tests/test_gpos.rs"]
23mod spec_tests;
24
25include!("../../generated/generated_gpos.rs");
26
27pub type PositionLookupList<'a> = super::layout::LookupList<'a, PositionLookup<'a>>;
29
30pub type PositionSequenceContext<'a> = super::layout::SequenceContext<'a>;
32
33pub type PositionChainContext<'a> = super::layout::ChainedSequenceContext<'a>;
35
36impl<'a> AnchorTable<'a> {
37 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 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
64pub 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 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 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}
144
145impl PairPosFormat2<'_> {
146 #[inline]
153 pub fn values(
154 &self,
155 class1: u16,
156 class2: u16,
157 context: &ValueContext,
158 ) -> Result<[Value; 2], ReadError> {
159 let format1 = self.value_format1();
160 let format1_len = format1.record_byte_len();
161 let format2 = self.value_format2();
162 let record_size = format1_len + format2.record_byte_len();
163 let data = self.offset_data();
164 let record_offset = (class1 as usize * record_size * self.class2_count() as usize)
166 + (class2 as usize * record_size)
167 + self.shape().class1_records_byte_range().start;
168 Ok([
169 Value::read(data, record_offset, format1, context)?,
170 Value::read(data, record_offset + format1_len, format2, context)?,
171 ])
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn pair_pos2_values_match_value_records() {
181 let data = FontData::new(font_test_data::gpos::PAIRPOSFORMAT2);
182 let table = PairPosFormat2::read(data).unwrap();
183 let class1_count = table.class1_count();
184 let class2_count = table.class2_count();
185 let records = table.class1_records();
186 let context = ValueContext::default();
187 for class1 in 0..class1_count {
188 let class1_record = records.get(class1 as usize).unwrap();
189 let class2_records = class1_record.class2_records();
190 for class2 in 0..class2_count {
191 let record = class2_records.get(class2 as usize).unwrap();
192 let value_records = [record.value_record1, record.value_record2]
193 .map(|rec| rec.value(data, &context).unwrap());
194 let values = table.values(class1, class2, &context).unwrap();
195 assert_eq!(value_records, values);
196 }
197 }
198 }
199}