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