Skip to main content

skrifa/color/
instance.rs

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