vello_common/
colr.rs

1// Copyright 2025 the Vello Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Drawing COLR glyphs.
5
6use crate::color::Srgb;
7use crate::color::{AlphaColor, DynamicColor};
8use crate::glyph::{ColorGlyph, OutlinePath};
9use crate::kurbo::{Affine, BezPath, Point, Rect, Shape};
10use crate::math::FloatExt;
11use crate::peniko::{self, BlendMode, ColorStops, Compose, Extend, Gradient, GradientKind, Mix};
12use alloc::boxed::Box;
13use alloc::vec;
14use alloc::vec::Vec;
15use core::fmt::Debug;
16use skrifa::color::{Brush, ColorPainter, ColorStop, CompositeMode, Transform};
17use skrifa::outline::DrawSettings;
18use skrifa::raw::TableProvider;
19use skrifa::raw::types::BoundingBox;
20use skrifa::{GlyphId, MetadataProvider};
21use smallvec::SmallVec;
22
23/// A trait for clients capable of rendering COLR glyphs.
24pub trait ColrRenderer {
25    /// Push a new clip layer.
26    fn push_clip_layer(&mut self, clip: &BezPath);
27    /// Push a new blend layer.
28    fn push_blend_layer(&mut self, blend_mode: BlendMode);
29    /// Fill the current area with the given solid color.
30    fn fill_solid(&mut self, color: AlphaColor<Srgb>);
31    /// Fill the current area with the given gradient color.
32    fn fill_gradient(&mut self, gradient: Gradient);
33    /// Set the transform for paints.
34    fn set_paint_transform(&mut self, affine: Affine);
35    /// Pop the last clip/blend layer.
36    fn pop_layer(&mut self);
37}
38
39/// An abstraction for painting COLR glyphs.
40pub struct ColrPainter<'a> {
41    transforms: Vec<Affine>,
42    color_glyph: Box<ColorGlyph<'a>>,
43    context_color: AlphaColor<Srgb>,
44    painter: &'a mut dyn ColrRenderer,
45    layer_count: u32,
46}
47
48impl Debug for ColrPainter<'_> {
49    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
50        f.debug_struct("ColrPainter()").finish()
51    }
52}
53
54impl<'a> ColrPainter<'a> {
55    /// Create a new COLR painter.
56    pub fn new(
57        color_glyph: Box<ColorGlyph<'a>>,
58        context_color: AlphaColor<Srgb>,
59        painter: &'a mut impl ColrRenderer,
60    ) -> Self {
61        Self {
62            transforms: vec![color_glyph.draw_transform],
63            color_glyph,
64            context_color,
65            painter,
66            layer_count: 0,
67        }
68    }
69
70    /// Paint the underlying glyph.
71    pub fn paint(&mut self) {
72        let color_glyph = self.color_glyph.skrifa_glyph.clone();
73        let location_ref = self.color_glyph.location;
74        // Ignore errors for now.
75        let _ = color_glyph.paint(location_ref, self);
76
77        // In certain malformed fonts (i.e. if there is a cycle), skrifa will not
78        // ensure that the push/pop count is the same, so we pop the remaining ones here.
79        for _ in 0..self.layer_count {
80            self.painter.pop_layer();
81        }
82    }
83
84    fn cur_transform(&self) -> Affine {
85        self.transforms.last().copied().unwrap_or_default()
86    }
87
88    fn palette_index_to_color(&self, palette_index: u16, alpha: f32) -> Option<AlphaColor<Srgb>> {
89        if palette_index != u16::MAX {
90            let color = self
91                .color_glyph
92                .font_ref
93                .cpal()
94                .ok()?
95                .color_records_array()?
96                .ok()?[palette_index as usize];
97
98            Some(
99                AlphaColor::from_rgba8(color.red, color.green, color.blue, color.alpha)
100                    .multiply_alpha(alpha),
101            )
102        } else {
103            Some(self.context_color.multiply_alpha(alpha))
104        }
105    }
106
107    fn convert_stops(&self, stops: &[ColorStop]) -> ColorStops {
108        let mut stops = stops
109            .iter()
110            .map(|s| {
111                let color = self
112                    .palette_index_to_color(s.palette_index, s.alpha)
113                    .unwrap_or(AlphaColor::BLACK);
114
115                peniko::ColorStop {
116                    offset: s.offset,
117                    color: DynamicColor::from_alpha_color(color),
118                }
119            })
120            .collect::<SmallVec<[peniko::ColorStop; 4]>>();
121
122        // Pad stops if necessary, since vello requires offsets
123        // to start at 0.0 and end at 1.0.
124        let first_stop = stops[0];
125        let last_stop = *stops.last().unwrap();
126
127        if first_stop.offset != 0.0 {
128            let mut new_stop = first_stop;
129            new_stop.offset = 0.0;
130            stops.insert(0, new_stop);
131        }
132
133        if last_stop.offset != 1.0 {
134            let mut new_stop = last_stop;
135            new_stop.offset = 1.0;
136            stops.push(new_stop);
137        }
138
139        // The COLR spec has the very specific requirement that if there are multiple stops with the
140        // offset 1.0, only the last one should be used. We abstract this away by removing all such
141        // superfluous stops.
142        while let Some(stop) = stops.get(stops.len() - 2).map(|s| s.offset) {
143            if (stop - 1.0).is_nearly_zero() {
144                stops.remove(stops.len() - 2);
145            } else {
146                break;
147            }
148        }
149
150        ColorStops(stops)
151    }
152}
153
154impl ColorPainter for ColrPainter<'_> {
155    fn push_transform(&mut self, t: Transform) {
156        let affine = Affine::new([
157            f64::from(t.xx),
158            f64::from(t.yx),
159            f64::from(t.xy),
160            f64::from(t.yy),
161            f64::from(t.dx),
162            f64::from(t.dy),
163        ]);
164        self.transforms.push(self.cur_transform() * affine);
165    }
166
167    fn pop_transform(&mut self) {
168        self.transforms.pop();
169    }
170
171    fn push_clip_glyph(&mut self, glyph_id: GlyphId) {
172        let mut outline_builder = OutlinePath::new();
173
174        let outline_glyphs = self.color_glyph.font_ref.outline_glyphs();
175        let Some(outline_glyph) = outline_glyphs.get(glyph_id) else {
176            return;
177        };
178
179        let _ = outline_glyph.draw(
180            DrawSettings::unhinted(
181                skrifa::instance::Size::unscaled(),
182                self.color_glyph.location,
183            ),
184            &mut outline_builder,
185        );
186
187        let finished = outline_builder.0;
188        let transformed = self.cur_transform() * finished;
189
190        self.painter.push_clip_layer(&transformed);
191        self.layer_count += 1;
192    }
193
194    fn push_clip_box(&mut self, clip_box: BoundingBox<f32>) {
195        let rect = Rect::new(
196            f64::from(clip_box.x_min),
197            f64::from(clip_box.y_min),
198            f64::from(clip_box.x_max),
199            f64::from(clip_box.y_max),
200        );
201        let transformed = self.cur_transform() * rect.to_path(0.1);
202
203        self.painter.push_clip_layer(&transformed);
204        self.layer_count += 1;
205    }
206
207    fn pop_clip(&mut self) {
208        self.painter.pop_layer();
209        self.layer_count -= 1;
210    }
211
212    fn fill(&mut self, brush: Brush<'_>) {
213        match brush {
214            Brush::Solid {
215                palette_index,
216                alpha,
217            } => {
218                let color = self
219                    .palette_index_to_color(palette_index, alpha)
220                    .unwrap_or(AlphaColor::BLACK);
221
222                self.painter.fill_solid(color);
223            }
224            Brush::LinearGradient {
225                p0,
226                p1,
227                color_stops,
228                extend,
229            } => {
230                let p0 = convert_point(p0);
231                let p1 = convert_point(p1);
232                let extend = convert_extend(extend);
233                let stops = self.convert_stops(color_stops);
234
235                if stops.len() == 1 {
236                    self.painter.fill_solid(stops[0].color.to_alpha_color());
237                } else {
238                    let grad = Gradient {
239                        kind: GradientKind::Linear { start: p0, end: p1 },
240                        stops,
241                        extend,
242                        ..Default::default()
243                    };
244                    self.painter.set_paint_transform(self.cur_transform());
245                    self.painter.fill_gradient(grad);
246                }
247            }
248            Brush::RadialGradient {
249                c0,
250                r0,
251                c1,
252                r1,
253                color_stops,
254                extend,
255            } => {
256                // TODO: Radial gradients with negative r0.
257
258                let p0 = convert_point(c0);
259                let p1 = convert_point(c1);
260                let extend = convert_extend(extend);
261                let stops = self.convert_stops(color_stops);
262
263                if r1 <= 0.0 || stops.len() == 1 {
264                    self.painter.fill_solid(stops[0].color.to_alpha_color());
265
266                    return;
267                }
268
269                let grad = Gradient {
270                    kind: GradientKind::Radial {
271                        start_center: p0,
272                        start_radius: r0,
273                        end_center: p1,
274                        end_radius: r1,
275                    },
276                    stops,
277                    extend,
278                    ..Default::default()
279                };
280
281                self.painter.set_paint_transform(self.cur_transform());
282                self.painter.fill_gradient(grad);
283            }
284            Brush::SweepGradient {
285                c0,
286                start_angle,
287                mut end_angle,
288                color_stops,
289                extend,
290            } => {
291                let p0 = convert_point(c0);
292                let extend = convert_extend(extend);
293                let stops = self.convert_stops(color_stops);
294
295                if stops.len() == 1 {
296                    self.painter.fill_solid(stops[0].color.to_alpha_color());
297
298                    return;
299                }
300
301                if start_angle == end_angle {
302                    match extend {
303                        Extend::Pad => {
304                            // Vello doesn't accept sweep gradient with same start and end
305                            // angle, so add an artificial, small offset.
306                            end_angle += 0.01;
307                        }
308                        _ => {
309                            // Cannot be reached,
310                            // see https://github.com/googlefonts/fontations/issues/1017.
311                            unreachable!()
312                        }
313                    }
314                }
315
316                let grad = Gradient {
317                    kind: GradientKind::Sweep {
318                        center: p0,
319                        start_angle,
320                        end_angle,
321                    },
322                    stops,
323                    extend,
324                    ..Default::default()
325                };
326
327                self.painter.set_paint_transform(self.cur_transform());
328                self.painter.fill_gradient(grad);
329            }
330        };
331    }
332
333    fn push_layer(&mut self, composite_mode: CompositeMode) {
334        let blend_mode = match composite_mode {
335            CompositeMode::Clear => BlendMode::new(Mix::Normal, Compose::Clear),
336            CompositeMode::Src => BlendMode::new(Mix::Normal, Compose::Copy),
337            CompositeMode::Dest => BlendMode::new(Mix::Normal, Compose::Dest),
338            CompositeMode::SrcOver => BlendMode::new(Mix::Normal, Compose::SrcOver),
339            CompositeMode::DestOver => BlendMode::new(Mix::Normal, Compose::DestOver),
340            CompositeMode::SrcIn => BlendMode::new(Mix::Normal, Compose::SrcIn),
341            CompositeMode::DestIn => BlendMode::new(Mix::Normal, Compose::DestIn),
342            CompositeMode::SrcOut => BlendMode::new(Mix::Normal, Compose::SrcOut),
343            CompositeMode::DestOut => BlendMode::new(Mix::Normal, Compose::DestOut),
344            CompositeMode::SrcAtop => BlendMode::new(Mix::Normal, Compose::SrcAtop),
345            CompositeMode::DestAtop => BlendMode::new(Mix::Normal, Compose::DestAtop),
346            CompositeMode::Xor => BlendMode::new(Mix::Normal, Compose::Xor),
347            CompositeMode::Plus => BlendMode::new(Mix::Normal, Compose::Plus),
348            CompositeMode::Screen => BlendMode::new(Mix::Screen, Compose::SrcOver),
349            CompositeMode::Overlay => BlendMode::new(Mix::Overlay, Compose::SrcOver),
350            CompositeMode::Darken => BlendMode::new(Mix::Darken, Compose::SrcOver),
351            CompositeMode::Lighten => BlendMode::new(Mix::Lighten, Compose::SrcOver),
352            CompositeMode::ColorDodge => BlendMode::new(Mix::ColorDodge, Compose::SrcOver),
353            CompositeMode::ColorBurn => BlendMode::new(Mix::ColorBurn, Compose::SrcOver),
354            CompositeMode::HardLight => BlendMode::new(Mix::HardLight, Compose::SrcOver),
355            CompositeMode::SoftLight => BlendMode::new(Mix::SoftLight, Compose::SrcOver),
356            CompositeMode::Difference => BlendMode::new(Mix::Difference, Compose::SrcOver),
357            CompositeMode::Exclusion => BlendMode::new(Mix::Exclusion, Compose::SrcOver),
358            CompositeMode::Multiply => BlendMode::new(Mix::Multiply, Compose::SrcOver),
359            CompositeMode::HslHue => BlendMode::new(Mix::Hue, Compose::SrcOver),
360            CompositeMode::HslSaturation => BlendMode::new(Mix::Saturation, Compose::SrcOver),
361            CompositeMode::HslColor => BlendMode::new(Mix::Color, Compose::SrcOver),
362            CompositeMode::HslLuminosity => BlendMode::new(Mix::Luminosity, Compose::SrcOver),
363            CompositeMode::Unknown => BlendMode::new(Mix::Normal, Compose::SrcOver),
364        };
365
366        self.painter.push_blend_layer(blend_mode);
367        self.layer_count += 1;
368    }
369
370    fn pop_layer(&mut self) {
371        self.painter.pop_layer();
372        self.layer_count -= 1;
373    }
374}
375
376fn convert_extend(extend: skrifa::color::Extend) -> Extend {
377    match extend {
378        skrifa::color::Extend::Pad => Extend::Pad,
379        skrifa::color::Extend::Repeat => Extend::Repeat,
380        skrifa::color::Extend::Reflect => Extend::Reflect,
381        skrifa::color::Extend::Unknown => Extend::Pad,
382    }
383}
384
385fn convert_point(point: skrifa::raw::types::Point<f32>) -> Point {
386    Point::new(f64::from(point.x), f64::from(point.y))
387}
388
389pub(crate) fn convert_bounding_box(rect: BoundingBox<f32>) -> Rect {
390    Rect::new(
391        f64::from(rect.x_min),
392        f64::from(rect.y_min),
393        f64::from(rect.x_max),
394        f64::from(rect.y_max),
395    )
396}