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 first 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),
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, if one of the components has the
263            // USE_MY_METRICS flag set, recurse into the glyph referenced
264            // by that component. Otherwise, return the composite glyph
265            // itself and the number of components as the point count.
266            let mut count = 0;
267            for component in composite.components() {
268                count += 1;
269                if component
270                    .flags
271                    .contains(CompositeGlyphFlags::USE_MY_METRICS)
272                {
273                    return find_glyph_and_point_count(
274                        glyf,
275                        loca,
276                        component.glyph.into(),
277                        recurse_depth + 1,
278                    );
279                }
280            }
281            Ok((glyph_id, count))
282        }
283    }
284}
285
286#[cfg(test)]
287mod tests {
288    use font_test_data::bebuffer::BeBuffer;
289
290    use super::*;
291    use crate::{FontRef, TableProvider};
292
293    // Shared tuples in the 'gvar' table of the Skia font, as printed
294    // in Apple's TrueType specification.
295    // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html
296    static SKIA_GVAR_SHARED_TUPLES_DATA: FontData = FontData::new(&[
297        0x40, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xC0,
298        0x00, 0xC0, 0x00, 0xC0, 0x00, 0x40, 0x00, 0xC0, 0x00, 0x40, 0x00, 0x40, 0x00, 0xC0, 0x00,
299        0x40, 0x00,
300    ]);
301
302    static SKIA_GVAR_I_DATA: FontData = FontData::new(&[
303        0x00, 0x08, 0x00, 0x24, 0x00, 0x33, 0x20, 0x00, 0x00, 0x15, 0x20, 0x01, 0x00, 0x1B, 0x20,
304        0x02, 0x00, 0x24, 0x20, 0x03, 0x00, 0x15, 0x20, 0x04, 0x00, 0x26, 0x20, 0x07, 0x00, 0x0D,
305        0x20, 0x06, 0x00, 0x1A, 0x20, 0x05, 0x00, 0x40, 0x01, 0x01, 0x01, 0x81, 0x80, 0x43, 0xFF,
306        0x7E, 0xFF, 0x7E, 0xFF, 0x7E, 0xFF, 0x7E, 0x00, 0x81, 0x45, 0x01, 0x01, 0x01, 0x03, 0x01,
307        0x04, 0x01, 0x04, 0x01, 0x04, 0x01, 0x02, 0x80, 0x40, 0x00, 0x82, 0x81, 0x81, 0x04, 0x3A,
308        0x5A, 0x3E, 0x43, 0x20, 0x81, 0x04, 0x0E, 0x40, 0x15, 0x45, 0x7C, 0x83, 0x00, 0x0D, 0x9E,
309        0xF3, 0xF2, 0xF0, 0xF0, 0xF0, 0xF0, 0xF3, 0x9E, 0xA0, 0xA1, 0xA1, 0xA1, 0x9F, 0x80, 0x00,
310        0x91, 0x81, 0x91, 0x00, 0x0D, 0x0A, 0x0A, 0x09, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A,
311        0x0A, 0x0A, 0x0A, 0x0B, 0x80, 0x00, 0x15, 0x81, 0x81, 0x00, 0xC4, 0x89, 0x00, 0xC4, 0x83,
312        0x00, 0x0D, 0x80, 0x99, 0x98, 0x96, 0x96, 0x96, 0x96, 0x99, 0x80, 0x82, 0x83, 0x83, 0x83,
313        0x81, 0x80, 0x40, 0xFF, 0x18, 0x81, 0x81, 0x04, 0xE6, 0xF9, 0x10, 0x21, 0x02, 0x81, 0x04,
314        0xE8, 0xE5, 0xEB, 0x4D, 0xDA, 0x83, 0x00, 0x0D, 0xCE, 0xD3, 0xD4, 0xD3, 0xD3, 0xD3, 0xD5,
315        0xD2, 0xCE, 0xCC, 0xCD, 0xCD, 0xCD, 0xCD, 0x80, 0x00, 0xA1, 0x81, 0x91, 0x00, 0x0D, 0x07,
316        0x03, 0x04, 0x02, 0x02, 0x02, 0x03, 0x03, 0x07, 0x07, 0x08, 0x08, 0x08, 0x07, 0x80, 0x00,
317        0x09, 0x81, 0x81, 0x00, 0x28, 0x40, 0x00, 0xA4, 0x02, 0x24, 0x24, 0x66, 0x81, 0x04, 0x08,
318        0xFA, 0xFA, 0xFA, 0x28, 0x83, 0x00, 0x82, 0x02, 0xFF, 0xFF, 0xFF, 0x83, 0x02, 0x01, 0x01,
319        0x01, 0x84, 0x91, 0x00, 0x80, 0x06, 0x07, 0x08, 0x08, 0x08, 0x08, 0x0A, 0x07, 0x80, 0x03,
320        0xFE, 0xFF, 0xFF, 0xFF, 0x81, 0x00, 0x08, 0x81, 0x82, 0x02, 0xEE, 0xEE, 0xEE, 0x8B, 0x6D,
321        0x00,
322    ]);
323
324    #[test]
325    fn test_shared_tuples() {
326        #[allow(overflowing_literals)]
327        const MINUS_ONE: F2Dot14 = F2Dot14::from_bits(0xC000);
328        assert_eq!(MINUS_ONE, F2Dot14::from_f32(-1.0));
329
330        static EXPECTED: &[(F2Dot14, F2Dot14)] = &[
331            (F2Dot14::ONE, F2Dot14::ZERO),
332            (MINUS_ONE, F2Dot14::ZERO),
333            (F2Dot14::ZERO, F2Dot14::ONE),
334            (F2Dot14::ZERO, MINUS_ONE),
335            (MINUS_ONE, MINUS_ONE),
336            (F2Dot14::ONE, MINUS_ONE),
337            (F2Dot14::ONE, F2Dot14::ONE),
338            (MINUS_ONE, F2Dot14::ONE),
339        ];
340
341        const N_AXES: u16 = 2;
342
343        let tuples =
344            SharedTuples::read(SKIA_GVAR_SHARED_TUPLES_DATA, EXPECTED.len() as u16, N_AXES)
345                .unwrap();
346        let tuple_vec: Vec<_> = tuples
347            .tuples()
348            .iter()
349            .map(|tup| {
350                let values = tup.unwrap().values();
351                assert_eq!(values.len(), N_AXES as usize);
352                (values[0].get(), values[1].get())
353            })
354            .collect();
355
356        assert_eq!(tuple_vec, EXPECTED);
357    }
358
359    // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html
360    #[test]
361    fn smoke_test() {
362        let header = GlyphVariationDataHeader::read(SKIA_GVAR_I_DATA).unwrap();
363        assert_eq!(header.serialized_data_offset(), 36);
364        assert_eq!(header.tuple_variation_count().count(), 8);
365        let shared_tuples = SharedTuples::read(SKIA_GVAR_SHARED_TUPLES_DATA, 8, 2).unwrap();
366
367        let vardata = GlyphVariationData::new(SKIA_GVAR_I_DATA, 2, shared_tuples).unwrap();
368        assert_eq!(vardata.tuple_count(), 8);
369        let deltas = vardata
370            .tuples()
371            .next()
372            .unwrap()
373            .deltas()
374            .collect::<Vec<_>>();
375        assert_eq!(deltas.len(), 18);
376        static EXPECTED: &[(i32, i32)] = &[
377            (257, 0),
378            (-127, 0),
379            (-128, 58),
380            (-130, 90),
381            (-130, 62),
382            (-130, 67),
383            (-130, 32),
384            (-127, 0),
385            (257, 0),
386            (259, 14),
387            (260, 64),
388            (260, 21),
389            (260, 69),
390            (258, 124),
391            (0, 0),
392            (130, 0),
393            (0, 0),
394            (0, 0),
395        ];
396        let expected = EXPECTED
397            .iter()
398            .copied()
399            .enumerate()
400            .map(|(pos, (x_delta, y_delta))| GlyphDelta {
401                position: pos as _,
402                x_delta,
403                y_delta,
404            })
405            .collect::<Vec<_>>();
406
407        for (a, b) in deltas.iter().zip(expected.iter()) {
408            assert_eq!(a, b);
409        }
410    }
411
412    #[test]
413    fn vazirmatn_var_a() {
414        let gvar = FontRef::new(font_test_data::VAZIRMATN_VAR)
415            .unwrap()
416            .gvar()
417            .unwrap();
418        let a_glyph_var = gvar.glyph_variation_data(GlyphId::new(1)).unwrap().unwrap();
419        assert_eq!(a_glyph_var.axis_count, 1);
420        let mut tuples = a_glyph_var.tuples();
421        let tup1 = tuples.next().unwrap();
422        assert_eq!(tup1.peak().values(), &[F2Dot14::from_f32(-1.0)]);
423        assert_eq!(tup1.deltas().count(), 18);
424        let x_vals = &[
425            -90, -134, 4, -6, -81, 18, -25, -33, -109, -121, -111, -111, -22, -22, 0, -113, 0, 0,
426        ];
427        let y_vals = &[
428            83, 0, 0, 0, 0, 0, 83, 0, 0, 0, -50, 54, 54, -50, 0, 0, -21, 0,
429        ];
430        assert_eq!(tup1.deltas().map(|d| d.x_delta).collect::<Vec<_>>(), x_vals);
431        assert_eq!(tup1.deltas().map(|d| d.y_delta).collect::<Vec<_>>(), y_vals);
432        let tup2 = tuples.next().unwrap();
433        assert_eq!(tup2.peak().values(), &[F2Dot14::from_f32(1.0)]);
434        let x_vals = &[
435            20, 147, -33, -53, 59, -90, 37, -6, 109, 90, -79, -79, -8, -8, 0, 59, 0, 0,
436        ];
437        let y_vals = &[
438            -177, 0, 0, 0, 0, 0, -177, 0, 0, 0, 4, -109, -109, 4, 0, 0, 9, 0,
439        ];
440
441        assert_eq!(tup2.deltas().map(|d| d.x_delta).collect::<Vec<_>>(), x_vals);
442        assert_eq!(tup2.deltas().map(|d| d.y_delta).collect::<Vec<_>>(), y_vals);
443        assert!(tuples.next().is_none());
444    }
445
446    #[test]
447    fn vazirmatn_var_agrave() {
448        let gvar = FontRef::new(font_test_data::VAZIRMATN_VAR)
449            .unwrap()
450            .gvar()
451            .unwrap();
452        let agrave_glyph_var = gvar.glyph_variation_data(GlyphId::new(2)).unwrap().unwrap();
453        let mut tuples = agrave_glyph_var.tuples();
454        let tup1 = tuples.next().unwrap();
455        assert_eq!(
456            tup1.deltas()
457                .map(|d| (d.position, d.x_delta, d.y_delta))
458                .collect::<Vec<_>>(),
459            &[(1, -51, 8), (3, -113, 0)]
460        );
461        let tup2 = tuples.next().unwrap();
462        assert_eq!(
463            tup2.deltas()
464                .map(|d| (d.position, d.x_delta, d.y_delta))
465                .collect::<Vec<_>>(),
466            &[(1, -54, -1), (3, 59, 0)]
467        );
468    }
469
470    #[test]
471    fn vazirmatn_var_grave() {
472        let gvar = FontRef::new(font_test_data::VAZIRMATN_VAR)
473            .unwrap()
474            .gvar()
475            .unwrap();
476        let grave_glyph_var = gvar.glyph_variation_data(GlyphId::new(3)).unwrap().unwrap();
477        let mut tuples = grave_glyph_var.tuples();
478        let tup1 = tuples.next().unwrap();
479        let tup2 = tuples.next().unwrap();
480        assert!(tuples.next().is_none());
481        assert_eq!(tup1.deltas().count(), 8);
482        assert_eq!(
483            tup2.deltas().map(|d| d.y_delta).collect::<Vec<_>>(),
484            &[0, -20, -20, 0, 0, 0, 0, 0]
485        );
486    }
487
488    #[test]
489    fn phantom_point_deltas() {
490        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
491        #[rustfmt::skip]
492        let a_cases = [
493            // (coords, deltas)
494            (&[0.0], [(0.0, 0.0); 4]),
495            (&[1.0], [(0.0, 0.0), (59.0, 0.0), (0.0, 9.0), (0.0, 0.0)]),
496            (&[-1.0], [(0.0, 0.0), (-113.0, 0.0), (0.0, -21.0), (0.0, 0.0)]),
497            (&[0.5], [(0.0, 0.0), (29.5, 0.0), (0.0, 4.5), (0.0, 0.0)]),
498            (&[-0.5], [(0.0, 0.0), (-56.5, 0.0), (0.0, -10.5), (0.0, 0.0)]),
499        ];
500        for (coords, deltas) in a_cases {
501            // This is simple glyph "A"
502            assert_eq!(
503                compute_phantom_deltas(&font, coords, GlyphId::new(1)),
504                deltas
505            );
506            // This is composite glyph "Agrave" with USE_MY_METRICS set on "A" so
507            // the deltas are the same
508            assert_eq!(
509                compute_phantom_deltas(&font, coords, GlyphId::new(2)),
510                deltas
511            );
512        }
513        #[rustfmt::skip]
514        let grave_cases = [
515            // (coords, deltas)
516            (&[0.0], [(0.0, 0.0); 4]),
517            (&[1.0], [(0.0, 0.0), (63.0, 0.0), (0.0, 0.0), (0.0, 0.0)]),
518            (&[-1.0], [(0.0, 0.0), (-96.0, 0.0), (0.0, 0.0), (0.0, 0.0)]),
519            (&[0.5], [(0.0, 0.0), (31.5, 0.0), (0.0, 0.0), (0.0, 0.0)]),
520            (&[-0.5], [(0.0, 0.0), (-48.0, 0.0), (0.0, 0.0), (0.0, 0.0)]),
521        ];
522        // This is simple glyph "grave"
523        for (coords, deltas) in grave_cases {
524            assert_eq!(
525                compute_phantom_deltas(&font, coords, GlyphId::new(3)),
526                deltas
527            );
528        }
529    }
530
531    fn compute_phantom_deltas(
532        font: &FontRef,
533        coords: &[f32],
534        glyph_id: GlyphId,
535    ) -> [(f32, f32); 4] {
536        let loca = font.loca(None).unwrap();
537        let glyf = font.glyf().unwrap();
538        let gvar = font.gvar().unwrap();
539        let coords = coords
540            .iter()
541            .map(|coord| F2Dot14::from_f32(*coord))
542            .collect::<Vec<_>>();
543        gvar.phantom_point_deltas(&glyf, &loca, &coords, glyph_id)
544            .unwrap()
545            .unwrap()
546            .map(|delta| delta.map(Fixed::to_f32))
547            .map(|p| (p.x, p.y))
548    }
549
550    // fuzzer: add with overflow when computing glyph data range
551    // ref: <https://g-issues.oss-fuzz.com/issues/385918147>
552    #[test]
553    fn avoid_data_range_overflow() {
554        // Construct a gvar table with data offsets that overflow
555        // a u32
556        let mut buf = BeBuffer::new();
557        // major/minor version
558        buf = buf.push(1u16).push(0u16);
559        // axis count
560        buf = buf.push(0u16);
561        // shared tuple count and offset
562        buf = buf.push(0u16).push(0u32);
563        // glyph count = 1
564        buf = buf.push(1u16);
565        // flags, bit 1 = 32 bit offsets
566        buf = buf.push(1u16);
567        // variation data offset
568        buf = buf.push(u32::MAX - 10);
569        // two 32-bit entries that overflow when added to the above offset
570        buf = buf.push(0u32).push(11u32);
571        let gvar = Gvar::read(buf.data().into()).unwrap();
572        // don't panic with overflow!
573        let _ = gvar.data_range_for_gid(GlyphId::new(0));
574    }
575}