Skip to main content

write_fonts/tables/
gvar.rs

1//! The gvar table
2
3include!("../../generated/generated_gvar.rs");
4
5use std::collections::HashMap;
6
7use indexmap::IndexMap;
8
9use crate::{
10    collections::HasLen,
11    tables::variations::{Deltas, TupleVariationStoreInputError},
12    OffsetMarker,
13};
14
15use super::variations::{
16    compute_shared_points, compute_tuple_variation_count, compute_tuple_variation_data_offset,
17    PackedDeltas, PackedPointNumbers, Tuple, TupleVariationCount, TupleVariationHeader,
18};
19
20pub use super::variations::Tent;
21
22pub mod iup;
23
24/// Variation data for a single glyph, before it is compiled
25#[derive(Clone, Debug)]
26pub struct GlyphVariations {
27    gid: GlyphId,
28    variations: Vec<GlyphDeltas>,
29}
30
31/// Glyph deltas for one point in the design space.
32pub type GlyphDeltas = Deltas<GlyphDelta>;
33
34/// An error representing invalid input when building a gvar table
35pub type GvarInputError = TupleVariationStoreInputError<GlyphId>;
36
37/// A delta for a single value in a glyph.
38///
39/// This includes a flag indicating whether or not this delta is required (i.e
40/// it cannot be interpolated from neighbouring deltas and coordinates).
41/// This is only relevant for simple glyphs; interpolatable points may be omitted
42/// in the final binary when doing so saves space.
43/// See <https://learn.microsoft.com/en-us/typography/opentype/spec/gvar#inferred-deltas-for-un-referenced-point-numbers>
44/// for more information.
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
46#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
47pub struct GlyphDelta {
48    pub x: i16,
49    pub y: i16,
50    /// This delta must be included, i.e. cannot be interpolated
51    pub required: bool,
52}
53
54impl Gvar {
55    /// Construct a gvar table from a vector of per-glyph variations and the axis count.
56    ///
57    /// Variations must be present for each glyph, but may be empty.
58    /// For non-empty variations, the axis count must be equal to the provided
59    /// axis count, as specified by the 'fvar' table.
60    pub fn new(
61        mut variations: Vec<GlyphVariations>,
62        axis_count: u16,
63    ) -> Result<Self, GvarInputError> {
64        fn compute_shared_peak_tuples(glyphs: &[GlyphVariations]) -> Vec<Tuple> {
65            const MAX_SHARED_TUPLES: usize = 4095;
66            let mut peak_tuple_counts = IndexMap::new();
67            for glyph in glyphs {
68                glyph.count_peak_tuples(&mut peak_tuple_counts);
69            }
70            let mut to_share = peak_tuple_counts
71                .into_iter()
72                .filter(|(_, n)| *n > 1)
73                .collect::<Vec<_>>();
74            // prefer IndexMap::sort_by_key over HashMap::sort_unstable_by_key so the
75            // order of the shared tuples with equal count doesn't change randomly
76            // but is kept stable to ensure builds are deterministic.
77            to_share.sort_by_key(|(_, n)| std::cmp::Reverse(*n));
78            to_share.truncate(MAX_SHARED_TUPLES);
79            to_share.into_iter().map(|(t, _)| t.to_owned()).collect()
80        }
81
82        for var in &variations {
83            var.validate()?;
84        }
85
86        if let Some(bad_var) = variations
87            .iter()
88            .find(|var| var.axis_count().is_some() && var.axis_count().unwrap() != axis_count)
89        {
90            return Err(TupleVariationStoreInputError::UnexpectedAxisCount {
91                index: bad_var.gid,
92                expected: axis_count,
93                actual: bad_var.axis_count().unwrap(),
94            });
95        }
96
97        let shared = compute_shared_peak_tuples(&variations);
98        let shared_idx_map = shared
99            .iter()
100            .enumerate()
101            .map(|(i, x)| (x, i as u16))
102            .collect();
103        variations.sort_unstable_by_key(|g| g.gid);
104        let glyphs = variations
105            .into_iter()
106            .map(|raw_g| raw_g.build(&shared_idx_map))
107            .collect();
108
109        Ok(Gvar {
110            axis_count,
111            shared_tuples: SharedTuples::new(shared).into(),
112            glyph_variation_data_offsets: glyphs,
113        })
114    }
115}
116
117impl GvarExtended {
118    /// Constructs GVAR, with 24-bit glyph counts and per-glyph data offsets.
119    pub fn new(variations: Vec<GlyphVariations>, axis_count: u16) -> Result<Self, GvarInputError> {
120        let table = Gvar::new(variations, axis_count)?;
121        Ok(Self {
122            axis_count: table.axis_count,
123            shared_tuples: table.shared_tuples,
124            glyph_variation_data_offsets: table.glyph_variation_data_offsets,
125        })
126    }
127}
128
129macro_rules! impl_gvar_writer {
130    ($table:ident, $count:ty, $extended:expr, $max_count:expr) => {
131        impl $table {
132            fn compute_flags(&self) -> GvarFlags {
133                let max_offset = self
134                    .glyph_variation_data_offsets
135                    .iter()
136                    .fold(0_u64, |acc, val| {
137                        let length = val.length_for_format($extended) as u64;
138                        acc + length + length % 2
139                    });
140
141                if max_offset / 2 <= (u16::MAX as u64) {
142                    GvarFlags::default()
143                } else {
144                    GvarFlags::LONG_OFFSETS
145                }
146            }
147
148            fn compute_glyph_count(&self) -> $count {
149                self.glyph_variation_data_offsets.len().try_into().unwrap()
150            }
151
152            fn compute_shared_tuples_offset(&self) -> u32 {
153                const BASE_OFFSET: usize = MajorMinor::RAW_BYTE_LEN
154                    + u16::RAW_BYTE_LEN // axis count
155                    + u16::RAW_BYTE_LEN // shared tuples count
156                    + Offset32::RAW_BYTE_LEN
157                    + <$count>::RAW_BYTE_LEN + u16::RAW_BYTE_LEN // glyph count, flags
158                    + u32::RAW_BYTE_LEN; // glyph_variation_data_array_offset
159
160                let bytes_per_offset = if self.compute_flags() == GvarFlags::LONG_OFFSETS {
161                    u32::RAW_BYTE_LEN
162                } else {
163                    u16::RAW_BYTE_LEN
164                };
165
166                let offsets_len = (self.glyph_variation_data_offsets.len() + 1) * bytes_per_offset;
167
168                (BASE_OFFSET + offsets_len).try_into().unwrap()
169            }
170
171            fn compute_data_array_offset(&self) -> u32 {
172                let shared_tuples_len: u32 =
173                    (array_len(&self.shared_tuples) * self.axis_count as usize * 2)
174                        .try_into()
175                        .unwrap();
176                self.compute_shared_tuples_offset() + shared_tuples_len
177            }
178
179            fn compile_variation_data(&self) -> GlyphDataWriter<'_> {
180                GlyphDataWriter {
181                    long_offsets: self.compute_flags() == GvarFlags::LONG_OFFSETS,
182                    extended: $extended,
183                    shared_tuples: &self.shared_tuples,
184                    data: &self.glyph_variation_data_offsets,
185                }
186            }
187
188            fn validate_glyph_variation_data(&self, ctx: &mut ValidationCtx) {
189                if self.glyph_variation_data_offsets.len() > $max_count {
190                    ctx.report("glyph count overflows");
191                }
192                for glyph in &self.glyph_variation_data_offsets {
193                    glyph.validate_for_format(ctx, $extended);
194                }
195                let long_offsets = self.compute_flags().contains(GvarFlags::LONG_OFFSETS);
196                let data_len: u64 = self.glyph_variation_data_offsets.iter().map(|glyph| {
197                    let len = glyph.length_for_format($extended) as u64;
198                    len + if long_offsets { 0 } else { len % 2 }
199                }).sum();
200                let prefix_len = 18 + <$count>::RAW_BYTE_LEN as u64
201                    + (self.glyph_variation_data_offsets.len() as u64 + 1) * if long_offsets { 4 } else { 2 }
202                    + array_len(&self.shared_tuples) as u64 * self.axis_count as u64 * 2;
203                if prefix_len + data_len > u32::MAX as u64 {
204                    ctx.report("glyph variation offsets overflow 32 bits");
205                }
206            }
207        }
208    };
209}
210
211impl_gvar_writer!(Gvar, u16, false, u16::MAX as usize);
212impl_gvar_writer!(GvarExtended, Uint24, true, usize::from(Uint24::MAX));
213
214impl GlyphVariations {
215    /// Construct a new set of variation deltas for a glyph.
216    pub fn new(gid: GlyphId, variations: Vec<GlyphDeltas>) -> Self {
217        Self { gid, variations }
218    }
219
220    /// called when we build gvar, so we only return errors in one place
221    fn validate(&self) -> Result<(), GvarInputError> {
222        let (axis_count, delta_len) = self
223            .variations
224            .first()
225            .map(|var| (var.peak_tuple.len(), var.deltas.len()))
226            .unwrap_or_default();
227        for var in &self.variations {
228            if var.peak_tuple.len() != axis_count {
229                return Err(TupleVariationStoreInputError::InconsistentAxisCount(
230                    self.gid,
231                ));
232            }
233            if let Some((start, end)) = var.intermediate_region.as_ref() {
234                if start.len() != axis_count || end.len() != axis_count {
235                    return Err(TupleVariationStoreInputError::InconsistentTupleLengths(
236                        self.gid,
237                    ));
238                }
239            }
240            if var.deltas.len() != delta_len {
241                return Err(TupleVariationStoreInputError::InconsistentDeltaLength(
242                    self.gid,
243                ));
244            }
245        }
246        Ok(())
247    }
248
249    /// Will be `None` if there are no variations for this glyph
250    pub fn axis_count(&self) -> Option<u16> {
251        self.variations.first().map(|var| var.peak_tuple.len())
252    }
253
254    fn count_peak_tuples<'a>(&'a self, counter: &mut IndexMap<&'a Tuple, usize>) {
255        for tuple in &self.variations {
256            *counter.entry(&tuple.peak_tuple).or_default() += 1;
257        }
258    }
259
260    fn build(self, shared_tuple_map: &HashMap<&Tuple, u16>) -> GlyphVariationData {
261        let shared_points = compute_shared_points(&self.variations);
262
263        let (tuple_headers, tuple_data): (Vec<_>, Vec<_>) = self
264            .variations
265            .into_iter()
266            .map(|tup| tup.build(shared_tuple_map, shared_points.as_ref()))
267            .unzip();
268
269        let mut temp = GlyphVariationData {
270            tuple_variation_headers: tuple_headers,
271            shared_point_numbers: shared_points,
272            per_tuple_data: tuple_data,
273            length: 0,
274        };
275
276        temp.length = temp.compute_size();
277        temp
278    }
279}
280
281impl GlyphDelta {
282    /// Create a new delta value.
283    pub fn new(x: i16, y: i16, required: bool) -> Self {
284        Self { x, y, required }
285    }
286
287    /// Create a new delta value that must be encoded (cannot be interpolated)
288    pub fn required(x: i16, y: i16) -> Self {
289        Self::new(x, y, true)
290    }
291
292    /// Create a new delta value that may be omitted (can be interpolated)
293    pub fn optional(x: i16, y: i16) -> Self {
294        Self::new(x, y, false)
295    }
296}
297
298impl GlyphDeltas {
299    /// Create a new set of deltas.
300    ///
301    /// A None delta means do not explicitly encode, typically because IUP suggests
302    /// it isn't required.
303    pub fn new(tents: Vec<Tent>, deltas: Vec<GlyphDelta>) -> Self {
304        let peak_tuple = Tuple::new(tents.iter().map(Tent::peak).collect());
305
306        // File size optimisation: if all the intermediates can be derived from
307        // the relevant peak values, don't serialize them.
308        // https://github.com/fonttools/fonttools/blob/b467579c/Lib/fontTools/ttLib/tables/TupleVariation.py#L184-L193
309        let intermediate_region = if tents.iter().any(Tent::requires_intermediate) {
310            Some(tents.iter().map(Tent::bounds).unzip())
311        } else {
312            None
313        };
314
315        // at construction time we build both iup optimized & not versions
316        // of ourselves, to determine what representation is most efficient;
317        // the caller will look at the generated packed points to decide which
318        // set should be shared.
319        let best_point_packing = Self::pick_best_point_number_repr(&deltas);
320        GlyphDeltas {
321            peak_tuple,
322            intermediate_region,
323            deltas,
324            best_point_packing,
325        }
326    }
327
328    // this is a type method just to expose it for testing, we call it before
329    // we finish instantiating self.
330    //
331    // we do a lot of duplicate work here with creating & throwing away
332    // buffers, and that can be improved at the cost of a bit more complexity
333    // <https://github.com/googlefonts/fontations/issues/635>
334    fn pick_best_point_number_repr(deltas: &[GlyphDelta]) -> PackedPointNumbers {
335        if deltas.iter().all(|d| d.required) {
336            return PackedPointNumbers::All;
337        }
338
339        let dense = Self::build_non_sparse_data(deltas);
340        let sparse = Self::build_sparse_data(deltas);
341        let dense_size = dense.compute_size();
342        let sparse_size = sparse.compute_size();
343        log::trace!("dense {dense_size}, sparse {sparse_size}");
344        if sparse_size < dense_size {
345            sparse.private_point_numbers.unwrap()
346        } else {
347            PackedPointNumbers::All
348        }
349    }
350
351    fn build_non_sparse_data(deltas: &[GlyphDelta]) -> GlyphTupleVariationData {
352        let (x_deltas, y_deltas) = deltas
353            .iter()
354            .map(|delta| (delta.x as i32, delta.y as i32))
355            .unzip();
356        GlyphTupleVariationData {
357            private_point_numbers: Some(PackedPointNumbers::All),
358            x_deltas: PackedDeltas::new(x_deltas),
359            y_deltas: PackedDeltas::new(y_deltas),
360        }
361    }
362
363    fn build_sparse_data(deltas: &[GlyphDelta]) -> GlyphTupleVariationData {
364        let (x_deltas, y_deltas) = deltas
365            .iter()
366            .filter_map(|delta| delta.required.then_some((delta.x as i32, delta.y as i32)))
367            .unzip();
368        let point_numbers = deltas
369            .iter()
370            .enumerate()
371            .filter_map(|(i, delta)| delta.required.then_some(i as u16))
372            .collect();
373        GlyphTupleVariationData {
374            private_point_numbers: Some(PackedPointNumbers::Some(point_numbers)),
375            x_deltas: PackedDeltas::new(x_deltas),
376            y_deltas: PackedDeltas::new(y_deltas),
377        }
378    }
379
380    // shared points is just "whatever points, if any, are shared." We are
381    // responsible for seeing if these are actually our points, in which case
382    // we are using shared points.
383    fn build(
384        self,
385        shared_tuple_map: &HashMap<&Tuple, u16>,
386        shared_points: Option<&PackedPointNumbers>,
387    ) -> (TupleVariationHeader, GlyphTupleVariationData) {
388        let GlyphDeltas {
389            peak_tuple,
390            intermediate_region,
391            deltas,
392            best_point_packing: point_numbers,
393        } = self;
394
395        let (idx, peak_tuple) = match shared_tuple_map.get(&peak_tuple) {
396            Some(idx) => (Some(*idx), None),
397            None => (None, Some(peak_tuple)),
398        };
399
400        let has_private_points = Some(&point_numbers) != shared_points;
401        let (x_deltas, y_deltas) = match &point_numbers {
402            PackedPointNumbers::All => deltas.iter().map(|d| (d.x as i32, d.y as i32)).unzip(),
403            PackedPointNumbers::Some(pts) => pts
404                .iter()
405                .map(|idx| {
406                    let delta = deltas[*idx as usize];
407                    (delta.x as i32, delta.y as i32)
408                })
409                .unzip(),
410        };
411
412        let data = GlyphTupleVariationData {
413            private_point_numbers: has_private_points.then_some(point_numbers),
414            x_deltas: PackedDeltas::new(x_deltas),
415            y_deltas: PackedDeltas::new(y_deltas),
416        };
417        let data_size = data.compute_size();
418
419        let header = TupleVariationHeader::new(
420            data_size,
421            idx,
422            peak_tuple,
423            intermediate_region,
424            has_private_points,
425        );
426
427        (header, data)
428    }
429}
430
431/// The serializable representation of a glyph's variation data
432#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
433#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
434pub struct GlyphVariationData {
435    tuple_variation_headers: Vec<TupleVariationHeader>,
436    // optional; present if multiple variations have the same point numbers
437    shared_point_numbers: Option<PackedPointNumbers>,
438    per_tuple_data: Vec<GlyphTupleVariationData>,
439    /// calculated length required to store this data
440    ///
441    /// we compute this once up front because we need to know it in a bunch
442    /// of different places (u32 because offsets are max u32)
443    length: u32,
444}
445
446/// The serializable representation of a single glyph tuple variation data
447#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
448#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
449struct GlyphTupleVariationData {
450    // this is possibly shared, if multiple are identical for a given glyph
451    private_point_numbers: Option<PackedPointNumbers>,
452    x_deltas: PackedDeltas,
453    y_deltas: PackedDeltas,
454}
455
456impl GlyphTupleVariationData {
457    fn compute_size(&self) -> u16 {
458        self.private_point_numbers
459            .as_ref()
460            .map(PackedPointNumbers::compute_size)
461            .unwrap_or_default()
462            .checked_add(self.x_deltas.compute_size())
463            .unwrap()
464            .checked_add(self.y_deltas.compute_size())
465            .unwrap()
466    }
467}
468
469impl FontWrite for GlyphTupleVariationData {
470    fn write_into(&self, writer: &mut TableWriter) {
471        self.private_point_numbers.write_into(writer);
472        self.x_deltas.write_into(writer);
473        self.y_deltas.write_into(writer);
474    }
475}
476
477struct GlyphDataWriter<'a> {
478    long_offsets: bool,
479    extended: bool,
480    shared_tuples: &'a SharedTuples,
481    data: &'a [GlyphVariationData],
482}
483
484impl FontWrite for GlyphDataWriter<'_> {
485    fn write_into(&self, writer: &mut TableWriter) {
486        if self.long_offsets {
487            let mut last = 0u32;
488            last.write_into(writer);
489
490            // write all the offsets
491            for glyph in self.data {
492                last += glyph.length_for_format(self.extended);
493                last.write_into(writer);
494            }
495        } else {
496            // for short offsets we divide the real offset by two; this means
497            // we will have to add padding if necessary
498            let mut last = 0u16;
499            last.write_into(writer);
500
501            // write all the offsets
502            for glyph in self.data {
503                let size = glyph.length_for_format(self.extended);
504                // ensure we're always rounding up to the next 2
505                let short_size = (size / 2) + size % 2;
506                last += short_size as u16;
507                last.write_into(writer);
508            }
509        }
510        // then write the shared tuples (should come here according to the spec)
511        // https://learn.microsoft.com/en-us/typography/opentype/spec/gvar#shared-tuples-array
512        // > The shared tuples array follows the GlyphVariationData offsets array at the end of the 'gvar' header.
513        self.shared_tuples.write_into(writer);
514        // then write the actual data
515        for glyph in self.data {
516            if !glyph.is_empty() {
517                glyph.write_with_format(writer, self.extended);
518                // Short offsets align glyph records relative to the data array,
519                // whose absolute start is odd for GVAR's 21-byte header.
520                if !self.long_offsets && glyph.length_for_format(self.extended) % 2 != 0 {
521                    writer.write_slice(&[0]);
522                }
523            }
524        }
525    }
526}
527
528impl GlyphVariationData {
529    fn compute_tuple_variation_count(&self) -> TupleVariationCount {
530        compute_tuple_variation_count(
531            self.tuple_variation_headers.len(),
532            self.shared_point_numbers.is_some(),
533        )
534    }
535
536    fn is_empty(&self) -> bool {
537        self.tuple_variation_headers.is_empty()
538    }
539
540    fn compute_data_offset(&self, extended: bool) -> u32 {
541        compute_tuple_variation_data_offset(
542            &self.tuple_variation_headers,
543            TupleVariationCount::RAW_BYTE_LEN + if extended { 3 } else { 2 },
544        )
545    }
546
547    fn compute_size(&self) -> u32 {
548        if self.is_empty() {
549            return 0;
550        }
551
552        let data_start = self.compute_data_offset(false);
553        let shared_point_len = self
554            .shared_point_numbers
555            .as_ref()
556            .map(|pts| pts.compute_size())
557            .unwrap_or_default() as u32;
558        let tuple_data_len = self
559            .per_tuple_data
560            .iter()
561            .fold(0u32, |acc, tup| acc + tup.compute_size() as u32);
562        data_start + shared_point_len + tuple_data_len
563    }
564
565    fn length_for_format(&self, extended: bool) -> u32 {
566        self.length + u32::from(extended && !self.is_empty())
567    }
568
569    fn validate_for_format(&self, ctx: &mut ValidationCtx, extended: bool) {
570        const MAX_TUPLE_VARIATIONS: usize = 4095;
571        if !(0..=MAX_TUPLE_VARIATIONS).contains(&self.tuple_variation_headers.len()) {
572            ctx.in_field("tuple_variation_headers", |ctx| {
573                ctx.report("expected 0-4095 tuple variation tables")
574            });
575        }
576        self.tuple_variation_headers.validate_impl(ctx);
577        let max_offset = if extended {
578            Uint24::MAX.to_u32()
579        } else {
580            u16::MAX as u32
581        };
582        if self.compute_data_offset(extended) > max_offset {
583            ctx.report("glyph variation data offset overflows");
584        }
585    }
586
587    fn write_with_format(&self, writer: &mut TableWriter, extended: bool) {
588        self.compute_tuple_variation_count().write_into(writer);
589        let offset = self.compute_data_offset(extended);
590        if extended {
591            Uint24::new(offset).write_into(writer);
592        } else {
593            (offset as u16).write_into(writer);
594        }
595        self.tuple_variation_headers.write_into(writer);
596        self.shared_point_numbers.write_into(writer);
597        self.per_tuple_data.write_into(writer);
598    }
599}
600
601impl Extend<F2Dot14> for Tuple {
602    fn extend<T: IntoIterator<Item = F2Dot14>>(&mut self, iter: T) {
603        self.values.extend(iter);
604    }
605}
606
607impl Validate for GlyphVariationData {
608    fn validate_impl(&self, ctx: &mut ValidationCtx) {
609        self.validate_for_format(ctx, false);
610    }
611}
612
613impl FontWrite for GlyphVariationData {
614    fn write_into(&self, writer: &mut TableWriter) {
615        self.write_with_format(writer, false);
616    }
617}
618
619impl HasLen for SharedTuples {
620    fn len(&self) -> usize {
621        self.tuples.len()
622    }
623}
624
625impl FontWrite for TupleVariationCount {
626    fn write_into(&self, writer: &mut TableWriter) {
627        self.bits().write_into(writer)
628    }
629}
630#[cfg(test)]
631mod tests {
632    use super::*;
633
634    #[test]
635    fn extended_gvar_roundtrip_short_and_long_offsets() {
636        for long_offsets in [false, true] {
637            let point_count = if long_offsets { 14000 } else { 4 };
638            let mut variations: Vec<_> = (0..65537)
639                .map(|gid| GlyphVariations::new(GlyphId::new(gid), vec![]))
640                .collect();
641            for gid in [0, 1, 65536] {
642                variations[gid] = GlyphVariations::new(
643                    GlyphId::new(gid as u32),
644                    vec![GlyphDeltas::new(
645                        peaks(vec![F2Dot14::ONE]),
646                        vec![GlyphDelta::required(300, -200); point_count],
647                    )],
648                );
649            }
650            let table = GvarExtended::new(variations.clone(), 1).unwrap();
651            let bytes = crate::dump_table(&table).unwrap();
652            let read =
653                read_fonts::tables::gvar::GvarExtended::read(bytes.as_slice().into()).unwrap();
654            assert_eq!(read.glyph_count().to_u32(), 65537);
655            assert_eq!(read.flags().contains(GvarFlags::LONG_OFFSETS), long_offsets);
656            assert_eq!(read.glyph_variation_data_array_offset() % 2, 1);
657            for gid in [0, 1, 65536] {
658                let var = read
659                    .glyph_variation_data(GlyphId::new(gid))
660                    .unwrap()
661                    .unwrap();
662                let tuples: Vec<_> = var.tuples().collect();
663                assert_eq!(tuples.len(), 1);
664                let deltas: Vec<_> = tuples[0].deltas().map(|d| (d.x_delta, d.y_delta)).collect();
665                assert_eq!(deltas, vec![(300, -200); point_count]);
666            }
667            assert!(read
668                .glyph_variation_data(GlyphId::new(2))
669                .unwrap()
670                .is_none());
671            let legacy = Gvar::new(variations, 1).unwrap();
672            assert!(crate::dump_table(&legacy).is_err());
673        }
674    }
675
676    #[test]
677    fn extended_gvar_data_offset_above_64k() {
678        let mut delta = GlyphDeltas::new(
679            peaks(vec![F2Dot14::ONE; 20]),
680            vec![GlyphDelta::required(1, 0)],
681        );
682        delta.intermediate_region = Some((
683            Tuple::new(vec![F2Dot14::ZERO; 20]),
684            Tuple::new(vec![F2Dot14::ONE; 20]),
685        ));
686        let variations = vec![GlyphVariations::new(GlyphId::new(0), vec![delta; 1000])];
687        let legacy = Gvar::new(variations.clone(), 20).unwrap();
688        assert!(crate::dump_table(&legacy).is_err());
689        let table = GvarExtended::new(variations, 20).unwrap();
690        let bytes = crate::dump_table(&table).unwrap();
691        let read = read_fonts::tables::gvar::GvarExtended::read(bytes.as_slice().into()).unwrap();
692        let raw = read.data_for_gid(GlyphId::new(0)).unwrap().unwrap();
693        let header = read_fonts::tables::gvar::GlyphVariationDataHeaderExtended::read(raw).unwrap();
694        assert!(header.serialized_data_offset().to_u32() > u16::MAX as u32);
695        let data = read.glyph_variation_data(GlyphId::new(0)).unwrap().unwrap();
696        assert_eq!(data.tuples().count(), 1000);
697        assert_eq!(data.active_tuples_at(&[F2Dot14::ONE; 20]).count(), 1000);
698    }
699
700    /// Helper function to concisely state test cases without intermediates.
701    fn peaks(peaks: Vec<F2Dot14>) -> Vec<Tent> {
702        peaks
703            .into_iter()
704            .map(|peak| Tent::new(peak, None))
705            .collect()
706    }
707
708    #[test]
709    fn gvar_smoke_test() {
710        let _ = env_logger::builder().is_test(true).try_init();
711        let table = Gvar::new(
712            vec![
713                GlyphVariations::new(GlyphId::new(0), vec![]),
714                GlyphVariations::new(
715                    GlyphId::new(1),
716                    vec![GlyphDeltas::new(
717                        peaks(vec![F2Dot14::from_f32(1.0), F2Dot14::from_f32(1.0)]),
718                        vec![
719                            GlyphDelta::required(30, 31),
720                            GlyphDelta::required(40, 41),
721                            GlyphDelta::required(-50, -49),
722                            GlyphDelta::required(101, 102),
723                            GlyphDelta::required(10, 11),
724                        ],
725                    )],
726                ),
727                GlyphVariations::new(
728                    GlyphId::new(2),
729                    vec![
730                        GlyphDeltas::new(
731                            peaks(vec![F2Dot14::from_f32(1.0), F2Dot14::from_f32(1.0)]),
732                            vec![
733                                GlyphDelta::required(11, -20),
734                                GlyphDelta::required(69, -41),
735                                GlyphDelta::required(-69, 49),
736                                GlyphDelta::required(168, 101),
737                                GlyphDelta::required(1, 2),
738                            ],
739                        ),
740                        GlyphDeltas::new(
741                            peaks(vec![F2Dot14::from_f32(0.8), F2Dot14::from_f32(1.0)]),
742                            vec![
743                                GlyphDelta::required(3, -200),
744                                GlyphDelta::required(4, -500),
745                                GlyphDelta::required(5, -800),
746                                GlyphDelta::required(6, -1200),
747                                GlyphDelta::required(7, -1500),
748                            ],
749                        ),
750                    ],
751                ),
752            ],
753            2,
754        )
755        .unwrap();
756
757        let g2 = &table.glyph_variation_data_offsets[1];
758        let computed = g2.compute_size();
759        let actual = crate::dump_table(g2).unwrap().len();
760        assert_eq!(computed as usize, actual);
761
762        let bytes = crate::dump_table(&table).unwrap();
763        let gvar = read_fonts::tables::gvar::Gvar::read(FontData::new(&bytes)).unwrap();
764        assert_eq!(gvar.version(), MajorMinor::VERSION_1_0);
765        assert_eq!(gvar.shared_tuple_count(), 1);
766        assert_eq!(gvar.glyph_count(), 3);
767        // Check offsets, the shared_tuples_offset should point to just after the table's header
768        assert_eq!(gvar.shared_tuples_offset(), Offset32::new(28));
769        assert_eq!(gvar.glyph_variation_data_array_offset(), 32);
770
771        let g1 = gvar.glyph_variation_data(GlyphId::new(1)).unwrap().unwrap();
772        let g1tup = g1.tuples().collect::<Vec<_>>();
773        assert_eq!(g1tup.len(), 1);
774
775        let (x, y): (Vec<_>, Vec<_>) = g1tup[0].deltas().map(|d| (d.x_delta, d.y_delta)).unzip();
776        assert_eq!(x, vec![30, 40, -50, 101, 10]);
777        assert_eq!(y, vec![31, 41, -49, 102, 11]);
778
779        let g2 = gvar.glyph_variation_data(GlyphId::new(2)).unwrap().unwrap();
780        let g2tup = g2.tuples().collect::<Vec<_>>();
781        assert_eq!(g2tup.len(), 2);
782
783        let (x, y): (Vec<_>, Vec<_>) = g2tup[0].deltas().map(|d| (d.x_delta, d.y_delta)).unzip();
784        assert_eq!(x, vec![11, 69, -69, 168, 1]);
785        assert_eq!(y, vec![-20, -41, 49, 101, 2]);
786
787        let (x, y): (Vec<_>, Vec<_>) = g2tup[1].deltas().map(|d| (d.x_delta, d.y_delta)).unzip();
788
789        assert_eq!(x, vec![3, 4, 5, 6, 7]);
790        assert_eq!(y, vec![-200, -500, -800, -1200, -1500]);
791    }
792
793    #[test]
794    fn use_iup_when_appropriate() {
795        // IFF iup provides space savings, we should prefer it.
796        let _ = env_logger::builder().is_test(true).try_init();
797        let gid = GlyphId::new(0);
798        let table = Gvar::new(
799            vec![GlyphVariations::new(
800                gid,
801                vec![GlyphDeltas::new(
802                    peaks(vec![F2Dot14::from_f32(1.0), F2Dot14::from_f32(1.0)]),
803                    vec![
804                        GlyphDelta::required(30, 31),
805                        GlyphDelta::optional(30, 31),
806                        GlyphDelta::optional(30, 31),
807                        GlyphDelta::required(101, 102),
808                        GlyphDelta::required(10, 11),
809                        GlyphDelta::optional(10, 11),
810                    ],
811                )],
812            )],
813            2,
814        )
815        .unwrap();
816
817        let bytes = crate::dump_table(&table).unwrap();
818        let gvar = read_fonts::tables::gvar::Gvar::read(FontData::new(&bytes)).unwrap();
819        assert_eq!(gvar.version(), MajorMinor::VERSION_1_0);
820        assert_eq!(gvar.shared_tuple_count(), 0);
821        assert_eq!(gvar.glyph_count(), 1);
822
823        let g1 = gvar.glyph_variation_data(gid).unwrap().unwrap();
824        let g1tup = g1.tuples().collect::<Vec<_>>();
825        assert_eq!(g1tup.len(), 1);
826        let tuple_variation = &g1tup[0];
827
828        assert!(!tuple_variation.has_deltas_for_all_points());
829        assert_eq!(
830            vec![0, 3, 4],
831            tuple_variation.point_numbers().collect::<Vec<_>>()
832        );
833
834        let points: Vec<_> = tuple_variation
835            .deltas()
836            .map(|d| (d.x_delta, d.y_delta))
837            .collect();
838        assert_eq!(points, vec![(30, 31), (101, 102), (10, 11)]);
839    }
840
841    #[test]
842    fn disregard_iup_when_appropriate() {
843        // if the cost of encoding the list of points is greater than the savings
844        // from omitting some deltas, we should just encode explicit zeros
845        let points = vec![
846            GlyphDelta::required(1, 2),
847            GlyphDelta::required(3, 4),
848            GlyphDelta::required(5, 6),
849            GlyphDelta::optional(5, 6),
850            GlyphDelta::required(7, 8),
851        ];
852        let gid = GlyphId::new(0);
853        let table = Gvar::new(
854            vec![GlyphVariations::new(
855                gid,
856                vec![GlyphDeltas::new(
857                    peaks(vec![F2Dot14::from_f32(1.0), F2Dot14::from_f32(1.0)]),
858                    points,
859                )],
860            )],
861            2,
862        )
863        .unwrap();
864        let bytes = crate::dump_table(&table).unwrap();
865        let gvar = read_fonts::tables::gvar::Gvar::read(FontData::new(&bytes)).unwrap();
866        assert_eq!(gvar.version(), MajorMinor::VERSION_1_0);
867        assert_eq!(gvar.shared_tuple_count(), 0);
868        assert_eq!(gvar.glyph_count(), 1);
869
870        let g1 = gvar.glyph_variation_data(gid).unwrap().unwrap();
871        let g1tup = g1.tuples().collect::<Vec<_>>();
872        assert_eq!(g1tup.len(), 1);
873        let tuple_variation = &g1tup[0];
874
875        assert!(tuple_variation.has_deltas_for_all_points());
876        let points: Vec<_> = tuple_variation
877            .deltas()
878            .map(|d| (d.x_delta, d.y_delta))
879            .collect();
880        assert_eq!(points, vec![(1, 2), (3, 4), (5, 6), (5, 6), (7, 8)]);
881    }
882
883    #[test]
884    fn share_points() {
885        let _ = env_logger::builder().is_test(true).try_init();
886        let variations = GlyphVariations::new(
887            GlyphId::new(0),
888            vec![
889                GlyphDeltas::new(
890                    peaks(vec![F2Dot14::from_f32(1.0), F2Dot14::from_f32(1.0)]),
891                    vec![
892                        GlyphDelta::required(1, 2),
893                        GlyphDelta::optional(3, 4),
894                        GlyphDelta::required(5, 6),
895                        GlyphDelta::optional(5, 6),
896                        GlyphDelta::required(7, 8),
897                        GlyphDelta::optional(7, 8),
898                    ],
899                ),
900                GlyphDeltas::new(
901                    peaks(vec![F2Dot14::from_f32(-1.0), F2Dot14::from_f32(-1.0)]),
902                    vec![
903                        GlyphDelta::required(10, 20),
904                        GlyphDelta::optional(30, 40),
905                        GlyphDelta::required(50, 60),
906                        GlyphDelta::optional(50, 60),
907                        GlyphDelta::required(70, 80),
908                        GlyphDelta::optional(70, 80),
909                    ],
910                ),
911            ],
912        );
913
914        assert_eq!(
915            compute_shared_points(&variations.variations),
916            Some(PackedPointNumbers::Some(vec![0, 2, 4]))
917        )
918    }
919
920    #[test]
921    fn share_all_points() {
922        let variations = GlyphVariations::new(
923            GlyphId::new(0),
924            vec![
925                GlyphDeltas::new(
926                    peaks(vec![F2Dot14::from_f32(1.0), F2Dot14::from_f32(1.0)]),
927                    vec![
928                        GlyphDelta::required(1, 2),
929                        GlyphDelta::required(3, 4),
930                        GlyphDelta::required(5, 6),
931                    ],
932                ),
933                GlyphDeltas::new(
934                    peaks(vec![F2Dot14::from_f32(-1.0), F2Dot14::from_f32(-1.0)]),
935                    vec![
936                        GlyphDelta::required(2, 4),
937                        GlyphDelta::required(6, 8),
938                        GlyphDelta::required(7, 9),
939                    ],
940                ),
941            ],
942        );
943
944        let shared_tups = HashMap::new();
945        let built = variations.build(&shared_tups);
946        assert_eq!(built.shared_point_numbers, Some(PackedPointNumbers::All))
947    }
948
949    // three tuples with three different packedpoint representations means
950    // that we should have no shared points
951    #[test]
952    fn dont_share_unique_points() {
953        let variations = GlyphVariations::new(
954            GlyphId::new(0),
955            vec![
956                GlyphDeltas::new(
957                    peaks(vec![F2Dot14::from_f32(1.0), F2Dot14::from_f32(1.0)]),
958                    vec![
959                        GlyphDelta::required(1, 2),
960                        GlyphDelta::optional(3, 4),
961                        GlyphDelta::required(5, 6),
962                        GlyphDelta::optional(5, 6),
963                        GlyphDelta::required(7, 8),
964                        GlyphDelta::optional(7, 8),
965                    ],
966                ),
967                GlyphDeltas::new(
968                    peaks(vec![F2Dot14::from_f32(-1.0), F2Dot14::from_f32(-1.0)]),
969                    vec![
970                        GlyphDelta::required(10, 20),
971                        GlyphDelta::required(35, 40),
972                        GlyphDelta::required(50, 60),
973                        GlyphDelta::optional(50, 60),
974                        GlyphDelta::required(70, 80),
975                        GlyphDelta::optional(70, 80),
976                    ],
977                ),
978                GlyphDeltas::new(
979                    peaks(vec![F2Dot14::from_f32(0.5), F2Dot14::from_f32(1.0)]),
980                    vec![
981                        GlyphDelta::required(1, 2),
982                        GlyphDelta::optional(3, 4),
983                        GlyphDelta::required(5, 6),
984                        GlyphDelta::optional(5, 6),
985                        GlyphDelta::optional(7, 8),
986                        GlyphDelta::optional(7, 8),
987                    ],
988                ),
989            ],
990        );
991
992        let shared_tups = HashMap::new();
993        let built = variations.build(&shared_tups);
994        assert!(built.shared_point_numbers.is_none());
995    }
996
997    // comparing our behaviour against what we know fonttools does.
998    #[test]
999    #[allow(non_snake_case)]
1000    fn oswald_Lcaron() {
1001        let _ = env_logger::builder().is_test(true).try_init();
1002        // in this glyph, it is more efficient to encode all points for the first
1003        // tuple, but sparse points for the second (the single y delta in the
1004        // second tuple means you can't encode the y-deltas as 'all zero')
1005        let variations = GlyphVariations::new(
1006            GlyphId::new(0),
1007            vec![
1008                GlyphDeltas::new(
1009                    peaks(vec![F2Dot14::from_f32(-1.0), F2Dot14::from_f32(-1.0)]),
1010                    vec![
1011                        GlyphDelta::optional(0, 0),
1012                        GlyphDelta::required(35, 0),
1013                        GlyphDelta::optional(0, 0),
1014                        GlyphDelta::required(-24, 0),
1015                        GlyphDelta::optional(0, 0),
1016                        GlyphDelta::optional(0, 0),
1017                    ],
1018                ),
1019                GlyphDeltas::new(
1020                    peaks(vec![F2Dot14::from_f32(1.0), F2Dot14::from_f32(1.0)]),
1021                    vec![
1022                        GlyphDelta::optional(0, 0),
1023                        GlyphDelta::required(26, 15),
1024                        GlyphDelta::optional(0, 0),
1025                        GlyphDelta::required(46, 0),
1026                        GlyphDelta::optional(0, 0),
1027                        GlyphDelta::optional(0, 0),
1028                    ],
1029                ),
1030            ],
1031        );
1032        assert!(compute_shared_points(&variations.variations).is_none());
1033        let tups = HashMap::new();
1034        let built = variations.build(&tups);
1035        assert_eq!(
1036            built.per_tuple_data[0].private_point_numbers,
1037            Some(PackedPointNumbers::All)
1038        );
1039        assert_eq!(
1040            built.per_tuple_data[1].private_point_numbers,
1041            Some(PackedPointNumbers::Some(vec![1, 3]))
1042        );
1043    }
1044
1045    #[test]
1046    fn compute_shared_points_is_deterministic() {
1047        // The deltas for glyph "etatonos.sc.ss06" in GoogleSans-VF are such that the
1048        // TupleVariationStore's shared set of point numbers could potentially be
1049        // computed as either PackedPointNumbers::All or PackedPointNumbers::Some([1, 3])
1050        // without affecting the size (or correctness) of the serialized data.
1051        // However we want to ensure that the result is deterministic, and doesn't
1052        // depend on e.g. HashMap random iteration order.
1053        // https://github.com/googlefonts/fontc/issues/647
1054        let _ = env_logger::builder().is_test(true).try_init();
1055        let variations = GlyphVariations::new(
1056            GlyphId::NOTDEF,
1057            vec![
1058                GlyphDeltas::new(
1059                    peaks(vec![
1060                        F2Dot14::from_f32(-1.0),
1061                        F2Dot14::from_f32(0.0),
1062                        F2Dot14::from_f32(0.0),
1063                    ]),
1064                    vec![
1065                        GlyphDelta::optional(0, 0),
1066                        GlyphDelta::required(-17, -4),
1067                        GlyphDelta::optional(0, 0),
1068                        GlyphDelta::required(-28, 0),
1069                        GlyphDelta::optional(0, 0),
1070                        GlyphDelta::optional(0, 0),
1071                    ],
1072                ),
1073                GlyphDeltas::new(
1074                    peaks(vec![
1075                        F2Dot14::from_f32(0.0),
1076                        F2Dot14::from_f32(1.0),
1077                        F2Dot14::from_f32(0.0),
1078                    ]),
1079                    vec![
1080                        GlyphDelta::optional(0, 0),
1081                        GlyphDelta::required(0, -10),
1082                        GlyphDelta::optional(0, 0),
1083                        GlyphDelta::required(34, 0),
1084                        GlyphDelta::optional(0, 0),
1085                        GlyphDelta::optional(0, 0),
1086                    ],
1087                ),
1088                GlyphDeltas::new(
1089                    peaks(vec![
1090                        F2Dot14::from_f32(0.0),
1091                        F2Dot14::from_f32(0.0),
1092                        F2Dot14::from_f32(-1.0),
1093                    ]),
1094                    vec![
1095                        GlyphDelta::required(0, 0),
1096                        GlyphDelta::optional(0, 0),
1097                        GlyphDelta::optional(0, 0),
1098                        GlyphDelta::optional(0, 0),
1099                        GlyphDelta::optional(0, 0),
1100                        GlyphDelta::optional(0, 0),
1101                    ],
1102                ),
1103                GlyphDeltas::new(
1104                    peaks(vec![
1105                        F2Dot14::from_f32(0.0),
1106                        F2Dot14::from_f32(0.0),
1107                        F2Dot14::from_f32(1.0),
1108                    ]),
1109                    vec![
1110                        GlyphDelta::required(0, 0),
1111                        GlyphDelta::optional(0, 0),
1112                        GlyphDelta::optional(0, 0),
1113                        GlyphDelta::optional(0, 0),
1114                        GlyphDelta::optional(0, 0),
1115                        GlyphDelta::optional(0, 0),
1116                    ],
1117                ),
1118                GlyphDeltas::new(
1119                    peaks(vec![
1120                        F2Dot14::from_f32(-1.0),
1121                        F2Dot14::from_f32(1.0),
1122                        F2Dot14::from_f32(0.0),
1123                    ]),
1124                    vec![
1125                        GlyphDelta::optional(0, 0),
1126                        GlyphDelta::required(-1, 10),
1127                        GlyphDelta::optional(0, 0),
1128                        GlyphDelta::required(-9, 0),
1129                        GlyphDelta::optional(0, 0),
1130                        GlyphDelta::optional(0, 0),
1131                    ],
1132                ),
1133                GlyphDeltas::new(
1134                    peaks(vec![
1135                        F2Dot14::from_f32(-1.0),
1136                        F2Dot14::from_f32(0.0),
1137                        F2Dot14::from_f32(-1.0),
1138                    ]),
1139                    vec![
1140                        GlyphDelta::required(0, 0),
1141                        GlyphDelta::optional(0, 0),
1142                        GlyphDelta::optional(0, 0),
1143                        GlyphDelta::optional(0, 0),
1144                        GlyphDelta::optional(0, 0),
1145                        GlyphDelta::optional(0, 0),
1146                    ],
1147                ),
1148                GlyphDeltas::new(
1149                    peaks(vec![
1150                        F2Dot14::from_f32(-1.0),
1151                        F2Dot14::from_f32(0.0),
1152                        F2Dot14::from_f32(1.0),
1153                    ]),
1154                    vec![
1155                        GlyphDelta::required(0, 0),
1156                        GlyphDelta::optional(0, 0),
1157                        GlyphDelta::optional(0, 0),
1158                        GlyphDelta::optional(0, 0),
1159                        GlyphDelta::optional(0, 0),
1160                        GlyphDelta::optional(0, 0),
1161                    ],
1162                ),
1163            ],
1164        );
1165
1166        assert_eq!(
1167            compute_shared_points(&variations.variations),
1168            // Also PackedPointNumbers::All would work, but Some([1, 3]) happens
1169            // to be the first one that fits the bill when iterating over the
1170            // tuple variations in the order they are listed for this glyph.
1171            Some(PackedPointNumbers::Some(vec![1, 3]))
1172        );
1173    }
1174
1175    // when using short offsets we store (real offset / 2), so all offsets must
1176    // be even, which means when we have an odd number of bytes we have to pad.
1177    fn make_31_bytes_of_variation_data() -> Vec<GlyphDeltas> {
1178        vec![
1179            GlyphDeltas::new(
1180                peaks(vec![F2Dot14::from_f32(-1.0), F2Dot14::from_f32(-1.0)]),
1181                vec![
1182                    GlyphDelta::optional(0, 0),
1183                    GlyphDelta::required(35, 0),
1184                    GlyphDelta::optional(0, 0),
1185                    GlyphDelta::required(-24, 0),
1186                    GlyphDelta::optional(0, 0),
1187                    GlyphDelta::optional(0, 0),
1188                ],
1189            ),
1190            GlyphDeltas::new(
1191                peaks(vec![F2Dot14::from_f32(1.0), F2Dot14::from_f32(1.0)]),
1192                vec![
1193                    GlyphDelta::optional(0, 0),
1194                    GlyphDelta::required(26, 15),
1195                    GlyphDelta::optional(0, 0),
1196                    GlyphDelta::required(46, 0),
1197                    GlyphDelta::optional(0, 0),
1198                    GlyphDelta::required(1, 0),
1199                ],
1200            ),
1201        ]
1202    }
1203
1204    // sanity checking my input data for two subsequent tests
1205    #[test]
1206    fn who_tests_the_testers() {
1207        let variations = GlyphVariations::new(GlyphId::NOTDEF, make_31_bytes_of_variation_data());
1208        let mut tupl_map = HashMap::new();
1209
1210        // without shared tuples this would be 39 bytes
1211        assert_eq!(variations.clone().build(&tupl_map).length, 39);
1212
1213        // to get our real size we need to mock up the shared tuples:
1214        tupl_map.insert(&variations.variations[0].peak_tuple, 1);
1215        tupl_map.insert(&variations.variations[1].peak_tuple, 2);
1216
1217        let built = variations.clone().build(&tupl_map);
1218        // we need an odd number to test impact of padding
1219        assert_eq!(built.length, 31);
1220    }
1221
1222    fn assert_test_offset_packing(n_glyphs: u16, should_be_short: bool) {
1223        let (offset_len, data_len, expected_flags) = if should_be_short {
1224            // when using short offset we need to pad data to ensure offset is even
1225            (u16::RAW_BYTE_LEN, 32, GvarFlags::empty())
1226        } else {
1227            (u32::RAW_BYTE_LEN, 31, GvarFlags::LONG_OFFSETS)
1228        };
1229
1230        let test_data = make_31_bytes_of_variation_data();
1231        let a_small_number_of_variations = (0..n_glyphs)
1232            .map(|i| GlyphVariations::new(GlyphId::from(i), test_data.clone()))
1233            .collect();
1234
1235        let gvar = Gvar::new(a_small_number_of_variations, 2).unwrap();
1236        assert_eq!(gvar.compute_flags(), expected_flags);
1237
1238        let writer = gvar.compile_variation_data();
1239        let mut sink = TableWriter::default();
1240        writer.write_into(&mut sink);
1241
1242        let bytes = sink.into_data().bytes;
1243        let expected_len = (n_glyphs + 1) as usize * offset_len // offsets
1244                             + 8 // shared tuples TODO remove magic number
1245                             + data_len * n_glyphs as usize; // rounded size of each glyph
1246        assert_eq!(bytes.len(), expected_len);
1247
1248        let dumped = crate::dump_table(&gvar).unwrap();
1249        let loaded = read_fonts::tables::gvar::Gvar::read(FontData::new(&dumped)).unwrap();
1250
1251        assert_eq!(loaded.glyph_count(), n_glyphs);
1252        assert_eq!(loaded.flags(), expected_flags);
1253        assert!(loaded
1254            .glyph_variation_data_offsets()
1255            .iter()
1256            .map(|off| off.unwrap().get())
1257            .enumerate()
1258            .all(|(i, off)| off as usize == i * data_len));
1259    }
1260
1261    #[test]
1262    fn prefer_short_offsets() {
1263        let _ = env_logger::builder().is_test(true).try_init();
1264        assert_test_offset_packing(5, true);
1265    }
1266
1267    #[test]
1268    fn use_long_offsets_when_necessary() {
1269        // 2**16 * 2 / (31 + 1 padding) (bytes per tuple) = 4096 should be the first
1270        // overflow
1271        let _ = env_logger::builder().is_test(true).try_init();
1272        assert_test_offset_packing(4095, true);
1273        assert_test_offset_packing(4096, false);
1274        assert_test_offset_packing(4097, false);
1275    }
1276
1277    #[test]
1278    fn shared_tuples_stable_order() {
1279        // Test that shared tuples are sorted stably and builds reproducible
1280        // https://github.com/googlefonts/fontc/issues/647
1281        let mut variations = Vec::new();
1282        for i in 0..2 {
1283            variations.push(GlyphVariations::new(
1284                GlyphId::new(i),
1285                vec![
1286                    GlyphDeltas::new(
1287                        peaks(vec![F2Dot14::from_f32(1.0)]),
1288                        vec![GlyphDelta::required(10, 20)],
1289                    ),
1290                    GlyphDeltas::new(
1291                        peaks(vec![F2Dot14::from_f32(-1.0)]),
1292                        vec![GlyphDelta::required(-10, -20)],
1293                    ),
1294                ],
1295            ))
1296        }
1297        for _ in 0..10 {
1298            let table = Gvar::new(variations.clone(), 1).unwrap();
1299            let bytes = crate::dump_table(&table).unwrap();
1300            let gvar = read_fonts::tables::gvar::Gvar::read(FontData::new(&bytes)).unwrap();
1301
1302            assert_eq!(gvar.shared_tuple_count(), 2);
1303            assert_eq!(
1304                gvar.shared_tuples()
1305                    .unwrap()
1306                    .tuples()
1307                    .iter()
1308                    .map(|t| t.unwrap().values.to_vec())
1309                    .collect::<Vec<_>>(),
1310                vec![vec![F2Dot14::from_f32(1.0)], vec![F2Dot14::from_f32(-1.0)]]
1311            );
1312        }
1313    }
1314
1315    #[test]
1316    fn unexpected_axis_count() {
1317        let variations = GlyphVariations::new(
1318            GlyphId::NOTDEF,
1319            vec![
1320                GlyphDeltas::new(
1321                    peaks(vec![F2Dot14::from_f32(1.0)]),
1322                    vec![GlyphDelta::required(1, 2)],
1323                ),
1324                GlyphDeltas::new(
1325                    peaks(vec![F2Dot14::from_f32(1.0)]),
1326                    vec![GlyphDelta::required(1, 2)],
1327                ),
1328            ],
1329        );
1330        let gvar = Gvar::new(vec![variations], 2);
1331        assert!(matches!(
1332            gvar,
1333            Err(TupleVariationStoreInputError::UnexpectedAxisCount {
1334                index: GlyphId::NOTDEF,
1335                expected: 2,
1336                actual: 1
1337            })
1338        ));
1339    }
1340
1341    #[test]
1342    fn empty_gvar_has_expected_axis_count() {
1343        let variations = GlyphVariations::new(GlyphId::NOTDEF, vec![]);
1344        let gvar = Gvar::new(vec![variations], 2).unwrap();
1345        assert_eq!(gvar.axis_count, 2);
1346    }
1347
1348    #[test]
1349    /// Test the logic for determining whether individual intermediates need to
1350    /// be serialised in the context of their peak coordinates.
1351    fn intermediates_only_when_explicit_needed() {
1352        let any_points = vec![]; // could be anything
1353
1354        // If an intermediate is not provided, one SHOULD NOT be serialised.
1355        let deltas = GlyphDeltas::new(
1356            vec![Tent::new(F2Dot14::from_f32(0.5), None)],
1357            any_points.clone(),
1358        );
1359        assert_eq!(deltas.intermediate_region, None);
1360
1361        // If an intermediate is provided but is equal to the implicit
1362        // intermediate from the peak, it SHOULD NOT be serialised.
1363        let deltas = GlyphDeltas::new(
1364            vec![Tent::new(
1365                F2Dot14::from_f32(0.5),
1366                Some(Tent::implied_intermediates_for_peak(F2Dot14::from_f32(0.5))),
1367            )],
1368            any_points.clone(),
1369        );
1370        assert_eq!(deltas.intermediate_region, None);
1371
1372        // If an intermediate is provided and it is not equal to the implicit
1373        // intermediate from the peak, it SHOULD be serialised.
1374        let deltas = GlyphDeltas::new(
1375            vec![Tent::new(
1376                F2Dot14::from_f32(0.5),
1377                Some((F2Dot14::from_f32(-0.3), F2Dot14::from_f32(0.4))),
1378            )],
1379            any_points.clone(),
1380        );
1381        assert_eq!(
1382            deltas.intermediate_region,
1383            Some((
1384                Tuple::new(vec![F2Dot14::from_f32(-0.3)]),
1385                Tuple::new(vec![F2Dot14::from_f32(0.4)]),
1386            ))
1387        );
1388    }
1389
1390    #[test]
1391    /// Test the logic for determining whether multiple intermediates need to be
1392    /// serialised in the context of their peak coordinates and each other.
1393    fn intermediates_only_when_at_least_one_needed() {
1394        let any_points = vec![]; // could be anything
1395
1396        // If every intermediate can be implied, none should be serialised.
1397        let deltas = GlyphDeltas::new(
1398            vec![
1399                Tent::new(F2Dot14::from_f32(0.5), None),
1400                Tent::new(F2Dot14::from_f32(0.5), None),
1401            ],
1402            any_points.clone(),
1403        );
1404        assert_eq!(deltas.intermediate_region, None);
1405
1406        // If even one intermediate cannot be implied, all should be serialised.
1407        let deltas = GlyphDeltas::new(
1408            vec![
1409                Tent::new(F2Dot14::from_f32(0.5), None),
1410                Tent::new(F2Dot14::from_f32(0.5), None),
1411                Tent::new(
1412                    F2Dot14::from_f32(0.5),
1413                    Some((F2Dot14::from_f32(-0.3), F2Dot14::from_f32(0.4))),
1414                ),
1415            ],
1416            any_points,
1417        );
1418        assert_eq!(
1419            deltas.intermediate_region,
1420            Some((
1421                Tuple::new(vec![
1422                    F2Dot14::from_f32(0.0),
1423                    F2Dot14::from_f32(0.0),
1424                    F2Dot14::from_f32(-0.3)
1425                ]),
1426                Tuple::new(vec![
1427                    F2Dot14::from_f32(0.5),
1428                    F2Dot14::from_f32(0.5),
1429                    F2Dot14::from_f32(0.4)
1430                ]),
1431            ))
1432        );
1433    }
1434}