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
7impl ReadArgs for PositionLookupList2 {
8    type Args = ();
9}
10
11impl<'a> FontRead<'a> for PositionLookupList2 {
12    fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
13        read_fonts::tables::gpos::PositionLookupList2::read(data).map(|x| x.to_owned_table())
14    }
15}
16
17use std::collections::HashSet;
18
19//use super::layout::value_record::ValueRecord;
20use super::{
21    layout::{
22        ChainedSequenceContext, ClassDef, CoverageTable, DeviceOrVariationIndex, FeatureList,
23        FeatureVariations, Lookup, LookupList, LookupSubtable, LookupType, ScriptList,
24        SequenceContext, VariationIndex,
25    },
26    variations::{common_builder::RemapVarStore, ivs_builder::VariationIndexRemapping},
27};
28
29#[cfg(test)]
30mod extended;
31#[cfg(test)]
32mod spec_tests;
33
34pub mod builders;
35mod value_record;
36pub use value_record::ValueRecord;
37
38/// A GPOS lookup list table.
39pub type PositionLookupList = LookupList<PositionLookup>;
40
41/// A lookup list with the original 16-bit offsets.
42pub type OriginalPositionLookupList = PositionLookupList;
43
44/// A lookup list with 32-bit offsets.
45pub type PositionLookupList2 = super::layout::LookupList2<PositionLookup>;
46
47super::layout::table_newtype!(
48    PositionSequenceContext,
49    SequenceContext,
50    read_fonts::tables::layout::SequenceContext<'a>
51);
52
53super::layout::table_newtype!(
54    PositionChainContext,
55    ChainedSequenceContext,
56    read_fonts::tables::layout::ChainedSequenceContext<'a>
57);
58
59impl Gpos {
60    /// Creates a GPOS table using the original 16-bit header offsets.
61    pub fn new(
62        script_list: ScriptList,
63        feature_list: FeatureList,
64        lookup_list: PositionLookupList,
65    ) -> Self {
66        Self {
67            script_list: Some(script_list).into(),
68            feature_list: Some(feature_list).into(),
69            lookup_list: Some(lookup_list).into(),
70            ..Default::default()
71        }
72    }
73
74    fn compute_version(&self) -> MajorMinor {
75        if self.script_list2.is_some()
76            || self.feature_list2.is_some()
77            || self.lookup_list2.is_some()
78        {
79            MajorMinor::new(1, 2)
80        } else if self.feature_variations.is_none() {
81            MajorMinor::VERSION_1_0
82        } else {
83            MajorMinor::VERSION_1_1
84        }
85    }
86}
87
88super::layout::lookup_type!(gpos, SinglePos, 1);
89super::layout::lookup_type!(gpos, PairPos, 2);
90super::layout::lookup_type!(gpos, CursivePosFormat1, 3);
91super::layout::lookup_type!(gpos, MarkBasePosFormat1, 4);
92super::layout::lookup_type!(gpos, MarkLigPosFormat1, 5);
93super::layout::lookup_type!(gpos, MarkMarkPosFormat1, 6);
94super::layout::lookup_type!(gpos, PositionSequenceContext, 7);
95super::layout::lookup_type!(gpos, PositionChainContext, 8);
96super::layout::lookup_type!(gpos, ExtensionSubtable, 9);
97
98impl<T: LookupSubtable + FontWrite> FontWrite for ExtensionPosFormat1<T> {
99    fn write_into(&self, writer: &mut TableWriter) {
100        1u16.write_into(writer);
101        T::TYPE.write_into(writer);
102        self.extension.write_into(writer);
103    }
104}
105
106// these can't have auto impls because the traits don't support generics
107impl ReadArgs for PositionLookup {
108    type Args = ();
109}
110
111impl<'a> FontRead<'a> for PositionLookup {
112    fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
113        read_fonts::tables::gpos::PositionLookup::read(data).map(|x| x.to_owned_table())
114    }
115}
116
117impl ReadArgs for PositionLookupList {
118    type Args = ();
119}
120
121impl<'a> FontRead<'a> for PositionLookupList {
122    fn read_with_args(data: FontData<'a>, _: ()) -> Result<Self, ReadError> {
123        read_fonts::tables::gpos::OriginalPositionLookupList::read(data).map(|x| x.to_owned_table())
124    }
125}
126
127impl SinglePosFormat1 {
128    fn compute_value_format(&self) -> ValueFormat {
129        self.value_record.format()
130    }
131}
132
133impl SinglePosFormat2 {
134    fn compute_value_format(&self) -> ValueFormat {
135        self.value_records
136            .first()
137            .map(ValueRecord::format)
138            .unwrap_or(ValueFormat::empty())
139    }
140}
141
142impl PairPosFormat1 {
143    fn compute_value_format1(&self) -> ValueFormat {
144        self.pair_sets
145            .first()
146            .and_then(|pairset| pairset.pair_value_records.first())
147            .map(|rec| rec.value_record1.format())
148            .unwrap_or(ValueFormat::empty())
149    }
150
151    fn compute_value_format2(&self) -> ValueFormat {
152        self.pair_sets
153            .first()
154            .and_then(|pairset| pairset.pair_value_records.first())
155            .map(|rec| rec.value_record2.format())
156            .unwrap_or(ValueFormat::empty())
157    }
158
159    fn check_format_consistency(&self, ctx: &mut ValidationCtx) {
160        let vf1 = self.compute_value_format1();
161        let vf2 = self.compute_value_format2();
162        ctx.with_array_items(self.pair_sets.iter(), |ctx, item| {
163            ctx.in_field("pair_value_records", |ctx| {
164                if item.pair_value_records.iter().any(|pairset| {
165                    pairset.value_record1.format() != vf1 || pairset.value_record2.format() != vf2
166                }) {
167                    ctx.report("all ValueRecords must have same format")
168                }
169            })
170        })
171    }
172}
173
174impl PairPosFormat2 {
175    fn compute_value_format1(&self) -> ValueFormat {
176        self.class1_records
177            .first()
178            .and_then(|rec| rec.class2_records.first())
179            .map(|rec| rec.value_record1.format())
180            .unwrap_or(ValueFormat::empty())
181    }
182
183    fn compute_value_format2(&self) -> ValueFormat {
184        self.class1_records
185            .first()
186            .and_then(|rec| rec.class2_records.first())
187            .map(|rec| rec.value_record2.format())
188            .unwrap_or(ValueFormat::empty())
189    }
190
191    fn compute_class1_count(&self) -> u16 {
192        self.class_def1.class_count().try_into().unwrap_or(0)
193    }
194
195    fn compute_class2_count(&self) -> u16 {
196        self.class_def2.class_count().try_into().unwrap_or(0)
197    }
198
199    fn check_length_and_format_conformance(&self, ctx: &mut ValidationCtx) {
200        let n_class_1s = self.class_def1.class_count();
201        let n_class_2s = self.class_def2.class_count();
202        if n_class_1s > u16::MAX as u32 || n_class_2s > u16::MAX as u32 {
203            ctx.report("pair positioning class counts must fit in 16 bits");
204        }
205        let format_1 = self.compute_value_format1();
206        let format_2 = self.compute_value_format2();
207        if self.class1_records.len() != n_class_1s as usize {
208            ctx.report("class1_records length must match number of class1 classes");
209        }
210        ctx.in_field("class1_records", |ctx| {
211            ctx.with_array_items(self.class1_records.iter(), |ctx, c1rec| {
212                if c1rec.class2_records.len() != n_class_2s as usize {
213                    ctx.report("class2_records length must match number of class2 classes ");
214                }
215                if c1rec.class2_records.iter().any(|rec| {
216                    rec.value_record1.format() != format_1 || rec.value_record2.format() != format_2
217                }) {
218                    ctx.report("all value records should report the same format");
219                }
220            })
221        });
222    }
223}
224
225impl MarkBasePosFormat1 {
226    fn compute_mark_class_count(&self) -> u16 {
227        self.mark_array.class_count()
228    }
229}
230
231impl MarkMarkPosFormat1 {
232    fn compute_mark_class_count(&self) -> u16 {
233        self.mark1_array.class_count()
234    }
235}
236
237impl MarkLigPosFormat1 {
238    fn compute_mark_class_count(&self) -> u16 {
239        self.mark_array.class_count()
240    }
241}
242
243impl MarkArray {
244    fn class_count(&self) -> u16 {
245        self.mark_records
246            .iter()
247            .map(|rec| rec.mark_class)
248            .collect::<HashSet<_>>()
249            .len() as u16
250    }
251}
252
253impl RemapVarStore<VariationIndex> for ValueRecord {
254    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
255        for table in [
256            self.x_placement_device.as_mut(),
257            self.y_placement_device.as_mut(),
258            self.x_advance_device.as_mut(),
259            self.y_advance_device.as_mut(),
260        ]
261        .into_iter()
262        .flatten()
263        {
264            table.remap_variation_indices(key_map)
265        }
266    }
267}
268
269impl RemapVarStore<VariationIndex> for DeviceOrVariationIndex {
270    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
271        if let DeviceOrVariationIndex::PendingVariationIndex(table) = self {
272            *self = key_map.get(table.delta_set_id).unwrap().into();
273        }
274    }
275}
276
277impl RemapVarStore<VariationIndex> for AnchorTable {
278    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
279        if let AnchorTable::Format3(table) = self {
280            table
281                .x_device
282                .as_mut()
283                .into_iter()
284                .chain(table.y_device.as_mut())
285                .for_each(|x| x.remap_variation_indices(key_map))
286        }
287    }
288}
289
290impl RemapVarStore<VariationIndex> for Gpos {
291    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
292        if let Some(list) = self.lookup_list.as_mut() {
293            for lookup in &mut list.lookups {
294                lookup.remap_variation_indices(key_map);
295            }
296        }
297        if let Some(list) = self.lookup_list2.as_mut() {
298            for lookup in &mut list.lookups {
299                lookup.remap_variation_indices(key_map);
300            }
301        }
302    }
303}
304
305impl RemapVarStore<VariationIndex> for PositionLookupList {
306    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
307        for lookup in &mut self.lookups {
308            lookup.remap_variation_indices(key_map)
309        }
310    }
311}
312
313impl RemapVarStore<VariationIndex> for PositionLookup {
314    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
315        match self {
316            PositionLookup::Single(lookup) => lookup.remap_variation_indices(key_map),
317            PositionLookup::Pair(lookup) => lookup.remap_variation_indices(key_map),
318            PositionLookup::Cursive(lookup) => lookup.remap_variation_indices(key_map),
319            PositionLookup::MarkToBase(lookup) => lookup.remap_variation_indices(key_map),
320            PositionLookup::MarkToLig(lookup) => lookup.remap_variation_indices(key_map),
321            PositionLookup::MarkToMark(lookup) => lookup.remap_variation_indices(key_map),
322            PositionLookup::Extension(lookup) => lookup.remap_variation_indices(key_map),
323
324            // don't contain any metrics directly
325            PositionLookup::Contextual(_) | PositionLookup::ChainContextual(_) => (),
326        }
327    }
328}
329
330impl RemapVarStore<VariationIndex> for ExtensionSubtable {
331    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
332        match self {
333            ExtensionSubtable::Single(table) => table.remap_variation_indices(key_map),
334            ExtensionSubtable::Pair(table) => table.remap_variation_indices(key_map),
335            ExtensionSubtable::Cursive(table) => table.remap_variation_indices(key_map),
336            ExtensionSubtable::MarkToBase(table) => table.remap_variation_indices(key_map),
337            ExtensionSubtable::MarkToLig(table) => table.remap_variation_indices(key_map),
338            ExtensionSubtable::MarkToMark(table) => table.remap_variation_indices(key_map),
339
340            // don't contain any metrics directly
341            ExtensionSubtable::Contextual(_) | ExtensionSubtable::ChainContextual(_) => (),
342        }
343    }
344}
345
346impl<T: RemapVarStore<VariationIndex>> RemapVarStore<VariationIndex> for ExtensionPosFormat1<T> {
347    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
348        self.extension.as_mut().remap_variation_indices(key_map)
349    }
350}
351
352impl<T: RemapVarStore<VariationIndex>> RemapVarStore<VariationIndex> for Lookup<T> {
353    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
354        for subtable in &mut self.subtables {
355            subtable.remap_variation_indices(key_map)
356        }
357    }
358}
359
360impl RemapVarStore<VariationIndex> for SinglePos {
361    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
362        match self {
363            SinglePos::Format1(table) => table.remap_variation_indices(key_map),
364            SinglePos::Format2(table) => table.remap_variation_indices(key_map),
365            SinglePos::Format3(table) => table.remap_variation_indices(key_map),
366            SinglePos::Format4(table) => table.remap_variation_indices(key_map),
367        }
368    }
369}
370
371impl RemapVarStore<VariationIndex> for SinglePosFormat1 {
372    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
373        self.value_record.remap_variation_indices(key_map);
374    }
375}
376
377impl RemapVarStore<VariationIndex> for SinglePosFormat2 {
378    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
379        for rec in &mut self.value_records {
380            rec.remap_variation_indices(key_map);
381        }
382    }
383}
384
385impl RemapVarStore<VariationIndex> for PairPosFormat1 {
386    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
387        for pairset in &mut self.pair_sets {
388            for pairrec in &mut pairset.pair_value_records {
389                pairrec.value_record1.remap_variation_indices(key_map);
390                pairrec.value_record2.remap_variation_indices(key_map);
391            }
392        }
393    }
394}
395
396impl RemapVarStore<VariationIndex> for PairPosFormat2 {
397    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
398        for class1rec in &mut self.class1_records {
399            for class2rec in &mut class1rec.class2_records {
400                class2rec.value_record1.remap_variation_indices(key_map);
401                class2rec.value_record2.remap_variation_indices(key_map);
402            }
403        }
404    }
405}
406
407impl RemapVarStore<VariationIndex> for PairPos {
408    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
409        match self {
410            PairPos::Format1(table) => table.remap_variation_indices(key_map),
411            PairPos::Format2(table) => table.remap_variation_indices(key_map),
412            PairPos::Format3(table) => table.remap_variation_indices(key_map),
413            PairPos::Format4(table) => table.remap_variation_indices(key_map),
414        }
415    }
416}
417
418impl RemapVarStore<VariationIndex> for MarkBasePosFormat1 {
419    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
420        self.mark_array.as_mut().remap_variation_indices(key_map);
421        for rec in &mut self.base_array.as_mut().base_records {
422            for anchor in &mut rec.base_anchors {
423                if let Some(anchor) = anchor.as_mut() {
424                    anchor.remap_variation_indices(key_map);
425                }
426            }
427        }
428    }
429}
430
431impl RemapVarStore<VariationIndex> for MarkMarkPosFormat1 {
432    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
433        self.mark1_array.as_mut().remap_variation_indices(key_map);
434        for rec in &mut self.mark2_array.as_mut().mark2_records {
435            for anchor in &mut rec.mark2_anchors {
436                if let Some(anchor) = anchor.as_mut() {
437                    anchor.remap_variation_indices(key_map);
438                }
439            }
440        }
441    }
442}
443
444impl RemapVarStore<VariationIndex> for MarkLigPosFormat1 {
445    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
446        self.mark_array.as_mut().remap_variation_indices(key_map);
447        for lig in &mut self.ligature_array.as_mut().ligature_attaches {
448            for rec in &mut lig.component_records {
449                for anchor in &mut rec.ligature_anchors {
450                    if let Some(anchor) = anchor.as_mut() {
451                        anchor.remap_variation_indices(key_map);
452                    }
453                }
454            }
455        }
456    }
457}
458
459impl RemapVarStore<VariationIndex> for CursivePosFormat1 {
460    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
461        for rec in &mut self.entry_exit_record {
462            for anchor in [rec.entry_anchor.as_mut(), rec.exit_anchor.as_mut()]
463                .into_iter()
464                .flatten()
465            {
466                anchor.remap_variation_indices(key_map);
467            }
468        }
469    }
470}
471
472impl RemapVarStore<VariationIndex> for MarkArray {
473    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
474        for rec in &mut self.mark_records {
475            rec.mark_anchor.remap_variation_indices(key_map);
476        }
477    }
478}
479
480super::layout::lookup_type!(gpos, CursivePos, 3);
481super::layout::lookup_type!(gpos, CursivePosFormat2, 3);
482super::layout::original_lookup!(PositionLookup, Cursive, CursivePosFormat1, CursivePos);
483
484super::layout::lookup_type!(gpos, MarkBasePos, 4);
485super::layout::lookup_type!(gpos, MarkBasePosFormat2, 4);
486super::layout::original_lookup!(PositionLookup, MarkToBase, MarkBasePosFormat1, MarkBasePos);
487
488super::layout::lookup_type!(gpos, MarkLigPos, 5);
489super::layout::lookup_type!(gpos, MarkLigPosFormat2, 5);
490super::layout::original_lookup!(PositionLookup, MarkToLig, MarkLigPosFormat1, MarkLigPos);
491
492super::layout::lookup_type!(gpos, MarkMarkPos, 6);
493super::layout::lookup_type!(gpos, MarkMarkPosFormat2, 6);
494super::layout::original_lookup!(PositionLookup, MarkToMark, MarkMarkPosFormat1, MarkMarkPos);
495
496impl SinglePosFormat3 {
497    fn compute_value_format(&self) -> ValueFormat {
498        self.value_record.format()
499    }
500}
501
502impl SinglePosFormat4 {
503    fn compute_value_format(&self) -> ValueFormat {
504        self.value_records
505            .first()
506            .map(ValueRecord::format)
507            .unwrap_or(ValueFormat::empty())
508    }
509
510    fn check_format_consistency(&self, ctx: &mut ValidationCtx) {
511        let format = self.compute_value_format();
512        if self
513            .value_records
514            .iter()
515            .any(|record| record.format() != format)
516        {
517            ctx.report("all ValueRecords must have same format");
518        }
519    }
520}
521
522impl PairPosFormat3 {
523    fn compute_value_format1(&self) -> ValueFormat {
524        self.pair_sets
525            .iter()
526            .find_map(|pairset| pairset.pair_value_records.first())
527            .map(|rec| rec.value_record1.format())
528            .unwrap_or(ValueFormat::empty())
529    }
530
531    fn compute_value_format2(&self) -> ValueFormat {
532        self.pair_sets
533            .iter()
534            .find_map(|pairset| pairset.pair_value_records.first())
535            .map(|rec| rec.value_record2.format())
536            .unwrap_or(ValueFormat::empty())
537    }
538
539    fn check_format_consistency(&self, ctx: &mut ValidationCtx) {
540        let vf1 = self.compute_value_format1();
541        let vf2 = self.compute_value_format2();
542        ctx.with_array_items(self.pair_sets.iter(), |ctx, item| {
543            ctx.in_field("pair_value_records", |ctx| {
544                if item.pair_value_records.iter().any(|pairset| {
545                    pairset.value_record1.format() != vf1 || pairset.value_record2.format() != vf2
546                }) {
547                    ctx.report("all ValueRecords must have same format")
548                }
549            })
550        })
551    }
552}
553
554impl PairPosFormat4 {
555    fn compute_value_format1(&self) -> ValueFormat {
556        self.class1_records
557            .first()
558            .and_then(|rec| rec.class2_records.first())
559            .map(|rec| rec.value_record1.format())
560            .unwrap_or(ValueFormat::empty())
561    }
562
563    fn compute_value_format2(&self) -> ValueFormat {
564        self.class1_records
565            .first()
566            .and_then(|rec| rec.class2_records.first())
567            .map(|rec| rec.value_record2.format())
568            .unwrap_or(ValueFormat::empty())
569    }
570
571    fn compute_class1_count(&self) -> u16 {
572        self.class1_records
573            .len()
574            .try_into()
575            .expect("class count exceeds 16 bits")
576    }
577
578    fn compute_class2_count(&self) -> u16 {
579        self.class1_records
580            .first()
581            .map_or(0, |row| row.class2_records.len())
582            .try_into()
583            .expect("class count exceeds 16 bits")
584    }
585
586    fn check_length_and_format_conformance(&self, ctx: &mut ValidationCtx) {
587        let n_class_1s = self.class1_records.len();
588        let n_class_2s = self
589            .class1_records
590            .first()
591            .map_or(0, |row| row.class2_records.len());
592        if n_class_1s > u16::MAX as usize || n_class_2s > u16::MAX as usize {
593            ctx.report("pair positioning class counts must fit in 16 bits");
594        }
595        let format_1 = self.compute_value_format1();
596        let format_2 = self.compute_value_format2();
597        if self
598            .class_def1
599            .iter()
600            .any(|(_, class)| class as usize >= n_class_1s)
601            || self
602                .class_def2
603                .iter()
604                .any(|(_, class)| class as usize >= n_class_2s)
605        {
606            ctx.report("class definitions must index the value record matrix");
607        }
608        ctx.in_field("class1_records", |ctx| {
609            ctx.with_array_items(self.class1_records.iter(), |ctx, c1rec| {
610                if c1rec.class2_records.len() != n_class_2s {
611                    ctx.report("class2_records length must match number of class2 classes ");
612                }
613                if c1rec.class2_records.iter().any(|rec| {
614                    rec.value_record1.format() != format_1 || rec.value_record2.format() != format_2
615                }) {
616                    ctx.report("all value records should report the same format");
617                }
618            })
619        });
620    }
621}
622
623impl MarkBasePosFormat2 {
624    fn compute_mark_class_count(&self) -> u16 {
625        self.mark_array
626            .class_count()
627            .try_into()
628            .expect("mark class count exceeds 16 bits")
629    }
630
631    fn check_class_dimensions(&self, ctx: &mut ValidationCtx) {
632        let classes = self.mark_array.class_count() as usize;
633        if (self
634            .base_array
635            .base_records
636            .iter()
637            .map(|rec| rec.base_anchors.len()))
638        .any(|len| len != classes)
639        {
640            ctx.report("anchor arrays must match the mark class count");
641        }
642    }
643}
644
645impl MarkMarkPosFormat2 {
646    fn compute_mark_class_count(&self) -> u16 {
647        self.mark1_array
648            .class_count()
649            .try_into()
650            .expect("mark class count exceeds 16 bits")
651    }
652
653    fn check_class_dimensions(&self, ctx: &mut ValidationCtx) {
654        let classes = self.mark1_array.class_count() as usize;
655        if (self
656            .mark2_array
657            .mark2_records
658            .iter()
659            .map(|rec| rec.mark2_anchors.len()))
660        .any(|len| len != classes)
661        {
662            ctx.report("anchor arrays must match the mark class count");
663        }
664    }
665}
666
667impl MarkLigPosFormat2 {
668    fn compute_mark_class_count(&self) -> u16 {
669        self.mark_array
670            .class_count()
671            .try_into()
672            .expect("mark class count exceeds 16 bits")
673    }
674
675    fn check_class_dimensions(&self, ctx: &mut ValidationCtx) {
676        let classes = self.mark_array.class_count() as usize;
677        if (self
678            .ligature_array
679            .ligature_attaches
680            .iter()
681            .flat_map(|lig| lig.component_records.iter())
682            .map(|rec| rec.ligature_anchors.len()))
683        .any(|len| len != classes)
684        {
685            ctx.report("anchor arrays must match the mark class count");
686        }
687    }
688}
689
690impl MarkArray2 {
691    fn class_count(&self) -> u32 {
692        self.mark_records
693            .iter()
694            .map(|rec| u32::from(rec.mark_class) + 1)
695            .max()
696            .unwrap_or(0)
697    }
698
699    fn check_class_count(&self, ctx: &mut ValidationCtx) {
700        if self.class_count() > u16::MAX as u32 {
701            ctx.report("mark class count must fit in 16 bits");
702        }
703    }
704}
705
706impl RemapVarStore<VariationIndex> for SinglePosFormat3 {
707    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
708        self.value_record.remap_variation_indices(key_map);
709    }
710}
711
712impl RemapVarStore<VariationIndex> for SinglePosFormat4 {
713    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
714        for rec in &mut self.value_records {
715            rec.remap_variation_indices(key_map);
716        }
717    }
718}
719
720impl RemapVarStore<VariationIndex> for PairPosFormat3 {
721    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
722        for pairset in &mut self.pair_sets {
723            for pairrec in &mut pairset.pair_value_records {
724                pairrec.value_record1.remap_variation_indices(key_map);
725                pairrec.value_record2.remap_variation_indices(key_map);
726            }
727        }
728    }
729}
730
731impl RemapVarStore<VariationIndex> for PairPosFormat4 {
732    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
733        for class1rec in &mut self.class1_records {
734            for class2rec in &mut class1rec.class2_records {
735                class2rec.value_record1.remap_variation_indices(key_map);
736                class2rec.value_record2.remap_variation_indices(key_map);
737            }
738        }
739    }
740}
741
742impl RemapVarStore<VariationIndex> for MarkBasePosFormat2 {
743    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
744        self.mark_array.as_mut().remap_variation_indices(key_map);
745        for rec in &mut self.base_array.as_mut().base_records {
746            for anchor in &mut rec.base_anchors {
747                if let Some(anchor) = anchor.as_mut() {
748                    anchor.remap_variation_indices(key_map);
749                }
750            }
751        }
752    }
753}
754
755impl RemapVarStore<VariationIndex> for MarkMarkPosFormat2 {
756    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
757        self.mark1_array.as_mut().remap_variation_indices(key_map);
758        for rec in &mut self.mark2_array.as_mut().mark2_records {
759            for anchor in &mut rec.mark2_anchors {
760                if let Some(anchor) = anchor.as_mut() {
761                    anchor.remap_variation_indices(key_map);
762                }
763            }
764        }
765    }
766}
767
768impl RemapVarStore<VariationIndex> for MarkLigPosFormat2 {
769    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
770        self.mark_array.as_mut().remap_variation_indices(key_map);
771        for lig in &mut self.ligature_array.as_mut().ligature_attaches {
772            for rec in &mut lig.component_records {
773                for anchor in &mut rec.ligature_anchors {
774                    if let Some(anchor) = anchor.as_mut() {
775                        anchor.remap_variation_indices(key_map);
776                    }
777                }
778            }
779        }
780    }
781}
782
783impl RemapVarStore<VariationIndex> for CursivePosFormat2 {
784    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
785        for rec in &mut self.entry_exit_record {
786            for anchor in [rec.entry_anchor.as_mut(), rec.exit_anchor.as_mut()]
787                .into_iter()
788                .flatten()
789            {
790                anchor.remap_variation_indices(key_map);
791            }
792        }
793    }
794}
795
796impl RemapVarStore<VariationIndex> for MarkArray2 {
797    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
798        for rec in &mut self.mark_records {
799            rec.mark_anchor.remap_variation_indices(key_map);
800        }
801    }
802}
803
804impl RemapVarStore<VariationIndex> for CursivePos {
805    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
806        match self {
807            Self::Format1(t) => t.remap_variation_indices(key_map),
808            Self::Format2(t) => t.remap_variation_indices(key_map),
809        }
810    }
811}
812
813impl RemapVarStore<VariationIndex> for MarkBasePos {
814    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
815        match self {
816            Self::Format1(t) => t.remap_variation_indices(key_map),
817            Self::Format2(t) => t.remap_variation_indices(key_map),
818        }
819    }
820}
821
822impl RemapVarStore<VariationIndex> for MarkMarkPos {
823    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
824        match self {
825            Self::Format1(t) => t.remap_variation_indices(key_map),
826            Self::Format2(t) => t.remap_variation_indices(key_map),
827        }
828    }
829}
830
831impl RemapVarStore<VariationIndex> for MarkLigPos {
832    fn remap_variation_indices(&mut self, key_map: &VariationIndexRemapping) {
833        match self {
834            Self::Format1(t) => t.remap_variation_indices(key_map),
835            Self::Format2(t) => t.remap_variation_indices(key_map),
836        }
837    }
838}
839
840#[cfg(test)]
841mod tests {
842
843    use read_fonts::tables::{gpos as read_gpos, layout::LookupFlag};
844
845    use crate::tables::layout::VariationIndex;
846
847    use super::*;
848
849    #[test]
850    fn remap_variation_indices_through_extension() {
851        use crate::tables::variations::{
852            ivs_builder::VariationStoreBuilder, RegionAxisCoordinates, VariationRegion,
853        };
854        use font_types::F2Dot14;
855
856        let region = VariationRegion::new(vec![RegionAxisCoordinates {
857            start_coord: F2Dot14::from_f32(0.0),
858            peak_coord: F2Dot14::from_f32(1.0),
859            end_coord: F2Dot14::from_f32(1.0),
860        }]);
861        let mut var_store = VariationStoreBuilder::new(1);
862        let temp_id = var_store.add_deltas(vec![(region, -20)]);
863        let (_, key_map) = var_store.build();
864
865        let value_record = ValueRecord::new()
866            .with_x_advance(-20)
867            .with_x_advance_device(DeviceOrVariationIndex::pending_variation_index(temp_id));
868        let pair_set = PairSet::new(vec![PairValueRecord::new(
869            GlyphId16::new(2),
870            value_record,
871            ValueRecord::default(),
872        )]);
873        let pair_pos = PairPos::format_1(
874            CoverageTable::format_1(vec![GlyphId16::new(1)]),
875            vec![pair_set],
876        );
877
878        // the same subtable, in a plain lookup and wrapped in an extension lookup
879        let mut plain =
880            PositionLookup::Pair(Lookup::new(LookupFlag::default(), vec![pair_pos.clone()]));
881        let mut extension = PositionLookup::Extension(Lookup::new(
882            LookupFlag::default(),
883            vec![ExtensionSubtable::Pair(ExtensionPosFormat1::new(
884                <PairPos as LookupSubtable>::TYPE.to_raw(),
885                pair_pos,
886            ))],
887        ));
888
889        plain.remap_variation_indices(&key_map);
890        extension.remap_variation_indices(&key_map);
891
892        // writing a PendingVariationIndex panics, so dump_table succeeding is the assertion
893        let plain_bytes = crate::dump_table(&plain).unwrap();
894        let ext_bytes = crate::dump_table(&extension).unwrap();
895
896        let parsed = read_gpos::PositionLookup::read(FontData::new(&ext_bytes)).unwrap();
897        let read_gpos::PositionSubtables::Pair(subtables) = parsed.subtables().unwrap() else {
898            panic!("expected a pair lookup");
899        };
900        let read_gpos::PairPos::Format1(subtable) = subtables.get(0).unwrap() else {
901            panic!("expected format 1");
902        };
903        let pair_set = subtable.pair_sets().get(0).unwrap();
904        let record = &pair_set.pair_value_records().get(0).unwrap();
905        let read_fonts::tables::layout::DeviceOrVariationIndex::VariationIndex(var_idx) =
906            record.value_record1.x_advance_device().unwrap().unwrap()
907        else {
908            panic!("expected a VariationIndex");
909        };
910        let expected = key_map.get(temp_id).unwrap();
911        assert_eq!(
912            (
913                var_idx.delta_set_outer_index(),
914                var_idx.delta_set_inner_index()
915            ),
916            (
917                expected.delta_set_outer_index,
918                expected.delta_set_inner_index
919            )
920        );
921        // the extension wrapper is the only difference, so the inner bytes must match
922        assert_eq!(plain_bytes.len() + 8, ext_bytes.len());
923    }
924
925    // adapted from/motivated by https://github.com/fonttools/fonttools/issues/471
926    #[test]
927    fn gpos_1_zero() {
928        let cov_one = CoverageTable::format_1(vec![GlyphId16::new(2)]);
929        let cov_two = CoverageTable::format_1(vec![GlyphId16::new(4)]);
930        let sub1 = SinglePos::format_1(cov_one, ValueRecord::default());
931        let sub2 = SinglePos::format_1(cov_two, ValueRecord::default().with_x_advance(500));
932        let lookup = Lookup::new(LookupFlag::default(), vec![sub1, sub2]);
933        let bytes = crate::dump_table(&lookup).unwrap();
934
935        let parsed = read_gpos::PositionLookup::read(FontData::new(&bytes)).unwrap();
936        let read_gpos::PositionLookup::Single(table) = parsed else {
937            panic!("something has gone seriously wrong");
938        };
939
940        assert_eq!(table.lookup_flag(), LookupFlag::empty());
941        assert_eq!(table.sub_table_count(), 2);
942        let read_gpos::SinglePos::Format1(sub1) = table.subtables().get(0).unwrap() else {
943            panic!("wrong table type");
944        };
945        let read_gpos::SinglePos::Format1(sub2) = table.subtables().get(1).unwrap() else {
946            panic!("wrong table type");
947        };
948
949        assert_eq!(sub1.value_format(), ValueFormat::empty());
950        assert!(sub1.value_record().is_empty());
951
952        assert_eq!(sub2.value_format(), ValueFormat::X_ADVANCE);
953        assert_eq!(sub2.value_record().x_advance(), Some(500));
954        assert_eq!(sub2.value_record().y_advance(), None);
955    }
956
957    // shared between a pair of tests below
958    fn make_rec(i: u16) -> ValueRecord {
959        // '0' here is shorthand for 'no device table'
960        if i == 0 {
961            return ValueRecord::new().with_explicit_value_format(ValueFormat::X_ADVANCE_DEVICE);
962        }
963        ValueRecord::new().with_x_advance_device(VariationIndex::new(0xff, i))
964    }
965
966    #[test]
967    fn compile_devices_pairpos2() {
968        let class1 = ClassDef::from_iter([(GlyphId16::new(5), 0), (GlyphId16::new(6), 1)]);
969        // class 0 is 'all the rest', here, always implicitly present
970        let class2 = ClassDef::from_iter([(GlyphId16::new(8), 1)]);
971
972        // two c1recs, each with two c2recs
973        let class1recs = vec![
974            Class1Record::new(vec![
975                Class2Record::new(make_rec(0), make_rec(0)),
976                Class2Record::new(make_rec(1), make_rec(2)),
977            ]),
978            Class1Record::new(vec![
979                Class2Record::new(make_rec(0), make_rec(0)),
980                Class2Record::new(make_rec(2), make_rec(3)),
981            ]),
982        ];
983        let coverage = class1.iter().map(|(gid, _)| gid).collect();
984        let a_table = PairPos::format_2(coverage, class1, class2, class1recs);
985
986        let bytes = crate::dump_table(&a_table).unwrap();
987        let read_back = PairPosFormat2::read(bytes.as_slice().into()).unwrap();
988
989        assert!(read_back.class1_records[0].class2_records[0]
990            .value_record1
991            .x_advance_device
992            .is_none());
993        assert!(read_back.class1_records[1].class2_records[1]
994            .value_record1
995            .x_advance_device
996            .is_some());
997
998        let DeviceOrVariationIndex::VariationIndex(dev2) = read_back.class1_records[0]
999            .class2_records[1]
1000            .value_record2
1001            .x_advance_device
1002            .as_ref()
1003            .unwrap()
1004        else {
1005            panic!("not a variation index")
1006        };
1007        assert_eq!(dev2.delta_set_inner_index, 2);
1008    }
1009
1010    #[should_panic(expected = "all value records should report the same format")]
1011    #[test]
1012    fn validate_bad_pairpos2() {
1013        let class1 = ClassDef::from_iter([(GlyphId16::new(5), 0), (GlyphId16::new(6), 1)]);
1014        // class 0 is 'all the rest', here, always implicitly present
1015        let class2 = ClassDef::from_iter([(GlyphId16::new(8), 1)]);
1016        let coverage = class1.iter().map(|(gid, _)| gid).collect();
1017
1018        // two c1recs, each with two c2recs
1019        let class1recs = vec![
1020            Class1Record::new(vec![
1021                Class2Record::new(make_rec(0), make_rec(0)),
1022                Class2Record::new(make_rec(1), make_rec(2)),
1023            ]),
1024            Class1Record::new(vec![
1025                Class2Record::new(make_rec(0), make_rec(0)),
1026                // this is now the wrong type
1027                Class2Record::new(make_rec(2), make_rec(3).with_x_advance(0x514)),
1028            ]),
1029        ];
1030        let ppf2 = PairPos::format_2(coverage, class1, class2, class1recs);
1031        crate::dump_table(&ppf2).unwrap();
1032    }
1033
1034    #[test]
1035    fn validate_pairpos1() {
1036        let coverage: CoverageTable = [1, 2].into_iter().map(GlyphId16::new).collect();
1037        let good_table = PairPosFormat1::new(
1038            coverage.clone(),
1039            vec![
1040                PairSet::new(vec![PairValueRecord::new(
1041                    GlyphId16::new(5),
1042                    ValueRecord::new().with_x_advance(5),
1043                    ValueRecord::new(),
1044                )]),
1045                PairSet::new(vec![PairValueRecord::new(
1046                    GlyphId16::new(1),
1047                    ValueRecord::new().with_x_advance(42),
1048                    ValueRecord::new(),
1049                )]),
1050            ],
1051        );
1052
1053        let bad_table = PairPosFormat1::new(
1054            coverage,
1055            vec![
1056                PairSet::new(vec![PairValueRecord::new(
1057                    GlyphId16::new(5),
1058                    ValueRecord::new().with_x_advance(5),
1059                    ValueRecord::new(),
1060                )]),
1061                PairSet::new(vec![PairValueRecord::new(
1062                    GlyphId16::new(1),
1063                    //this is a different format, which is not okay
1064                    ValueRecord::new().with_x_placement(42),
1065                    ValueRecord::new(),
1066                )]),
1067            ],
1068        );
1069
1070        assert!(crate::dump_table(&good_table).is_ok());
1071        assert!(matches!(
1072            crate::dump_table(&bad_table),
1073            Err(crate::error::Error::ValidationFailed(_))
1074        ));
1075    }
1076}