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