Skip to main content

write_fonts/tables/
mvar.rs

1//! The [MVAR](https://learn.microsoft.com/en-us/typography/opentype/spec/mvar) table
2
3include!("../../generated/generated_mvar.rs");
4
5use super::variations::ItemVariationStore;
6use std::mem::size_of;
7
8impl Mvar {
9    /// Construct a new `MVAR` table.
10    pub fn new(
11        version: MajorMinor,
12        item_variation_store: Option<ItemVariationStore>,
13        value_records: Vec<ValueRecord>,
14    ) -> Self {
15        Self {
16            version,
17            value_record_size: size_of::<ValueRecord>() as u16,
18            value_record_count: value_records.len() as u16,
19            item_variation_store: item_variation_store.into(),
20            value_records,
21        }
22    }
23}
24
25#[cfg(test)]
26mod tests {
27    use font_types::{F2Dot14, Tag};
28    use read_fonts::tables::mvar as read_mvar;
29
30    use crate::dump_table;
31    use crate::tables::variations::{
32        ivs_builder::VariationStoreBuilder, RegionAxisCoordinates, VariationRegion,
33    };
34
35    use super::*;
36
37    #[test]
38    fn empty_smoke_test() {
39        let table = Mvar::new(MajorMinor::new(1, 0), None, vec![]);
40
41        let bytes = dump_table(&table).unwrap();
42        let read = read_mvar::Mvar::read(FontData::new(&bytes)).unwrap();
43
44        assert_eq!(read.version(), table.version);
45        assert_eq!(read.value_record_count(), 0);
46        assert_eq!(read.value_record_size(), 8);
47        assert!(read.item_variation_store().is_none());
48        assert_eq!(read.value_records().len(), 0);
49    }
50
51    fn reg_coords(min: f32, default: f32, max: f32) -> RegionAxisCoordinates {
52        RegionAxisCoordinates {
53            start_coord: F2Dot14::from_f32(min),
54            peak_coord: F2Dot14::from_f32(default),
55            end_coord: F2Dot14::from_f32(max),
56        }
57    }
58
59    fn test_regions() -> [VariationRegion; 3] {
60        [
61            VariationRegion::new(vec![reg_coords(0.0, 1.0, 1.0)]),
62            VariationRegion::new(vec![reg_coords(0.0, 0.5, 1.0)]),
63            VariationRegion::new(vec![reg_coords(0.5, 1.0, 1.0)]),
64        ]
65    }
66
67    fn read_metric_delta(mvar: &read_mvar::Mvar, tag: &[u8; 4], coords: &[f32]) -> f64 {
68        let coords = coords
69            .iter()
70            .map(|c| F2Dot14::from_f32(*c))
71            .collect::<Vec<_>>();
72        mvar.metric_delta(Tag::new(tag), &coords).unwrap().to_f64()
73    }
74
75    fn assert_value_record(actual: &read_mvar::ValueRecord, expected: ValueRecord) {
76        assert_eq!(actual.value_tag(), expected.value_tag);
77        assert_eq!(
78            actual.delta_set_outer_index(),
79            expected.delta_set_outer_index
80        );
81        assert_eq!(
82            actual.delta_set_inner_index(),
83            expected.delta_set_inner_index
84        );
85    }
86
87    #[test]
88    fn simple_smoke_test() {
89        let [r1, r2, r3] = test_regions();
90        let mut builder = VariationStoreBuilder::new(1);
91        let delta_ids = vec![
92            // deltas for horizontal ascender 'hasc' only defined for 1 region
93            builder.add_deltas(vec![(r1, 10)]),
94            // deltas for horizontal descender 'hdsc' defined for 2 regions
95            builder.add_deltas(vec![(r2, -20), (r3, -30)]),
96        ];
97        let (varstore, index_map) = builder.build();
98
99        let mut value_records = Vec::new();
100        for (tag, temp_id) in [b"hasc", b"hdsc"].into_iter().zip(delta_ids.into_iter()) {
101            let varidx = index_map.get(temp_id).unwrap();
102            let value_record = ValueRecord::new(
103                Tag::new(tag),
104                varidx.delta_set_outer_index,
105                varidx.delta_set_inner_index,
106            );
107            value_records.push(value_record);
108        }
109
110        let table = Mvar::new(MajorMinor::new(1, 0), Some(varstore), value_records);
111
112        let bytes = dump_table(&table).unwrap();
113        let read = read_mvar::Mvar::read(FontData::new(&bytes)).unwrap();
114
115        assert_eq!(read.version(), table.version);
116        assert_eq!(read.value_record_count(), 2);
117        assert_eq!(read.value_record_size(), 8);
118        assert!(read.item_variation_store().is_some());
119
120        assert_value_record(
121            &read.value_records()[0],
122            ValueRecord::new(Tag::new(b"hasc"), 0, 1),
123        );
124        assert_eq!(read_metric_delta(&read, b"hasc", &[0.0]), 0.0);
125        // at axis coord 0.5, the interpolated delta will be half of r1's delta
126        assert_eq!(read_metric_delta(&read, b"hasc", &[0.5]), 5.0);
127        assert_eq!(read_metric_delta(&read, b"hasc", &[1.0]), 10.0);
128
129        assert_value_record(
130            &read.value_records()[1],
131            ValueRecord::new(Tag::new(b"hdsc"), 0, 0),
132        );
133        assert_eq!(read_metric_delta(&read, b"hdsc", &[0.0]), 0.0);
134        // this coincides with the peak of intermediate region r2, hence != 30.0/2
135        assert_eq!(read_metric_delta(&read, b"hdsc", &[0.5]), -20.0);
136        assert_eq!(read_metric_delta(&read, b"hdsc", &[1.0]), -30.0);
137    }
138}