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