1use 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
23pub trait ColrRenderer {
25 fn push_clip_layer(&mut self, clip: &BezPath);
27 fn push_blend_layer(&mut self, blend_mode: BlendMode);
29 fn fill_solid(&mut self, color: AlphaColor<Srgb>);
31 fn fill_gradient(&mut self, gradient: Gradient);
33 fn set_paint_transform(&mut self, affine: Affine);
35 fn pop_layer(&mut self);
37}
38
39pub 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 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 pub fn paint(&mut self) {
72 let color_glyph = self.color_glyph.skrifa_glyph.clone();
73 let location_ref = self.color_glyph.location;
74 let _ = color_glyph.paint(location_ref, self);
76
77 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 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 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 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 end_angle += 0.01;
307 }
308 _ => {
309 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}