skrifa/color/
instance.rs

1//! COLR table instance.
2
3use read_fonts::{
4    tables::{
5        colr::*,
6        variations::{
7            DeltaSetIndex, DeltaSetIndexMap, FloatItemDelta, FloatItemDeltaTarget,
8            ItemVariationStore,
9        },
10    },
11    types::{BoundingBox, F2Dot14, GlyphId16, Point},
12    ReadError,
13};
14
15use core::ops::{Deref, Range};
16
17/// Unique paint identifier used for detecting cycles in the paint graph.
18pub type PaintId = usize;
19
20/// Combination of a `COLR` table and a location in variation space for
21/// resolving paints.
22///
23/// See [`resolve_paint`], [`ColorStops::resolve`] and [`resolve_clip_box`].
24#[derive(Clone)]
25pub struct ColrInstance<'a> {
26    colr: Colr<'a>,
27    index_map: Option<DeltaSetIndexMap<'a>>,
28    var_store: Option<ItemVariationStore<'a>>,
29    coords: &'a [F2Dot14],
30}
31
32impl<'a> ColrInstance<'a> {
33    /// Creates a new instance for the given `COLR` table and normalized variation
34    /// coordinates.
35    pub fn new(colr: Colr<'a>, coords: &'a [F2Dot14]) -> Self {
36        let index_map = colr.var_index_map().and_then(|res| res.ok());
37        let var_store = colr.item_variation_store().and_then(|res| res.ok());
38        Self {
39            colr,
40            coords,
41            index_map,
42            var_store,
43        }
44    }
45
46    /// Computes a sequence of N variation deltas starting at the given
47    /// `var_base` index.
48    fn var_deltas<const N: usize>(&self, var_index_base: u32) -> [FloatItemDelta; N] {
49        // Magic value that indicates deltas should not be applied.
50        const NO_VARIATION_DELTAS: u32 = 0xFFFFFFFF;
51        // Note: FreeType never returns an error for these lookups, so
52        // we do the same and just `unwrap_or_default` on var store
53        // errors.
54        // See <https://gitlab.freedesktop.org/freetype/freetype/-/blob/fc01e7dd/src/sfnt/ttcolr.c#L574>
55        let mut deltas = [FloatItemDelta::ZERO; N];
56        if self.coords.is_empty()
57            || self.var_store.is_none()
58            || var_index_base == NO_VARIATION_DELTAS
59        {
60            return deltas;
61        }
62        // Avoid overflow if var_index_base + N > u32::MAX
63        let actual_count = ((u32::MAX - var_index_base) as usize).min(N);
64        let var_store = self.var_store.as_ref().unwrap();
65        if let Some(index_map) = self.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_float_delta(delta_ix, self.coords)
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                // If we don't have a var index map, use our index as the inner
78                // component and set the outer to 0.
79                let delta_ix = DeltaSetIndex {
80                    outer: 0,
81                    inner: var_index as u16,
82                };
83                *delta = var_store
84                    .compute_float_delta(delta_ix, self.coords)
85                    .unwrap_or_default();
86            }
87        }
88        deltas
89    }
90}
91
92impl<'a> Deref for ColrInstance<'a> {
93    type Target = Colr<'a>;
94
95    fn deref(&self) -> &Self::Target {
96        &self.colr
97    }
98}
99
100/// Resolves a clip box, applying variation deltas using the given
101/// instance.
102pub fn resolve_clip_box(instance: &ColrInstance, clip_box: &ClipBox) -> BoundingBox<f32> {
103    match clip_box {
104        ClipBox::Format1(cbox) => BoundingBox {
105            x_min: cbox.x_min().to_i16() as f32,
106            y_min: cbox.y_min().to_i16() as f32,
107            x_max: cbox.x_max().to_i16() as f32,
108            y_max: cbox.y_max().to_i16() as f32,
109        },
110        ClipBox::Format2(cbox) => {
111            let deltas = instance.var_deltas::<4>(cbox.var_index_base());
112            BoundingBox {
113                x_min: cbox.x_min().apply_float_delta(deltas[0]),
114                y_min: cbox.y_min().apply_float_delta(deltas[1]),
115                x_max: cbox.x_max().apply_float_delta(deltas[2]),
116                y_max: cbox.y_max().apply_float_delta(deltas[3]),
117            }
118        }
119    }
120}
121
122/// Simplified version of a [`ColorStop`] or [`VarColorStop`] with applied
123/// variation deltas.
124#[derive(Clone, Debug)]
125pub struct ResolvedColorStop {
126    pub offset: f32,
127    pub palette_index: u16,
128    pub alpha: f32,
129}
130
131/// Collection of [`ColorStop`] or [`VarColorStop`].
132// Note: only one of these fields is used at any given time, but this structure
133// was chosen over the obvious enum approach for simplicity in generating a
134// single concrete type for the `impl Iterator` return type of the `resolve`
135// method.
136#[derive(Clone)]
137pub struct ColorStops<'a> {
138    stops: &'a [ColorStop],
139    var_stops: &'a [VarColorStop],
140}
141
142impl ColorStops<'_> {
143    pub fn len(&self) -> usize {
144        self.stops.len() + self.var_stops.len()
145    }
146
147    pub fn is_empty(&self) -> bool {
148        self.stops.is_empty() && self.var_stops.is_empty()
149    }
150}
151
152impl<'a> From<ColorLine<'a>> for ColorStops<'a> {
153    fn from(value: ColorLine<'a>) -> Self {
154        Self {
155            stops: value.color_stops(),
156            var_stops: &[],
157        }
158    }
159}
160
161impl<'a> From<VarColorLine<'a>> for ColorStops<'a> {
162    fn from(value: VarColorLine<'a>) -> Self {
163        Self {
164            stops: &[],
165            var_stops: value.color_stops(),
166        }
167    }
168}
169
170impl<'a> ColorStops<'a> {
171    /// Returns an iterator yielding resolved color stops with variation deltas
172    /// applied.
173    pub fn resolve(
174        &self,
175        instance: &'a ColrInstance<'a>,
176    ) -> impl Iterator<Item = ResolvedColorStop> + 'a {
177        self.stops
178            .iter()
179            .map(|stop| ResolvedColorStop {
180                offset: stop.stop_offset().to_f32(),
181                palette_index: stop.palette_index(),
182                alpha: stop.alpha().to_f32(),
183            })
184            .chain(self.var_stops.iter().map(|stop| {
185                let deltas = instance.var_deltas::<2>(stop.var_index_base());
186                ResolvedColorStop {
187                    offset: stop.stop_offset().apply_float_delta(deltas[0]),
188                    palette_index: stop.palette_index(),
189                    alpha: stop.alpha().apply_float_delta(deltas[1]),
190                }
191            }))
192    }
193}
194
195/// Simplified version of `Paint` with applied variation deltas.
196///
197/// These are constructed with the [`resolve_paint`] function.
198///
199/// This is roughly equivalent to FreeType's
200/// [`FT_COLR_Paint`](https://freetype.org/freetype2/docs/reference/ft2-layer_management.html#ft_colr_paint)
201/// type.
202pub enum ResolvedPaint<'a> {
203    ColrLayers {
204        range: Range<usize>,
205    },
206    Solid {
207        palette_index: u16,
208        alpha: f32,
209    },
210    LinearGradient {
211        x0: f32,
212        y0: f32,
213        x1: f32,
214        y1: f32,
215        x2: f32,
216        y2: f32,
217        color_stops: ColorStops<'a>,
218        extend: Extend,
219    },
220    RadialGradient {
221        x0: f32,
222        y0: f32,
223        radius0: f32,
224        x1: f32,
225        y1: f32,
226        radius1: f32,
227        color_stops: ColorStops<'a>,
228        extend: Extend,
229    },
230    SweepGradient {
231        center_x: f32,
232        center_y: f32,
233        start_angle: f32,
234        end_angle: f32,
235        color_stops: ColorStops<'a>,
236        extend: Extend,
237    },
238    Glyph {
239        glyph_id: GlyphId16,
240        paint: Paint<'a>,
241    },
242    ColrGlyph {
243        glyph_id: GlyphId16,
244    },
245    Transform {
246        xx: f32,
247        yx: f32,
248        xy: f32,
249        yy: f32,
250        dx: f32,
251        dy: f32,
252        paint: Paint<'a>,
253    },
254    Translate {
255        dx: f32,
256        dy: f32,
257        paint: Paint<'a>,
258    },
259    Scale {
260        scale_x: f32,
261        scale_y: f32,
262        around_center: Option<Point<f32>>,
263        paint: Paint<'a>,
264    },
265    Rotate {
266        angle: f32,
267        around_center: Option<Point<f32>>,
268        paint: Paint<'a>,
269    },
270    Skew {
271        x_skew_angle: f32,
272        y_skew_angle: f32,
273        around_center: Option<Point<f32>>,
274        paint: Paint<'a>,
275    },
276    Composite {
277        source_paint: Paint<'a>,
278        mode: CompositeMode,
279        backdrop_paint: Paint<'a>,
280    },
281}
282
283/// Resolves this paint with the given instance.
284///
285/// Resolving means that all numeric values are converted to 32-bit floating
286/// point, variation deltas are applied (also computed fully in floating
287/// point), and the various transform paints are collapsed into a single value
288/// for their category (transform, translate, scale, rotate and skew).
289///
290/// This provides a simpler type for consumers that are more interested
291/// in extracting the semantics of the graph rather than working with the
292/// raw encoded structures.
293pub fn resolve_paint<'a>(
294    instance: &ColrInstance<'a>,
295    paint: &Paint<'a>,
296) -> Result<ResolvedPaint<'a>, ReadError> {
297    Ok(match paint {
298        Paint::ColrLayers(layers) => {
299            let start = layers.first_layer_index() as usize;
300            ResolvedPaint::ColrLayers {
301                range: start..start + layers.num_layers() as usize,
302            }
303        }
304        Paint::Solid(solid) => ResolvedPaint::Solid {
305            palette_index: solid.palette_index(),
306            alpha: solid.alpha().to_f32(),
307        },
308        Paint::VarSolid(solid) => {
309            let deltas = instance.var_deltas::<1>(solid.var_index_base());
310            ResolvedPaint::Solid {
311                palette_index: solid.palette_index(),
312                alpha: solid.alpha().apply_float_delta(deltas[0]),
313            }
314        }
315        Paint::LinearGradient(gradient) => {
316            let color_line = gradient.color_line()?;
317            let extend = color_line.extend();
318            ResolvedPaint::LinearGradient {
319                x0: gradient.x0().to_i16() as f32,
320                y0: gradient.y0().to_i16() as f32,
321                x1: gradient.x1().to_i16() as f32,
322                y1: gradient.y1().to_i16() as f32,
323                x2: gradient.x2().to_i16() as f32,
324                y2: gradient.y2().to_i16() as f32,
325                color_stops: color_line.into(),
326                extend,
327            }
328        }
329        Paint::VarLinearGradient(gradient) => {
330            let color_line = gradient.color_line()?;
331            let extend = color_line.extend();
332            let deltas = instance.var_deltas::<6>(gradient.var_index_base());
333            ResolvedPaint::LinearGradient {
334                x0: gradient.x0().apply_float_delta(deltas[0]),
335                y0: gradient.y0().apply_float_delta(deltas[1]),
336                x1: gradient.x1().apply_float_delta(deltas[2]),
337                y1: gradient.y1().apply_float_delta(deltas[3]),
338                x2: gradient.x2().apply_float_delta(deltas[4]),
339                y2: gradient.y2().apply_float_delta(deltas[5]),
340                color_stops: color_line.into(),
341                extend,
342            }
343        }
344        Paint::RadialGradient(gradient) => {
345            let color_line = gradient.color_line()?;
346            let extend = color_line.extend();
347            ResolvedPaint::RadialGradient {
348                x0: gradient.x0().to_i16() as f32,
349                y0: gradient.y0().to_i16() as f32,
350                radius0: gradient.radius0().to_u16() as f32,
351                x1: gradient.x1().to_i16() as f32,
352                y1: gradient.y1().to_i16() as f32,
353                radius1: gradient.radius1().to_u16() as f32,
354                color_stops: color_line.into(),
355                extend,
356            }
357        }
358        Paint::VarRadialGradient(gradient) => {
359            let color_line = gradient.color_line()?;
360            let extend = color_line.extend();
361            let deltas = instance.var_deltas::<6>(gradient.var_index_base());
362            ResolvedPaint::RadialGradient {
363                x0: gradient.x0().apply_float_delta(deltas[0]),
364                y0: gradient.y0().apply_float_delta(deltas[1]),
365                radius0: gradient.radius0().apply_float_delta(deltas[2]),
366                x1: gradient.x1().apply_float_delta(deltas[3]),
367                y1: gradient.y1().apply_float_delta(deltas[4]),
368                radius1: gradient.radius1().apply_float_delta(deltas[5]),
369                color_stops: color_line.into(),
370                extend,
371            }
372        }
373        Paint::SweepGradient(gradient) => {
374            let color_line = gradient.color_line()?;
375            let extend = color_line.extend();
376            ResolvedPaint::SweepGradient {
377                center_x: gradient.center_x().to_i16() as f32,
378                center_y: gradient.center_y().to_i16() as f32,
379                start_angle: gradient.start_angle().to_f32(),
380                end_angle: gradient.end_angle().to_f32(),
381                color_stops: color_line.into(),
382                extend,
383            }
384        }
385        Paint::VarSweepGradient(gradient) => {
386            let color_line = gradient.color_line()?;
387            let extend = color_line.extend();
388            let deltas = instance.var_deltas::<4>(gradient.var_index_base());
389            ResolvedPaint::SweepGradient {
390                center_x: gradient.center_x().apply_float_delta(deltas[0]),
391                center_y: gradient.center_y().apply_float_delta(deltas[1]),
392                start_angle: gradient.start_angle().apply_float_delta(deltas[2]),
393                end_angle: gradient.end_angle().apply_float_delta(deltas[3]),
394                color_stops: color_line.into(),
395                extend,
396            }
397        }
398        Paint::Glyph(glyph) => ResolvedPaint::Glyph {
399            glyph_id: glyph.glyph_id(),
400            paint: glyph.paint()?,
401        },
402        Paint::ColrGlyph(glyph) => ResolvedPaint::ColrGlyph {
403            glyph_id: glyph.glyph_id(),
404        },
405        Paint::Transform(transform) => {
406            let affine = transform.transform()?;
407            let paint = transform.paint()?;
408            ResolvedPaint::Transform {
409                xx: affine.xx().to_f32(),
410                yx: affine.yx().to_f32(),
411                xy: affine.xy().to_f32(),
412                yy: affine.yy().to_f32(),
413                dx: affine.dx().to_f32(),
414                dy: affine.dy().to_f32(),
415                paint,
416            }
417        }
418        Paint::VarTransform(transform) => {
419            let affine = transform.transform()?;
420            let paint = transform.paint()?;
421            let deltas = instance.var_deltas::<6>(affine.var_index_base());
422            ResolvedPaint::Transform {
423                xx: affine.xx().apply_float_delta(deltas[0]),
424                yx: affine.yx().apply_float_delta(deltas[1]),
425                xy: affine.xy().apply_float_delta(deltas[2]),
426                yy: affine.yy().apply_float_delta(deltas[3]),
427                dx: affine.dx().apply_float_delta(deltas[4]),
428                dy: affine.dy().apply_float_delta(deltas[5]),
429                paint,
430            }
431        }
432        Paint::Translate(transform) => ResolvedPaint::Translate {
433            dx: transform.dx().to_i16() as f32,
434            dy: transform.dy().to_i16() as f32,
435            paint: transform.paint()?,
436        },
437        Paint::VarTranslate(transform) => {
438            let deltas = instance.var_deltas::<2>(transform.var_index_base());
439            ResolvedPaint::Translate {
440                dx: transform.dx().apply_float_delta(deltas[0]),
441                dy: transform.dy().apply_float_delta(deltas[1]),
442                paint: transform.paint()?,
443            }
444        }
445        Paint::Scale(transform) => ResolvedPaint::Scale {
446            scale_x: transform.scale_x().to_f32(),
447            scale_y: transform.scale_y().to_f32(),
448            around_center: None,
449            paint: transform.paint()?,
450        },
451        Paint::VarScale(transform) => {
452            let deltas = instance.var_deltas::<2>(transform.var_index_base());
453            ResolvedPaint::Scale {
454                scale_x: transform.scale_x().apply_float_delta(deltas[0]),
455                scale_y: transform.scale_y().apply_float_delta(deltas[1]),
456                around_center: None,
457                paint: transform.paint()?,
458            }
459        }
460        Paint::ScaleAroundCenter(transform) => ResolvedPaint::Scale {
461            scale_x: transform.scale_x().to_f32(),
462            scale_y: transform.scale_y().to_f32(),
463            around_center: Some(Point::new(
464                transform.center_x().to_i16() as f32,
465                transform.center_y().to_i16() as f32,
466            )),
467            paint: transform.paint()?,
468        },
469        Paint::VarScaleAroundCenter(transform) => {
470            let deltas = instance.var_deltas::<4>(transform.var_index_base());
471            ResolvedPaint::Scale {
472                scale_x: transform.scale_x().apply_float_delta(deltas[0]),
473                scale_y: transform.scale_y().apply_float_delta(deltas[1]),
474                around_center: Some(Point::new(
475                    transform.center_x().apply_float_delta(deltas[2]),
476                    transform.center_y().apply_float_delta(deltas[3]),
477                )),
478                paint: transform.paint()?,
479            }
480        }
481        Paint::ScaleUniform(transform) => {
482            let scale = transform.scale().to_f32();
483            ResolvedPaint::Scale {
484                scale_x: scale,
485                scale_y: scale,
486                around_center: None,
487                paint: transform.paint()?,
488            }
489        }
490        Paint::VarScaleUniform(transform) => {
491            let deltas = instance.var_deltas::<1>(transform.var_index_base());
492            let scale = transform.scale().apply_float_delta(deltas[0]);
493            ResolvedPaint::Scale {
494                scale_x: scale,
495                scale_y: scale,
496                around_center: None,
497                paint: transform.paint()?,
498            }
499        }
500        Paint::ScaleUniformAroundCenter(transform) => {
501            let scale = transform.scale().to_f32();
502            ResolvedPaint::Scale {
503                scale_x: scale,
504                scale_y: scale,
505                around_center: Some(Point::new(
506                    transform.center_x().to_i16() as f32,
507                    transform.center_y().to_i16() as f32,
508                )),
509                paint: transform.paint()?,
510            }
511        }
512        Paint::VarScaleUniformAroundCenter(transform) => {
513            let deltas = instance.var_deltas::<3>(transform.var_index_base());
514            let scale = transform.scale().apply_float_delta(deltas[0]);
515            ResolvedPaint::Scale {
516                scale_x: scale,
517                scale_y: scale,
518                around_center: Some(Point::new(
519                    transform.center_x().apply_float_delta(deltas[1]),
520                    transform.center_y().apply_float_delta(deltas[2]),
521                )),
522                paint: transform.paint()?,
523            }
524        }
525        Paint::Rotate(transform) => ResolvedPaint::Rotate {
526            angle: transform.angle().to_f32(),
527            around_center: None,
528            paint: transform.paint()?,
529        },
530        Paint::VarRotate(transform) => {
531            let deltas = instance.var_deltas::<1>(transform.var_index_base());
532            ResolvedPaint::Rotate {
533                angle: transform.angle().apply_float_delta(deltas[0]),
534                around_center: None,
535                paint: transform.paint()?,
536            }
537        }
538        Paint::RotateAroundCenter(transform) => ResolvedPaint::Rotate {
539            angle: transform.angle().to_f32(),
540            around_center: Some(Point::new(
541                transform.center_x().to_i16() as f32,
542                transform.center_y().to_i16() as f32,
543            )),
544            paint: transform.paint()?,
545        },
546        Paint::VarRotateAroundCenter(transform) => {
547            let deltas = instance.var_deltas::<3>(transform.var_index_base());
548            ResolvedPaint::Rotate {
549                angle: transform.angle().apply_float_delta(deltas[0]),
550                around_center: Some(Point::new(
551                    transform.center_x().apply_float_delta(deltas[1]),
552                    transform.center_y().apply_float_delta(deltas[2]),
553                )),
554                paint: transform.paint()?,
555            }
556        }
557        Paint::Skew(transform) => ResolvedPaint::Skew {
558            x_skew_angle: transform.x_skew_angle().to_f32(),
559            y_skew_angle: transform.y_skew_angle().to_f32(),
560            around_center: None,
561            paint: transform.paint()?,
562        },
563        Paint::VarSkew(transform) => {
564            let deltas = instance.var_deltas::<2>(transform.var_index_base());
565            ResolvedPaint::Skew {
566                x_skew_angle: transform.x_skew_angle().apply_float_delta(deltas[0]),
567                y_skew_angle: transform.y_skew_angle().apply_float_delta(deltas[1]),
568                around_center: None,
569                paint: transform.paint()?,
570            }
571        }
572        Paint::SkewAroundCenter(transform) => ResolvedPaint::Skew {
573            x_skew_angle: transform.x_skew_angle().to_f32(),
574            y_skew_angle: transform.y_skew_angle().to_f32(),
575            around_center: Some(Point::new(
576                transform.center_x().to_i16() as f32,
577                transform.center_y().to_i16() as f32,
578            )),
579            paint: transform.paint()?,
580        },
581        Paint::VarSkewAroundCenter(transform) => {
582            let deltas = instance.var_deltas::<4>(transform.var_index_base());
583            ResolvedPaint::Skew {
584                x_skew_angle: transform.x_skew_angle().apply_float_delta(deltas[0]),
585                y_skew_angle: transform.y_skew_angle().apply_float_delta(deltas[1]),
586                around_center: Some(Point::new(
587                    transform.center_x().apply_float_delta(deltas[2]),
588                    transform.center_y().apply_float_delta(deltas[3]),
589                )),
590                paint: transform.paint()?,
591            }
592        }
593        Paint::Composite(composite) => ResolvedPaint::Composite {
594            source_paint: composite.source_paint()?,
595            mode: composite.composite_mode(),
596            backdrop_paint: composite.backdrop_paint()?,
597        },
598    })
599}
600
601#[cfg(test)]
602mod tests {
603    use super::*;
604    use raw::{FontRef, TableProvider};
605
606    /// OSS Fuzz caught add with overflow when computing delta indices.
607    /// See <https://oss-fuzz.com/testcase-detail/5180237819478016>
608    /// and <https://g-issues.oss-fuzz.com/issues/439498857>
609    #[test]
610    fn var_delta_index_overflow() {
611        let font = FontRef::new(font_test_data::COLRV0V1_VARIABLE).unwrap();
612        let coords = &[F2Dot14::from_f32(0.5)];
613        let instance = ColrInstance::new(font.colr().unwrap(), coords);
614        // Just don't panic with overflow
615        let _: [FloatItemDelta; 4] = instance.var_deltas(0xFFFFFFFE);
616    }
617}