1#[path = "./value_record.rs"]
6mod value_record;
7
8#[cfg(feature = "std")]
9mod closure;
10
11use crate::array::ComputedArray;
12
13use super::layout::header::HeaderOffset;
14pub use super::layout::{
16 ClassDef, CoverageTable, Device, DeviceOrVariationIndex, FeatureList, FeatureVariations,
17 Lookup, ScriptList,
18};
19use super::layout::{ExtensionLookup, LookupFlag, SelectedLookupList, Subtables};
20pub use value_record::ValueRecord;
21
22#[cfg(test)]
23#[path = "../tests/test_gpos.rs"]
24mod spec_tests;
25
26use crate::types::Compatible;
27
28include!("../../generated/generated_gpos.rs");
29
30impl<'a> Gpos<'a> {
31 pub(crate) fn selected_script_list_offset(&self) -> Result<HeaderOffset, ReadError> {
33 self.select_offset(self.script_list_offset(), self.script_list2_offset())
34 }
35
36 pub(crate) fn selected_feature_list_offset(&self) -> Result<HeaderOffset, ReadError> {
38 self.select_offset(self.feature_list_offset(), self.feature_list2_offset())
39 }
40
41 pub(crate) fn selected_lookup_list_offset(&self) -> Result<HeaderOffset, ReadError> {
43 self.select_offset(self.lookup_list_offset(), self.lookup_list2_offset())
44 }
45
46 fn select_offset(
47 &self,
48 original: Nullable<Offset16>,
49 extended: Option<Nullable<Offset32>>,
50 ) -> Result<HeaderOffset, ReadError> {
51 HeaderOffset::select(original, extended, self.version().compatible((1, 2)))
52 }
53
54 pub fn script_list(&self) -> Result<ScriptList<'a>, ReadError> {
56 self.selected_script_list_offset()?
57 .offset()
58 .resolve(self.offset_data())
59 }
60
61 pub fn original_script_list(&self) -> Option<Result<ScriptList<'a>, ReadError>> {
63 self.script_list_offset().resolve(self.offset_data())
64 }
65
66 pub fn feature_list(&self) -> Result<FeatureList<'a>, ReadError> {
68 self.selected_feature_list_offset()?
69 .offset()
70 .resolve(self.offset_data())
71 }
72
73 pub fn original_feature_list(&self) -> Option<Result<FeatureList<'a>, ReadError>> {
75 self.feature_list_offset().resolve(self.offset_data())
76 }
77
78 pub fn lookup_list(&self) -> Result<PositionLookupList<'a>, ReadError> {
80 let data = self.offset_data();
81 match self.selected_lookup_list_offset()? {
82 HeaderOffset::Original(offset) => {
83 offset.resolve(data).map(SelectedLookupList::Original)
84 }
85 HeaderOffset::Extended(offset) => {
86 offset.resolve(data).map(SelectedLookupList::Extended)
87 }
88 }
89 }
90
91 pub fn original_lookup_list(
93 &self,
94 ) -> Option<Result<OriginalPositionLookupList<'a>, ReadError>> {
95 self.lookup_list_offset().resolve(self.offset_data())
96 }
97}
98
99pub type PositionLookupList<'a> = super::layout::SelectedLookupList<'a, PositionLookup<'a>>;
101
102pub type OriginalPositionLookupList<'a> = super::layout::LookupList<'a, PositionLookup<'a>>;
104
105pub type PositionLookupList2<'a> = super::layout::LookupList2<'a, PositionLookup<'a>>;
107
108pub type PositionSequenceContext<'a> = super::layout::SequenceContext<'a>;
110
111pub type PositionChainContext<'a> = super::layout::ChainedSequenceContext<'a>;
113
114impl<'a> AnchorTable<'a> {
115 pub fn x_device(&self) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
118 match self {
119 AnchorTable::Format3(inner) => inner.x_device(),
120 _ => None,
121 }
122 }
123
124 pub fn y_device(&self) -> Option<Result<DeviceOrVariationIndex<'a>, ReadError>> {
127 match self {
128 AnchorTable::Format3(inner) => inner.y_device(),
129 _ => None,
130 }
131 }
132}
133
134impl<'a, T: FontRead<'a, Args = ()>> ExtensionLookup<'a, T> for ExtensionPosFormat1<'a, T> {
135 fn extension(&self) -> Result<T, ReadError> {
136 self.extension()
137 }
138}
139
140type PosSubtables<'a, T> = Subtables<'a, T, ExtensionPosFormat1<'a, T>>;
141
142pub enum PositionSubtables<'a> {
148 Single(PosSubtables<'a, SinglePos<'a>>),
149 Pair(PosSubtables<'a, PairPos<'a>>),
150 Cursive(PosSubtables<'a, CursivePos<'a>>),
151 MarkToBase(PosSubtables<'a, MarkBasePos<'a>>),
152 MarkToLig(PosSubtables<'a, MarkLigPos<'a>>),
153 MarkToMark(PosSubtables<'a, MarkMarkPos<'a>>),
154 Contextual(PosSubtables<'a, PositionSequenceContext<'a>>),
155 ChainContextual(PosSubtables<'a, PositionChainContext<'a>>),
156 EmptyExtension,
158}
159
160impl<'a> PositionLookup<'a> {
161 pub fn lookup_flag(&self) -> LookupFlag {
162 self.of_unit_type().lookup_flag()
163 }
164
165 pub fn lookup_type(&self) -> u16 {
167 self.of_unit_type().lookup_type()
168 }
169
170 pub fn mark_filtering_set(&self) -> Option<u16> {
171 self.of_unit_type().mark_filtering_set()
172 }
173
174 pub fn subtables(&self) -> Result<PositionSubtables<'a>, ReadError> {
179 let raw_lookup = self.of_unit_type();
180 let offsets = raw_lookup.subtable_offsets();
181 let data = raw_lookup.offset_data();
182 match raw_lookup.lookup_type() {
183 1 => Ok(PositionSubtables::Single(Subtables::new(offsets, data))),
184 2 => Ok(PositionSubtables::Pair(Subtables::new(offsets, data))),
185 3 => Ok(PositionSubtables::Cursive(Subtables::new(offsets, data))),
186 4 => Ok(PositionSubtables::MarkToBase(Subtables::new(offsets, data))),
187 5 => Ok(PositionSubtables::MarkToLig(Subtables::new(offsets, data))),
188 6 => Ok(PositionSubtables::MarkToMark(Subtables::new(offsets, data))),
189 7 => Ok(PositionSubtables::Contextual(Subtables::new(offsets, data))),
190 8 => Ok(PositionSubtables::ChainContextual(Subtables::new(
191 offsets, data,
192 ))),
193 9 => {
194 let Some(lookup_type) = offsets.iter().find_map(|off| {
198 off.get()
199 .resolve::<ExtensionPosFormat1<()>>(data)
200 .ok()
201 .map(|ext| ext.extension_lookup_type())
202 }) else {
203 return Ok(PositionSubtables::EmptyExtension);
204 };
205
206 match lookup_type {
207 1 => Ok(PositionSubtables::Single(Subtables::new_ext(offsets, data))),
208 2 => Ok(PositionSubtables::Pair(Subtables::new_ext(offsets, data))),
209 3 => Ok(PositionSubtables::Cursive(Subtables::new_ext(
210 offsets, data,
211 ))),
212 4 => Ok(PositionSubtables::MarkToBase(Subtables::new_ext(
213 offsets, data,
214 ))),
215 5 => Ok(PositionSubtables::MarkToLig(Subtables::new_ext(
216 offsets, data,
217 ))),
218 6 => Ok(PositionSubtables::MarkToMark(Subtables::new_ext(
219 offsets, data,
220 ))),
221 7 => Ok(PositionSubtables::Contextual(Subtables::new_ext(
222 offsets, data,
223 ))),
224 8 => Ok(PositionSubtables::ChainContextual(Subtables::new_ext(
225 offsets, data,
226 ))),
227 other => Err(ReadError::InvalidFormat(other as _)),
228 }
229 }
230 other => Err(ReadError::InvalidFormat(other as _)),
231 }
232 }
233}
234
235impl<'a> PairSet<'a> {
237 pub fn find_pair_value_record(&self, second_glyph: GlyphId16) -> Option<PairValueRecord<'a>> {
242 let records = self.pair_value_records();
243 let (mut lo, mut hi) = (0usize, records.len());
244 while lo < hi {
245 #[allow(clippy::manual_midpoint)]
248 let mid = (lo + hi) / 2;
249 let record = records.get(mid).ok()?;
250 match record.second_glyph().cmp(&second_glyph) {
251 core::cmp::Ordering::Less => lo = mid + 1,
252 core::cmp::Ordering::Greater => hi = mid,
253 core::cmp::Ordering::Equal => return Some(record),
254 }
255 }
256 None
257 }
258}
259
260#[derive(Copy, Clone)]
268pub struct ClassValueRecords<'a> {
269 data: FontData<'a>,
270 start: u32,
271 record_size: u32,
272 format1_len: u32,
273 class1_count: u16,
274 class2_count: u16,
275 format1: ValueFormat,
276 format2: ValueFormat,
277}
278
279impl<'a> ClassValueRecords<'a> {
280 #[inline]
282 pub fn class1_count(&self) -> u16 {
283 self.class1_count
284 }
285
286 #[inline]
288 pub fn class2_count(&self) -> u16 {
289 self.class2_count
290 }
291
292 #[inline]
296 pub fn get(&self, class1: u16, class2: u16) -> Option<[ValueRecord<'a>; 2]> {
297 if class1 >= self.class1_count || class2 >= self.class2_count {
298 return None;
299 }
300 let record_offset = self.start as usize
304 + class1 as usize * self.record_size as usize * self.class2_count as usize
305 + class2 as usize * self.record_size as usize;
306 Some([
307 ValueRecord::new(self.data, record_offset, self.format1),
308 ValueRecord::new(
309 self.data,
310 record_offset + self.format1_len as usize,
311 self.format2,
312 ),
313 ])
314 }
315}
316
317impl<'a> PairPosFormat2<'a> {
318 pub fn class_value_records(&self) -> ClassValueRecords<'a> {
321 let format1 = self.value_format1();
322 let format2 = self.value_format2();
323 let format1_len = format1.record_byte_len();
324 ClassValueRecords {
325 data: self.offset_data(),
326 start: self.class1_records_byte_range().start as u32,
327 record_size: (format1_len + format2.record_byte_len()) as u32,
328 format1_len: format1_len as u32,
329 class1_count: self.class1_count(),
330 class2_count: self.class2_count(),
331 format1,
332 format2,
333 }
334 }
335
336 #[inline]
343 pub fn value_record_refs(&self, class1: u16, class2: u16) -> Option<[ValueRecord<'a>; 2]> {
344 self.class_value_records().get(class1, class2)
345 }
346}
347
348impl<'a> PairPosFormat4<'a> {
349 pub fn class_value_records(&self) -> ClassValueRecords<'a> {
352 let format1 = self.value_format1();
353 let format2 = self.value_format2();
354 let format1_len = format1.record_byte_len();
355 ClassValueRecords {
356 data: self.offset_data(),
357 start: self.class1_records_byte_range().start as u32,
358 record_size: (format1_len + format2.record_byte_len()) as u32,
359 format1_len: format1_len as u32,
360 class1_count: self.class1_count(),
361 class2_count: self.class2_count(),
362 format1,
363 format2,
364 }
365 }
366
367 #[inline]
374 pub fn value_record_refs(&self, class1: u16, class2: u16) -> Option<[ValueRecord<'a>; 2]> {
375 self.class_value_records().get(class1, class2)
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382
383 fn assert_same_record(a: ValueRecord, b: ValueRecord) {
386 assert_eq!(a.format(), b.format());
387 assert_eq!(a.bytes(), b.bytes());
388 assert_eq!(a, b);
389 }
390
391 #[test]
395 fn pair_pos2_hoisted_matches_nested() {
396 let data = FontData::new(font_test_data::gpos::PAIRPOSFORMAT2);
397 let table = PairPosFormat2::read(data).unwrap();
398 let class1_count = table.class1_count();
399 let class2_count = table.class2_count();
400 assert!(class1_count > 0 && class2_count > 0);
401
402 let hoisted = table.class_value_records();
403 let rows = table.class1_records();
404 for class1 in 0..class1_count {
405 let row = rows.get(class1 as usize).unwrap();
406 let cells = row.class2_records();
407 for class2 in 0..class2_count {
408 let cell = cells.get(class2 as usize).unwrap();
409 let [a, b] = hoisted.get(class1, class2).unwrap();
410 assert_same_record(a, cell.value_record1);
411 assert_same_record(b, cell.value_record2);
412 }
413 }
414 assert!(hoisted.get(class1_count, 0).is_none());
415 assert!(hoisted.get(0, class2_count).is_none());
416 }
417
418 #[test]
420 fn pair_set_find_matches_scan() {
421 let data = FontData::new(font_test_data::gpos::PAIRPOSFORMAT1);
422 let table = PairPosFormat1::read(data).unwrap();
423 let mut seen = 0;
424 for set_offset in table.pair_set_offsets() {
425 let pair_set = set_offset
426 .get()
427 .resolve_with_args::<PairSet>(data, (table.value_format1(), table.value_format2()))
428 .unwrap();
429 let records = pair_set.pair_value_records();
430 assert!(!records.is_empty());
431 for i in 0..records.len() {
432 let want = records.get(i).unwrap();
433 let found = pair_set
434 .find_pair_value_record(want.second_glyph())
435 .unwrap();
436 assert_eq!(found.second_glyph(), want.second_glyph());
437 assert_same_record(found.value_record1, want.value_record1);
438 assert_same_record(found.value_record2, want.value_record2);
439 seen += 1;
440 }
441 assert!(pair_set
442 .find_pair_value_record(GlyphId16::new(0xFFFF))
443 .is_none());
444 }
445 assert!(seen > 0);
446 }
447
448 #[test]
451 fn value_record_resolves_devices_against_its_table() {
452 let data = FontData::new(font_test_data::gpos::VALUEFORMATTABLE);
453 let table = SinglePosFormat1::read(data).unwrap();
454 let record = table.value_record();
455 assert!(record
456 .format()
457 .intersects(ValueFormat::ANY_DEVICE_OR_VARIDX));
458 assert!(record.offset() > 0);
461 assert!(record.x_placement_device().unwrap().is_ok());
462 assert!(record.y_advance_device().unwrap().is_ok());
463 }
464
465 #[test]
466 fn single_pos2_records_are_evenly_spaced() {
467 let data = FontData::new(font_test_data::gpos::SINGLEPOSFORMAT2);
468 let table = SinglePosFormat2::read(data).unwrap();
469 let records = table.value_records();
470 let stride = table.value_format().record_byte_len();
471 assert!(records.len() > 1);
472 let first = records.get(0).unwrap();
473 for i in 0..records.len() {
474 let rec = records.get(i).unwrap();
475 assert_eq!(rec.offset(), first.offset() + i * stride);
476 assert_eq!(rec.format(), table.value_format());
477 }
478 assert!(records.get(records.len()).is_err());
479 }
480
481 #[test]
482 fn default_for_generics() {
483 let ExtensionSubtable::Single(inner) = ExtensionSubtable::default() else {
484 panic!("this is quite bad");
485 };
486
487 assert_eq!(inner.extension_lookup_type(), 0);
490
491 let SinglePos::Format1(_hmm) = inner.extension().unwrap_or_default() else {
492 panic!("unexpected");
493 };
494 }
495}