Skip to main content

write_fonts/tables/
gpos.rs

1//! the [GPOS] table
2//!
3//! [GPOS]: https://docs.microsoft.com/en-us/typography/opentype/spec/gpos
4
5include!("../../generated/generated_gpos.rs");
6
7use std::collections::HashSet;
8
9//use super::layout::value_record::ValueRecord;
10use 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
26/// A GPOS lookup list table.
27pub 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
69// these can't have auto impls because the traits don't support generics
70impl 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
274            // don't contain any metrics directly
275            PositionLookup::Contextual(_)
276            | PositionLookup::ChainContextual(_)
277            | PositionLookup::Extension(_) => (),
278        }
279    }
280}
281
282impl<T: RemapVarStore<VariationIndex>> RemapVarStore<VariationIndex> for Lookup<T> {
283    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
284        for subtable in &mut self.subtables {
285            subtable.remap_variation_indices(key_map)
286        }
287    }
288}
289
290impl RemapVarStore<VariationIndex> for SinglePos {
291    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
292        match self {
293            SinglePos::Format1(table) => table.remap_variation_indices(key_map),
294            SinglePos::Format2(table) => table.remap_variation_indices(key_map),
295        }
296    }
297}
298
299impl RemapVarStore<VariationIndex> for SinglePosFormat1 {
300    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
301        self.value_record.remap_variation_indices(key_map);
302    }
303}
304
305impl RemapVarStore<VariationIndex> for SinglePosFormat2 {
306    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
307        for rec in &mut self.value_records {
308            rec.remap_variation_indices(key_map);
309        }
310    }
311}
312
313impl RemapVarStore<VariationIndex> for PairPosFormat1 {
314    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
315        for pairset in &mut self.pair_sets {
316            for pairrec in &mut pairset.pair_value_records {
317                pairrec.value_record1.remap_variation_indices(key_map);
318                pairrec.value_record2.remap_variation_indices(key_map);
319            }
320        }
321    }
322}
323
324impl RemapVarStore<VariationIndex> for PairPosFormat2 {
325    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
326        for class1rec in &mut self.class1_records {
327            for class2rec in &mut class1rec.class2_records {
328                class2rec.value_record1.remap_variation_indices(key_map);
329                class2rec.value_record2.remap_variation_indices(key_map);
330            }
331        }
332    }
333}
334
335impl RemapVarStore<VariationIndex> for PairPos {
336    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
337        match self {
338            PairPos::Format1(table) => table.remap_variation_indices(key_map),
339            PairPos::Format2(table) => table.remap_variation_indices(key_map),
340        }
341    }
342}
343
344impl RemapVarStore<VariationIndex> for MarkBasePosFormat1 {
345    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
346        self.mark_array.as_mut().remap_variation_indices(key_map);
347        for rec in &mut self.base_array.as_mut().base_records {
348            for anchor in &mut rec.base_anchors {
349                if let Some(anchor) = anchor.as_mut() {
350                    anchor.remap_variation_indices(key_map);
351                }
352            }
353        }
354    }
355}
356
357impl RemapVarStore<VariationIndex> for MarkMarkPosFormat1 {
358    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
359        self.mark1_array.as_mut().remap_variation_indices(key_map);
360        for rec in &mut self.mark2_array.as_mut().mark2_records {
361            for anchor in &mut rec.mark2_anchors {
362                if let Some(anchor) = anchor.as_mut() {
363                    anchor.remap_variation_indices(key_map);
364                }
365            }
366        }
367    }
368}
369
370impl RemapVarStore<VariationIndex> for MarkLigPosFormat1 {
371    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
372        self.mark_array.as_mut().remap_variation_indices(key_map);
373        for lig in &mut self.ligature_array.as_mut().ligature_attaches {
374            for rec in &mut lig.component_records {
375                for anchor in &mut rec.ligature_anchors {
376                    if let Some(anchor) = anchor.as_mut() {
377                        anchor.remap_variation_indices(key_map);
378                    }
379                }
380            }
381        }
382    }
383}
384
385impl RemapVarStore<VariationIndex> for CursivePosFormat1 {
386    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
387        for rec in &mut self.entry_exit_record {
388            for anchor in [rec.entry_anchor.as_mut(), rec.exit_anchor.as_mut()]
389                .into_iter()
390                .flatten()
391            {
392                anchor.remap_variation_indices(key_map);
393            }
394        }
395    }
396}
397
398impl RemapVarStore<VariationIndex> for MarkArray {
399    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
400        for rec in &mut self.mark_records {
401            rec.mark_anchor.remap_variation_indices(key_map);
402        }
403    }
404}
405
406#[cfg(test)]
407mod tests {
408
409    use read_fonts::tables::{gpos as read_gpos, layout::LookupFlag};
410
411    use crate::tables::layout::VariationIndex;
412
413    use super::*;
414
415    // adapted from/motivated by https://github.com/fonttools/fonttools/issues/471
416    #[test]
417    fn gpos_1_zero() {
418        let cov_one = CoverageTable::format_1(vec![GlyphId16::new(2)]);
419        let cov_two = CoverageTable::format_1(vec![GlyphId16::new(4)]);
420        let sub1 = SinglePos::format_1(cov_one, ValueRecord::default());
421        let sub2 = SinglePos::format_1(cov_two, ValueRecord::default().with_x_advance(500));
422        let lookup = Lookup::new(LookupFlag::default(), vec![sub1, sub2]);
423        let bytes = crate::dump_table(&lookup).unwrap();
424
425        let parsed = read_gpos::PositionLookup::read(FontData::new(&bytes)).unwrap();
426        let read_gpos::PositionLookup::Single(table) = parsed else {
427            panic!("something has gone seriously wrong");
428        };
429
430        assert_eq!(table.lookup_flag(), LookupFlag::empty());
431        assert_eq!(table.sub_table_count(), 2);
432        let read_gpos::SinglePos::Format1(sub1) = table.subtables().get(0).unwrap() else {
433            panic!("wrong table type");
434        };
435        let read_gpos::SinglePos::Format1(sub2) = table.subtables().get(1).unwrap() else {
436            panic!("wrong table type");
437        };
438
439        assert_eq!(sub1.value_format(), ValueFormat::empty());
440        assert_eq!(sub1.value_record(), read_gpos::ValueRecord::default());
441
442        assert_eq!(sub2.value_format(), ValueFormat::X_ADVANCE);
443        assert_eq!(
444            sub2.value_record(),
445            read_gpos::ValueRecord {
446                x_advance: Some(500.into()),
447                ..Default::default()
448            }
449        );
450    }
451
452    // shared between a pair of tests below
453    fn make_rec(i: u16) -> ValueRecord {
454        // '0' here is shorthand for 'no device table'
455        if i == 0 {
456            return ValueRecord::new().with_explicit_value_format(ValueFormat::X_ADVANCE_DEVICE);
457        }
458        ValueRecord::new().with_x_advance_device(VariationIndex::new(0xff, i))
459    }
460
461    #[test]
462    fn compile_devices_pairpos2() {
463        let class1 = ClassDef::from_iter([(GlyphId16::new(5), 0), (GlyphId16::new(6), 1)]);
464        // class 0 is 'all the rest', here, always implicitly present
465        let class2 = ClassDef::from_iter([(GlyphId16::new(8), 1)]);
466
467        // two c1recs, each with two c2recs
468        let class1recs = vec![
469            Class1Record::new(vec![
470                Class2Record::new(make_rec(0), make_rec(0)),
471                Class2Record::new(make_rec(1), make_rec(2)),
472            ]),
473            Class1Record::new(vec![
474                Class2Record::new(make_rec(0), make_rec(0)),
475                Class2Record::new(make_rec(2), make_rec(3)),
476            ]),
477        ];
478        let coverage = class1.iter().map(|(gid, _)| gid).collect();
479        let a_table = PairPos::format_2(coverage, class1, class2, class1recs);
480
481        let bytes = crate::dump_table(&a_table).unwrap();
482        let read_back = PairPosFormat2::read(bytes.as_slice().into()).unwrap();
483
484        assert!(read_back.class1_records[0].class2_records[0]
485            .value_record1
486            .x_advance_device
487            .is_none());
488        assert!(read_back.class1_records[1].class2_records[1]
489            .value_record1
490            .x_advance_device
491            .is_some());
492
493        let DeviceOrVariationIndex::VariationIndex(dev2) = read_back.class1_records[0]
494            .class2_records[1]
495            .value_record2
496            .x_advance_device
497            .as_ref()
498            .unwrap()
499        else {
500            panic!("not a variation index")
501        };
502        assert_eq!(dev2.delta_set_inner_index, 2);
503    }
504
505    #[should_panic(expected = "all value records should report the same format")]
506    #[test]
507    fn validate_bad_pairpos2() {
508        let class1 = ClassDef::from_iter([(GlyphId16::new(5), 0), (GlyphId16::new(6), 1)]);
509        // class 0 is 'all the rest', here, always implicitly present
510        let class2 = ClassDef::from_iter([(GlyphId16::new(8), 1)]);
511        let coverage = class1.iter().map(|(gid, _)| gid).collect();
512
513        // two c1recs, each with two c2recs
514        let class1recs = vec![
515            Class1Record::new(vec![
516                Class2Record::new(make_rec(0), make_rec(0)),
517                Class2Record::new(make_rec(1), make_rec(2)),
518            ]),
519            Class1Record::new(vec![
520                Class2Record::new(make_rec(0), make_rec(0)),
521                // this is now the wrong type
522                Class2Record::new(make_rec(2), make_rec(3).with_x_advance(0x514)),
523            ]),
524        ];
525        let ppf2 = PairPos::format_2(coverage, class1, class2, class1recs);
526        crate::dump_table(&ppf2).unwrap();
527    }
528
529    #[test]
530    fn validate_pairpos1() {
531        let coverage: CoverageTable = [1, 2].into_iter().map(GlyphId16::new).collect();
532        let good_table = PairPosFormat1::new(
533            coverage.clone(),
534            vec![
535                PairSet::new(vec![PairValueRecord::new(
536                    GlyphId16::new(5),
537                    ValueRecord::new().with_x_advance(5),
538                    ValueRecord::new(),
539                )]),
540                PairSet::new(vec![PairValueRecord::new(
541                    GlyphId16::new(1),
542                    ValueRecord::new().with_x_advance(42),
543                    ValueRecord::new(),
544                )]),
545            ],
546        );
547
548        let bad_table = PairPosFormat1::new(
549            coverage,
550            vec![
551                PairSet::new(vec![PairValueRecord::new(
552                    GlyphId16::new(5),
553                    ValueRecord::new().with_x_advance(5),
554                    ValueRecord::new(),
555                )]),
556                PairSet::new(vec![PairValueRecord::new(
557                    GlyphId16::new(1),
558                    //this is a different format, which is not okay
559                    ValueRecord::new().with_x_placement(42),
560                    ValueRecord::new(),
561                )]),
562            ],
563        );
564
565        assert!(crate::dump_table(&good_table).is_ok());
566        assert!(matches!(
567            crate::dump_table(&bad_table),
568            Err(crate::error::Error::ValidationFailed(_))
569        ));
570    }
571}