1 //! The [gvar (Glyph Variations)](https://learn.microsoft.com/en-us/typography/opentype/spec/gvar)
2 //! table
3 
4 include!("../../generated/generated_gvar.rs");
5 
6 use core::iter::Skip;
7 
8 use super::variations::{
9     DeltaRunIter, PackedDeltas, PackedPointNumbers, PackedPointNumbersIter, Tuple,
10     TupleVariationCount, TupleVariationHeader, TupleVariationHeaderIter,
11 };
12 
13 #[derive(Clone, Copy, Debug)]
14 pub struct U16Or32(u32);
15 
16 impl ReadArgs for U16Or32 {
17     type Args = GvarFlags;
18 }
19 
20 impl ComputeSize for U16Or32 {
compute_size(args: &GvarFlags) -> usize21     fn compute_size(args: &GvarFlags) -> usize {
22         if args.contains(GvarFlags::LONG_OFFSETS) {
23             4
24         } else {
25             2
26         }
27     }
28 }
29 
30 impl FontReadWithArgs<'_> for U16Or32 {
read_with_args(data: FontData<'_>, args: &Self::Args) -> Result<Self, ReadError>31     fn read_with_args(data: FontData<'_>, args: &Self::Args) -> Result<Self, ReadError> {
32         if args.contains(GvarFlags::LONG_OFFSETS) {
33             data.read_at::<u32>(0).map(Self)
34         } else {
35             data.read_at::<u16>(0).map(|v| Self(v as u32 * 2))
36         }
37     }
38 }
39 
40 impl U16Or32 {
41     #[inline]
get(self) -> u3242     pub fn get(self) -> u32 {
43         self.0
44     }
45 }
46 
47 #[derive(Clone)]
48 pub struct GlyphVariationData<'a> {
49     axis_count: u16,
50     shared_tuples: SharedTuples<'a>,
51     shared_point_numbers: Option<PackedPointNumbers<'a>>,
52     tuple_count: TupleVariationCount,
53     // the data for all the tuple variation headers
54     header_data: FontData<'a>,
55     // the data for all the tuple bodies
56     serialized_data: FontData<'a>,
57 }
58 
59 impl<'a> GlyphVariationDataHeader<'a> {
raw_tuple_header_data(&self) -> FontData<'a>60     fn raw_tuple_header_data(&self) -> FontData<'a> {
61         let range = self.shape.tuple_variation_headers_byte_range();
62         self.data.split_off(range.start).unwrap()
63     }
64 }
65 
66 impl<'a> Gvar<'a> {
data_for_gid(&self, gid: GlyphId) -> Result<FontData<'a>, ReadError>67     fn data_for_gid(&self, gid: GlyphId) -> Result<FontData<'a>, ReadError> {
68         let start_idx = gid.to_u16() as usize;
69         let end_idx = start_idx + 1;
70         let data_start = self.glyph_variation_data_array_offset();
71         let start = data_start + self.glyph_variation_data_offsets().get(start_idx)?.get();
72         let end = data_start + self.glyph_variation_data_offsets().get(end_idx)?.get();
73 
74         self.data
75             .slice(start as usize..end as usize)
76             .ok_or(ReadError::OutOfBounds)
77     }
78 
79     /// Get the variation data for a specific glyph.
glyph_variation_data(&self, gid: GlyphId) -> Result<GlyphVariationData<'a>, ReadError>80     pub fn glyph_variation_data(&self, gid: GlyphId) -> Result<GlyphVariationData<'a>, ReadError> {
81         let shared_tuples = self.shared_tuples()?;
82         let axis_count = self.axis_count();
83         let data = self.data_for_gid(gid)?;
84         GlyphVariationData::new(data, axis_count, shared_tuples)
85     }
86 }
87 
88 impl<'a> GlyphVariationData<'a> {
new( data: FontData<'a>, axis_count: u16, shared_tuples: SharedTuples<'a>, ) -> Result<Self, ReadError>89     pub(crate) fn new(
90         data: FontData<'a>,
91         axis_count: u16,
92         shared_tuples: SharedTuples<'a>,
93     ) -> Result<Self, ReadError> {
94         let header = GlyphVariationDataHeader::read(data)?;
95 
96         let header_data = header.raw_tuple_header_data();
97         let count = header.tuple_variation_count();
98         let data = header.serialized_data()?;
99 
100         // if there are shared point numbers, get them now
101         let (shared_point_numbers, serialized_data) =
102             if header.tuple_variation_count().shared_point_numbers() {
103                 let (packed, data) = PackedPointNumbers::split_off_front(data);
104                 (Some(packed), data)
105             } else {
106                 (None, data)
107             };
108 
109         Ok(GlyphVariationData {
110             tuple_count: count,
111             axis_count,
112             shared_tuples,
113             shared_point_numbers,
114             header_data,
115             serialized_data,
116         })
117     }
118 
119     /// Return an iterator over all of the variation tuples for this glyph.
tuples(&self) -> TupleVariationIter<'a>120     pub fn tuples(&self) -> TupleVariationIter<'a> {
121         TupleVariationIter {
122             current: 0,
123             parent: self.clone(),
124             header_iter: TupleVariationHeaderIter::new(
125                 self.header_data,
126                 self.tuple_count(),
127                 self.axis_count,
128             ),
129             serialized_data: self.serialized_data,
130         }
131     }
132 
133     /// Returns an iterator over all of the pairs of (variation tuple, scalar)
134     /// for this glyph that are active for the given set of normalized
135     /// coordinates.
active_tuples_at( &self, coords: &'a [F2Dot14], ) -> impl Iterator<Item = (TupleVariation<'a>, Fixed)> + 'a136     pub fn active_tuples_at(
137         &self,
138         coords: &'a [F2Dot14],
139     ) -> impl Iterator<Item = (TupleVariation<'a>, Fixed)> + 'a {
140         self.tuples().filter_map(|tuple| {
141             let scaler = tuple.compute_scalar(coords)?;
142             Some((tuple, scaler))
143         })
144     }
145 
tuple_count(&self) -> usize146     fn tuple_count(&self) -> usize {
147         self.tuple_count.count() as usize
148     }
149 }
150 
151 /// An iterator over the [`TupleVariation`]s for a specific glyph.
152 pub struct TupleVariationIter<'a> {
153     current: usize,
154     parent: GlyphVariationData<'a>,
155     header_iter: TupleVariationHeaderIter<'a>,
156     serialized_data: FontData<'a>,
157 }
158 
159 impl<'a> TupleVariationIter<'a> {
next_tuple(&mut self) -> Option<TupleVariation<'a>>160     fn next_tuple(&mut self) -> Option<TupleVariation<'a>> {
161         if self.parent.tuple_count() == self.current {
162             return None;
163         }
164         self.current += 1;
165 
166         // FIXME: is it okay to discard an error here?
167         let header = self.header_iter.next()?.ok()?;
168         let data_len = header.variation_data_size() as usize;
169         let var_data = self.serialized_data.take_up_to(data_len)?;
170 
171         let (point_numbers, packed_deltas) = if header.tuple_index().private_point_numbers() {
172             PackedPointNumbers::split_off_front(var_data)
173         } else {
174             (self.parent.shared_point_numbers.clone()?, var_data)
175         };
176         Some(TupleVariation {
177             axis_count: self.parent.axis_count,
178             header,
179             shared_tuples: self.parent.shared_tuples.clone(),
180             packed_deltas: PackedDeltas::new(packed_deltas),
181             point_numbers,
182         })
183     }
184 }
185 
186 impl<'a> Iterator for TupleVariationIter<'a> {
187     type Item = TupleVariation<'a>;
188 
next(&mut self) -> Option<Self::Item>189     fn next(&mut self) -> Option<Self::Item> {
190         self.next_tuple()
191     }
192 }
193 
194 /// A single set of tuple variation data
195 #[derive(Clone)]
196 pub struct TupleVariation<'a> {
197     axis_count: u16,
198     header: TupleVariationHeader<'a>,
199     shared_tuples: SharedTuples<'a>,
200     packed_deltas: PackedDeltas<'a>,
201     point_numbers: PackedPointNumbers<'a>,
202 }
203 
204 impl<'a> TupleVariation<'a> {
205     /// Returns true if this tuple provides deltas for all points in a glyph.
has_deltas_for_all_points(&self) -> bool206     pub fn has_deltas_for_all_points(&self) -> bool {
207         self.point_numbers.count() == 0
208     }
209 
point_numbers(&'a self) -> PackedPointNumbersIter<'a>210     pub fn point_numbers(&'a self) -> PackedPointNumbersIter<'a> {
211         self.point_numbers.iter()
212     }
213 
214     /// Returns the 'peak' tuple for this variation
peak(&self) -> Tuple<'a>215     pub fn peak(&self) -> Tuple<'a> {
216         self.header
217             .tuple_index()
218             .tuple_records_index()
219             .and_then(|idx| self.shared_tuples.tuples().get(idx as usize).ok())
220             .or_else(|| self.header.peak_tuple())
221             .unwrap_or_default()
222     }
223 
224     // transcribed from pinot/moscato
225     /// Compute the scalar for a this tuple at a given point in design space.
226     ///
227     /// The `coords` slice must be of lesser or equal length to the number of axes.
228     /// If it is less, missing (trailing) axes will be assumed to have zero values.
229     ///
230     /// Returns `None` if this tuple is not applicable at the provided coordinates
231     /// (e.g. if the resulting scalar is zero).
compute_scalar(&self, coords: &[F2Dot14]) -> Option<Fixed>232     pub fn compute_scalar(&self, coords: &[F2Dot14]) -> Option<Fixed> {
233         const ZERO: Fixed = Fixed::ZERO;
234         let mut scalar = Fixed::ONE;
235         let peak = self.peak();
236         let inter_start = self.header.intermediate_start_tuple();
237         let inter_end = self.header.intermediate_end_tuple();
238         if peak.len() != self.axis_count as usize {
239             return None;
240         }
241 
242         for i in 0..self.axis_count {
243             let i = i as usize;
244             let coord = coords.get(i).copied().unwrap_or_default().to_fixed();
245             let peak = peak.get(i).unwrap_or_default().to_fixed();
246             if peak == ZERO || peak == coord {
247                 continue;
248             }
249 
250             if coord == ZERO {
251                 return None;
252             }
253 
254             if let (Some(inter_start), Some(inter_end)) = (&inter_start, &inter_end) {
255                 let start = inter_start.get(i).unwrap_or_default().to_fixed();
256                 let end = inter_end.get(i).unwrap_or_default().to_fixed();
257                 if coord <= start || coord >= end {
258                     return None;
259                 }
260                 if coord < peak {
261                     scalar = scalar.mul_div(coord - start, peak - start);
262                 } else {
263                     scalar = scalar.mul_div(end - coord, end - peak);
264                 }
265             } else {
266                 if coord < peak.min(ZERO) || coord > peak.max(ZERO) {
267                     return None;
268                 }
269                 scalar = scalar.mul_div(coord, peak);
270             }
271         }
272         Some(scalar)
273     }
274 
275     /// Iterate over the deltas for this tuple.
276     ///
277     /// This does not account for scaling. Returns only explicitly encoded
278     /// deltas, e.g. an omission by IUP will not be present.
deltas(&'a self) -> DeltaIter<'a>279     pub fn deltas(&'a self) -> DeltaIter<'a> {
280         DeltaIter::new(&self.point_numbers, &self.packed_deltas)
281     }
282 }
283 
284 /// An iterator over the deltas for a glyph.
285 #[derive(Clone, Debug)]
286 pub struct DeltaIter<'a> {
287     pub cur: usize,
288     // if None all points get deltas, if Some specifies subset of points that do
289     points: Option<PackedPointNumbersIter<'a>>,
290     next_point: usize,
291     x_iter: DeltaRunIter<'a>,
292     y_iter: Skip<DeltaRunIter<'a>>,
293 }
294 
295 impl<'a> DeltaIter<'a> {
new(points: &'a PackedPointNumbers, deltas: &'a PackedDeltas) -> DeltaIter<'a>296     fn new(points: &'a PackedPointNumbers, deltas: &'a PackedDeltas) -> DeltaIter<'a> {
297         let mut points = points.iter();
298         let next_point = points.next();
299         let num_encoded_points = deltas.count() / 2; // x and y encoded independently
300         DeltaIter {
301             cur: 0,
302             points: next_point.map(|_| points),
303             next_point: next_point.unwrap_or_default() as usize,
304             x_iter: deltas.iter(),
305             y_iter: deltas.iter().skip(num_encoded_points),
306         }
307     }
308 }
309 
310 /// Delta information for a single point or component in a glyph.
311 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
312 pub struct GlyphDelta {
313     /// the point or component id
314     pub position: u16,
315     /// The x delta
316     pub x_delta: i16,
317     /// The y delta
318     pub y_delta: i16,
319 }
320 
321 impl GlyphDelta {
322     /// Applies a tuple scalar to this delta.
apply_scalar(self, scalar: Fixed) -> Point<Fixed>323     pub fn apply_scalar(self, scalar: Fixed) -> Point<Fixed> {
324         Point::new(self.x_delta as i32, self.y_delta as i32).map(Fixed::from_i32) * scalar
325     }
326 }
327 
328 impl<'a> Iterator for DeltaIter<'a> {
329     type Item = GlyphDelta;
330 
next(&mut self) -> Option<Self::Item>331     fn next(&mut self) -> Option<Self::Item> {
332         let (position, dx, dy) = loop {
333             let position = if let Some(points) = &mut self.points {
334                 // if we have points then result is sparse; only some points have deltas
335                 if self.cur > self.next_point {
336                     self.next_point = points.next()? as usize;
337                 }
338                 self.next_point
339             } else {
340                 // no points, every point has a delta. Just take the next one.
341                 self.cur
342             };
343             if position == self.cur {
344                 break (position, self.x_iter.next()?, self.y_iter.next()?);
345             }
346             self.cur += 1;
347         };
348         self.cur += 1;
349         Some(GlyphDelta {
350             position: position as u16,
351             x_delta: dx,
352             y_delta: dy,
353         })
354     }
355 }
356 
357 #[cfg(test)]
358 mod tests {
359     use super::*;
360     use crate::{FontRef, TableProvider};
361 
362     // Shared tuples in the 'gvar' table of the Skia font, as printed
363     // in Apple's TrueType specification.
364     // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html
365     static SKIA_GVAR_SHARED_TUPLES_DATA: FontData = FontData::new(&[
366         0x40, 0x00, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0xC0,
367         0x00, 0xC0, 0x00, 0xC0, 0x00, 0x40, 0x00, 0xC0, 0x00, 0x40, 0x00, 0x40, 0x00, 0xC0, 0x00,
368         0x40, 0x00,
369     ]);
370 
371     static SKIA_GVAR_I_DATA: FontData = FontData::new(&[
372         0x00, 0x08, 0x00, 0x24, 0x00, 0x33, 0x20, 0x00, 0x00, 0x15, 0x20, 0x01, 0x00, 0x1B, 0x20,
373         0x02, 0x00, 0x24, 0x20, 0x03, 0x00, 0x15, 0x20, 0x04, 0x00, 0x26, 0x20, 0x07, 0x00, 0x0D,
374         0x20, 0x06, 0x00, 0x1A, 0x20, 0x05, 0x00, 0x40, 0x01, 0x01, 0x01, 0x81, 0x80, 0x43, 0xFF,
375         0x7E, 0xFF, 0x7E, 0xFF, 0x7E, 0xFF, 0x7E, 0x00, 0x81, 0x45, 0x01, 0x01, 0x01, 0x03, 0x01,
376         0x04, 0x01, 0x04, 0x01, 0x04, 0x01, 0x02, 0x80, 0x40, 0x00, 0x82, 0x81, 0x81, 0x04, 0x3A,
377         0x5A, 0x3E, 0x43, 0x20, 0x81, 0x04, 0x0E, 0x40, 0x15, 0x45, 0x7C, 0x83, 0x00, 0x0D, 0x9E,
378         0xF3, 0xF2, 0xF0, 0xF0, 0xF0, 0xF0, 0xF3, 0x9E, 0xA0, 0xA1, 0xA1, 0xA1, 0x9F, 0x80, 0x00,
379         0x91, 0x81, 0x91, 0x00, 0x0D, 0x0A, 0x0A, 0x09, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A, 0x0A,
380         0x0A, 0x0A, 0x0A, 0x0B, 0x80, 0x00, 0x15, 0x81, 0x81, 0x00, 0xC4, 0x89, 0x00, 0xC4, 0x83,
381         0x00, 0x0D, 0x80, 0x99, 0x98, 0x96, 0x96, 0x96, 0x96, 0x99, 0x80, 0x82, 0x83, 0x83, 0x83,
382         0x81, 0x80, 0x40, 0xFF, 0x18, 0x81, 0x81, 0x04, 0xE6, 0xF9, 0x10, 0x21, 0x02, 0x81, 0x04,
383         0xE8, 0xE5, 0xEB, 0x4D, 0xDA, 0x83, 0x00, 0x0D, 0xCE, 0xD3, 0xD4, 0xD3, 0xD3, 0xD3, 0xD5,
384         0xD2, 0xCE, 0xCC, 0xCD, 0xCD, 0xCD, 0xCD, 0x80, 0x00, 0xA1, 0x81, 0x91, 0x00, 0x0D, 0x07,
385         0x03, 0x04, 0x02, 0x02, 0x02, 0x03, 0x03, 0x07, 0x07, 0x08, 0x08, 0x08, 0x07, 0x80, 0x00,
386         0x09, 0x81, 0x81, 0x00, 0x28, 0x40, 0x00, 0xA4, 0x02, 0x24, 0x24, 0x66, 0x81, 0x04, 0x08,
387         0xFA, 0xFA, 0xFA, 0x28, 0x83, 0x00, 0x82, 0x02, 0xFF, 0xFF, 0xFF, 0x83, 0x02, 0x01, 0x01,
388         0x01, 0x84, 0x91, 0x00, 0x80, 0x06, 0x07, 0x08, 0x08, 0x08, 0x08, 0x0A, 0x07, 0x80, 0x03,
389         0xFE, 0xFF, 0xFF, 0xFF, 0x81, 0x00, 0x08, 0x81, 0x82, 0x02, 0xEE, 0xEE, 0xEE, 0x8B, 0x6D,
390         0x00,
391     ]);
392 
393     #[test]
test_shared_tuples()394     fn test_shared_tuples() {
395         #[allow(overflowing_literals)]
396         const MINUS_ONE: F2Dot14 = F2Dot14::from_bits(0xC000);
397         assert_eq!(MINUS_ONE, F2Dot14::from_f32(-1.0));
398 
399         static EXPECTED: &[(F2Dot14, F2Dot14)] = &[
400             (F2Dot14::ONE, F2Dot14::ZERO),
401             (MINUS_ONE, F2Dot14::ZERO),
402             (F2Dot14::ZERO, F2Dot14::ONE),
403             (F2Dot14::ZERO, MINUS_ONE),
404             (MINUS_ONE, MINUS_ONE),
405             (F2Dot14::ONE, MINUS_ONE),
406             (F2Dot14::ONE, F2Dot14::ONE),
407             (MINUS_ONE, F2Dot14::ONE),
408         ];
409 
410         const N_AXES: u16 = 2;
411 
412         let tuples =
413             SharedTuples::read(SKIA_GVAR_SHARED_TUPLES_DATA, EXPECTED.len() as u16, N_AXES)
414                 .unwrap();
415         let tuple_vec: Vec<_> = tuples
416             .tuples()
417             .iter()
418             .map(|tup| {
419                 let values = tup.unwrap().values();
420                 assert_eq!(values.len(), N_AXES as usize);
421                 (values[0].get(), values[1].get())
422             })
423             .collect();
424 
425         assert_eq!(tuple_vec, EXPECTED);
426     }
427 
428     // https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6gvar.html
429     #[test]
smoke_test()430     fn smoke_test() {
431         let header = GlyphVariationDataHeader::read(SKIA_GVAR_I_DATA).unwrap();
432         assert_eq!(header.serialized_data_offset(), 36);
433         assert_eq!(header.tuple_variation_count().count(), 8);
434         let shared_tuples = SharedTuples::read(SKIA_GVAR_SHARED_TUPLES_DATA, 8, 2).unwrap();
435 
436         let vardata = GlyphVariationData::new(SKIA_GVAR_I_DATA, 2, shared_tuples).unwrap();
437         assert_eq!(vardata.tuple_count(), 8);
438         let deltas = vardata
439             .tuples()
440             .next()
441             .unwrap()
442             .deltas()
443             .collect::<Vec<_>>();
444         assert_eq!(deltas.len(), 18);
445         static EXPECTED: &[(i16, i16)] = &[
446             (257, 0),
447             (-127, 0),
448             (-128, 58),
449             (-130, 90),
450             (-130, 62),
451             (-130, 67),
452             (-130, 32),
453             (-127, 0),
454             (257, 0),
455             (259, 14),
456             (260, 64),
457             (260, 21),
458             (260, 69),
459             (258, 124),
460             (0, 0),
461             (130, 0),
462             (0, 0),
463             (0, 0),
464         ];
465         let expected = EXPECTED
466             .iter()
467             .copied()
468             .enumerate()
469             .map(|(pos, (x_delta, y_delta))| GlyphDelta {
470                 position: pos as _,
471                 x_delta,
472                 y_delta,
473             })
474             .collect::<Vec<_>>();
475 
476         for (a, b) in deltas.iter().zip(expected.iter()) {
477             assert_eq!(a, b);
478         }
479     }
480 
481     #[test]
vazirmatn_var_a()482     fn vazirmatn_var_a() {
483         let gvar = FontRef::new(font_test_data::VAZIRMATN_VAR)
484             .unwrap()
485             .gvar()
486             .unwrap();
487         let a_glyph_var = gvar.glyph_variation_data(GlyphId::new(1)).unwrap();
488         assert_eq!(a_glyph_var.axis_count, 1);
489         let mut tuples = a_glyph_var.tuples();
490         let tup1 = tuples.next().unwrap();
491         assert_eq!(tup1.peak().values(), &[F2Dot14::from_f32(-1.0)]);
492         assert_eq!(tup1.deltas().count(), 18);
493         let x_vals = &[
494             -90, -134, 4, -6, -81, 18, -25, -33, -109, -121, -111, -111, -22, -22, 0, -113, 0, 0,
495         ];
496         let y_vals = &[83, 0, 0, 0, 0, 0, 83, 0, 0, 0, -50, 54, 54, -50, 0, 0, 0, 0];
497         assert_eq!(tup1.deltas().map(|d| d.x_delta).collect::<Vec<_>>(), x_vals);
498         assert_eq!(tup1.deltas().map(|d| d.y_delta).collect::<Vec<_>>(), y_vals);
499         let tup2 = tuples.next().unwrap();
500         assert_eq!(tup2.peak().values(), &[F2Dot14::from_f32(1.0)]);
501         let x_vals = &[
502             20, 147, -33, -53, 59, -90, 37, -6, 109, 90, -79, -79, -8, -8, 0, 59, 0, 0,
503         ];
504         let y_vals = &[
505             -177, 0, 0, 0, 0, 0, -177, 0, 0, 0, 4, -109, -109, 4, 0, 0, 0, 0,
506         ];
507 
508         assert_eq!(tup2.deltas().map(|d| d.x_delta).collect::<Vec<_>>(), x_vals);
509         assert_eq!(tup2.deltas().map(|d| d.y_delta).collect::<Vec<_>>(), y_vals);
510         assert!(tuples.next().is_none());
511     }
512 
513     #[test]
vazirmatn_var_agrave()514     fn vazirmatn_var_agrave() {
515         let gvar = FontRef::new(font_test_data::VAZIRMATN_VAR)
516             .unwrap()
517             .gvar()
518             .unwrap();
519         let agrave_glyph_var = gvar.glyph_variation_data(GlyphId::new(2)).unwrap();
520         let mut tuples = agrave_glyph_var.tuples();
521         let tup1 = tuples.next().unwrap();
522         assert_eq!(
523             tup1.deltas()
524                 .map(|d| (d.position, d.x_delta, d.y_delta))
525                 .collect::<Vec<_>>(),
526             &[(1, -51, 8), (3, -113, 0)]
527         );
528         let tup2 = tuples.next().unwrap();
529         assert_eq!(
530             tup2.deltas()
531                 .map(|d| (d.position, d.x_delta, d.y_delta))
532                 .collect::<Vec<_>>(),
533             &[(1, -54, -1), (3, 59, 0)]
534         );
535     }
536 
537     #[test]
vazirmatn_var_grave()538     fn vazirmatn_var_grave() {
539         let gvar = FontRef::new(font_test_data::VAZIRMATN_VAR)
540             .unwrap()
541             .gvar()
542             .unwrap();
543         let grave_glyph_var = gvar.glyph_variation_data(GlyphId::new(3)).unwrap();
544         let mut tuples = grave_glyph_var.tuples();
545         let tup1 = tuples.next().unwrap();
546         let tup2 = tuples.next().unwrap();
547         assert!(tuples.next().is_none());
548         assert_eq!(tup1.deltas().count(), 8);
549         assert_eq!(
550             tup2.deltas().map(|d| d.y_delta).collect::<Vec<_>>(),
551             &[0, -20, -20, 0, 0, 0, 0, 0]
552         );
553     }
554 }
555