Skip to main content

read_fonts/tables/gvar/
deltas.rs

1//! Computation of glyph point deltas from the `gvar` table.
2//!
3//! Tuples in a glyph's variation data come in two shapes. A *dense* tuple
4//! carries a delta for every point and is simply accumulated. A *sparse* tuple
5//! carries deltas for a subset, and the deltas for the remaining points have to
6//! be inferred by interpolating between the nearest referenced points on either
7//! side, in the manner of the `IUP` hinting instruction.
8//!
9//! See [inferred deltas for un-referenced point numbers](https://learn.microsoft.com/en-us/typography/opentype/spec/gvar#inferred-deltas-for-un-referenced-point-numbers).
10
11use core::ops::Range;
12
13use super::{GlyphDelta, GlyphVariationData, Gvar};
14use crate::{
15    tables::{
16        glyf::{PointCoord, PointFlags, PointMarker, PHANTOM_POINT_COUNT},
17        variations::TupleVariation,
18    },
19    types::{F2Dot14, Fixed, GlyphId, Point},
20    ReadError,
21};
22
23/// Caller-provided storage for [`Gvar::simple_deltas`] and
24/// [`GlyphVariationData::simple_deltas`].
25///
26/// Both slices must be at least as long as the glyph's point count, including
27/// the phantom points.
28pub struct DeltaBuffers<'a, D: PointCoord> {
29    /// Receives the computed deltas.
30    pub deltas: &'a mut [Point<D>],
31    /// Working space used to interpolate the deltas of points that a sparse
32    /// tuple does not reference.
33    pub iup: &'a mut [Point<D>],
34}
35
36impl Gvar<'_> {
37    /// Computes the deltas for the points of a simple glyph at the given
38    /// location in variation space.
39    ///
40    /// Looks the glyph's variation data up and hands off to
41    /// [`GlyphVariationData::simple_deltas`], which documents the arguments.
42    ///
43    /// Returns `true` if the glyph has variation data, and `false` if it does
44    /// not — in which case the deltas are zeroed. Note that they are zeroed
45    /// whichever it returns, so they never retain values from a previous call.
46    pub fn simple_deltas<C, D>(
47        &self,
48        glyph_id: GlyphId,
49        coords: &[F2Dot14],
50        points: &[Point<C>],
51        flags: &mut [PointFlags],
52        contours: &[u16],
53        buffers: &mut DeltaBuffers<'_, D>,
54    ) -> Result<bool, ReadError>
55    where
56        C: PointCoord,
57        D: PointCoord + From<C>,
58    {
59        check_simple_buffers(points, flags, buffers)?;
60        let Ok(Some(var_data)) = self.glyph_variation_data(glyph_id) else {
61            // Missing or malformed variation data for a glyph is not an error.
62            zero(buffers.deltas);
63            return Ok(false);
64        };
65        var_data.simple_deltas(coords, points, flags, contours, buffers)?;
66        Ok(true)
67    }
68
69    /// Computes the deltas for the component offsets of a composite glyph at
70    /// the given location in variation space.
71    ///
72    /// Looks the glyph's variation data up and hands off to
73    /// [`GlyphVariationData::composite_deltas`].
74    ///
75    /// `deltas` must have one entry per component plus four for the phantom
76    /// points.
77    ///
78    /// Returns `true` if the glyph has variation data, and `false` if it does
79    /// not — in which case `deltas` is zeroed.
80    pub fn composite_deltas<D: PointCoord>(
81        &self,
82        glyph_id: GlyphId,
83        coords: &[F2Dot14],
84        deltas: &mut [Point<D>],
85    ) -> Result<bool, ReadError> {
86        let Ok(Some(var_data)) = self.glyph_variation_data(glyph_id) else {
87            zero(deltas);
88            return Ok(false);
89        };
90        var_data.composite_deltas(coords, deltas)?;
91        Ok(true)
92    }
93}
94
95impl GlyphVariationData<'_> {
96    /// Computes the deltas for the points of a simple glyph at the given
97    /// location in variation space.
98    ///
99    /// Deltas for points that a sparse tuple does not reference are inferred by
100    /// interpolation, so this needs the glyph's points and contour end points
101    /// in addition to the buffers it writes.
102    ///
103    /// * `points` and `contours` describe the unvaried glyph. `points` must
104    ///   include the four phantom points.
105    /// * `flags` is used as scratch: the [`PointMarker::HAS_DELTA`] marker is
106    ///   cleared and set as tuples are processed.
107    /// * `buffers` supplies the output and interpolation storage.
108    ///
109    /// `flags` and both buffers must be at least as long as `points`, which
110    /// is what everything here is indexed by.
111    ///
112    /// The deltas are zeroed before anything else happens, so they never
113    /// retain values from a previous call.
114    ///
115    /// See [`Gvar::simple_deltas`] to look a glyph's data up first.
116    pub fn simple_deltas<C, D>(
117        &self,
118        coords: &[F2Dot14],
119        points: &[Point<C>],
120        flags: &mut [PointFlags],
121        contours: &[u16],
122        buffers: &mut DeltaBuffers<'_, D>,
123    ) -> Result<(), ReadError>
124    where
125        C: PointCoord,
126        D: PointCoord + From<C>,
127    {
128        check_simple_buffers(points, flags, buffers)?;
129        let DeltaBuffers { deltas, iup } = buffers;
130        self.accumulate_deltas(coords, deltas, |scalar, tuple, deltas| {
131            // Prepare the working buffer by converting the points to 16.16,
132            // then drop the markers left by the previous tuple. Kept as two
133            // passes: fused, the read-modify-write on the flags blocks the
134            // conversion from vectorizing.
135            for (point, iup_point) in points.iter().zip(&mut iup[..]) {
136                *iup_point = point.map(D::from);
137            }
138            for flag in flags.iter_mut() {
139                flag.clear_marker(PointMarker::HAS_DELTA);
140            }
141            tuple.accumulate_sparse_deltas(iup, flags, scalar)?;
142            interpolate_deltas(points, flags, contours, &mut iup[..])
143                .ok_or(ReadError::OutOfBounds)?;
144            for ((delta, point), iup_point) in deltas.iter_mut().zip(points).zip(iup.iter()) {
145                *delta += *iup_point - point.map(D::from);
146            }
147            Ok(())
148        })
149    }
150
151    /// Computes the deltas for the component offsets of a composite glyph at
152    /// the given location in variation space.
153    ///
154    /// Interpolation is meaningless for component offsets, so this skips the
155    /// expensive part of [`Self::simple_deltas`] and needs no scratch.
156    ///
157    /// `deltas` must have one entry per component plus four for the phantom
158    /// points, and is zeroed first.
159    ///
160    /// See [`Gvar::composite_deltas`] to look a glyph's data up first.
161    pub fn composite_deltas<D: PointCoord>(
162        &self,
163        coords: &[F2Dot14],
164        deltas: &mut [Point<D>],
165    ) -> Result<(), ReadError> {
166        self.accumulate_deltas(coords, deltas, |scalar, tuple, deltas| {
167            for tuple_delta in tuple.deltas() {
168                let ix = tuple_delta.position as usize;
169                if let Some(delta) = deltas.get_mut(ix) {
170                    *delta += tuple_delta.apply_scalar(scalar);
171                }
172            }
173            Ok(())
174        })
175    }
176
177    /// The parts shared by simple and composite glyph processing.
178    ///
179    /// Zeroes `deltas`, then accumulates every tuple that is active at
180    /// `coords`. Dense tuples are handled here; sparse tuples are passed to
181    /// `apply_sparse_tuple`, which differs between the two glyph kinds.
182    fn accumulate_deltas<D: PointCoord>(
183        &self,
184        coords: &[F2Dot14],
185        deltas: &mut [Point<D>],
186        mut apply_sparse_tuple: impl FnMut(
187            Fixed,
188            TupleVariation<GlyphDelta>,
189            &mut [Point<D>],
190        ) -> Result<(), ReadError>,
191    ) -> Result<(), ReadError> {
192        // Callers must never observe values left over from a previous glyph.
193        zero(deltas);
194        for (tuple, scalar) in self.active_tuples_at(coords) {
195            if tuple.has_deltas_for_all_points() {
196                // Fast path: the tuple covers every point, so the deltas can be
197                // accumulated directly with no interpolation.
198                tuple.accumulate_dense_deltas(deltas, scalar)?;
199            } else {
200                apply_sparse_tuple(scalar, tuple, deltas)?;
201            }
202        }
203        Ok(())
204    }
205}
206
207/// The buffer requirements shared by both entry points to simple glyph
208/// processing.
209///
210/// Everything hinges on `points`, which carries the glyph's real point count:
211/// each of the others is indexed or zipped by point, so one that is short
212/// either truncates the result silently or fails later with a less useful
213/// error.
214fn check_simple_buffers<C: PointCoord, D: PointCoord>(
215    points: &[Point<C>],
216    flags: &[PointFlags],
217    buffers: &DeltaBuffers<'_, D>,
218) -> Result<(), ReadError> {
219    let count = points.len();
220    if count < PHANTOM_POINT_COUNT
221        || flags.len() < count
222        || buffers.deltas.len() < count
223        || buffers.iup.len() < count
224    {
225        return Err(ReadError::InvalidArrayLen);
226    }
227    Ok(())
228}
229
230fn zero<D: PointCoord>(deltas: &mut [Point<D>]) {
231    for delta in deltas.iter_mut() {
232        *delta = Default::default();
233    }
234}
235
236/// Interpolates the points that the current tuple did not reference, in the
237/// manner of the `IUP` hinting instruction.
238///
239/// Points carrying an explicit delta are marked with [`PointMarker::HAS_DELTA`]
240/// in `flags`. Each contour is handled independently.
241///
242/// Modeled after the FreeType implementation:
243/// <https://github.com/freetype/freetype/blob/bbfcd79eacb4985d4b68783565f4b494aa64516b/src/truetype/ttgxvar.c#L3881>
244fn interpolate_deltas<C, D>(
245    points: &[Point<C>],
246    flags: &[PointFlags],
247    contours: &[u16],
248    out_points: &mut [Point<D>],
249) -> Option<()>
250where
251    C: PointCoord,
252    D: PointCoord + From<C>,
253{
254    let mut start = 0usize;
255    for &end in contours {
256        let end = end as usize;
257        // A contour ending before the previous one did is malformed. Skip it
258        // without advancing, so the contours that follow still line up with
259        // the points they describe.
260        if end < start {
261            continue;
262        }
263        let range = start..end + 1;
264        start = end + 1;
265        // Slicing all three to the same range once means everything below can
266        // work in contour local indices, with no further bounds checks.
267        Contour {
268            points: points.get(range.clone())?,
269            flags: flags.get(range.clone())?,
270            out_points: out_points.get_mut(range)?,
271        }
272        .interpolate_untouched();
273    }
274    Some(())
275}
276
277/// One contour's worth of points, as input coordinates and the moved
278/// coordinates being derived from them.
279///
280/// All three slices have the same length, so indices are interchangeable
281/// between them and are always in bounds.
282struct Contour<'a, C, D>
283where
284    C: PointCoord,
285    D: PointCoord + From<C>,
286{
287    points: &'a [Point<C>],
288    flags: &'a [PointFlags],
289    out_points: &'a mut [Point<D>],
290}
291
292impl<C, D> Contour<'_, C, D>
293where
294    C: PointCoord,
295    D: PointCoord + From<C>,
296{
297    /// Infers the deltas of every point in this contour that the current tuple
298    /// did not reference.
299    fn interpolate_untouched(&mut self) {
300        // Copy the reference out so iterating it does not borrow `self`.
301        let flags = self.flags;
302        let mut referenced = flags
303            .iter()
304            .enumerate()
305            .filter(|(_, flag)| flag.has_marker(PointMarker::HAS_DELTA))
306            .map(|(ix, _)| ix);
307        let Some(first) = referenced.next() else {
308            // Nothing in this contour is referenced, so the tuple does not
309            // affect it at all.
310            return;
311        };
312        // Interpolate each run of unreferenced points between two references.
313        let mut last = first;
314        for next in referenced {
315            self.interpolate(last + 1..next, last, next);
316            last = next;
317        }
318        if last == first {
319            // A single reference carries the whole contour with it.
320            self.shift(first);
321        } else {
322            // The points after the last reference and those before the first
323            // wrap around, and are bracketed by that same pair. Both ranges are
324            // empty when there is nothing to do.
325            let len = self.out_points.len();
326            self.interpolate(last + 1..len, last, first);
327            self.interpolate(0..first, last, first);
328        }
329    }
330
331    /// Shifts the whole contour by the delta of its one referenced point.
332    ///
333    /// Modeled after the FreeType implementation: <https://github.com/freetype/freetype/blob/bbfcd79eacb4985d4b68783565f4b494aa64516b/src/truetype/ttgxvar.c#L3776>
334    fn shift(&mut self, reference: usize) {
335        let delta = self.out_points[reference] - self.points[reference].map(D::from);
336        if delta.x == D::zeroed() && delta.y == D::zeroed() {
337            return;
338        }
339        // Every point but the reference itself, which already carries it.
340        let (before, rest) = self.out_points.split_at_mut(reference);
341        for out_point in before {
342            *out_point += delta;
343        }
344        for out_point in &mut rest[1..] {
345            *out_point += delta;
346        }
347    }
348
349    /// Interpolates the unreferenced points in `range` between the referenced
350    /// points at `ref1` and `ref2`.
351    ///
352    /// Modeled after the FreeType implementation: <https://github.com/freetype/freetype/blob/bbfcd79eacb4985d4b68783565f4b494aa64516b/src/truetype/ttgxvar.c#L3813>
353    ///
354    /// For details on the algorithm, see: <https://learn.microsoft.com/en-us/typography/opentype/spec/gvar#inferred-deltas-for-un-referenced-point-numbers>
355    fn interpolate(&mut self, range: Range<usize>, ref1: usize, ref2: usize) {
356        if range.is_empty() {
357            return;
358        }
359        // FreeType uses pointer tricks to handle x and y with a single piece of
360        // code. Try a macro instead.
361        macro_rules! interp_coord {
362            ($coord:ident) => {
363                // Order the references by coordinate, which can differ between
364                // the two axes.
365                let (lo, hi) = if self.points[ref1].$coord > self.points[ref2].$coord {
366                    (ref2, ref1)
367                } else {
368                    (ref1, ref2)
369                };
370                let in1 = D::from(self.points[lo].$coord);
371                let in2 = D::from(self.points[hi].$coord);
372                let out1 = self.out_points[lo].$coord;
373                let out2 = self.out_points[hi].$coord;
374                // If the references share a coordinate but moved apart, the
375                // inferred delta is zero and the coordinate is left alone.
376                if in1 != in2 || out1 == out2 {
377                    let scale = if in1 != in2 {
378                        (out2 - out1) / (in2 - in1)
379                    } else {
380                        D::zeroed()
381                    };
382                    let d1 = out1 - in1;
383                    let d2 = out2 - in2;
384                    for (point, out_point) in self.points[range.clone()]
385                        .iter()
386                        .zip(&mut self.out_points[range.clone()])
387                    {
388                        let coord = D::from(point.$coord);
389                        // Outside the references the nearer delta is applied
390                        // wholesale; between them it is interpolated.
391                        out_point.$coord = if coord <= in1 {
392                            coord + d1
393                        } else if coord >= in2 {
394                            coord + d2
395                        } else {
396                            out1 + (coord - in1) * scale
397                        };
398                    }
399                }
400            };
401        }
402        interp_coord!(x);
403        interp_coord!(y);
404    }
405}
406
407#[cfg(test)]
408mod tests {
409    use super::*;
410    use crate::{
411        tables::{
412            glyf::{Glyf, Glyph},
413            loca::Loca,
414        },
415        FontRef, TableProvider,
416    };
417    use alloc::{vec, vec::Vec};
418
419    fn make_points(tuples: &[(i32, i32)]) -> Vec<Point<i32>> {
420        tuples.iter().map(|&(x, y)| Point::new(x, y)).collect()
421    }
422
423    /// Seeds the working buffer the way [`Gvar::simple_deltas`] does: every
424    /// point converted to 16.16 with its explicit delta already applied, and
425    /// `HAS_DELTA` set for the points that carry one.
426    fn make_working_points_and_flags(
427        points: &[Point<i32>],
428        deltas: &[Point<i32>],
429    ) -> (Vec<Point<Fixed>>, Vec<PointFlags>) {
430        let working_points = points
431            .iter()
432            .zip(deltas)
433            .map(|(point, delta)| point.map(Fixed::from_i32) + delta.map(Fixed::from_i32))
434            .collect();
435        let flags = deltas
436            .iter()
437            .map(|delta| {
438                let mut flags = PointFlags::default();
439                if delta.x != 0 || delta.y != 0 {
440                    flags.set_marker(PointMarker::HAS_DELTA);
441                }
442                flags
443            })
444            .collect();
445        (working_points, flags)
446    }
447
448    /// Runs interpolation and returns the resulting x coordinates as integers.
449    fn interpolated_x(
450        points: &[Point<i32>],
451        deltas: &[Point<i32>],
452        contours: &[u16],
453    ) -> Option<Vec<i32>> {
454        let (mut working, flags) = make_working_points_and_flags(points, deltas);
455        interpolate_deltas(points, &flags, contours, &mut working)?;
456        Some(working.iter().map(|p| p.x.to_i32()).collect())
457    }
458
459    #[test]
460    fn shift() {
461        let points = make_points(&[(245, 630), (260, 700), (305, 680)]);
462        // Single delta triggers a full contour shift.
463        let deltas = make_points(&[(20, -10), (0, 0), (0, 0)]);
464        let (mut working_points, flags) = make_working_points_and_flags(&points, &deltas);
465        interpolate_deltas(&points, &flags, &[2], &mut working_points).unwrap();
466        let expected = &[
467            Point::new(265, 620).map(Fixed::from_i32),
468            Point::new(280, 690).map(Fixed::from_i32),
469            Point::new(325, 670).map(Fixed::from_i32),
470        ];
471        assert_eq!(&working_points, expected);
472    }
473
474    #[test]
475    fn interpolate() {
476        // Test taken from the spec:
477        // https://learn.microsoft.com/en-us/typography/opentype/spec/gvar#inferred-deltas-for-un-referenced-point-numbers
478        // with a minor adjustment to account for the precision of our fixed point math.
479        let points = make_points(&[(245, 630), (260, 700), (305, 680)]);
480        let deltas = make_points(&[(28, -62), (0, 0), (-42, -57)]);
481        let (mut working_points, flags) = make_working_points_and_flags(&points, &deltas);
482        interpolate_deltas(&points, &flags, &[2], &mut working_points).unwrap();
483        assert_eq!(
484            working_points[1],
485            Point::new(
486                Fixed::from_f64(260.0 + 10.4999237060547),
487                Fixed::from_f64(700.0 - 57.0)
488            )
489        );
490    }
491
492    /// Points below the first reference take its delta, points above the second
493    /// take that one, and points between them are interpolated. Coordinates are
494    /// chosen so the interpolation scale is exactly 2 and the 16.16 arithmetic
495    /// is exact.
496    #[test]
497    fn interpolate_clamps_outside_the_reference_range() {
498        //                       below    ref1    between   ref2     above
499        let points = make_points(&[(0, 0), (10, 0), (15, 0), (20, 0), (30, 0)]);
500        let deltas = make_points(&[(0, 0), (4, 0), (0, 0), (14, 0), (0, 0)]);
501        assert_eq!(
502            interpolated_x(&points, &deltas, &[4]).unwrap(),
503            // 0 + d1, 10 + 4, 14 + (15-10)*2, 20 + 14, 30 + d2
504            vec![4, 14, 24, 34, 44]
505        );
506    }
507
508    /// A contour whose points carry no deltas at all is left exactly as it was.
509    #[test]
510    fn contour_without_deltas_is_untouched() {
511        let points = make_points(&[(0, 0), (10, 0), (20, 0)]);
512        let deltas = make_points(&[(0, 0), (0, 0), (0, 0)]);
513        assert_eq!(
514            interpolated_x(&points, &deltas, &[2]).unwrap(),
515            vec![0, 10, 20]
516        );
517    }
518
519    /// Every point having an explicit delta leaves nothing to interpolate.
520    #[test]
521    fn fully_referenced_contour_is_untouched() {
522        let points = make_points(&[(0, 0), (10, 0), (20, 0)]);
523        let deltas = make_points(&[(1, 0), (2, 0), (3, 0)]);
524        assert_eq!(
525            interpolated_x(&points, &deltas, &[2]).unwrap(),
526            vec![1, 12, 23]
527        );
528    }
529
530    /// Deltas in one contour must not leak into another.
531    #[test]
532    fn contours_are_independent() {
533        let points = make_points(&[(0, 0), (10, 0), (20, 0), (100, 0), (110, 0), (120, 0)]);
534        // Only the first contour has a delta, so it shifts as a whole while the
535        // second stays put.
536        let deltas = make_points(&[(5, 0), (0, 0), (0, 0), (0, 0), (0, 0), (0, 0)]);
537        assert_eq!(
538            interpolated_x(&points, &deltas, &[2, 5]).unwrap(),
539            vec![5, 15, 25, 100, 110, 120]
540        );
541    }
542
543    /// The points before the first referenced point wrap around and use the
544    /// last referenced point as their other reference.
545    #[test]
546    fn points_before_first_reference_wrap_around() {
547        let points = make_points(&[(0, 0), (10, 0), (20, 0), (30, 0)]);
548        // References at 1 and 2; points 0 and 3 fall outside them and take the
549        // delta of the nearer reference.
550        let deltas = make_points(&[(0, 0), (6, 0), (6, 0), (0, 0)]);
551        assert_eq!(
552            interpolated_x(&points, &deltas, &[3]).unwrap(),
553            vec![6, 16, 26, 36]
554        );
555    }
556
557    /// When both references share a coordinate but move differently the
558    /// inferred delta is zero, so that coordinate is left alone.
559    #[test]
560    fn equal_reference_coords_with_different_deltas_infer_nothing() {
561        let points = make_points(&[(10, 0), (10, 5), (10, 10)]);
562        let deltas = make_points(&[(2, 0), (0, 0), (6, 0)]);
563        let (mut working, flags) = make_working_points_and_flags(&points, &deltas);
564        interpolate_deltas(&points, &flags, &[2], &mut working).unwrap();
565        // x: both references sit at 10 but move differently, so point 1 keeps
566        // its x. y: interpolated normally, and with no y deltas it stays at 5.
567        assert_eq!(working[1], Point::new(10, 5).map(Fixed::from_i32));
568    }
569
570    /// A contour end point that goes backwards is skipped rather than panicking
571    /// or corrupting the points around it.
572    #[test]
573    fn out_of_order_contour_end_is_skipped() {
574        let points = make_points(&[(0, 0), (10, 0), (20, 0), (30, 0)]);
575        let deltas = make_points(&[(5, 0), (0, 0), (0, 0), (0, 0)]);
576        // The second contour ends before the first one did.
577        assert_eq!(
578            interpolated_x(&points, &deltas, &[3, 1]).unwrap(),
579            vec![5, 15, 25, 35]
580        );
581    }
582
583    /// A contour end point past the end of the point array is reported rather
584    /// than read out of bounds.
585    #[test]
586    fn contour_end_past_last_point_is_rejected() {
587        let points = make_points(&[(0, 0), (10, 0)]);
588        let deltas = make_points(&[(5, 0), (0, 0)]);
589        assert!(interpolated_x(&points, &deltas, &[9]).is_none());
590    }
591
592    // ---- end to end, against a real variable font -------------------------
593
594    /// Vazirmatn has a single `wght` axis. Glyph 1 is a simple glyph with
595    /// variation data, glyph 2 is a composite with variation data, and glyph 0
596    /// is empty and has none.
597    const VAR_GID: GlyphId = GlyphId::new(1);
598    const COMPOSITE_GID: GlyphId = GlyphId::new(2);
599    const NO_VAR_GID: GlyphId = GlyphId::new(0);
600
601    /// A simple glyph loaded with its point buffer sized to include the
602    /// phantom points, as [`Gvar::simple_deltas`] requires.
603    struct TestGlyph<'a> {
604        gvar: Gvar<'a>,
605        points: Vec<Point<i32>>,
606        flags: Vec<PointFlags>,
607        contours: Vec<u16>,
608    }
609
610    impl<'a> TestGlyph<'a> {
611        fn new(font: &FontRef<'a>, gid: GlyphId) -> Self {
612            let glyf: Glyf<'a> = font.glyf().unwrap();
613            let loca: Loca<'a> = font.loca(None).unwrap();
614            let Some(Glyph::Simple(simple)) = loca.get_glyf(gid, &glyf).unwrap() else {
615                panic!("expected a simple glyph");
616            };
617            let n = simple.num_points();
618            let total = n + PHANTOM_POINT_COUNT;
619            let mut points = vec![Point::<i32>::default(); total];
620            let mut flags = vec![PointFlags::default(); total];
621            simple
622                .read_points_fast(&mut points[..n], &mut flags[..n])
623                .unwrap();
624            let contours = simple
625                .end_pts_of_contours()
626                .iter()
627                .map(|c| c.get())
628                .collect();
629            Self {
630                gvar: font.gvar().unwrap(),
631                points,
632                flags,
633                contours,
634            }
635        }
636
637        fn deltas(&mut self, gid: GlyphId, coords: &[F2Dot14]) -> (bool, Vec<Point<Fixed>>) {
638            let total = self.points.len();
639            let mut deltas = vec![Point::<Fixed>::default(); total];
640            let mut iup = vec![Point::<Fixed>::default(); total];
641            let mut buffers = DeltaBuffers {
642                deltas: &mut deltas,
643                iup: &mut iup,
644            };
645            let varied = self
646                .gvar
647                .simple_deltas(
648                    gid,
649                    coords,
650                    &self.points,
651                    &mut self.flags,
652                    &self.contours,
653                    &mut buffers,
654                )
655                .unwrap();
656            (varied, deltas)
657        }
658    }
659
660    #[test]
661    fn simple_deltas_at_default_location_are_zero() {
662        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
663        let mut glyph = TestGlyph::new(&font, VAR_GID);
664        // The glyph has variation data, but no tuple is active at the default
665        // location, so every delta is zero.
666        let (varied, deltas) = glyph.deltas(VAR_GID, &[F2Dot14::from_f32(0.0)]);
667        assert!(varied);
668        assert!(deltas.iter().all(|d| *d == Point::default()));
669    }
670
671    #[test]
672    fn simple_deltas_at_extreme_move_points() {
673        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
674        let mut glyph = TestGlyph::new(&font, VAR_GID);
675        let (varied, deltas) = glyph.deltas(VAR_GID, &[F2Dot14::from_f32(1.0)]);
676        assert!(varied);
677        let outline_deltas = &deltas[..deltas.len() - PHANTOM_POINT_COUNT];
678        assert!(
679            outline_deltas.iter().any(|d| *d != Point::default()),
680            "expected at least one non-zero outline delta"
681        );
682    }
683
684    /// Deltas scale with position along the axis: half way along moves points,
685    /// and by less than the extreme does.
686    #[test]
687    fn simple_deltas_scale_along_the_axis() {
688        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
689        let mut glyph = TestGlyph::new(&font, VAR_GID);
690        let (_, half) = glyph.deltas(VAR_GID, &[F2Dot14::from_f32(0.5)]);
691        let (_, full) = glyph.deltas(VAR_GID, &[F2Dot14::from_f32(1.0)]);
692        let magnitude = |ds: &[Point<Fixed>]| -> f64 {
693            ds.iter()
694                .map(|d| d.x.to_f64().abs() + d.y.to_f64().abs())
695                .sum()
696        };
697        let (half_sum, full_sum) = (magnitude(&half), magnitude(&full));
698        assert!(half_sum > 0.0);
699        assert!(
700            half_sum < full_sum,
701            "half {half_sum} should be less than full {full_sum}"
702        );
703    }
704
705    /// Repeated calls must not accumulate: each starts from a zeroed buffer.
706    #[test]
707    fn deltas_do_not_accumulate_across_calls() {
708        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
709        let mut glyph = TestGlyph::new(&font, VAR_GID);
710        let (_, once) = glyph.deltas(VAR_GID, &[F2Dot14::from_f32(1.0)]);
711        let (_, twice) = glyph.deltas(VAR_GID, &[F2Dot14::from_f32(1.0)]);
712        assert_eq!(once, twice);
713    }
714
715    /// A glyph with no variation data reports `false` and leaves the buffer
716    /// zeroed rather than untouched, so a reused buffer cannot leak deltas from
717    /// a previously processed glyph.
718    #[test]
719    fn simple_deltas_without_variation_data_zeroes_buffer() {
720        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
721        let mut glyph = TestGlyph::new(&font, VAR_GID);
722        let (varied, deltas) = glyph.deltas(NO_VAR_GID, &[F2Dot14::from_f32(1.0)]);
723        assert!(!varied);
724        assert!(deltas.iter().all(|d| *d == Point::default()));
725    }
726
727    #[test]
728    fn simple_deltas_rejects_short_iup_buffer() {
729        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
730        let gvar = font.gvar().unwrap();
731        let points = [Point::<i32>::default(); 8];
732        let mut flags = [PointFlags::default(); 8];
733        let mut deltas = [Point::<Fixed>::default(); 8];
734        let mut iup = [Point::<Fixed>::default(); 4];
735        let mut buffers = DeltaBuffers {
736            deltas: &mut deltas,
737            iup: &mut iup,
738        };
739        assert!(matches!(
740            gvar.simple_deltas(VAR_GID, &[], &points, &mut flags, &[7], &mut buffers),
741            Err(ReadError::InvalidArrayLen)
742        ));
743    }
744
745    #[test]
746    fn simple_deltas_rejects_missing_phantom_points() {
747        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
748        let gvar = font.gvar().unwrap();
749        // Fewer points than there are phantom points.
750        let points = [Point::<i32>::default(); 3];
751        let mut flags = [PointFlags::default(); 3];
752        let mut deltas = [Point::<Fixed>::default(); 3];
753        let mut iup = [Point::<Fixed>::default(); 3];
754        let mut buffers = DeltaBuffers {
755            deltas: &mut deltas,
756            iup: &mut iup,
757        };
758        assert!(matches!(
759            gvar.simple_deltas(VAR_GID, &[], &points, &mut flags, &[2], &mut buffers),
760            Err(ReadError::InvalidArrayLen)
761        ));
762    }
763
764    #[test]
765    fn composite_deltas_without_variation_data_zeroes_buffer() {
766        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
767        let gvar = font.gvar().unwrap();
768        let coords = [F2Dot14::from_f32(0.5)];
769        let mut deltas = [Point::new(Fixed::from_i32(7), Fixed::from_i32(9)); 8];
770        let varied = gvar
771            .composite_deltas(NO_VAR_GID, &coords, &mut deltas)
772            .unwrap();
773        assert!(!varied);
774        assert!(deltas.iter().all(|d| *d == Point::default()));
775    }
776
777    #[test]
778    fn composite_deltas_with_variation_data_reports_true() {
779        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
780        let gvar = font.gvar().unwrap();
781        let coords = [F2Dot14::from_f32(1.0)];
782        let mut deltas = [Point::<Fixed>::default(); 8];
783        let varied = gvar
784            .composite_deltas(COMPOSITE_GID, &coords, &mut deltas)
785            .unwrap();
786        assert!(varied);
787    }
788
789    /// Deltas whose position falls past the end of the buffer are ignored
790    /// rather than treated as an error, since a composite may legitimately have
791    /// fewer components than the variation data describes.
792    #[test]
793    fn composite_deltas_tolerates_short_buffer() {
794        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
795        let gvar = font.gvar().unwrap();
796        let coords = [F2Dot14::from_f32(1.0)];
797        let mut deltas = [Point::<Fixed>::default(); 1];
798        assert!(gvar
799            .composite_deltas(COMPOSITE_GID, &coords, &mut deltas)
800            .is_ok());
801    }
802
803    /// Going through `Gvar` is the same as looking the data up and calling
804    /// `GlyphVariationData` directly.
805    #[test]
806    fn both_layers_agree() {
807        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
808        let gvar = font.gvar().unwrap();
809        let glyf = font.glyf().unwrap();
810        let loca = font.loca(None).unwrap();
811        let coords = [F2Dot14::from_f32(0.5)];
812        for gid in 0..font.maxp().unwrap().num_glyphs() {
813            let glyph_id = GlyphId::from(gid);
814            let Some(Glyph::Simple(simple)) = loca.get_glyf(glyph_id, &glyf).unwrap() else {
815                continue;
816            };
817            let count = simple.num_points() + PHANTOM_POINT_COUNT;
818            let points = vec![Point::<i32>::default(); count];
819            let contours: Vec<u16> = simple
820                .end_pts_of_contours()
821                .iter()
822                .map(|e| e.get())
823                .collect();
824
825            let mut through_gvar = vec![Point::<Fixed>::default(); count];
826            let mut iup = vec![Point::<Fixed>::default(); count];
827            let mut flags = vec![PointFlags::default(); count];
828            let had_data = gvar
829                .simple_deltas(
830                    glyph_id,
831                    &coords,
832                    &points,
833                    &mut flags,
834                    &contours,
835                    &mut DeltaBuffers {
836                        deltas: &mut through_gvar,
837                        iup: &mut iup,
838                    },
839                )
840                .unwrap();
841
842            let var_data = gvar.glyph_variation_data(glyph_id).unwrap();
843            assert_eq!(had_data, var_data.is_some(), "gid {gid}");
844            let Some(var_data) = var_data else { continue };
845
846            let mut direct = vec![Point::<Fixed>::default(); count];
847            let mut iup = vec![Point::<Fixed>::default(); count];
848            let mut flags = vec![PointFlags::default(); count];
849            var_data
850                .simple_deltas(
851                    &coords,
852                    &points,
853                    &mut flags,
854                    &contours,
855                    &mut DeltaBuffers {
856                        deltas: &mut direct,
857                        iup: &mut iup,
858                    },
859                )
860                .unwrap();
861            assert_eq!(through_gvar, direct, "gid {gid}");
862        }
863    }
864
865    /// The `Gvar` wrapper zeroes the deltas for a glyph with no variation
866    /// data, so a reused buffer never leaks the previous glyph's values.
867    #[test]
868    fn a_glyph_without_data_still_zeroes() {
869        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
870        let gvar = font.gvar().unwrap();
871        // Past the end of the table, so there is certainly nothing there.
872        let missing = GlyphId::new(0xFFFF);
873        let mut deltas = vec![Point::new(Fixed::from_i32(7), Fixed::from_i32(9)); 8];
874        assert!(!gvar.composite_deltas(missing, &[], &mut deltas).unwrap());
875        assert!(deltas.iter().all(|d| *d == Point::default()));
876
877        let points = vec![Point::<i32>::default(); 8];
878        let mut deltas = vec![Point::new(Fixed::from_i32(7), Fixed::from_i32(9)); 8];
879        let mut iup = vec![Point::<Fixed>::default(); 8];
880        let mut flags = vec![PointFlags::default(); 8];
881        assert!(!gvar
882            .simple_deltas(
883                missing,
884                &[],
885                &points,
886                &mut flags,
887                &[7],
888                &mut DeltaBuffers {
889                    deltas: &mut deltas,
890                    iup: &mut iup,
891                },
892            )
893            .unwrap());
894        assert!(deltas.iter().all(|d| *d == Point::default()));
895    }
896
897    /// Every buffer that is indexed by point is checked against the point
898    /// count, by both entry points, before anything is written.
899    #[test]
900    fn short_buffers_are_rejected_by_both() {
901        let font = FontRef::new(font_test_data::VAZIRMATN_VAR).unwrap();
902        let gvar = font.gvar().unwrap();
903        let glyph_id = GlyphId::new(2);
904        let var_data = gvar.glyph_variation_data(glyph_id).unwrap().unwrap();
905        const COUNT: usize = 8;
906
907        // Each case leaves one buffer one element short of `points`.
908        for short in ["flags", "deltas", "iup", "points"] {
909            let points = vec![Point::<i32>::default(); if short == "points" { 3 } else { COUNT }];
910            let mut flags =
911                vec![PointFlags::default(); if short == "flags" { COUNT - 1 } else { COUNT }];
912            let mut deltas =
913                vec![Point::<Fixed>::default(); if short == "deltas" { COUNT - 1 } else { COUNT }];
914            let mut iup =
915                vec![Point::<Fixed>::default(); if short == "iup" { COUNT - 1 } else { COUNT }];
916            let mut buffers = DeltaBuffers {
917                deltas: &mut deltas,
918                iup: &mut iup,
919            };
920            assert!(
921                matches!(
922                    gvar.simple_deltas(glyph_id, &[], &points, &mut flags, &[7], &mut buffers),
923                    Err(ReadError::InvalidArrayLen)
924                ),
925                "Gvar accepted a short {short}"
926            );
927            assert!(
928                matches!(
929                    var_data.simple_deltas(&[], &points, &mut flags, &[7], &mut buffers),
930                    Err(ReadError::InvalidArrayLen)
931                ),
932                "GlyphVariationData accepted a short {short}"
933            );
934        }
935
936        // The same sizes, none of them short, are accepted.
937        let points = vec![Point::<i32>::default(); COUNT];
938        let mut flags = vec![PointFlags::default(); COUNT];
939        let mut deltas = vec![Point::<Fixed>::default(); COUNT];
940        let mut iup = vec![Point::<Fixed>::default(); COUNT];
941        let mut buffers = DeltaBuffers {
942            deltas: &mut deltas,
943            iup: &mut iup,
944        };
945        assert!(gvar
946            .simple_deltas(glyph_id, &[], &points, &mut flags, &[7], &mut buffers)
947            .is_ok());
948    }
949}