read_fonts/tables/
gvar.rs

1//! The [gvar (Glyph Variations)](https://learn.microsoft.com/en-us/typography/opentype/spec/gvar)
2//! table
3
4include!("../../generated/generated_gvar.rs");
5
6use super::{
7    glyf::{CompositeGlyphFlags, Glyf, Glyph, PointCoord},
8    loca::Loca,
9    variations::{
10        PackedPointNumbers, Tuple, TupleDelta, TupleVariationCount, TupleVariationData,
11        TupleVariationHeader,
12    },
13};
14
15/// Variation data specialized for the glyph variations table.
16pub type GlyphVariationData<'a> = TupleVariationData<'a, GlyphDelta>;
17
18#[derive(Clone, Copy, Debug)]
19pub struct U16Or32(u32);
20
21impl ReadArgs for U16Or32 {
22    type Args = GvarFlags;
23}
24
25impl ComputeSize for U16Or32 {
26    fn compute_size(args: &GvarFlags) -> Result<usize, ReadError> {
27        Ok(if args.contains(GvarFlags::LONG_OFFSETS) {
28            4
29        } else {
30            2
31        })
32    }
33}
34
35impl FontReadWithArgs<'_> for U16Or32 {
36    fn read_with_args(data: FontData<'_>, args: &Self::Args) -> Result<Self, ReadError> {
37        if args.contains(GvarFlags::LONG_OFFSETS) {
38            data.read_at::<u32>(0).map(Self)
39        } else {
40            data.read_at::<u16>(0).map(|v| Self(v as u32 * 2))
41        }
42    }
43}
44
45impl U16Or32 {
46    #[inline]
47    pub fn get(self) -> u32 {
48        self.0
49    }
50}
51
52impl<'a> GlyphVariationDataHeader<'a> {
53    fn raw_tuple_header_data(&self) -> FontData<'a> {
54        let range = self.shape.tuple_variation_headers_byte_range();
55        self.data.split_off(range.start).unwrap()
56    }
57}
58
59impl<'a> Gvar<'a> {
60    /// Return the raw data for this gid.
61    ///
62    /// If there is no variation data for the glyph, returns `Ok(None)`.
63    pub fn data_for_gid(&self, gid: GlyphId) -> Result<Option<FontData<'a>>, ReadError> {
64        let range = self.data_range_for_gid(gid)?;
65        if range.is_empty() {
66            return Ok(None);
67        }
68        match self.data.slice(range) {
69            Some(data) => Ok(Some(data)),
70            None => Err(ReadError::OutOfBounds),
71        }
72    }
73
74    pub fn glyph_variation_data_for_range(
75        &self,
76        offset_range: Range<usize>,
77    ) -> Result<FontData<'a>, ReadError> {
78        let base = self.glyph_variation_data_array_offset() as usize;
79        let start = base
80            .checked_add(offset_range.start)
81            .ok_or(ReadError::OutOfBounds)?;
82        let end = base
83            .checked_add(offset_range.end)
84            .ok_or(ReadError::OutOfBounds)?;
85        self.data.slice(start..end).ok_or(ReadError::OutOfBounds)
86    }
87
88    pub fn as_bytes(&self) -> &[u8] {
89        self.data.as_bytes()
90    }
91
92    fn data_range_for_gid(&self, gid: GlyphId) -> Result<Range<usize>, ReadError> {
93        let start_idx = gid.to_u32() as usize;
94        let end_idx = start_idx + 1;
95        let data_start = self.glyph_variation_data_array_offset();
96        let start =
97            data_start.checked_add(self.glyph_variation_data_offsets().get(start_idx)?.get());
98        let end = data_start.checked_add(self.glyph_variation_data_offsets().get(end_idx)?.get());
99        let (Some(start), Some(end)) = (start, end) else {
100            return Err(ReadError::OutOfBounds);
101        };
102        Ok(start as usize..end as usize)
103    }
104
105    /// Get the variation data for a specific glyph.
106    ///
107    /// Returns `Ok(None)` if there is no variation data for this glyph, and
108    /// returns an error if there is data but it is malformed.
109    pub fn glyph_variation_data(
110        &self,
111        gid: GlyphId,
112    ) -> Result<Option<GlyphVariationData<'a>>, ReadError> {
113        let shared_tuples = self.shared_tuples()?;
114        let axis_count = self.axis_count();
115        let data = self.data_for_gid(gid)?;
116        data.map(|data| GlyphVariationData::new(data, axis_count, shared_tuples))
117            .transpose()
118    }
119
120    /// Returns the phantom point deltas for the given variation coordinates
121    /// and glyph identifier, if variation data exists for the glyph.
122    ///
123    /// The resulting array will contain four deltas:
124    /// `[left, right, top, bottom]`.
125    pub fn phantom_point_deltas(
126        &self,
127        glyf: &Glyf,
128        loca: &Loca,
129        coords: &[F2Dot14],
130        glyph_id: GlyphId,
131    ) -> Result<Option<[Point<Fixed>; 4]>, ReadError> {
132        // For any given glyph, there's only one outline that contributes to
133        // metrics deltas (via "phantom points"). For simple glyphs, that is
134        // the glyph itself. For composite glyphs, it is the last component
135        // in the tree that has the USE_MY_METRICS flag set or, if there are
136        // none, the composite glyph itself.
137        //
138        // This searches for the glyph that meets that criteria and also
139        // returns the point count (for composites, this is the component
140        // count), so that we know where the deltas for phantom points start
141        // in the variation data.
142        let (glyph_id, point_count) = find_glyph_and_point_count(glyf, loca, glyph_id, 0)?;
143        let mut phantom_deltas = [Point::default(); 4];
144        let phantom_range = point_count..point_count + 4;
145        let Some(var_data) = self.glyph_variation_data(glyph_id)? else {
146            return Ok(None);
147        };
148        // Note that phantom points can never belong to a contour so we don't have
149        // to handle the IUP case here.
150        for (tuple, scalar) in var_data.active_tuples_at(coords) {
151            for tuple_delta in tuple.deltas() {
152                let ix = tuple_delta.position as usize;
153                if phantom_range.contains(&ix) {
154                    phantom_deltas[ix - phantom_range.start] += tuple_delta.apply_scalar(scalar);
155                }
156            }
157        }
158        Ok(Some(phantom_deltas))
159    }
160}
161
162impl<'a> GlyphVariationData<'a> {
163    pub(crate) fn new(
164        data: FontData<'a>,
165        axis_count: u16,
166        shared_tuples: SharedTuples<'a>,
167    ) -> Result<Self, ReadError> {
168        let header = GlyphVariationDataHeader::read(data)?;
169
170        let header_data = header.raw_tuple_header_data();
171        let count = header.tuple_variation_count();
172        let data = header.serialized_data()?;
173
174        // if there are shared point numbers, get them now
175        let (shared_point_numbers, serialized_data) =
176            if header.tuple_variation_count().shared_point_numbers() {
177                let (packed, data) = PackedPointNumbers::split_off_front(data);
178                (Some(packed), data)
179            } else {
180                (None, data)
181            };
182
183        Ok(GlyphVariationData {
184            tuple_count: count,
185            axis_count,
186            shared_tuples: Some(shared_tuples.tuples()),
187            shared_point_numbers,
188            header_data,
189            serialized_data,
190            _marker: std::marker::PhantomData,
191        })
192    }
193}
194
195/// Delta information for a single point or component in a glyph.
196#[derive(Clone, Copy, Debug, PartialEq, Eq)]
197pub struct GlyphDelta {
198    /// The point or component index.
199    pub position: u16,
200    /// The x delta.
201    pub x_delta: i32,
202    /// The y delta.
203    pub y_delta: i32,
204}
205
206impl GlyphDelta {
207    /// Applies a tuple scalar to this delta.
208    pub fn apply_scalar<D: PointCoord>(self, scalar: Fixed) -> Point<D> {
209        let scalar = D::from_fixed(scalar);
210        Point::new(self.x_delta, self.y_delta).map(D::from_i32) * scalar
211    }
212}
213
214impl TupleDelta for GlyphDelta {
215    fn is_point() -> bool {
216        true
217    }
218
219    fn new(position: u16, x: i32, y: i32) -> Self {
220        Self {
221            position,
222            x_delta: x,
223            y_delta: y,
224        }
225    }
226}
227
228/// Given a glyph identifier, searches for the glyph that contains the actual
229/// metrics for rendering.
230///
231/// For simple glyphs, that is simply the requested glyph. For composites, it
232/// depends on the USE_MY_METRICS flag.
233///
234/// Returns the resulting glyph identifier and the number of points (or
235/// components) in that glyph. This count represents the start of the phantom
236/// points.
237fn find_glyph_and_point_count(
238    glyf: &Glyf,
239    loca: &Loca,
240    glyph_id: GlyphId,
241    recurse_depth: usize,
242) -> Result<(GlyphId, usize), ReadError> {
243    // Matches HB's nesting limit
244    const RECURSION_LIMIT: usize = 64;
245    if recurse_depth > RECURSION_LIMIT {
246        return Err(ReadError::MalformedData(
247            "nesting too deep in composite glyph",
248        ));
249    }
250    let glyph = loca.get_glyf(glyph_id, glyf)?;
251    let Some(glyph) = glyph else {
252        // Empty glyphs might still contain gvar data that
253        // only affects phantom points
254        return Ok((glyph_id, 0));
255    };
256    match glyph {
257        Glyph::Simple(simple) => {
258            // Simple glyphs always use their own metrics
259            Ok((glyph_id, simple.num_points()))
260        }
261        Glyph::Composite(composite) => {
262            // For composite glyphs, recurse into the glyph referenced by the
263            // *last* component that has the USE_MY_METRICS flag set.
264            // Otherwise, return the composite glyph itself and the number of
265            // components as the point count.
266            // https://learn.microsoft.com/en-us/typography/opentype/spec/gvar#point-numbers-and-processing-for-composite-glyphs
267            let (count, inherit_metrics) = composite.component_glyphs_and_flags().fold(
268                (0, None),
269                |(count, inherit_metrics), (component_id, flags)| {
270                    let has_flag = flags.contains(CompositeGlyphFlags::USE_MY_METRICS);
271                    let preferred = has_flag.then_some(component_id).or(inherit_metrics);
272
273                    (count + 1, preferred)
274                },
275            );
276
277            if let Some(component) = inherit_metrics {
278                find_glyph_and_point_count(glyf, loca, component.into(), recurse_depth + 1)
279            } else {
280                Ok((glyph_id, count))
281            }
282        }
283    }
284}
285
286#[cfg(test)]
287mod tests {
288    use std::collections::HashMap;
289
290    use font_test_data::bebuffer::BeBuffer;
291
292    use super::*;
293    use crate::{FontRef, TableProvider};
294
295    // Shared tuples in the 'gvar' table of the Skia font, as printed
296    // in Apple's TrueType specification.
297    // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html
298    static SKIA_GVAR_SHARED_TUPLES_DATA: FontData = FontData::new(&[
299        0x40, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xC0,
300        0x00, 0xC0, 0x00, 0xC0, 0x00, 0x40, 0x00, 0xC0, 0x00, 0x40, 0x00, 0x40, 0x00, 0xC0, 0x00,
301        0x40, 0x00,
302    ]);
303
304    static SKIA_GVAR_I_DATA: FontData = FontData::new(&[
305        0x00, 0x08, 0x00, 0x24, 0x00, 0x33, 0x20, 0x00, 0x00, 0x15, 0x20, 0x01, 0x00, 0x1B, 0x20,
306        0x02, 0x00, 0x24, 0x20, 0x03, 0x00, 0x15, 0x20, 0x04, 0x00, 0x26, 0x20, 0x07, 0x00, 0x0D,
307        0x20, 0x06, 0x00, 0x1A, 0x20, 0x05, 0x00, 0x40, 0x01, 0x01, 0x01, 0x81, 0x80, 0x43, 0xFF,
308        0x7E, 0xFF, 0x7E, 0xFF, 0x7E, 0xFF, 0x7E, 0x00, 0x81, 0x45, 0x01, 0x01, 0x01, 0x03, 0x01,
309        0x04, 0x01, 0x04, 0x01, 0x04, 0x01, 0x02, 0x80, 0x40, 0x00, 0x82, 0x81, 0x81, 0x04, 0x3A,
310        0x5A, 0x3E, 0x43, 0x20, 0x81, 0x04, 0x0E, 0x40, 0x15, 0x45, 0x7C, 0x83, 0x00, 0x0D, 0x9E,
311        0xF3, 0xF2, 0xF0, 0xF0, 0xF0, 0xF0, 0xF3, 0x9E, 0xA0, 0xA1, 0xA1, 0xA1, 0x9F, 0x80, 0x00,
312        0x91, 0x81, 0x91, 0x00, 0x0D, 0x0A, 0x0A, 0x09, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A,
313        0x0A, 0x0A, 0x0A, 0x0B, 0x80, 0x00, 0x15, 0x81, 0x81, 0x00, 0xC4, 0x89, 0x00, 0xC4, 0x83,
314        0x00, 0x0D, 0x80, 0x99, 0x98, 0x96, 0x96, 0x96, 0x96, 0x99, 0x80, 0x82, 0x83, 0x83, 0x83,
315        0x81, 0x80, 0x40, 0xFF, 0x18, 0x81, 0x81, 0x04, 0xE6, 0xF9, 0x10, 0x21, 0x02, 0x81, 0x04,
316        0xE8, 0xE5, 0xEB, 0x4D, 0xDA, 0x83, 0x00, 0x0D, 0xCE, 0xD3, 0xD4, 0xD3, 0xD3, 0xD3, 0xD5,
317        0xD2, 0xCE, 0xCC, 0xCD, 0xCD, 0xCD, 0xCD, 0x80, 0x00, 0xA1, 0x81, 0x91, 0x00, 0x0D, 0x07,
318        0x03, 0x04, 0x02, 0x02, 0x02, 0x03, 0x03, 0x07, 0x07, 0x08, 0x08, 0x08, 0x07, 0x80, 0x00,
319        0x09, 0x81, 0x81, 0x00, 0x28, 0x40, 0x00, 0xA4, 0x02, 0x24, 0x24, 0x66, 0x81, 0x04, 0x08,
320        0xFA, 0xFA, 0xFA, 0x28, 0x83, 0x00, 0x82, 0x02, 0xFF, 0xFF, 0xFF, 0x83, 0x02, 0x01, 0x01,
321        0x01, 0x84, 0x91, 0x00, 0x80, 0x06, 0x07, 0x08, 0x08, 0x08, 0x08, 0x0A, 0x07, 0x80, 0x03,
322        0xFE, 0xFF, 0xFF, 0xFF, 0x81, 0x00, 0x08, 0x81, 0x82, 0x02, 0xEE, 0xEE, 0xEE, 0x8B, 0x6D,
323        0x00,
324    ]);
325
326    #[test]
327    fn test_shared_tuples() {
328        #[allow(overflowing_literals)]
329        const MINUS_ONE: F2Dot14 = F2Dot14::from_bits(0xC000);
330        assert_eq!(MINUS_ONE, F2Dot14::from_f32(-1.0));
331
332        static EXPECTED: &[(F2Dot14, F2Dot14)] = &[
333            (F2Dot14::ONE, F2Dot14::ZERO),
334            (MINUS_ONE, F2Dot14::ZERO),
335            (F2Dot14::ZERO, F2Dot14::ONE),
336            (F2Dot14::ZERO, MINUS_ONE),
337            (MINUS_ONE, MINUS_ONE),
338            (F2Dot14::ONE, MINUS_ONE),
339            (F2Dot14::ONE, F2Dot14::ONE),
340            (MINUS_ONE, F2Dot14::ONE),
341        ];
342
343        const N_AXES: u16 = 2;
344
345        let tuples =
346            SharedTuples::read(SKIA_GVAR_SHARED_TUPLES_DATA, EXPECTED.len() as u16, N_AXES)
347                .unwrap();
348        let tuple_vec: Vec<_> = tuples
349            .tuples()
350            .iter()
351            .map(|tup| {
352                let values = tup.unwrap().values();
353                assert_eq!(values.len(), N_AXES as usize);
354                (values[0].get(), values[1].get())
355            })
356            .collect();
357
358        assert_eq!(tuple_vec, EXPECTED);
359    }
360
361    // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html
362    #[test]
363    fn smoke_test() {
364        let header = GlyphVariationDataHeader::read(SKIA_GVAR_I_DATA).unwrap();
365        assert_eq!(header.serialized_data_offset(), 36);
366        assert_eq!(header.tuple_variation_count().count(), 8);
367        let shared_tuples = SharedTuples::read(SKIA_GVAR_SHARED_TUPLES_DATA, 8, 2).unwrap();
368
369        let vardata = GlyphVariationData::new(SKIA_GVAR_I_DATA, 2, shared_tuples).unwrap();
370        assert_eq!(vardata.tuple_count(), 8);
371        let deltas = vardata
372            .tuples()
373            .next()
374            .unwrap()
375            .deltas()
376            .collect::<Vec<_>>();
377        assert_eq!(deltas.len(), 18);
378        static EXPECTED: &[(i32, i32)] = &[
379            (257, 0),
380            (-127, 0),
381            (-128, 58),
382            (-130, 90),
383            (-130, 62),
384            (-130, 67),
385            (-130, 32),
386            (-127, 0),
387            (257, 0),
388            (259, 14),
389            (260, 64),
390            (260, 21),
391            (260, 69),
392            (258, 124),
393            (0, 0),
394            (130, 0),
395            (0, 0),
396            (0, 0),
397        ];
398        let expected = EXPECTED
399            .iter()
400            .copied()
401            .enumerate()
402            .map(|(pos, (x_delta, y_delta))| GlyphDelta {
403                position: pos as _,
404                x_delta,
405                y_delta,
406            })
407            .collect::<Vec<_>>();
408
409        for (a, b) in deltas.iter().zip(expected.iter()) {
410            assert_eq!(a, b);
411        }
412    }
413
414    #[test]
415    fn vazirmatn_var_a() {
416        let gvar = FontRef::new(font_test_data::VAZIRMATN_VAR)
417            .unwrap()
418            .gvar()
419            .unwrap();
420        let a_glyph_var = gvar.glyph_variation_data(GlyphId::new(1)).unwrap().unwrap();
421        assert_eq!(a_glyph_var.axis_count, 1);
422        let mut tuples = a_glyph_var.tuples();
423        let tup1 = tuples.next().unwrap();
424        assert_eq!(tup1.peak().values(), &[F2Dot14::from_f32(-1.0)]);
425        assert_eq!(tup1.deltas().count(), 18);
426        let x_vals = &[
427            -90, -134, 4, -6, -81, 18, -25, -33, -109, -121, -111, -111, -22, -22, 0, -113, 0, 0,
428        ];
429        let y_vals = &[
430            83, 0, 0, 0, 0, 0, 83, 0, 0, 0, -50, 54, 54, -50, 0, 0, -21, 0,
431        ];
432        assert_eq!(tup1.deltas().map(|d| d.x_delta).collect::<Vec<_>>(), x_vals);
433        assert_eq!(tup1.deltas().map(|d| d.y_delta).collect::<Vec<_>>(), y_vals);
434        let tup2 = tuples.next().unwrap();
435        assert_eq!(tup2.peak().values(), &[F2Dot14::from_f32(1.0)]);
436        let x_vals = &[
437            20, 147, -33, -53, 59, -90, 37, -6, 109, 90, -79, -79, -8, -8, 0, 59, 0, 0,
438        ];
439        let y_vals = &[
440            -177, 0, 0, 0, 0, 0, -177, 0, 0, 0, 4, -109, -109, 4, 0, 0, 9, 0,
441        ];
442
443        assert_eq!(tup2.deltas().map(|d| d.x_delta).collect::<Vec<_>>(), x_vals);
444        assert_eq!(tup2.deltas().map(|d| d.y_delta).collect::<Vec<_>>(), y_vals);
445        assert!(tuples.next().is_none());
446    }
447
448    #[test]
449    fn vazirmatn_var_agrave() {
450        let gvar = FontRef::new(font_test_data::VAZIRMATN_VAR)
451            .unwrap()
452            .gvar()
453            .unwrap();
454        let agrave_glyph_var = gvar.glyph_variation_data(GlyphId::new(2)).unwrap().unwrap();
455        let mut tuples = agrave_glyph_var.tuples();
456        let tup1 = tuples.next().unwrap();
457        assert_eq!(
458            tup1.deltas()
459                .map(|d| (d.position, d.x_delta, d.y_delta))
460                .collect::<Vec<_>>(),
461            &[(1, -51, 8), (3, -113, 0)]
462        );
463        let tup2 = tuples.next().unwrap();
464        assert_eq!(
465            tup2.deltas()
466                .map(|d| (d.position, d.x_delta, d.y_delta))
467                .collect::<Vec<_>>(),
468            &[(1, -54, -1), (3, 59, 0)]
469        );
470    }
471
472    #[test]
473    fn vazirmatn_var_grave() {
474        let gvar = FontRef::new(font_test_data::VAZIRMATN_VAR)
475            .unwrap()
476            .gvar()
477            .unwrap();
478        let grave_glyph_var = gvar.glyph_variation_data(GlyphId::new(3)).unwrap().unwrap();
479        let mut tuples = grave_glyph_var.tuples();
480        let tup1 = tuples.next().unwrap();
481        let tup2 = tuples.next().unwrap();
482        assert!(tuples.next().is_none());
483        assert_eq!(tup1.deltas().count(), 8);
484        assert_eq!(
485            tup2.deltas().map(|d| d.y_delta).collect::<Vec<_>>(),
486            &[0, -20, -20, 0, 0, 0, 0, 0]
487        );
488    }
489
490    #[test]
491    fn phantom_point_deltas() {
492        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
493        #[rustfmt::skip]
494        let a_cases = [
495            // (coords, deltas)
496            (&[0.0], [(0.0, 0.0); 4]),
497            (&[1.0], [(0.0, 0.0), (59.0, 0.0), (0.0, 9.0), (0.0, 0.0)]),
498            (&[-1.0], [(0.0, 0.0), (-113.0, 0.0), (0.0, -21.0), (0.0, 0.0)]),
499            (&[0.5], [(0.0, 0.0), (29.5, 0.0), (0.0, 4.5), (0.0, 0.0)]),
500            (&[-0.5], [(0.0, 0.0), (-56.5, 0.0), (0.0, -10.5), (0.0, 0.0)]),
501        ];
502        for (coords, deltas) in a_cases {
503            // This is simple glyph "A"
504            assert_eq!(
505                compute_phantom_deltas(&font, coords, GlyphId::new(1)),
506                deltas
507            );
508            // This is composite glyph "Agrave" with USE_MY_METRICS set on "A" so
509            // the deltas are the same
510            assert_eq!(
511                compute_phantom_deltas(&font, coords, GlyphId::new(2)),
512                deltas
513            );
514        }
515        #[rustfmt::skip]
516        let grave_cases = [
517            // (coords, deltas)
518            (&[0.0], [(0.0, 0.0); 4]),
519            (&[1.0], [(0.0, 0.0), (63.0, 0.0), (0.0, 0.0), (0.0, 0.0)]),
520            (&[-1.0], [(0.0, 0.0), (-96.0, 0.0), (0.0, 0.0), (0.0, 0.0)]),
521            (&[0.5], [(0.0, 0.0), (31.5, 0.0), (0.0, 0.0), (0.0, 0.0)]),
522            (&[-0.5], [(0.0, 0.0), (-48.0, 0.0), (0.0, 0.0), (0.0, 0.0)]),
523        ];
524        // This is simple glyph "grave"
525        for (coords, deltas) in grave_cases {
526            assert_eq!(
527                compute_phantom_deltas(&font, coords, GlyphId::new(3)),
528                deltas
529            );
530        }
531    }
532
533    fn compute_phantom_deltas(
534        font: &FontRef,
535        coords: &[f32],
536        glyph_id: GlyphId,
537    ) -> [(f32, f32); 4] {
538        let loca = font.loca(None).unwrap();
539        let glyf = font.glyf().unwrap();
540        let gvar = font.gvar().unwrap();
541        let coords = coords
542            .iter()
543            .map(|coord| F2Dot14::from_f32(*coord))
544            .collect::<Vec<_>>();
545        gvar.phantom_point_deltas(&glyf, &loca, &coords, glyph_id)
546            .unwrap()
547            .unwrap()
548            .map(|delta| delta.map(Fixed::to_f32))
549            .map(|p| (p.x, p.y))
550    }
551
552    // fuzzer: add with overflow when computing glyph data range
553    // ref: <https://g-issues.oss-fuzz.com/issues/385918147>
554    #[test]
555    fn avoid_data_range_overflow() {
556        // Construct a gvar table with data offsets that overflow
557        // a u32
558        let mut buf = BeBuffer::new();
559        // major/minor version
560        buf = buf.push(1u16).push(0u16);
561        // axis count
562        buf = buf.push(0u16);
563        // shared tuple count and offset
564        buf = buf.push(0u16).push(0u32);
565        // glyph count = 1
566        buf = buf.push(1u16);
567        // flags, bit 1 = 32 bit offsets
568        buf = buf.push(1u16);
569        // variation data offset
570        buf = buf.push(u32::MAX - 10);
571        // two 32-bit entries that overflow when added to the above offset
572        buf = buf.push(0u32).push(11u32);
573        let gvar = Gvar::read(buf.data().into()).unwrap();
574        // don't panic with overflow!
575        let _ = gvar.data_range_for_gid(GlyphId::new(0));
576    }
577
578    // Test that we select the correct component to derive metrics from,
579    // considering ambiguity. Only covers the shallow case.
580    #[test]
581    fn follow_use_my_metrics() {
582        // Load the font and required tables
583        let font = FontRef::new(font_test_data::gvar::USE_MY_METRICS).unwrap();
584        let glyf = font.glyf().unwrap();
585        let loca = font.loca(None).unwrap();
586        let post = font.post().unwrap();
587
588        // Grab names for test quality-of-life and legibility
589        let gids = (0..post.num_glyphs().unwrap())
590            .map(GlyphId16::new)
591            .map(|gid| (post.glyph_name(gid).unwrap(), gid))
592            .collect::<HashMap<_, _>>();
593
594        // Test various cases
595        let (source, _) =
596            find_glyph_and_point_count(&glyf, &loca, gids["neither"].into(), 5).unwrap();
597        assert_eq!(
598            source, gids["neither"],
599            "a composite without any USE_MY_METRICS components should use its own metrics"
600        );
601
602        let (source, _) =
603            find_glyph_and_point_count(&glyf, &loca, gids["firstonly"].into(), 5).unwrap();
604        assert_eq!(
605            source, gids["first"],
606            "a composite with a single USE_MY_METRICS component should use that component's metrics"
607        );
608
609        let (source, _) = find_glyph_and_point_count(&glyf, &loca, gids["both"].into(), 5).unwrap();
610        assert_eq!(
611            source, gids["second"],
612            "a composite with multiple USE_MY_METRICS components should use the last flagged component's metrics"
613         );
614    }
615}