Skip to main content

write_fonts/tables/
gvar.rs

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