allsorts_subset_browser/tables/variable_fonts/
avar.rs

1#![deny(missing_docs)]
2
3//! `avar` Axis Variations Table
4//!
5//! The axis variations table (`avar`) is an optional table used in variable
6//! fonts. It can be used to modify aspects of how a design varies for different
7//! instances along a particular design-variation axis. Specifically, it allows
8//! modification of the coordinate normalization that is used when processing
9//! variation data for a particular variation instance.
10//!
11//! <https://learn.microsoft.com/en-us/typography/opentype/spec/avar>
12
13use crate::binary::read::{ReadArray, ReadBinary, ReadCtxt, ReadFrom, ReadScope, ReadUnchecked};
14use crate::error::ParseError;
15use crate::tables::{F2Dot14, Fixed};
16
17/// `avar` Axis Variations Table.
18pub struct AvarTable<'a> {
19    /// Major version number of the axis variations table.
20    pub major_version: u16,
21    /// Minor version number of the axis variations table.
22    pub minor_version: u16,
23    /// The number of variation axes for this font.
24    pub axis_count: u16,
25    segments_map_scope: ReadScope<'a>,
26}
27
28/// Segment map record.
29///
30/// Contains an array of mappings from a normalised coordinate value to a
31/// modified value.
32pub struct SegmentMap<'a> {
33    /// The array of axis value map records for this axis.
34    axis_value_maps: ReadArray<'a, AxisValueMap>,
35}
36
37/// A mapping from a normalised coordinate value to a modified value.
38#[derive(Debug, Copy, Clone, Eq, PartialEq)]
39pub struct AxisValueMap {
40    /// A normalized coordinate value obtained using default normalization.
41    pub from_coordinate: F2Dot14,
42    /// The modified, normalized coordinate value.
43    pub to_coordinate: F2Dot14,
44}
45
46impl AvarTable<'_> {
47    /// Iterate over the segment maps.
48    ///
49    /// To retrieve the segment map for a specific index use [Iterator::nth].
50    pub fn segment_maps(&self) -> impl Iterator<Item = SegmentMap<'_>> {
51        (0..self.axis_count).scan(self.segments_map_scope.ctxt(), |ctxt, _i| {
52            ctxt.read::<SegmentMap<'_>>().ok()
53        })
54    }
55}
56
57impl ReadBinary for AvarTable<'_> {
58    type HostType<'a> = AvarTable<'a>;
59
60    fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<Self::HostType<'a>, ParseError> {
61        let major_version = ctxt.read_u16be()?;
62        ctxt.check_version(major_version == 1)?;
63        let minor_version = ctxt.read_u16be()?;
64        let _reserved = ctxt.read_u16be()?;
65        let axis_count = ctxt.read_u16be()?;
66
67        let segment_map_scope = ctxt.scope();
68        let mut segment_maps_len = 0;
69
70        for _ in 0..axis_count {
71            let segment_map = ctxt.read::<SegmentMap<'_>>()?;
72            // + 2 for the 16-bit position map count
73            segment_maps_len += segment_map.axis_value_maps.len() * AxisValueMap::SIZE + 2
74        }
75
76        let segments_map_scope = segment_map_scope.offset_length(0, segment_maps_len)?;
77
78        Ok(AvarTable {
79            major_version,
80            minor_version,
81            axis_count,
82            segments_map_scope,
83        })
84    }
85}
86
87impl SegmentMap<'_> {
88    /// Iterate over the axis value mappings.
89    pub fn axis_value_mappings(&self) -> impl Iterator<Item = AxisValueMap> + '_ {
90        self.axis_value_maps.iter()
91    }
92
93    /// Performs `avar` normalization to a value that has already been default
94    /// normalised.
95    ///
96    /// `normalised_value` should be in the range [-1, +1].
97    pub fn normalize(&self, mut normalized_value: Fixed) -> Fixed {
98        // Scan the axis value maps for the first record that has a
99        // from_coordinate >= default_normalised_value
100        let mut start_seg: Option<AxisValueMap> = None;
101        for end_seg in self.axis_value_mappings() {
102            // From the spec: Note that endSeg cannot be the first map record, which is for
103            // -1.
104            if let Some(start_seg) = start_seg {
105                let end_seg_from_coordinate = Fixed::from(end_seg.from_coordinate);
106                if end_seg_from_coordinate == normalized_value {
107                    normalized_value = end_seg.to_coordinate.into();
108                    break;
109                } else if end_seg_from_coordinate > normalized_value {
110                    // if start_seg is None then this is the first axis value map record, which
111                    // can't be the end seg
112                    let ratio = (normalized_value - Fixed::from(start_seg.from_coordinate))
113                        / (Fixed::from(end_seg.from_coordinate)
114                            - Fixed::from(start_seg.from_coordinate));
115                    normalized_value = Fixed::from(start_seg.to_coordinate)
116                        + ratio
117                            * (Fixed::from(end_seg.to_coordinate)
118                                - Fixed::from(start_seg.to_coordinate));
119                    break;
120                }
121            }
122            start_seg = Some(end_seg);
123        }
124        normalized_value
125    }
126}
127
128impl ReadBinary for SegmentMap<'_> {
129    type HostType<'a> = SegmentMap<'a>;
130
131    fn read<'a>(ctxt: &mut ReadCtxt<'a>) -> Result<Self::HostType<'a>, ParseError> {
132        let position_map_count = ctxt.read_u16be()?;
133        let axis_value_maps = ctxt.read_array::<AxisValueMap>(usize::from(position_map_count))?;
134
135        Ok(SegmentMap { axis_value_maps })
136    }
137}
138
139impl ReadFrom for AxisValueMap {
140    type ReadType = (F2Dot14, F2Dot14);
141
142    fn read_from((from_coordinate, to_coordinate): (F2Dot14, F2Dot14)) -> Self {
143        AxisValueMap {
144            from_coordinate,
145            to_coordinate,
146        }
147    }
148}
149
150#[cfg(test)]
151mod tests {
152    use super::{AvarTable, AxisValueMap, F2Dot14, ReadScope};
153    use crate::binary::write::{WriteBinary, WriteBuffer};
154    use crate::binary::U16Be;
155    use crate::error::ReadWriteError;
156    use crate::font_data::FontData;
157    use crate::tables::variable_fonts::avar::SegmentMap;
158    use crate::tables::{Fixed, FontTableProvider};
159    use crate::tag;
160    use crate::tests::{assert_fixed_close, read_fixture};
161
162    #[test]
163    fn avar() {
164        let buffer = read_fixture("tests/fonts/opentype/NotoSans-VF.abc.ttf");
165        let scope = ReadScope::new(&buffer);
166        let font_file = scope
167            .read::<FontData<'_>>()
168            .expect("unable to parse font file");
169        let table_provider = font_file
170            .table_provider(0)
171            .expect("unable to create font provider");
172        let avar_data = table_provider
173            .read_table_data(tag::AVAR)
174            .expect("unable to read avar table data");
175        let avar = ReadScope::new(&avar_data).read::<AvarTable<'_>>().unwrap();
176
177        let segment_maps = avar
178            .segment_maps()
179            .map(|segment_map| segment_map.axis_value_mappings().collect::<Vec<_>>())
180            .collect::<Vec<_>>();
181        let expected = vec![
182            vec![
183                AxisValueMap {
184                    from_coordinate: F2Dot14::from(-1.0),
185                    to_coordinate: F2Dot14::from(-1.0),
186                },
187                AxisValueMap {
188                    from_coordinate: F2Dot14::from(-0.6667),
189                    to_coordinate: F2Dot14::from(-0.7969),
190                },
191                AxisValueMap {
192                    from_coordinate: F2Dot14::from(-0.3333),
193                    to_coordinate: F2Dot14::from(-0.5),
194                },
195                AxisValueMap {
196                    from_coordinate: F2Dot14::from(0.0),
197                    to_coordinate: F2Dot14::from(0.0),
198                },
199                AxisValueMap {
200                    from_coordinate: F2Dot14::from(0.2),
201                    to_coordinate: F2Dot14::from(0.18),
202                },
203                AxisValueMap {
204                    from_coordinate: F2Dot14::from(0.4),
205                    to_coordinate: F2Dot14::from(0.38),
206                },
207                AxisValueMap {
208                    from_coordinate: F2Dot14::from(0.6),
209                    to_coordinate: F2Dot14::from(0.61),
210                },
211                AxisValueMap {
212                    from_coordinate: F2Dot14::from(0.8),
213                    to_coordinate: F2Dot14::from(0.79),
214                },
215                AxisValueMap {
216                    from_coordinate: F2Dot14::from(1.0),
217                    to_coordinate: F2Dot14::from(1.0),
218                },
219            ],
220            vec![
221                AxisValueMap {
222                    from_coordinate: F2Dot14::from(-1.0),
223                    to_coordinate: F2Dot14::from(-1.0),
224                },
225                AxisValueMap {
226                    from_coordinate: F2Dot14::from(-0.6667),
227                    to_coordinate: F2Dot14::from(-0.7),
228                },
229                AxisValueMap {
230                    from_coordinate: F2Dot14::from(-0.3333),
231                    to_coordinate: F2Dot14::from(-0.36664),
232                },
233                AxisValueMap {
234                    from_coordinate: F2Dot14::from(0.0),
235                    to_coordinate: F2Dot14::from(0.0),
236                },
237                AxisValueMap {
238                    from_coordinate: F2Dot14::from(1.0),
239                    to_coordinate: F2Dot14::from(1.0),
240                },
241            ],
242            vec![
243                AxisValueMap {
244                    from_coordinate: F2Dot14::from(-1.0),
245                    to_coordinate: F2Dot14::from(-1.0),
246                },
247                AxisValueMap {
248                    from_coordinate: F2Dot14::from(0.0),
249                    to_coordinate: F2Dot14::from(0.0),
250                },
251                AxisValueMap {
252                    from_coordinate: F2Dot14::from(1.0),
253                    to_coordinate: F2Dot14::from(1.0),
254                },
255            ],
256        ];
257        assert_eq!(segment_maps, expected);
258    }
259
260    #[test]
261    fn test_avar_normalize() -> Result<(), ReadWriteError> {
262        // Build test segment. Example data from:
263        // https://learn.microsoft.com/en-us/typography/opentype/spec/otvaroverview#avar-normalization-example
264        let mut buf = WriteBuffer::new();
265        U16Be::write(&mut buf, 6u16)?; // position_map_count
266        [
267            (-1.0, -1.0),
268            (-0.75, -0.5),
269            (0., 0.),
270            (0.4, 0.4),
271            (0.6, 0.9),
272            (1.0, 1.0),
273        ]
274        .iter()
275        .copied()
276        .try_for_each(|(from_coord, to_coord)| {
277            F2Dot14::write(&mut buf, F2Dot14::from(from_coord))?;
278            F2Dot14::write(&mut buf, F2Dot14::from(to_coord))
279        })?;
280
281        let data = buf.into_inner();
282        let mut ctxt = ReadScope::new(&data).ctxt();
283        let segment_map = ctxt.read::<SegmentMap<'_>>()?;
284        [
285            (-1.0, -1.0),
286            (-0.75, -0.5),
287            (-0.5, -0.3333),
288            (-0.25, -0.1667),
289            (0., 0.),
290            (0.25, 0.25),
291            (0.5, 0.65),
292            (0.75, 0.9375),
293            (1.0, 1.0),
294        ]
295        .iter()
296        .copied()
297        .for_each(|(input, expected)| {
298            assert_fixed_close(segment_map.normalize(Fixed::from(input)), expected);
299        });
300
301        Ok(())
302    }
303}