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