Skip to main content

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