1include!("../../generated/generated_gpos.rs");
6
7use std::collections::HashSet;
8
9use super::{
11 layout::{
12 ChainedSequenceContext, ClassDef, CoverageTable, DeviceOrVariationIndex, FeatureList,
13 FeatureVariations, Lookup, LookupList, LookupSubtable, LookupType, ScriptList,
14 SequenceContext, VariationIndex,
15 },
16 variations::{common_builder::RemapVarStore, ivs_builder::VariationIndexRemapping},
17};
18
19#[cfg(test)]
20mod spec_tests;
21
22pub mod builders;
23mod value_record;
24pub use value_record::ValueRecord;
25
26pub type PositionLookupList = LookupList<PositionLookup>;
28
29super::layout::table_newtype!(
30 PositionSequenceContext,
31 SequenceContext,
32 read_fonts::tables::layout::SequenceContext<'a>
33);
34
35super::layout::table_newtype!(
36 PositionChainContext,
37 ChainedSequenceContext,
38 read_fonts::tables::layout::ChainedSequenceContext<'a>
39);
40
41impl Gpos {
42 fn compute_version(&self) -> MajorMinor {
43 if self.feature_variations.is_none() {
44 MajorMinor::VERSION_1_0
45 } else {
46 MajorMinor::VERSION_1_1
47 }
48 }
49}
50
51super::layout::lookup_type!(gpos, SinglePos, 1);
52super::layout::lookup_type!(gpos, PairPos, 2);
53super::layout::lookup_type!(gpos, CursivePosFormat1, 3);
54super::layout::lookup_type!(gpos, MarkBasePosFormat1, 4);
55super::layout::lookup_type!(gpos, MarkLigPosFormat1, 5);
56super::layout::lookup_type!(gpos, MarkMarkPosFormat1, 6);
57super::layout::lookup_type!(gpos, PositionSequenceContext, 7);
58super::layout::lookup_type!(gpos, PositionChainContext, 8);
59super::layout::lookup_type!(gpos, ExtensionSubtable, 9);
60
61impl<T: LookupSubtable + FontWrite> FontWrite for ExtensionPosFormat1<T> {
62 fn write_into(&self, writer: &mut TableWriter) {
63 1u16.write_into(writer);
64 T::TYPE.write_into(writer);
65 self.extension.write_into(writer);
66 }
67}
68
69impl ReadArgs for PositionLookup {
71 type Args = ();
72}
73
74impl<'a> FontRead<'a> for PositionLookup {
75 fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
76 read_fonts::tables::gpos::PositionLookup::read(data).map(|x| x.to_owned_table())
77 }
78}
79
80impl ReadArgs for PositionLookupList {
81 type Args = ();
82}
83
84impl<'a> FontRead<'a> for PositionLookupList {
85 fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
86 read_fonts::tables::gpos::PositionLookupList::read(data).map(|x| x.to_owned_table())
87 }
88}
89
90impl SinglePosFormat1 {
91 fn compute_value_format(&self) -> ValueFormat {
92 self.value_record.format()
93 }
94}
95
96impl SinglePosFormat2 {
97 fn compute_value_format(&self) -> ValueFormat {
98 self.value_records
99 .first()
100 .map(ValueRecord::format)
101 .unwrap_or(ValueFormat::empty())
102 }
103}
104
105impl PairPosFormat1 {
106 fn compute_value_format1(&self) -> ValueFormat {
107 self.pair_sets
108 .first()
109 .and_then(|pairset| pairset.pair_value_records.first())
110 .map(|rec| rec.value_record1.format())
111 .unwrap_or(ValueFormat::empty())
112 }
113
114 fn compute_value_format2(&self) -> ValueFormat {
115 self.pair_sets
116 .first()
117 .and_then(|pairset| pairset.pair_value_records.first())
118 .map(|rec| rec.value_record2.format())
119 .unwrap_or(ValueFormat::empty())
120 }
121
122 fn check_format_consistency(&self, ctx: &mut ValidationCtx) {
123 let vf1 = self.compute_value_format1();
124 let vf2 = self.compute_value_format2();
125 ctx.with_array_items(self.pair_sets.iter(), |ctx, item| {
126 ctx.in_field("pair_value_records", |ctx| {
127 if item.pair_value_records.iter().any(|pairset| {
128 pairset.value_record1.format() != vf1 || pairset.value_record2.format() != vf2
129 }) {
130 ctx.report("all ValueRecords must have same format")
131 }
132 })
133 })
134 }
135}
136
137impl PairPosFormat2 {
138 fn compute_value_format1(&self) -> ValueFormat {
139 self.class1_records
140 .first()
141 .and_then(|rec| rec.class2_records.first())
142 .map(|rec| rec.value_record1.format())
143 .unwrap_or(ValueFormat::empty())
144 }
145
146 fn compute_value_format2(&self) -> ValueFormat {
147 self.class1_records
148 .first()
149 .and_then(|rec| rec.class2_records.first())
150 .map(|rec| rec.value_record2.format())
151 .unwrap_or(ValueFormat::empty())
152 }
153
154 fn compute_class1_count(&self) -> u16 {
155 self.class_def1.class_count()
156 }
157
158 fn compute_class2_count(&self) -> u16 {
159 self.class_def2.class_count()
160 }
161
162 fn check_length_and_format_conformance(&self, ctx: &mut ValidationCtx) {
163 let n_class_1s = self.class_def1.class_count();
164 let n_class_2s = self.class_def2.class_count();
165 let format_1 = self.compute_value_format1();
166 let format_2 = self.compute_value_format2();
167 if self.class1_records.len() != n_class_1s as usize {
168 ctx.report("class1_records length must match number of class1 classes");
169 }
170 ctx.in_field("class1_records", |ctx| {
171 ctx.with_array_items(self.class1_records.iter(), |ctx, c1rec| {
172 if c1rec.class2_records.len() != n_class_2s as usize {
173 ctx.report("class2_records length must match number of class2 classes ");
174 }
175 if c1rec.class2_records.iter().any(|rec| {
176 rec.value_record1.format() != format_1 || rec.value_record2.format() != format_2
177 }) {
178 ctx.report("all value records should report the same format");
179 }
180 })
181 });
182 }
183}
184
185impl MarkBasePosFormat1 {
186 fn compute_mark_class_count(&self) -> u16 {
187 self.mark_array.class_count()
188 }
189}
190
191impl MarkMarkPosFormat1 {
192 fn compute_mark_class_count(&self) -> u16 {
193 self.mark1_array.class_count()
194 }
195}
196
197impl MarkLigPosFormat1 {
198 fn compute_mark_class_count(&self) -> u16 {
199 self.mark_array.class_count()
200 }
201}
202
203impl MarkArray {
204 fn class_count(&self) -> u16 {
205 self.mark_records
206 .iter()
207 .map(|rec| rec.mark_class)
208 .collect::<HashSet<_>>()
209 .len() as u16
210 }
211}
212
213impl RemapVarStore<VariationIndex> for ValueRecord {
214 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
215 for table in [
216 self.x_placement_device.as_mut(),
217 self.y_placement_device.as_mut(),
218 self.x_advance_device.as_mut(),
219 self.y_advance_device.as_mut(),
220 ]
221 .into_iter()
222 .flatten()
223 {
224 table.remap_variation_indices(key_map)
225 }
226 }
227}
228
229impl RemapVarStore<VariationIndex> for DeviceOrVariationIndex {
230 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
231 if let DeviceOrVariationIndex::PendingVariationIndex(table) = self {
232 *self = key_map.get(table.delta_set_id).unwrap().into();
233 }
234 }
235}
236
237impl RemapVarStore<VariationIndex> for AnchorTable {
238 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
239 if let AnchorTable::Format3(table) = self {
240 table
241 .x_device
242 .as_mut()
243 .into_iter()
244 .chain(table.y_device.as_mut())
245 .for_each(|x| x.remap_variation_indices(key_map))
246 }
247 }
248}
249
250impl RemapVarStore<VariationIndex> for Gpos {
251 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
252 self.lookup_list.as_mut().remap_variation_indices(key_map)
253 }
254}
255
256impl RemapVarStore<VariationIndex> for PositionLookupList {
257 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
258 for lookup in &mut self.lookups {
259 lookup.remap_variation_indices(key_map)
260 }
261 }
262}
263
264impl RemapVarStore<VariationIndex> for PositionLookup {
265 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
266 match self {
267 PositionLookup::Single(lookup) => lookup.remap_variation_indices(key_map),
268 PositionLookup::Pair(lookup) => lookup.remap_variation_indices(key_map),
269 PositionLookup::Cursive(lookup) => lookup.remap_variation_indices(key_map),
270 PositionLookup::MarkToBase(lookup) => lookup.remap_variation_indices(key_map),
271 PositionLookup::MarkToLig(lookup) => lookup.remap_variation_indices(key_map),
272 PositionLookup::MarkToMark(lookup) => lookup.remap_variation_indices(key_map),
273 PositionLookup::Extension(lookup) => lookup.remap_variation_indices(key_map),
274
275 PositionLookup::Contextual(_) | PositionLookup::ChainContextual(_) => (),
277 }
278 }
279}
280
281impl RemapVarStore<VariationIndex> for ExtensionSubtable {
282 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
283 match self {
284 ExtensionSubtable::Single(table) => table.remap_variation_indices(key_map),
285 ExtensionSubtable::Pair(table) => table.remap_variation_indices(key_map),
286 ExtensionSubtable::Cursive(table) => table.remap_variation_indices(key_map),
287 ExtensionSubtable::MarkToBase(table) => table.remap_variation_indices(key_map),
288 ExtensionSubtable::MarkToLig(table) => table.remap_variation_indices(key_map),
289 ExtensionSubtable::MarkToMark(table) => table.remap_variation_indices(key_map),
290
291 ExtensionSubtable::Contextual(_) | ExtensionSubtable::ChainContextual(_) => (),
293 }
294 }
295}
296
297impl<T: RemapVarStore<VariationIndex>> RemapVarStore<VariationIndex> for ExtensionPosFormat1<T> {
298 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
299 self.extension.as_mut().remap_variation_indices(key_map)
300 }
301}
302
303impl<T: RemapVarStore<VariationIndex>> RemapVarStore<VariationIndex> for Lookup<T> {
304 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
305 for subtable in &mut self.subtables {
306 subtable.remap_variation_indices(key_map)
307 }
308 }
309}
310
311impl RemapVarStore<VariationIndex> for SinglePos {
312 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
313 match self {
314 SinglePos::Format1(table) => table.remap_variation_indices(key_map),
315 SinglePos::Format2(table) => table.remap_variation_indices(key_map),
316 }
317 }
318}
319
320impl RemapVarStore<VariationIndex> for SinglePosFormat1 {
321 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
322 self.value_record.remap_variation_indices(key_map);
323 }
324}
325
326impl RemapVarStore<VariationIndex> for SinglePosFormat2 {
327 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
328 for rec in &mut self.value_records {
329 rec.remap_variation_indices(key_map);
330 }
331 }
332}
333
334impl RemapVarStore<VariationIndex> for PairPosFormat1 {
335 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
336 for pairset in &mut self.pair_sets {
337 for pairrec in &mut pairset.pair_value_records {
338 pairrec.value_record1.remap_variation_indices(key_map);
339 pairrec.value_record2.remap_variation_indices(key_map);
340 }
341 }
342 }
343}
344
345impl RemapVarStore<VariationIndex> for PairPosFormat2 {
346 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
347 for class1rec in &mut self.class1_records {
348 for class2rec in &mut class1rec.class2_records {
349 class2rec.value_record1.remap_variation_indices(key_map);
350 class2rec.value_record2.remap_variation_indices(key_map);
351 }
352 }
353 }
354}
355
356impl RemapVarStore<VariationIndex> for PairPos {
357 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
358 match self {
359 PairPos::Format1(table) => table.remap_variation_indices(key_map),
360 PairPos::Format2(table) => table.remap_variation_indices(key_map),
361 }
362 }
363}
364
365impl RemapVarStore<VariationIndex> for MarkBasePosFormat1 {
366 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
367 self.mark_array.as_mut().remap_variation_indices(key_map);
368 for rec in &mut self.base_array.as_mut().base_records {
369 for anchor in &mut rec.base_anchors {
370 if let Some(anchor) = anchor.as_mut() {
371 anchor.remap_variation_indices(key_map);
372 }
373 }
374 }
375 }
376}
377
378impl RemapVarStore<VariationIndex> for MarkMarkPosFormat1 {
379 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
380 self.mark1_array.as_mut().remap_variation_indices(key_map);
381 for rec in &mut self.mark2_array.as_mut().mark2_records {
382 for anchor in &mut rec.mark2_anchors {
383 if let Some(anchor) = anchor.as_mut() {
384 anchor.remap_variation_indices(key_map);
385 }
386 }
387 }
388 }
389}
390
391impl RemapVarStore<VariationIndex> for MarkLigPosFormat1 {
392 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
393 self.mark_array.as_mut().remap_variation_indices(key_map);
394 for lig in &mut self.ligature_array.as_mut().ligature_attaches {
395 for rec in &mut lig.component_records {
396 for anchor in &mut rec.ligature_anchors {
397 if let Some(anchor) = anchor.as_mut() {
398 anchor.remap_variation_indices(key_map);
399 }
400 }
401 }
402 }
403 }
404}
405
406impl RemapVarStore<VariationIndex> for CursivePosFormat1 {
407 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
408 for rec in &mut self.entry_exit_record {
409 for anchor in [rec.entry_anchor.as_mut(), rec.exit_anchor.as_mut()]
410 .into_iter()
411 .flatten()
412 {
413 anchor.remap_variation_indices(key_map);
414 }
415 }
416 }
417}
418
419impl RemapVarStore<VariationIndex> for MarkArray {
420 fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
421 for rec in &mut self.mark_records {
422 rec.mark_anchor.remap_variation_indices(key_map);
423 }
424 }
425}
426
427#[cfg(test)]
428mod tests {
429
430 use read_fonts::tables::{gpos as read_gpos, layout::LookupFlag};
431
432 use crate::tables::layout::VariationIndex;
433
434 use super::*;
435
436 #[test]
437 fn remap_variation_indices_through_extension() {
438 use crate::tables::variations::{
439 ivs_builder::VariationStoreBuilder, RegionAxisCoordinates, VariationRegion,
440 };
441 use font_types::F2Dot14;
442
443 let region = VariationRegion::new(vec![RegionAxisCoordinates {
444 start_coord: F2Dot14::from_f32(0.0),
445 peak_coord: F2Dot14::from_f32(1.0),
446 end_coord: F2Dot14::from_f32(1.0),
447 }]);
448 let mut var_store = VariationStoreBuilder::new(1);
449 let temp_id = var_store.add_deltas(vec![(region, -20)]);
450 let (_, key_map) = var_store.build();
451
452 let value_record = ValueRecord::new()
453 .with_x_advance(-20)
454 .with_x_advance_device(DeviceOrVariationIndex::pending_variation_index(temp_id));
455 let pair_set = PairSet::new(vec![PairValueRecord::new(
456 GlyphId16::new(2),
457 value_record,
458 ValueRecord::default(),
459 )]);
460 let pair_pos = PairPos::format_1(
461 CoverageTable::format_1(vec![GlyphId16::new(1)]),
462 vec![pair_set],
463 );
464
465 let mut plain =
467 PositionLookup::Pair(Lookup::new(LookupFlag::default(), vec![pair_pos.clone()]));
468 let mut extension = PositionLookup::Extension(Lookup::new(
469 LookupFlag::default(),
470 vec![ExtensionSubtable::Pair(ExtensionPosFormat1::new(
471 <PairPos as LookupSubtable>::TYPE.to_raw(),
472 pair_pos,
473 ))],
474 ));
475
476 plain.remap_variation_indices(&key_map);
477 extension.remap_variation_indices(&key_map);
478
479 let plain_bytes = crate::dump_table(&plain).unwrap();
481 let ext_bytes = crate::dump_table(&extension).unwrap();
482
483 let parsed = read_gpos::PositionLookup::read(FontData::new(&ext_bytes)).unwrap();
484 let read_gpos::PositionSubtables::Pair(subtables) = parsed.subtables().unwrap() else {
485 panic!("expected a pair lookup");
486 };
487 let read_gpos::PairPos::Format1(subtable) = subtables.get(0).unwrap() else {
488 panic!("expected format 1");
489 };
490 let pair_set = subtable.pair_sets().get(0).unwrap();
491 let record = &pair_set.pair_value_records().get(0).unwrap();
492 let read_fonts::tables::layout::DeviceOrVariationIndex::VariationIndex(var_idx) =
493 record.value_record1.x_advance_device().unwrap().unwrap()
494 else {
495 panic!("expected a VariationIndex");
496 };
497 let expected = key_map.get(temp_id).unwrap();
498 assert_eq!(
499 (
500 var_idx.delta_set_outer_index(),
501 var_idx.delta_set_inner_index()
502 ),
503 (
504 expected.delta_set_outer_index,
505 expected.delta_set_inner_index
506 )
507 );
508 assert_eq!(plain_bytes.len() + 8, ext_bytes.len());
510 }
511
512 #[test]
514 fn gpos_1_zero() {
515 let cov_one = CoverageTable::format_1(vec![GlyphId16::new(2)]);
516 let cov_two = CoverageTable::format_1(vec![GlyphId16::new(4)]);
517 let sub1 = SinglePos::format_1(cov_one, ValueRecord::default());
518 let sub2 = SinglePos::format_1(cov_two, ValueRecord::default().with_x_advance(500));
519 let lookup = Lookup::new(LookupFlag::default(), vec![sub1, sub2]);
520 let bytes = crate::dump_table(&lookup).unwrap();
521
522 let parsed = read_gpos::PositionLookup::read(FontData::new(&bytes)).unwrap();
523 let read_gpos::PositionLookup::Single(table) = parsed else {
524 panic!("something has gone seriously wrong");
525 };
526
527 assert_eq!(table.lookup_flag(), LookupFlag::empty());
528 assert_eq!(table.sub_table_count(), 2);
529 let read_gpos::SinglePos::Format1(sub1) = table.subtables().get(0).unwrap() else {
530 panic!("wrong table type");
531 };
532 let read_gpos::SinglePos::Format1(sub2) = table.subtables().get(1).unwrap() else {
533 panic!("wrong table type");
534 };
535
536 assert_eq!(sub1.value_format(), ValueFormat::empty());
537 assert!(sub1.value_record().is_empty());
538
539 assert_eq!(sub2.value_format(), ValueFormat::X_ADVANCE);
540 assert_eq!(sub2.value_record().x_advance(), Some(500));
541 assert_eq!(sub2.value_record().y_advance(), None);
542 }
543
544 fn make_rec(i: u16) -> ValueRecord {
546 if i == 0 {
548 return ValueRecord::new().with_explicit_value_format(ValueFormat::X_ADVANCE_DEVICE);
549 }
550 ValueRecord::new().with_x_advance_device(VariationIndex::new(0xff, i))
551 }
552
553 #[test]
554 fn compile_devices_pairpos2() {
555 let class1 = ClassDef::from_iter([(GlyphId16::new(5), 0), (GlyphId16::new(6), 1)]);
556 let class2 = ClassDef::from_iter([(GlyphId16::new(8), 1)]);
558
559 let class1recs = vec![
561 Class1Record::new(vec![
562 Class2Record::new(make_rec(0), make_rec(0)),
563 Class2Record::new(make_rec(1), make_rec(2)),
564 ]),
565 Class1Record::new(vec![
566 Class2Record::new(make_rec(0), make_rec(0)),
567 Class2Record::new(make_rec(2), make_rec(3)),
568 ]),
569 ];
570 let coverage = class1.iter().map(|(gid, _)| gid).collect();
571 let a_table = PairPos::format_2(coverage, class1, class2, class1recs);
572
573 let bytes = crate::dump_table(&a_table).unwrap();
574 let read_back = PairPosFormat2::read(bytes.as_slice().into()).unwrap();
575
576 assert!(read_back.class1_records[0].class2_records[0]
577 .value_record1
578 .x_advance_device
579 .is_none());
580 assert!(read_back.class1_records[1].class2_records[1]
581 .value_record1
582 .x_advance_device
583 .is_some());
584
585 let DeviceOrVariationIndex::VariationIndex(dev2) = read_back.class1_records[0]
586 .class2_records[1]
587 .value_record2
588 .x_advance_device
589 .as_ref()
590 .unwrap()
591 else {
592 panic!("not a variation index")
593 };
594 assert_eq!(dev2.delta_set_inner_index, 2);
595 }
596
597 #[should_panic(expected = "all value records should report the same format")]
598 #[test]
599 fn validate_bad_pairpos2() {
600 let class1 = ClassDef::from_iter([(GlyphId16::new(5), 0), (GlyphId16::new(6), 1)]);
601 let class2 = ClassDef::from_iter([(GlyphId16::new(8), 1)]);
603 let coverage = class1.iter().map(|(gid, _)| gid).collect();
604
605 let class1recs = vec![
607 Class1Record::new(vec![
608 Class2Record::new(make_rec(0), make_rec(0)),
609 Class2Record::new(make_rec(1), make_rec(2)),
610 ]),
611 Class1Record::new(vec![
612 Class2Record::new(make_rec(0), make_rec(0)),
613 Class2Record::new(make_rec(2), make_rec(3).with_x_advance(0x514)),
615 ]),
616 ];
617 let ppf2 = PairPos::format_2(coverage, class1, class2, class1recs);
618 crate::dump_table(&ppf2).unwrap();
619 }
620
621 #[test]
622 fn validate_pairpos1() {
623 let coverage: CoverageTable = [1, 2].into_iter().map(GlyphId16::new).collect();
624 let good_table = PairPosFormat1::new(
625 coverage.clone(),
626 vec![
627 PairSet::new(vec![PairValueRecord::new(
628 GlyphId16::new(5),
629 ValueRecord::new().with_x_advance(5),
630 ValueRecord::new(),
631 )]),
632 PairSet::new(vec![PairValueRecord::new(
633 GlyphId16::new(1),
634 ValueRecord::new().with_x_advance(42),
635 ValueRecord::new(),
636 )]),
637 ],
638 );
639
640 let bad_table = PairPosFormat1::new(
641 coverage,
642 vec![
643 PairSet::new(vec![PairValueRecord::new(
644 GlyphId16::new(5),
645 ValueRecord::new().with_x_advance(5),
646 ValueRecord::new(),
647 )]),
648 PairSet::new(vec![PairValueRecord::new(
649 GlyphId16::new(1),
650 ValueRecord::new().with_x_placement(42),
652 ValueRecord::new(),
653 )]),
654 ],
655 );
656
657 assert!(crate::dump_table(&good_table).is_ok());
658 assert!(matches!(
659 crate::dump_table(&bad_table),
660 Err(crate::error::Error::ValidationFailed(_))
661 ));
662 }
663}