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,
15    },
16    variations::ivs_builder::{RemapVariationIndices, 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<'a> FontRead<'a> for PositionLookup {
71    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
72        read_fonts::tables::gpos::PositionLookup::read(data).map(|x| x.to_owned_table())
73    }
74}
75
76impl<'a> FontRead<'a> for PositionLookupList {
77    fn read(data: FontData<'a>) -> Result<Self, ReadError> {
78        read_fonts::tables::gpos::PositionLookupList::read(data).map(|x| x.to_owned_table())
79    }
80}
81
82impl SinglePosFormat1 {
83    fn compute_value_format(&self) -> ValueFormat {
84        self.value_record.format()
85    }
86}
87
88impl SinglePosFormat2 {
89    fn compute_value_format(&self) -> ValueFormat {
90        self.value_records
91            .first()
92            .map(ValueRecord::format)
93            .unwrap_or(ValueFormat::empty())
94    }
95}
96
97impl PairPosFormat1 {
98    fn compute_value_format1(&self) -> ValueFormat {
99        self.pair_sets
100            .first()
101            .and_then(|pairset| pairset.pair_value_records.first())
102            .map(|rec| rec.value_record1.format())
103            .unwrap_or(ValueFormat::empty())
104    }
105
106    fn compute_value_format2(&self) -> ValueFormat {
107        self.pair_sets
108            .first()
109            .and_then(|pairset| pairset.pair_value_records.first())
110            .map(|rec| rec.value_record2.format())
111            .unwrap_or(ValueFormat::empty())
112    }
113
114    fn check_format_consistency(&self, ctx: &mut ValidationCtx) {
115        let vf1 = self.compute_value_format1();
116        let vf2 = self.compute_value_format2();
117        ctx.with_array_items(self.pair_sets.iter(), |ctx, item| {
118            ctx.in_field("pair_value_records", |ctx| {
119                if item.pair_value_records.iter().any(|pairset| {
120                    pairset.value_record1.format() != vf1 || pairset.value_record2.format() != vf2
121                }) {
122                    ctx.report("all ValueRecords must have same format")
123                }
124            })
125        })
126    }
127}
128
129impl PairPosFormat2 {
130    fn compute_value_format1(&self) -> ValueFormat {
131        self.class1_records
132            .first()
133            .and_then(|rec| rec.class2_records.first())
134            .map(|rec| rec.value_record1.format())
135            .unwrap_or(ValueFormat::empty())
136    }
137
138    fn compute_value_format2(&self) -> ValueFormat {
139        self.class1_records
140            .first()
141            .and_then(|rec| rec.class2_records.first())
142            .map(|rec| rec.value_record2.format())
143            .unwrap_or(ValueFormat::empty())
144    }
145
146    fn compute_class1_count(&self) -> u16 {
147        self.class_def1.class_count()
148    }
149
150    fn compute_class2_count(&self) -> u16 {
151        self.class_def2.class_count()
152    }
153
154    fn check_length_and_format_conformance(&self, ctx: &mut ValidationCtx) {
155        let n_class_1s = self.class_def1.class_count();
156        let n_class_2s = self.class_def2.class_count();
157        let format_1 = self.compute_value_format1();
158        let format_2 = self.compute_value_format2();
159        if self.class1_records.len() != n_class_1s as usize {
160            ctx.report("class1_records length must match number of class1 classes");
161        }
162        ctx.in_field("class1_records", |ctx| {
163            ctx.with_array_items(self.class1_records.iter(), |ctx, c1rec| {
164                if c1rec.class2_records.len() != n_class_2s as usize {
165                    ctx.report("class2_records length must match number of class2 classes ");
166                }
167                if c1rec.class2_records.iter().any(|rec| {
168                    rec.value_record1.format() != format_1 || rec.value_record2.format() != format_2
169                }) {
170                    ctx.report("all value records should report the same format");
171                }
172            })
173        });
174    }
175}
176
177impl MarkBasePosFormat1 {
178    fn compute_mark_class_count(&self) -> u16 {
179        self.mark_array.class_count()
180    }
181}
182
183impl MarkMarkPosFormat1 {
184    fn compute_mark_class_count(&self) -> u16 {
185        self.mark1_array.class_count()
186    }
187}
188
189impl MarkLigPosFormat1 {
190    fn compute_mark_class_count(&self) -> u16 {
191        self.mark_array.class_count()
192    }
193}
194
195impl MarkArray {
196    fn class_count(&self) -> u16 {
197        self.mark_records
198            .iter()
199            .map(|rec| rec.mark_class)
200            .collect::<HashSet<_>>()
201            .len() as u16
202    }
203}
204
205impl RemapVariationIndices for ValueRecord {
206    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
207        for table in [
208            self.x_placement_device.as_mut(),
209            self.y_placement_device.as_mut(),
210            self.x_advance_device.as_mut(),
211            self.y_advance_device.as_mut(),
212        ]
213        .into_iter()
214        .flatten()
215        {
216            table.remap_variation_indices(key_map)
217        }
218    }
219}
220
221impl RemapVariationIndices for DeviceOrVariationIndex {
222    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
223        if let DeviceOrVariationIndex::PendingVariationIndex(table) = self {
224            *self = key_map.get(table.delta_set_id).unwrap().into();
225        }
226    }
227}
228
229impl RemapVariationIndices for AnchorTable {
230    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
231        if let AnchorTable::Format3(table) = self {
232            table
233                .x_device
234                .as_mut()
235                .into_iter()
236                .chain(table.y_device.as_mut())
237                .for_each(|x| x.remap_variation_indices(key_map))
238        }
239    }
240}
241
242impl RemapVariationIndices for Gpos {
243    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
244        self.lookup_list.as_mut().remap_variation_indices(key_map)
245    }
246}
247
248impl RemapVariationIndices for PositionLookupList {
249    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
250        for lookup in &mut self.lookups {
251            lookup.remap_variation_indices(key_map)
252        }
253    }
254}
255
256impl RemapVariationIndices for PositionLookup {
257    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
258        match self {
259            PositionLookup::Single(lookup) => lookup.remap_variation_indices(key_map),
260            PositionLookup::Pair(lookup) => lookup.remap_variation_indices(key_map),
261            PositionLookup::Cursive(lookup) => lookup.remap_variation_indices(key_map),
262            PositionLookup::MarkToBase(lookup) => lookup.remap_variation_indices(key_map),
263            PositionLookup::MarkToLig(lookup) => lookup.remap_variation_indices(key_map),
264            PositionLookup::MarkToMark(lookup) => lookup.remap_variation_indices(key_map),
265
266            // don't contain any metrics directly
267            PositionLookup::Contextual(_)
268            | PositionLookup::ChainContextual(_)
269            | PositionLookup::Extension(_) => (),
270        }
271    }
272}
273
274impl<T: RemapVariationIndices> RemapVariationIndices for Lookup<T> {
275    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
276        for subtable in &mut self.subtables {
277            subtable.remap_variation_indices(key_map)
278        }
279    }
280}
281
282impl RemapVariationIndices for SinglePos {
283    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
284        match self {
285            SinglePos::Format1(table) => table.remap_variation_indices(key_map),
286            SinglePos::Format2(table) => table.remap_variation_indices(key_map),
287        }
288    }
289}
290
291impl RemapVariationIndices for SinglePosFormat1 {
292    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
293        self.value_record.remap_variation_indices(key_map);
294    }
295}
296
297impl RemapVariationIndices for SinglePosFormat2 {
298    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
299        for rec in &mut self.value_records {
300            rec.remap_variation_indices(key_map);
301        }
302    }
303}
304
305impl RemapVariationIndices for PairPosFormat1 {
306    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
307        for pairset in &mut self.pair_sets {
308            for pairrec in &mut pairset.pair_value_records {
309                pairrec.value_record1.remap_variation_indices(key_map);
310                pairrec.value_record2.remap_variation_indices(key_map);
311            }
312        }
313    }
314}
315
316impl RemapVariationIndices for PairPosFormat2 {
317    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
318        for class1rec in &mut self.class1_records {
319            for class2rec in &mut class1rec.class2_records {
320                class2rec.value_record1.remap_variation_indices(key_map);
321                class2rec.value_record2.remap_variation_indices(key_map);
322            }
323        }
324    }
325}
326
327impl RemapVariationIndices for PairPos {
328    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
329        match self {
330            PairPos::Format1(table) => table.remap_variation_indices(key_map),
331            PairPos::Format2(table) => table.remap_variation_indices(key_map),
332        }
333    }
334}
335
336impl RemapVariationIndices for MarkBasePosFormat1 {
337    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
338        self.mark_array.as_mut().remap_variation_indices(key_map);
339        for rec in &mut self.base_array.as_mut().base_records {
340            for anchor in &mut rec.base_anchors {
341                if let Some(anchor) = anchor.as_mut() {
342                    anchor.remap_variation_indices(key_map);
343                }
344            }
345        }
346    }
347}
348
349impl RemapVariationIndices for MarkMarkPosFormat1 {
350    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
351        self.mark1_array.as_mut().remap_variation_indices(key_map);
352        for rec in &mut self.mark2_array.as_mut().mark2_records {
353            for anchor in &mut rec.mark2_anchors {
354                if let Some(anchor) = anchor.as_mut() {
355                    anchor.remap_variation_indices(key_map);
356                }
357            }
358        }
359    }
360}
361
362impl RemapVariationIndices for MarkLigPosFormat1 {
363    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
364        self.mark_array.as_mut().remap_variation_indices(key_map);
365        for lig in &mut self.ligature_array.as_mut().ligature_attaches {
366            for rec in &mut lig.component_records {
367                for anchor in &mut rec.ligature_anchors {
368                    if let Some(anchor) = anchor.as_mut() {
369                        anchor.remap_variation_indices(key_map);
370                    }
371                }
372            }
373        }
374    }
375}
376
377impl RemapVariationIndices for CursivePosFormat1 {
378    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
379        for rec in &mut self.entry_exit_record {
380            for anchor in [rec.entry_anchor.as_mut(), rec.exit_anchor.as_mut()]
381                .into_iter()
382                .flatten()
383            {
384                anchor.remap_variation_indices(key_map);
385            }
386        }
387    }
388}
389
390impl RemapVariationIndices for MarkArray {
391    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
392        for rec in &mut self.mark_records {
393            rec.mark_anchor.remap_variation_indices(key_map);
394        }
395    }
396}
397
398#[cfg(test)]
399mod tests {
400
401    use read_fonts::tables::{gpos as read_gpos, layout::LookupFlag};
402
403    use crate::tables::layout::VariationIndex;
404
405    use super::*;
406
407    // adapted from/motivated by https://github.com/fonttools/fonttools/issues/471
408    #[test]
409    fn gpos_1_zero() {
410        let cov_one = CoverageTable::format_1(vec![GlyphId16::new(2)]);
411        let cov_two = CoverageTable::format_1(vec![GlyphId16::new(4)]);
412        let sub1 = SinglePos::format_1(cov_one, ValueRecord::default());
413        let sub2 = SinglePos::format_1(cov_two, ValueRecord::default().with_x_advance(500));
414        let lookup = Lookup::new(LookupFlag::default(), vec![sub1, sub2]);
415        let bytes = crate::dump_table(&lookup).unwrap();
416
417        let parsed = read_gpos::PositionLookup::read(FontData::new(&bytes)).unwrap();
418        let read_gpos::PositionLookup::Single(table) = parsed else {
419            panic!("something has gone seriously wrong");
420        };
421
422        assert_eq!(table.lookup_flag(), LookupFlag::empty());
423        assert_eq!(table.sub_table_count(), 2);
424        let read_gpos::SinglePos::Format1(sub1) = table.subtables().get(0).unwrap() else {
425            panic!("wrong table type");
426        };
427        let read_gpos::SinglePos::Format1(sub2) = table.subtables().get(1).unwrap() else {
428            panic!("wrong table type");
429        };
430
431        assert_eq!(sub1.value_format(), ValueFormat::empty());
432        assert_eq!(sub1.value_record(), read_gpos::ValueRecord::default());
433
434        assert_eq!(sub2.value_format(), ValueFormat::X_ADVANCE);
435        assert_eq!(
436            sub2.value_record(),
437            read_gpos::ValueRecord {
438                x_advance: Some(500.into()),
439                ..Default::default()
440            }
441        );
442    }
443
444    // shared between a pair of tests below
445    fn make_rec(i: u16) -> ValueRecord {
446        // '0' here is shorthand for 'no device table'
447        if i == 0 {
448            return ValueRecord::new().with_explicit_value_format(ValueFormat::X_ADVANCE_DEVICE);
449        }
450        ValueRecord::new().with_x_advance_device(VariationIndex::new(0xff, i))
451    }
452
453    #[test]
454    fn compile_devices_pairpos2() {
455        let class1 = ClassDef::from_iter([(GlyphId16::new(5), 0), (GlyphId16::new(6), 1)]);
456        // class 0 is 'all the rest', here, always implicitly present
457        let class2 = ClassDef::from_iter([(GlyphId16::new(8), 1)]);
458
459        // two c1recs, each with two c2recs
460        let class1recs = vec![
461            Class1Record::new(vec![
462                Class2Record::new(make_rec(0), make_rec(0)),
463                Class2Record::new(make_rec(1), make_rec(2)),
464            ]),
465            Class1Record::new(vec![
466                Class2Record::new(make_rec(0), make_rec(0)),
467                Class2Record::new(make_rec(2), make_rec(3)),
468            ]),
469        ];
470        let coverage = class1.iter().map(|(gid, _)| gid).collect();
471        let a_table = PairPos::format_2(coverage, class1, class2, class1recs);
472
473        let bytes = crate::dump_table(&a_table).unwrap();
474        let read_back = PairPosFormat2::read(bytes.as_slice().into()).unwrap();
475
476        assert!(read_back.class1_records[0].class2_records[0]
477            .value_record1
478            .x_advance_device
479            .is_none());
480        assert!(read_back.class1_records[1].class2_records[1]
481            .value_record1
482            .x_advance_device
483            .is_some());
484
485        let DeviceOrVariationIndex::VariationIndex(dev2) = read_back.class1_records[0]
486            .class2_records[1]
487            .value_record2
488            .x_advance_device
489            .as_ref()
490            .unwrap()
491        else {
492            panic!("not a variation index")
493        };
494        assert_eq!(dev2.delta_set_inner_index, 2);
495    }
496
497    #[should_panic(expected = "all value records should report the same format")]
498    #[test]
499    fn validate_bad_pairpos2() {
500        let class1 = ClassDef::from_iter([(GlyphId16::new(5), 0), (GlyphId16::new(6), 1)]);
501        // class 0 is 'all the rest', here, always implicitly present
502        let class2 = ClassDef::from_iter([(GlyphId16::new(8), 1)]);
503        let coverage = class1.iter().map(|(gid, _)| gid).collect();
504
505        // two c1recs, each with two c2recs
506        let class1recs = vec![
507            Class1Record::new(vec![
508                Class2Record::new(make_rec(0), make_rec(0)),
509                Class2Record::new(make_rec(1), make_rec(2)),
510            ]),
511            Class1Record::new(vec![
512                Class2Record::new(make_rec(0), make_rec(0)),
513                // this is now the wrong type
514                Class2Record::new(make_rec(2), make_rec(3).with_x_advance(0x514)),
515            ]),
516        ];
517        let ppf2 = PairPos::format_2(coverage, class1, class2, class1recs);
518        crate::dump_table(&ppf2).unwrap();
519    }
520
521    #[test]
522    fn validate_pairpos1() {
523        let coverage: CoverageTable = [1, 2].into_iter().map(GlyphId16::new).collect();
524        let good_table = PairPosFormat1::new(
525            coverage.clone(),
526            vec![
527                PairSet::new(vec![PairValueRecord::new(
528                    GlyphId16::new(5),
529                    ValueRecord::new().with_x_advance(5),
530                    ValueRecord::new(),
531                )]),
532                PairSet::new(vec![PairValueRecord::new(
533                    GlyphId16::new(1),
534                    ValueRecord::new().with_x_advance(42),
535                    ValueRecord::new(),
536                )]),
537            ],
538        );
539
540        let bad_table = PairPosFormat1::new(
541            coverage,
542            vec![
543                PairSet::new(vec![PairValueRecord::new(
544                    GlyphId16::new(5),
545                    ValueRecord::new().with_x_advance(5),
546                    ValueRecord::new(),
547                )]),
548                PairSet::new(vec![PairValueRecord::new(
549                    GlyphId16::new(1),
550                    //this is a different format, which is not okay
551                    ValueRecord::new().with_x_placement(42),
552                    ValueRecord::new(),
553                )]),
554            ],
555        );
556
557        assert!(crate::dump_table(&good_table).is_ok());
558        assert!(matches!(
559            crate::dump_table(&bad_table),
560            Err(crate::error::Error::ValidationFailed(_))
561        ));
562    }
563}