Skip to main content

read_fonts/model/glyph/color/
instance.rs

1//! Resolution of `COLR` paints at a location in variation space.
2
3use super::{traversal::ColorStopVec, Brush, Error};
4use crate::{
5    model::font::ColrTables,
6    tables::{
7        colr::*,
8        variations::{DeltaSetIndex, NO_VARIATION_INDEX},
9    },
10    types::{BoundingBox, F2Dot14, F48Dot16, FWord, Fixed, GlyphId, GlyphId16, Matrix, Point},
11};
12use core::ops::{Deref, Range};
13
14#[cfg(feature = "libm")]
15#[allow(unused_imports)]
16use core_maths::CoreFloat;
17
18/// Unique paint identifier used for detecting cycles in the paint graph.
19pub(super) type PaintId = usize;
20
21/// A `COLR` table bound to a location in variation space for resolving
22/// paints.
23///
24/// See [`resolve_paint`], [`ColorStops::resolve`] and [`resolve_clip_box`].
25#[derive(Clone, Copy)]
26pub(super) struct ColrInstance<'a> {
27    tables: &'a ColrTables<'a>,
28    coords: &'a [F2Dot14],
29    /// The variation store's region scalars at `coords`, where they are
30    /// known. Any region past the end is computed for each delta.
31    scalars: &'a [Fixed],
32}
33
34impl<'a> ColrInstance<'a> {
35    /// Creates an instance for `tables` at the normalized variation
36    /// coordinates `coords`, with the region `scalars` already computed
37    /// there.
38    pub(super) fn new(
39        tables: &'a ColrTables<'a>,
40        coords: &'a [F2Dot14],
41        scalars: &'a [Fixed],
42    ) -> Self {
43        Self {
44            tables,
45            coords,
46            scalars,
47        }
48    }
49
50    /// Computes a sequence of `N` variation deltas starting at the given
51    /// variation index base.
52    fn var_deltas<const N: usize>(&self, var_index_base: u32) -> [F48Dot16; N] {
53        // FreeType never fails these lookups, so neither does this: an
54        // unreadable delta is zero.
55        // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/fc01e7dd/src/sfnt/ttcolr.c#L574>
56        let mut deltas = [F48Dot16::ZERO; N];
57        let Some(var_store) = self.tables.var_store.as_ref() else {
58            return deltas;
59        };
60        if self.coords.is_empty() || var_index_base == NO_VARIATION_INDEX {
61            return deltas;
62        }
63        // Avoid overflow if var_index_base + N > u32::MAX
64        let actual_count = ((u32::MAX - var_index_base) as usize).min(N);
65        if let Some(index_map) = self.tables.index_map.as_ref() {
66            for (i, delta) in deltas.iter_mut().enumerate().take(actual_count) {
67                let var_index = var_index_base + i as u32;
68                if let Ok(delta_ix) = index_map.get(var_index) {
69                    *delta = var_store
70                        .compute_delta_with_scalars(delta_ix, self.coords, self.scalars)
71                        .unwrap_or_default();
72                }
73            }
74        } else {
75            for (i, delta) in deltas.iter_mut().enumerate().take(actual_count) {
76                let var_index = var_index_base + i as u32;
77                // Without a var index map, the mapping is implicit: the
78                // index is a delta set index with the outer component in the
79                // high word and the inner component in the low word.
80                let delta_ix = DeltaSetIndex {
81                    outer: (var_index >> 16) as u16,
82                    inner: var_index as u16,
83                };
84                *delta = var_store
85                    .compute_delta_with_scalars(delta_ix, self.coords, self.scalars)
86                    .unwrap_or_default();
87            }
88        }
89        deltas
90    }
91}
92
93impl<'a> Deref for ColrInstance<'a> {
94    type Target = Colr<'a>;
95
96    fn deref(&self) -> &Self::Target {
97        &self.tables.colr
98    }
99}
100
101/// Resolves a clip box, applying variation deltas using the given instance.
102pub(super) fn resolve_clip_box(instance: &ColrInstance, clip_box: &ClipBox) -> BoundingBox<f32> {
103    match clip_box {
104        ClipBox::Format1(clip_box) => BoundingBox {
105            x_min: clip_box.x_min().to_i16() as f32,
106            y_min: clip_box.y_min().to_i16() as f32,
107            x_max: clip_box.x_max().to_i16() as f32,
108            y_max: clip_box.y_max().to_i16() as f32,
109        },
110        ClipBox::Format2(clip_box) => {
111            let deltas = instance.var_deltas::<4>(clip_box.var_index_base());
112            BoundingBox {
113                x_min: clip_box.x_min().apply_delta(deltas[0]),
114                y_min: clip_box.y_min().apply_delta(deltas[1]),
115                x_max: clip_box.x_max().apply_delta(deltas[2]),
116                y_max: clip_box.y_max().apply_delta(deltas[3]),
117            }
118        }
119    }
120}
121
122/// Resolves a clip box for measuring glyph extents, as HarfBuzz does.
123///
124/// Each variation delta is rounded to a whole unit before it is added.
125pub(super) fn resolve_clip_box_for_extents(
126    instance: &ColrInstance,
127    clip_box: &ClipBox,
128) -> BoundingBox<f32> {
129    match clip_box {
130        ClipBox::Format1(_) => resolve_clip_box(instance, clip_box),
131        ClipBox::Format2(clip_box) => {
132            let deltas = instance.var_deltas::<4>(clip_box.var_index_base());
133            let edge =
134                |value: FWord, delta: F48Dot16| value.to_i16() as f32 + delta.to_f32().round();
135            BoundingBox {
136                x_min: edge(clip_box.x_min(), deltas[0]),
137                y_min: edge(clip_box.y_min(), deltas[1]),
138                x_max: edge(clip_box.x_max(), deltas[2]),
139                y_max: edge(clip_box.y_max(), deltas[3]),
140            }
141        }
142    }
143}
144
145/// A [`ColorStop`] or [`VarColorStop`] with its variation deltas applied.
146#[derive(Clone, Debug)]
147pub(super) struct ResolvedColorStop {
148    pub offset: f32,
149    pub palette_index: u16,
150    pub alpha: f32,
151}
152
153/// Color stops from a [`ColorLine`] or [`VarColorLine`].
154// Only one of these fields is used at any given time, but this was chosen
155// over the obvious enum for simplicity in generating a single concrete type
156// for the `impl Iterator` return type of `resolve`.
157#[derive(Clone)]
158pub(super) struct ColorStops<'a> {
159    stops: &'a [ColorStop],
160    var_stops: &'a [VarColorStop],
161}
162
163impl<'a> From<ColorLine<'a>> for ColorStops<'a> {
164    fn from(value: ColorLine<'a>) -> Self {
165        Self {
166            stops: value.color_stops(),
167            var_stops: &[],
168        }
169    }
170}
171
172impl<'a> From<VarColorLine<'a>> for ColorStops<'a> {
173    fn from(value: VarColorLine<'a>) -> Self {
174        Self {
175            stops: &[],
176            var_stops: value.color_stops(),
177        }
178    }
179}
180
181impl<'a> ColorStops<'a> {
182    /// Returns an iterator yielding resolved color stops with variation
183    /// deltas applied.
184    pub(super) fn resolve(
185        &self,
186        instance: &'a ColrInstance<'a>,
187    ) -> impl Iterator<Item = ResolvedColorStop> + 'a {
188        self.stops
189            .iter()
190            .map(|stop| ResolvedColorStop {
191                offset: stop.stop_offset().to_f32(),
192                palette_index: stop.palette_index(),
193                alpha: stop.alpha().to_f32(),
194            })
195            .chain(self.var_stops.iter().map(|stop| {
196                let deltas = instance.var_deltas::<2>(stop.var_index_base());
197                ResolvedColorStop {
198                    offset: stop.stop_offset().apply_delta(deltas[0]),
199                    palette_index: stop.palette_index(),
200                    alpha: stop.alpha().apply_delta(deltas[1]),
201                }
202            }))
203    }
204}
205
206/// Like `Option<Brush>`, with an extra variant for a valid brush that paints
207/// nothing.
208pub(super) enum MaybeBrush<'a> {
209    Some(Brush<'a>),
210    /// Valid brush but produces no rendering
211    NonRendering,
212    /// Not a brush
213    None,
214}
215
216/// A `Paint` with its variation deltas applied.
217///
218/// Created by [`resolve_paint`]. This is roughly equivalent to FreeType's
219/// [`FT_COLR_Paint`](https://freetype.org/freetype2/docs/reference/ft2-layer_management.html#ft_colr_paint).
220pub(super) enum ResolvedPaint<'a> {
221    ColrLayers {
222        range: Range<usize>,
223    },
224    Solid {
225        palette_index: u16,
226        alpha: f32,
227    },
228    LinearGradient {
229        x0: f32,
230        y0: f32,
231        x1: f32,
232        y1: f32,
233        x2: f32,
234        y2: f32,
235        color_stops: ColorStops<'a>,
236        extend: Extend,
237    },
238    RadialGradient {
239        x0: f32,
240        y0: f32,
241        radius0: f32,
242        x1: f32,
243        y1: f32,
244        radius1: f32,
245        color_stops: ColorStops<'a>,
246        extend: Extend,
247    },
248    SweepGradient {
249        center_x: f32,
250        center_y: f32,
251        start_angle: f32,
252        end_angle: f32,
253        color_stops: ColorStops<'a>,
254        extend: Extend,
255    },
256    Glyph {
257        glyph: GlyphId,
258        paint: Paint<'a>,
259    },
260    ColrGlyph {
261        glyph: GlyphId16,
262    },
263    Transform {
264        xx: f32,
265        yx: f32,
266        xy: f32,
267        yy: f32,
268        dx: f32,
269        dy: f32,
270        paint: Paint<'a>,
271    },
272    Translate {
273        dx: f32,
274        dy: f32,
275        paint: Paint<'a>,
276    },
277    Scale {
278        scale_x: f32,
279        scale_y: f32,
280        around_center: Option<Point<f32>>,
281        paint: Paint<'a>,
282    },
283    Rotate {
284        angle: f32,
285        around_center: Option<Point<f32>>,
286        paint: Paint<'a>,
287    },
288    Skew {
289        x_skew_angle: f32,
290        y_skew_angle: f32,
291        around_center: Option<Point<f32>>,
292        paint: Paint<'a>,
293    },
294    Composite {
295        source_paint: Paint<'a>,
296        mode: CompositeMode,
297        backdrop_paint: Paint<'a>,
298    },
299}
300
301impl<'a> ResolvedPaint<'a> {
302    pub(super) fn as_transform(&self) -> Option<(Matrix<f32>, Paint<'a>)> {
303        match self {
304            ResolvedPaint::Rotate {
305                angle,
306                around_center,
307                paint,
308            } => {
309                let sin_v = (angle * 180.0).to_radians().sin();
310                let cos_v = (angle * 180.0).to_radians().cos();
311                let mut out_transform = Matrix {
312                    xx: cos_v,
313                    xy: -sin_v,
314                    yx: sin_v,
315                    yy: cos_v,
316                    ..Default::default()
317                };
318
319                fn scalar_dot_product(a: f32, b: f32, c: f32, d: f32) -> f32 {
320                    a * b + c * d
321                }
322
323                if let Some(center) = around_center {
324                    out_transform.dx = scalar_dot_product(sin_v, center.y, 1.0 - cos_v, center.x);
325                    out_transform.dy = scalar_dot_product(-sin_v, center.x, 1.0 - cos_v, center.y);
326                }
327                Some((out_transform, paint.clone()))
328            }
329            ResolvedPaint::Scale {
330                scale_x,
331                scale_y,
332                around_center,
333                paint,
334            } => {
335                let mut out_transform = Matrix {
336                    xx: *scale_x,
337                    yy: *scale_y,
338                    ..Matrix::default()
339                };
340
341                if let Some(center) = around_center {
342                    out_transform.dx = center.x - scale_x * center.x;
343                    out_transform.dy = center.y - scale_y * center.y;
344                }
345                Some((out_transform, paint.clone()))
346            }
347            ResolvedPaint::Skew {
348                x_skew_angle,
349                y_skew_angle,
350                around_center,
351                paint,
352            } => {
353                let tan_x = (x_skew_angle * 180.0).to_radians().tan();
354                let tan_y = (y_skew_angle * 180.0).to_radians().tan();
355                let mut out_transform = Matrix {
356                    xy: -tan_x,
357                    yx: tan_y,
358                    ..Matrix::default()
359                };
360
361                if let Some(center) = around_center {
362                    out_transform.dx = tan_x * center.y;
363                    out_transform.dy = -tan_y * center.x;
364                }
365                Some((out_transform, paint.clone()))
366            }
367            ResolvedPaint::Transform {
368                xx,
369                yx,
370                xy,
371                yy,
372                dx,
373                dy,
374                paint,
375            } => Some((
376                Matrix {
377                    xx: *xx,
378                    yx: *yx,
379                    xy: *xy,
380                    yy: *yy,
381                    dx: *dx,
382                    dy: *dy,
383                },
384                paint.clone(),
385            )),
386            ResolvedPaint::Translate { dx, dy, paint, .. } => Some((
387                Matrix {
388                    dx: *dx,
389                    dy: *dy,
390                    ..Default::default()
391                },
392                paint.clone(),
393            )),
394            _ => None,
395        }
396    }
397
398    pub(super) fn as_brush<'s>(
399        &self,
400        instance: &ColrInstance,
401        resolved_stops: &'s mut ColorStopVec,
402    ) -> Result<MaybeBrush<'s>, Error> {
403        match self {
404            ResolvedPaint::Solid {
405                palette_index,
406                alpha,
407            } => Ok(MaybeBrush::Some(Brush::Solid {
408                palette_index: *palette_index,
409                alpha: *alpha,
410            })),
411            ResolvedPaint::LinearGradient {
412                x0,
413                y0,
414                x1,
415                y1,
416                x2,
417                y2,
418                color_stops,
419                extend,
420            } => {
421                let mut p0 = Point::new(*x0, *y0);
422                let p1 = Point::new(*x1, *y1);
423                let p2 = Point::new(*x2, *y2);
424
425                let dot_product = |a: Point<f32>, b: Point<f32>| -> f32 { a.x * b.x + a.y * b.y };
426                let cross_product = |a: Point<f32>, b: Point<f32>| -> f32 { a.x * b.y - a.y * b.x };
427                let project_onto = |vector: Point<f32>, point: Point<f32>| -> Point<f32> {
428                    let length = (point.x * point.x + point.y * point.y).sqrt();
429                    if length == 0.0 {
430                        return Point::default();
431                    }
432                    let mut point_normalized = point / length;
433                    point_normalized *= dot_product(vector, point) / length;
434                    point_normalized
435                };
436
437                make_sorted_resolved_stops(color_stops, instance, resolved_stops);
438
439                // If p0p1 or p0p2 are degenerate probably nothing should be drawn.
440                // If p0p1 and p0p2 are parallel then one side is the first color and the other side is
441                // the last color, depending on the direction.
442                // For now, just use the first color.
443                if p1 == p0 || p2 == p0 || cross_product(p1 - p0, p2 - p0) == 0.0 {
444                    if let Some(stop) = resolved_stops.first() {
445                        return Ok(MaybeBrush::Some(Brush::Solid {
446                            palette_index: stop.palette_index,
447                            alpha: stop.alpha,
448                        }));
449                    };
450                    return Ok(MaybeBrush::NonRendering);
451                }
452
453                // Follow implementation note in nanoemoji:
454                // https://github.com/googlefonts/nanoemoji/blob/0ac6e7bb4d8202db692574d8530a9b643f1b3b3c/src/nanoemoji/svg.py#L188
455                // to compute a new gradient end point P3 as the orthogonal
456                // projection of the vector from p0 to p1 onto a line perpendicular
457                // to line p0p2 and passing through p0.
458                let mut perpendicular_to_p2 = p2 - p0;
459                perpendicular_to_p2 = Point::new(perpendicular_to_p2.y, -perpendicular_to_p2.x);
460                let mut p3 = p0 + project_onto(p1 - p0, perpendicular_to_p2);
461
462                match (
463                    resolved_stops.first().cloned(),
464                    resolved_stops.last().cloned(),
465                ) {
466                    (None, _) | (_, None) => {}
467                    (Some(first_stop), Some(last_stop)) => {
468                        let mut color_stop_range = last_stop.offset - first_stop.offset;
469
470                        // Nothing can be drawn for this situation.
471                        if color_stop_range == 0.0 && extend != &Extend::Pad {
472                            return Ok(MaybeBrush::NonRendering);
473                        }
474
475                        // In the Pad case, for providing normalized stops in the 0 to 1 range to the client,
476                        // insert a color stop at the end. Adding this stop will paint the equivalent gradient,
477                        // because: All font-specified color stops are in the same spot, mode is pad, so
478                        // everything before this spot is painted with the first color, everything after this spot
479                        // is painted with the last color. Not adding this stop would skip the projection below along
480                        // the p0-p3 axis and result in specifying non-normalized color stops to the shader.
481
482                        if color_stop_range == 0.0 && extend == &Extend::Pad {
483                            let mut extra_stop = last_stop;
484                            extra_stop.offset += 1.0;
485                            resolved_stops.push(extra_stop);
486
487                            color_stop_range = 1.0;
488                        }
489
490                        debug_assert!(color_stop_range != 0.0);
491
492                        if color_stop_range != 1.0 || first_stop.offset != 0.0 {
493                            let p0_p3 = p3 - p0;
494                            let p0_offset = p0_p3 * first_stop.offset;
495                            let p3_offset = p0_p3 * last_stop.offset;
496
497                            p3 = p0 + p3_offset;
498                            p0 += p0_offset;
499
500                            let scale_factor = 1.0 / color_stop_range;
501                            let start_offset = first_stop.offset;
502
503                            for stop in resolved_stops.iter_mut() {
504                                stop.offset = (stop.offset - start_offset) * scale_factor;
505                            }
506                        }
507
508                        return Ok(MaybeBrush::Some(Brush::LinearGradient {
509                            p0,
510                            p1: p3,
511                            color_stops: resolved_stops.as_slice(),
512                            extend: *extend,
513                        }));
514                    }
515                }
516
517                Ok(MaybeBrush::NonRendering)
518            }
519            ResolvedPaint::RadialGradient {
520                x0,
521                y0,
522                radius0,
523                x1,
524                y1,
525                radius1,
526                color_stops,
527                extend,
528            } => {
529                let mut c0 = Point::new(*x0, *y0);
530                let mut c1 = Point::new(*x1, *y1);
531                let mut radius0 = *radius0;
532                let mut radius1 = *radius1;
533
534                make_sorted_resolved_stops(color_stops, instance, resolved_stops);
535
536                match (
537                    resolved_stops.first().cloned(),
538                    resolved_stops.last().cloned(),
539                ) {
540                    (None, _) | (_, None) => {}
541                    (Some(first_stop), Some(last_stop)) => {
542                        let mut color_stop_range = last_stop.offset - first_stop.offset;
543                        // Nothing can be drawn for this situation.
544                        if color_stop_range == 0.0 && extend != &Extend::Pad {
545                            return Ok(MaybeBrush::NonRendering);
546                        }
547
548                        // In the Pad case, for providing normalized stops in the 0 to 1 range to the client,
549                        // insert a color stop at the end. See LinearGradient for more details.
550
551                        if color_stop_range == 0.0 && extend == &Extend::Pad {
552                            let mut extra_stop = last_stop;
553                            extra_stop.offset += 1.0;
554                            resolved_stops.push(extra_stop);
555                            color_stop_range = 1.0;
556                        }
557
558                        debug_assert!(color_stop_range != 0.0);
559
560                        // If the colorStopRange is 0 at this point, the default behavior of the shader is to
561                        // clamp to 1 color stops that are above 1, clamp to 0 for color stops that are below 0,
562                        // and repeat the outer color stops at 0 and 1 if the color stops are inside the
563                        // range. That will result in the correct rendering.
564                        if color_stop_range != 1.0 || first_stop.offset != 0.0 {
565                            let c0_to_c1 = c1 - c0;
566                            let radius_diff = radius1 - radius0;
567                            let scale_factor = 1.0 / color_stop_range;
568
569                            let c0_offset = c0_to_c1 * first_stop.offset;
570                            let c1_offset = c0_to_c1 * last_stop.offset;
571                            let stops_start_offset = first_stop.offset;
572
573                            // Order of reassignments is important to avoid shadowing variables.
574                            c1 = c0 + c1_offset;
575                            c0 += c0_offset;
576                            radius1 = radius0 + radius_diff * last_stop.offset;
577                            radius0 += radius_diff * first_stop.offset;
578
579                            for stop in resolved_stops.iter_mut() {
580                                stop.offset = (stop.offset - stops_start_offset) * scale_factor;
581                            }
582                        }
583
584                        return Ok(MaybeBrush::Some(Brush::RadialGradient {
585                            c0,
586                            r0: radius0,
587                            c1,
588                            r1: radius1,
589                            color_stops: resolved_stops.as_slice(),
590                            extend: *extend,
591                        }));
592                    }
593                }
594                Ok(MaybeBrush::NonRendering)
595            }
596            ResolvedPaint::SweepGradient {
597                center_x,
598                center_y,
599                start_angle,
600                end_angle,
601                color_stops,
602                extend,
603            } => {
604                // OpenType 1.9.1 adds a shift to the angle to ease specification of a 0 to 360
605                // degree sweep.
606                let sweep_angle_to_degrees = |angle| angle * 180.0 + 180.0;
607
608                let start_angle = sweep_angle_to_degrees(start_angle);
609                let end_angle = sweep_angle_to_degrees(end_angle);
610
611                // Stop normalization for sweep:
612
613                let sector_angle = end_angle - start_angle;
614
615                make_sorted_resolved_stops(color_stops, instance, resolved_stops);
616                if resolved_stops.is_empty() {
617                    return Ok(MaybeBrush::NonRendering);
618                }
619
620                match (
621                    resolved_stops.first().cloned(),
622                    resolved_stops.last().cloned(),
623                ) {
624                    (None, _) | (_, None) => {}
625                    (Some(first_stop), Some(last_stop)) => {
626                        let mut color_stop_range = last_stop.offset - first_stop.offset;
627
628                        let mut start_angle_scaled = start_angle + sector_angle * first_stop.offset;
629                        let mut end_angle_scaled = start_angle + sector_angle * last_stop.offset;
630
631                        let start_offset = first_stop.offset;
632
633                        // Nothing can be drawn for this situation.
634                        if color_stop_range == 0.0 && extend != &Extend::Pad {
635                            return Ok(MaybeBrush::NonRendering);
636                        }
637
638                        // In the Pad case, if the color_stop_range is 0 insert a color stop at the end before
639                        // normalizing. Adding this stop will paint the equivalent gradient, because: All font
640                        // specified color stops are in the same spot, mode is pad, so everything before this
641                        // spot is painted with the first color, everything after this spot is painted with
642                        // the last color. Not adding this stop will skip the projection and result in
643                        // specifying non-normalized color stops to the shader.
644                        if color_stop_range == 0.0 && extend == &Extend::Pad {
645                            let mut offset_last = last_stop;
646                            offset_last.offset += 1.0;
647                            resolved_stops.push(offset_last);
648                            color_stop_range = 1.0;
649                        }
650
651                        debug_assert!(color_stop_range != 0.0);
652
653                        let scale_factor = 1.0 / color_stop_range;
654
655                        for shift_stop in resolved_stops.iter_mut() {
656                            shift_stop.offset = (shift_stop.offset - start_offset) * scale_factor;
657                        }
658
659                        // /* https://docs.microsoft.com/en-us/typography/opentype/spec/colr#sweep-gradients
660                        //  * "The angles are expressed in counter-clockwise degrees from
661                        //  * the direction of the positive x-axis on the design
662                        //  * grid. [...]  The color line progresses from the start angle
663                        //  * to the end angle in the counter-clockwise direction;" -
664                        //  * Convert angles and stops from counter-clockwise to clockwise
665                        //  * for the shader if the gradient is not already reversed due to
666                        //  * start angle being larger than end angle. */
667                        start_angle_scaled = 360.0 - start_angle_scaled;
668                        end_angle_scaled = 360.0 - end_angle_scaled;
669
670                        if start_angle_scaled >= end_angle_scaled {
671                            (start_angle_scaled, end_angle_scaled) =
672                                (end_angle_scaled, start_angle_scaled);
673                            resolved_stops.reverse();
674                            for stop in resolved_stops.iter_mut() {
675                                stop.offset = 1.0 - stop.offset;
676                            }
677                        }
678
679                        // https://learn.microsoft.com/en-us/typography/opentype/spec/colr#sweep-gradients
680                        // "If the color line's extend mode is reflect or repeat
681                        // and start and end angle are equal, nothing shall be drawn."
682                        if start_angle_scaled == end_angle_scaled && extend != &Extend::Pad {
683                            return Ok(MaybeBrush::NonRendering);
684                        }
685
686                        return Ok(MaybeBrush::Some(Brush::SweepGradient {
687                            c0: Point::new(*center_x, *center_y),
688                            start_angle: start_angle_scaled,
689                            end_angle: end_angle_scaled,
690                            color_stops: resolved_stops.as_slice(),
691                            extend: *extend,
692                        }));
693                    }
694                }
695                Ok(MaybeBrush::NonRendering)
696            }
697            _ => Ok(MaybeBrush::None),
698        }
699    }
700}
701
702fn make_sorted_resolved_stops(
703    stops: &ColorStops,
704    instance: &ColrInstance,
705    out_stops: &mut ColorStopVec,
706) {
707    let color_stop_iter = stops.resolve(instance).map(|stop| stop.into());
708    out_stops.clear();
709    for stop in color_stop_iter {
710        out_stops.push(stop);
711    }
712    out_stops.sort_by(|a, b| {
713        a.offset
714            .partial_cmp(&b.offset)
715            .unwrap_or(core::cmp::Ordering::Equal)
716    });
717}
718
719/// Resolves this paint with the given instance.
720///
721/// Resolving means that all numeric values are converted to 32-bit floating
722/// point, variation deltas are applied (also computed fully in floating
723/// point), and the various transform paints are collapsed into a single value
724/// for their category (transform, translate, scale, rotate and skew).
725///
726/// This provides a simpler type for consumers that are more interested
727/// in extracting the semantics of the graph rather than working with the
728/// raw encoded structures.
729pub(super) fn resolve_paint<'a>(
730    instance: &ColrInstance<'a>,
731    paint: &Paint<'a>,
732) -> Result<ResolvedPaint<'a>, Error> {
733    Ok(match paint {
734        Paint::ColrLayers(layers) => {
735            // A range that overflows, as it can on a 32-bit target, names no
736            // layer the table could hold.
737            let start = layers.first_layer_index() as usize;
738            let end = start
739                .checked_add(layers.num_layers() as usize)
740                .ok_or(Error::Malformed)?;
741            ResolvedPaint::ColrLayers { range: start..end }
742        }
743        Paint::Solid(solid) => ResolvedPaint::Solid {
744            palette_index: solid.palette_index(),
745            alpha: solid.alpha().to_f32(),
746        },
747        Paint::VarSolid(solid) => {
748            let deltas = instance.var_deltas::<1>(solid.var_index_base());
749            ResolvedPaint::Solid {
750                palette_index: solid.palette_index(),
751                alpha: solid.alpha().apply_delta(deltas[0]),
752            }
753        }
754        Paint::LinearGradient(gradient) => {
755            let color_line = gradient.color_line().map_err(|_| Error::Malformed)?;
756            let extend = color_line.extend();
757            ResolvedPaint::LinearGradient {
758                x0: gradient.x0().to_i16() as f32,
759                y0: gradient.y0().to_i16() as f32,
760                x1: gradient.x1().to_i16() as f32,
761                y1: gradient.y1().to_i16() as f32,
762                x2: gradient.x2().to_i16() as f32,
763                y2: gradient.y2().to_i16() as f32,
764                color_stops: color_line.into(),
765                extend,
766            }
767        }
768        Paint::VarLinearGradient(gradient) => {
769            let color_line = gradient.color_line().map_err(|_| Error::Malformed)?;
770            let extend = color_line.extend();
771            let deltas = instance.var_deltas::<6>(gradient.var_index_base());
772            ResolvedPaint::LinearGradient {
773                x0: gradient.x0().apply_delta(deltas[0]),
774                y0: gradient.y0().apply_delta(deltas[1]),
775                x1: gradient.x1().apply_delta(deltas[2]),
776                y1: gradient.y1().apply_delta(deltas[3]),
777                x2: gradient.x2().apply_delta(deltas[4]),
778                y2: gradient.y2().apply_delta(deltas[5]),
779                color_stops: color_line.into(),
780                extend,
781            }
782        }
783        Paint::RadialGradient(gradient) => {
784            let color_line = gradient.color_line().map_err(|_| Error::Malformed)?;
785            let extend = color_line.extend();
786            ResolvedPaint::RadialGradient {
787                x0: gradient.x0().to_i16() as f32,
788                y0: gradient.y0().to_i16() as f32,
789                radius0: gradient.radius0().to_u16() as f32,
790                x1: gradient.x1().to_i16() as f32,
791                y1: gradient.y1().to_i16() as f32,
792                radius1: gradient.radius1().to_u16() as f32,
793                color_stops: color_line.into(),
794                extend,
795            }
796        }
797        Paint::VarRadialGradient(gradient) => {
798            let color_line = gradient.color_line().map_err(|_| Error::Malformed)?;
799            let extend = color_line.extend();
800            let deltas = instance.var_deltas::<6>(gradient.var_index_base());
801            ResolvedPaint::RadialGradient {
802                x0: gradient.x0().apply_delta(deltas[0]),
803                y0: gradient.y0().apply_delta(deltas[1]),
804                radius0: gradient.radius0().apply_delta(deltas[2]),
805                x1: gradient.x1().apply_delta(deltas[3]),
806                y1: gradient.y1().apply_delta(deltas[4]),
807                radius1: gradient.radius1().apply_delta(deltas[5]),
808                color_stops: color_line.into(),
809                extend,
810            }
811        }
812        Paint::SweepGradient(gradient) => {
813            let color_line = gradient.color_line().map_err(|_| Error::Malformed)?;
814            let extend = color_line.extend();
815            ResolvedPaint::SweepGradient {
816                center_x: gradient.center_x().to_i16() as f32,
817                center_y: gradient.center_y().to_i16() as f32,
818                start_angle: gradient.start_angle().to_f32(),
819                end_angle: gradient.end_angle().to_f32(),
820                color_stops: color_line.into(),
821                extend,
822            }
823        }
824        Paint::VarSweepGradient(gradient) => {
825            let color_line = gradient.color_line().map_err(|_| Error::Malformed)?;
826            let extend = color_line.extend();
827            let deltas = instance.var_deltas::<4>(gradient.var_index_base());
828            ResolvedPaint::SweepGradient {
829                center_x: gradient.center_x().apply_delta(deltas[0]),
830                center_y: gradient.center_y().apply_delta(deltas[1]),
831                start_angle: gradient.start_angle().apply_delta(deltas[2]),
832                end_angle: gradient.end_angle().apply_delta(deltas[3]),
833                color_stops: color_line.into(),
834                extend,
835            }
836        }
837        Paint::Glyph(glyph) => ResolvedPaint::Glyph {
838            glyph: glyph.glyph_id().into(),
839            paint: glyph.paint().map_err(|_| Error::Malformed)?,
840        },
841        Paint::Glyph2(glyph) => ResolvedPaint::Glyph {
842            glyph: glyph.glyph_id().into(),
843            paint: glyph.paint().map_err(|_| Error::Malformed)?,
844        },
845        Paint::ColrGlyph(glyph) => ResolvedPaint::ColrGlyph {
846            glyph: glyph.glyph_id(),
847        },
848        Paint::Transform(transform) => {
849            let affine = transform.transform().map_err(|_| Error::Malformed)?;
850            let paint = transform.paint().map_err(|_| Error::Malformed)?;
851            ResolvedPaint::Transform {
852                xx: affine.xx().to_f32(),
853                yx: affine.yx().to_f32(),
854                xy: affine.xy().to_f32(),
855                yy: affine.yy().to_f32(),
856                dx: affine.dx().to_f32(),
857                dy: affine.dy().to_f32(),
858                paint,
859            }
860        }
861        Paint::VarTransform(transform) => {
862            let affine = transform.transform().map_err(|_| Error::Malformed)?;
863            let paint = transform.paint().map_err(|_| Error::Malformed)?;
864            let deltas = instance.var_deltas::<6>(affine.var_index_base());
865            ResolvedPaint::Transform {
866                xx: affine.xx().apply_delta(deltas[0]),
867                yx: affine.yx().apply_delta(deltas[1]),
868                xy: affine.xy().apply_delta(deltas[2]),
869                yy: affine.yy().apply_delta(deltas[3]),
870                dx: affine.dx().apply_delta(deltas[4]),
871                dy: affine.dy().apply_delta(deltas[5]),
872                paint,
873            }
874        }
875        Paint::Translate(transform) => ResolvedPaint::Translate {
876            dx: transform.dx().to_i16() as f32,
877            dy: transform.dy().to_i16() as f32,
878            paint: transform.paint().map_err(|_| Error::Malformed)?,
879        },
880        Paint::VarTranslate(transform) => {
881            let deltas = instance.var_deltas::<2>(transform.var_index_base());
882            ResolvedPaint::Translate {
883                dx: transform.dx().apply_delta(deltas[0]),
884                dy: transform.dy().apply_delta(deltas[1]),
885                paint: transform.paint().map_err(|_| Error::Malformed)?,
886            }
887        }
888        Paint::Scale(transform) => ResolvedPaint::Scale {
889            scale_x: transform.scale_x().to_f32(),
890            scale_y: transform.scale_y().to_f32(),
891            around_center: None,
892            paint: transform.paint().map_err(|_| Error::Malformed)?,
893        },
894        Paint::VarScale(transform) => {
895            let deltas = instance.var_deltas::<2>(transform.var_index_base());
896            ResolvedPaint::Scale {
897                scale_x: transform.scale_x().apply_delta(deltas[0]),
898                scale_y: transform.scale_y().apply_delta(deltas[1]),
899                around_center: None,
900                paint: transform.paint().map_err(|_| Error::Malformed)?,
901            }
902        }
903        Paint::ScaleAroundCenter(transform) => ResolvedPaint::Scale {
904            scale_x: transform.scale_x().to_f32(),
905            scale_y: transform.scale_y().to_f32(),
906            around_center: Some(Point::new(
907                transform.center_x().to_i16() as f32,
908                transform.center_y().to_i16() as f32,
909            )),
910            paint: transform.paint().map_err(|_| Error::Malformed)?,
911        },
912        Paint::VarScaleAroundCenter(transform) => {
913            let deltas = instance.var_deltas::<4>(transform.var_index_base());
914            ResolvedPaint::Scale {
915                scale_x: transform.scale_x().apply_delta(deltas[0]),
916                scale_y: transform.scale_y().apply_delta(deltas[1]),
917                around_center: Some(Point::new(
918                    transform.center_x().apply_delta(deltas[2]),
919                    transform.center_y().apply_delta(deltas[3]),
920                )),
921                paint: transform.paint().map_err(|_| Error::Malformed)?,
922            }
923        }
924        Paint::ScaleUniform(transform) => {
925            let scale = transform.scale().to_f32();
926            ResolvedPaint::Scale {
927                scale_x: scale,
928                scale_y: scale,
929                around_center: None,
930                paint: transform.paint().map_err(|_| Error::Malformed)?,
931            }
932        }
933        Paint::VarScaleUniform(transform) => {
934            let deltas = instance.var_deltas::<1>(transform.var_index_base());
935            let scale = transform.scale().apply_delta(deltas[0]);
936            ResolvedPaint::Scale {
937                scale_x: scale,
938                scale_y: scale,
939                around_center: None,
940                paint: transform.paint().map_err(|_| Error::Malformed)?,
941            }
942        }
943        Paint::ScaleUniformAroundCenter(transform) => {
944            let scale = transform.scale().to_f32();
945            ResolvedPaint::Scale {
946                scale_x: scale,
947                scale_y: scale,
948                around_center: Some(Point::new(
949                    transform.center_x().to_i16() as f32,
950                    transform.center_y().to_i16() as f32,
951                )),
952                paint: transform.paint().map_err(|_| Error::Malformed)?,
953            }
954        }
955        Paint::VarScaleUniformAroundCenter(transform) => {
956            let deltas = instance.var_deltas::<3>(transform.var_index_base());
957            let scale = transform.scale().apply_delta(deltas[0]);
958            ResolvedPaint::Scale {
959                scale_x: scale,
960                scale_y: scale,
961                around_center: Some(Point::new(
962                    transform.center_x().apply_delta(deltas[1]),
963                    transform.center_y().apply_delta(deltas[2]),
964                )),
965                paint: transform.paint().map_err(|_| Error::Malformed)?,
966            }
967        }
968        Paint::Rotate(transform) => ResolvedPaint::Rotate {
969            angle: transform.angle().to_f32(),
970            around_center: None,
971            paint: transform.paint().map_err(|_| Error::Malformed)?,
972        },
973        Paint::VarRotate(transform) => {
974            let deltas = instance.var_deltas::<1>(transform.var_index_base());
975            ResolvedPaint::Rotate {
976                angle: transform.angle().apply_delta(deltas[0]),
977                around_center: None,
978                paint: transform.paint().map_err(|_| Error::Malformed)?,
979            }
980        }
981        Paint::RotateAroundCenter(transform) => ResolvedPaint::Rotate {
982            angle: transform.angle().to_f32(),
983            around_center: Some(Point::new(
984                transform.center_x().to_i16() as f32,
985                transform.center_y().to_i16() as f32,
986            )),
987            paint: transform.paint().map_err(|_| Error::Malformed)?,
988        },
989        Paint::VarRotateAroundCenter(transform) => {
990            let deltas = instance.var_deltas::<3>(transform.var_index_base());
991            ResolvedPaint::Rotate {
992                angle: transform.angle().apply_delta(deltas[0]),
993                around_center: Some(Point::new(
994                    transform.center_x().apply_delta(deltas[1]),
995                    transform.center_y().apply_delta(deltas[2]),
996                )),
997                paint: transform.paint().map_err(|_| Error::Malformed)?,
998            }
999        }
1000        Paint::Skew(transform) => ResolvedPaint::Skew {
1001            x_skew_angle: transform.x_skew_angle().to_f32(),
1002            y_skew_angle: transform.y_skew_angle().to_f32(),
1003            around_center: None,
1004            paint: transform.paint().map_err(|_| Error::Malformed)?,
1005        },
1006        Paint::VarSkew(transform) => {
1007            let deltas = instance.var_deltas::<2>(transform.var_index_base());
1008            ResolvedPaint::Skew {
1009                x_skew_angle: transform.x_skew_angle().apply_delta(deltas[0]),
1010                y_skew_angle: transform.y_skew_angle().apply_delta(deltas[1]),
1011                around_center: None,
1012                paint: transform.paint().map_err(|_| Error::Malformed)?,
1013            }
1014        }
1015        Paint::SkewAroundCenter(transform) => ResolvedPaint::Skew {
1016            x_skew_angle: transform.x_skew_angle().to_f32(),
1017            y_skew_angle: transform.y_skew_angle().to_f32(),
1018            around_center: Some(Point::new(
1019                transform.center_x().to_i16() as f32,
1020                transform.center_y().to_i16() as f32,
1021            )),
1022            paint: transform.paint().map_err(|_| Error::Malformed)?,
1023        },
1024        Paint::VarSkewAroundCenter(transform) => {
1025            let deltas = instance.var_deltas::<4>(transform.var_index_base());
1026            ResolvedPaint::Skew {
1027                x_skew_angle: transform.x_skew_angle().apply_delta(deltas[0]),
1028                y_skew_angle: transform.y_skew_angle().apply_delta(deltas[1]),
1029                around_center: Some(Point::new(
1030                    transform.center_x().apply_delta(deltas[2]),
1031                    transform.center_y().apply_delta(deltas[3]),
1032                )),
1033                paint: transform.paint().map_err(|_| Error::Malformed)?,
1034            }
1035        }
1036        Paint::Composite(composite) => ResolvedPaint::Composite {
1037            source_paint: composite.source_paint().map_err(|_| Error::Malformed)?,
1038            mode: composite.composite_mode(),
1039            backdrop_paint: composite.backdrop_paint().map_err(|_| Error::Malformed)?,
1040        },
1041    })
1042}
1043
1044#[cfg(test)]
1045mod tests {
1046    use super::*;
1047    use crate::{FontRef, TableProvider};
1048
1049    /// OSS Fuzz caught add with overflow when computing delta indices.
1050    /// See <https://oss-fuzz.com/testcase-detail/5180237819478016>
1051    /// and <https://g-issues.oss-fuzz.com/issues/439498857>
1052    #[test]
1053    fn var_delta_index_overflow() {
1054        let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
1055        let coords = &[F2Dot14::from_f32(0.5)];
1056        let tables = ColrTables::new(font.colr().unwrap());
1057        let instance = ColrInstance::new(&tables, coords, &[]);
1058        // Just don't panic with overflow
1059        let _: [F48Dot16; 4] = instance.var_deltas(0xFFFFFFFE);
1060    }
1061
1062    /// Without a `VarIndexMap`, a variation index is a delta-set index with
1063    /// the outer index in the high word and the inner index in the low word.
1064    /// See <https://learn.microsoft.com/en-us/typography/opentype/spec/colr#colr-table-and-opentype-font-variations>
1065    // Deltas from the scalars a varied font caches match deltas that compute
1066    // every region.
1067    #[test]
1068    fn cached_scalars_give_the_same_deltas() {
1069        use crate::model::{Font, NormalizedCoord};
1070        let font = Font::new(font_test_data::COLRV0V1_VARIABLE, 0).unwrap();
1071        let axes = font.axes().len() as usize;
1072        for value in [-1.0, -0.3, 0.6, 1.0] {
1073            let varied = font
1074                .instance_builder()
1075                .normalized_coords(core::iter::repeat_n(NormalizedCoord::from_f32(value), axes))
1076                .build();
1077            let tables = varied.colr_tables().unwrap();
1078            let scalars = varied.colr_scalars();
1079            assert!(!scalars.is_empty());
1080            let cached = ColrInstance::new(tables, varied.normalized_coords(), scalars);
1081            let computed = ColrInstance::new(tables, varied.normalized_coords(), &[]);
1082            for base in 0..512 {
1083                assert_eq!(
1084                    cached.var_deltas::<4>(base),
1085                    computed.var_deltas::<4>(base),
1086                    "{value} {base}"
1087                );
1088            }
1089        }
1090        assert!(font.colr_scalars().is_empty());
1091    }
1092
1093    #[test]
1094    fn var_deltas_implicit_mapping_uses_outer_index() {
1095        use crate::FontRead;
1096        use font_test_data::bebuffer::BeBuffer;
1097
1098        let buf = BeBuffer::new()
1099            // COLR v1 header with only an item variation store
1100            .push(1u16) // version
1101            .push(0u16) // numBaseGlyphRecords
1102            .push(0u32) // baseGlyphRecordsOffset
1103            .push(0u32) // layerRecordsOffset
1104            .push(0u16) // numLayerRecords
1105            .push(0u32) // baseGlyphListOffset
1106            .push(0u32) // layerListOffset
1107            .push(0u32) // clipListOffset
1108            .push(0u32) // varIndexMapOffset (NULL)
1109            .push(34u32) // itemVariationStoreOffset
1110            // ItemVariationStore
1111            .push(1u16) // format
1112            .push(16u32) // variationRegionListOffset
1113            .push(2u16) // itemVariationDataCount
1114            .extend([26u32, 36]) // itemVariationDataOffsets
1115            // VariationRegionList: one axis, one region
1116            .extend([1u16, 1])
1117            .extend([F2Dot14::ZERO, F2Dot14::ONE, F2Dot14::ONE])
1118            // ItemVariationData 0: a single item with a delta of 100
1119            .extend([1u16, 1, 1, 0])
1120            .push(100i16)
1121            // ItemVariationData 1: a single item with a delta of 200
1122            .extend([1u16, 1, 1, 0])
1123            .push(200i16);
1124        let colr = Colr::read(buf.data().into()).unwrap();
1125        let tables = ColrTables::new(colr);
1126        let coords = &[F2Dot14::ONE];
1127        let instance = ColrInstance::new(&tables, coords, &[]);
1128        assert!(tables.index_map.is_none());
1129        assert_eq!(
1130            instance.var_deltas::<1>(0x0000_0000),
1131            [F48Dot16::from_i32(100)]
1132        );
1133        assert_eq!(
1134            instance.var_deltas::<1>(0x0001_0000),
1135            [F48Dot16::from_i32(200)]
1136        );
1137    }
1138}