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