1use crate::atlas::commands::AtlasPaint;
7use crate::color::Srgb;
8use crate::color::{AlphaColor, DynamicColor};
9use crate::glyph::{
10 CachedOutline, FontEmbolden, FontInfo, GlyphColr, OutlineCacheSession, OutlinePath,
11 VarLookupKey,
12};
13use crate::interface::DrawSink;
14use crate::kurbo::{Affine, Point, Rect, Shape};
15use crate::peniko::{self, BlendMode, ColorStops, Compose, Extend, Gradient, Mix};
16use crate::util::FloatExt;
17use alloc::sync::Arc;
18use alloc::vec;
19use alloc::vec::Vec;
20use core::fmt::Debug;
21use peniko::{LinearGradientPosition, RadialGradientPosition, SweepGradientPosition};
22use skrifa::color::{Brush, ColorPainter, ColorStop, CompositeMode, Transform};
23use skrifa::instance::LocationRef;
24use skrifa::outline::OutlineGlyphCollection;
25use skrifa::raw::TableProvider;
26use skrifa::raw::types::BoundingBox;
27use skrifa::{FontRef, GlyphId, MetadataProvider};
28use smallvec::SmallVec;
29
30trait ColrDrawSinkExt: DrawSink {
31 fn fill_with_paint(&mut self, rect: &Rect, paint: AtlasPaint) {
32 self.set_paint(paint);
33 self.fill_rect(rect);
34 }
35
36 fn fill_solid(&mut self, rect: &Rect, color: AlphaColor<Srgb>) {
37 self.fill_with_paint(rect, AtlasPaint::Solid(color));
38 }
39
40 fn fill_gradient(&mut self, rect: &Rect, gradient: Gradient) {
41 self.fill_with_paint(rect, AtlasPaint::Gradient(gradient));
42 }
43}
44
45impl<T: DrawSink + ?Sized> ColrDrawSinkExt for T {}
46
47pub(crate) struct ColrPainter<'a, 'b> {
49 transforms: Vec<Affine>,
50 colr_glyph: &'a GlyphColr<'a>,
51 outline_glyphs: OutlineGlyphCollection<'a>,
52 outline_cache: &'a mut OutlineCacheSession<'b>,
53 clip_outline: OutlinePath,
54 context_color: AlphaColor<Srgb>,
55 painter: &'a mut dyn DrawSink,
56 stack: Vec<ColrStackEntry>,
57 skip_blend_layers: bool,
58}
59
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61enum ColrStackEntry {
62 ClipPath,
63 BlendLayer,
64}
65
66impl Debug for ColrPainter<'_, '_> {
67 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
68 f.debug_struct("ColrPainter()").finish()
69 }
70}
71
72impl<'a, 'b> ColrPainter<'a, 'b> {
73 pub(crate) fn new(
75 colr_glyph: &'a GlyphColr<'a>,
76 context_color: AlphaColor<Srgb>,
77 painter: &'a mut dyn DrawSink,
78 outline_cache: &'a mut OutlineCacheSession<'b>,
79 ) -> Self {
80 Self {
81 transforms: vec![colr_glyph.draw_transform],
82 colr_glyph,
83 outline_glyphs: colr_glyph.font_ref.outline_glyphs(),
84 outline_cache,
85 clip_outline: OutlinePath::new(),
86 context_color,
87 painter,
88 stack: Vec::new(),
89 skip_blend_layers: !colr_glyph.has_non_default_blend,
92 }
93 }
94
95 pub(crate) fn paint(&mut self) {
97 let skrifa_glyph = self.colr_glyph.skrifa_glyph.clone();
98 let location_ref = self.colr_glyph.location;
99 let _ = skrifa_glyph.paint(location_ref, self);
101
102 while let Some(entry) = self.stack.pop() {
105 match entry {
106 ColrStackEntry::ClipPath => self.painter.pop_clip_path(),
107 ColrStackEntry::BlendLayer => self.painter.pop_layer(),
108 }
109 }
110 }
111
112 fn cur_transform(&self) -> Affine {
113 self.transforms.last().copied().unwrap_or_default()
114 }
115
116 fn get_outline(&mut self, glyph_id: GlyphId) -> Option<CachedOutline<'_>> {
117 let outline_glyph = self.outline_glyphs.get(glyph_id)?;
118
119 Some(self.outline_cache.get_or_insert(
120 glyph_id.to_u32(),
121 self.colr_glyph.font_info,
122 self.colr_glyph.font_info.upem,
123 FontEmbolden::default(),
124 VarLookupKey::new(self.colr_glyph.location.coords()),
125 &outline_glyph,
126 None,
127 ))
128 }
129
130 fn palette_index_to_color(&self, palette_index: u16, alpha: f32) -> Option<AlphaColor<Srgb>> {
131 if palette_index != u16::MAX {
132 let color = self
133 .colr_glyph
134 .font_ref
135 .cpal()
136 .ok()?
137 .color_records_array()?
138 .ok()?[palette_index as usize];
139
140 Some(
141 AlphaColor::from_rgba8(color.red, color.green, color.blue, color.alpha)
142 .multiply_alpha(alpha),
143 )
144 } else {
145 Some(self.context_color.multiply_alpha(alpha))
146 }
147 }
148
149 fn convert_stops(&self, stops: &[ColorStop]) -> ColorStops {
150 let mut stops = stops
151 .iter()
152 .map(|s| {
153 let color = self
154 .palette_index_to_color(s.palette_index, s.alpha)
155 .unwrap_or(AlphaColor::BLACK);
156
157 peniko::ColorStop {
158 offset: s.offset,
159 color: DynamicColor::from_alpha_color(color),
160 }
161 })
162 .collect::<SmallVec<[peniko::ColorStop; 4]>>();
163
164 let first_stop = stops[0];
167 let last_stop = *stops.last().unwrap();
168
169 if first_stop.offset != 0.0 {
170 let mut new_stop = first_stop;
171 new_stop.offset = 0.0;
172 stops.insert(0, new_stop);
173 }
174
175 if last_stop.offset != 1.0 {
176 let mut new_stop = last_stop;
177 new_stop.offset = 1.0;
178 stops.push(new_stop);
179 }
180
181 while let Some(stop) = stops.get(stops.len() - 2).map(|s| s.offset) {
185 if (stop - 1.0).is_nearly_zero() {
186 stops.remove(stops.len() - 2);
187 } else {
188 break;
189 }
190 }
191
192 ColorStops(stops)
193 }
194
195 fn push_clip(&mut self) {
196 self.painter.push_clip_path(&self.clip_outline.path);
197 self.stack.push(ColrStackEntry::ClipPath);
198 }
199
200 fn pop_stack_entry(&mut self, expected: ColrStackEntry) -> bool {
201 #[cfg(test)]
204 assert_eq!(
205 self.stack.last().copied(),
206 Some(expected),
207 "assertion should always be true for valid fonts"
208 );
209
210 if self.stack.last().copied() == Some(expected) {
211 self.stack.pop();
212
213 true
214 } else {
215 false
216 }
217 }
218}
219
220pub(crate) struct ColrGlyphInfo {
221 pub(crate) bbox: Option<Rect>,
225 pub(crate) has_non_default_blend: bool,
227}
228
229pub(crate) fn get_colr_info<'a, 'b>(
230 font_ref: &'a FontRef<'a>,
231 color_glyph: &skrifa::color::ColorGlyph<'a>,
232 location: LocationRef<'a>,
233 outline_cache: &'a mut OutlineCacheSession<'b>,
234 font_info: FontInfo,
235) -> ColrGlyphInfo {
236 let mut extractor = GlyphInfoExtractor::new(font_ref, location, outline_cache, font_info);
237 let _ = color_glyph.paint(location, &mut extractor);
238 extractor.finish()
239}
240
241struct GlyphInfoExtractor<'a, 'b> {
242 transforms: Vec<Affine>,
243 clip_stack: Vec<Rect>,
244 coarse_bbox: Option<Rect>,
245 has_non_default_blend: bool,
246 outline_glyphs: OutlineGlyphCollection<'a>,
247 outline_cache: &'a mut OutlineCacheSession<'b>,
248 location: LocationRef<'a>,
249 font_info: FontInfo,
250}
251
252impl<'a, 'b> GlyphInfoExtractor<'a, 'b> {
253 fn new(
254 font_ref: &'a FontRef<'a>,
255 location: LocationRef<'a>,
256 outline_cache: &'a mut OutlineCacheSession<'b>,
257 font_info: FontInfo,
258 ) -> Self {
259 Self {
260 transforms: vec![Affine::IDENTITY],
261 clip_stack: Vec::new(),
262 coarse_bbox: None,
263 has_non_default_blend: false,
264 outline_glyphs: font_ref.outline_glyphs(),
265 outline_cache,
266 location,
267 font_info,
268 }
269 }
270
271 fn cur_transform(&self) -> Affine {
272 self.transforms.last().copied().unwrap_or_default()
273 }
274
275 fn push_clip_bbox(&mut self, clip_bbox: Rect) {
276 let active = self
277 .clip_stack
278 .last()
279 .copied()
280 .map_or(clip_bbox, |parent| parent.intersect(clip_bbox));
281 self.coarse_bbox = Some(
282 self.coarse_bbox
283 .map_or(active, |coarse_bbox| coarse_bbox.union(active)),
284 );
285 self.clip_stack.push(active);
286 }
287
288 fn transform_rect(&self, rect: Rect) -> Rect {
289 self.cur_transform().transform_rect_bbox(rect)
290 }
291
292 fn get_outline(&mut self, glyph_id: GlyphId) -> Option<CachedOutline<'_>> {
293 let outline_glyph = self.outline_glyphs.get(glyph_id)?;
294
295 Some(self.outline_cache.get_or_insert(
296 glyph_id.to_u32(),
297 self.font_info,
298 self.font_info.upem,
299 FontEmbolden::default(),
300 VarLookupKey::new(self.location.coords()),
301 &outline_glyph,
302 None,
303 ))
304 }
305
306 fn finish(self) -> ColrGlyphInfo {
307 ColrGlyphInfo {
308 bbox: self.coarse_bbox,
309 has_non_default_blend: self.has_non_default_blend,
310 }
311 }
312}
313
314impl ColorPainter for ColrPainter<'_, '_> {
315 fn push_transform(&mut self, t: Transform) {
316 self.transforms
317 .push(self.cur_transform() * convert_affine(t));
318 }
319
320 fn pop_transform(&mut self) {
321 self.transforms.pop();
322 }
323
324 fn push_clip_glyph(&mut self, glyph_id: GlyphId) {
325 let outline = {
326 let Some(outline) = self.get_outline(glyph_id) else {
327 return;
328 };
329 Arc::clone(outline.path)
330 };
331
332 self.clip_outline.reuse();
333 self.clip_outline.path.extend(outline.iter());
336 self.clip_outline.path.apply_affine(self.cur_transform());
337 self.push_clip();
338 }
339
340 fn push_clip_box(&mut self, clip_box: BoundingBox<f32>) {
341 let rect = Rect::new(
342 f64::from(clip_box.x_min),
343 f64::from(clip_box.y_min),
344 f64::from(clip_box.x_max),
345 f64::from(clip_box.y_max),
346 );
347 let transformed = self.cur_transform().transform_rect_bbox(rect);
348 self.clip_outline.reuse();
349 self.clip_outline
351 .path
352 .extend(transformed.path_elements(0.1));
353 self.push_clip();
354 }
355
356 fn pop_clip(&mut self) {
357 if self.pop_stack_entry(ColrStackEntry::ClipPath) {
358 self.painter.pop_clip_path();
359 }
360 }
361
362 fn fill(&mut self, brush: Brush<'_>) {
363 let fill_rect = &self.colr_glyph.area.ceil();
366
367 match brush {
368 Brush::Solid {
369 palette_index,
370 alpha,
371 } => {
372 let color = self
373 .palette_index_to_color(palette_index, alpha)
374 .unwrap_or(AlphaColor::BLACK);
375
376 self.painter.fill_solid(fill_rect, color);
377 }
378 Brush::LinearGradient {
379 p0,
380 p1,
381 color_stops,
382 extend,
383 } => {
384 let p0 = convert_point(p0);
385 let p1 = convert_point(p1);
386 let extend = convert_extend(extend);
387 let stops = self.convert_stops(color_stops);
388
389 if stops.len() == 1 {
390 self.painter
391 .fill_solid(fill_rect, stops[0].color.to_alpha_color());
392 } else {
393 let grad = Gradient {
394 kind: LinearGradientPosition { start: p0, end: p1 }.into(),
395 stops,
396 extend,
397 ..Default::default()
398 };
399 self.painter.set_paint_transform(self.cur_transform());
400 self.painter.fill_gradient(fill_rect, grad);
401 }
402 }
403 Brush::RadialGradient {
404 c0,
405 r0,
406 c1,
407 r1,
408 color_stops,
409 extend,
410 } => {
411 let p0 = convert_point(c0);
414 let p1 = convert_point(c1);
415 let extend = convert_extend(extend);
416 let stops = self.convert_stops(color_stops);
417
418 if r1 <= 0.0 || stops.len() == 1 {
419 self.painter
420 .fill_solid(fill_rect, stops[0].color.to_alpha_color());
421
422 return;
423 }
424
425 let grad = Gradient {
426 kind: RadialGradientPosition {
427 start_center: p0,
428 start_radius: r0,
429 end_center: p1,
430 end_radius: r1,
431 }
432 .into(),
433 stops,
434 extend,
435 ..Default::default()
436 };
437
438 self.painter.set_paint_transform(self.cur_transform());
439 self.painter.fill_gradient(fill_rect, grad);
440 }
441 Brush::SweepGradient {
442 c0,
443 start_angle,
444 mut end_angle,
445 color_stops,
446 extend,
447 } => {
448 let p0 = convert_point(c0);
449 let extend = convert_extend(extend);
450 let stops = self.convert_stops(color_stops);
451
452 if stops.len() == 1 {
453 self.painter
454 .fill_solid(fill_rect, stops[0].color.to_alpha_color());
455
456 return;
457 }
458
459 if start_angle == end_angle {
460 match extend {
461 Extend::Pad => {
462 end_angle += 0.01;
465 }
466 _ => {
467 unreachable!()
470 }
471 }
472 }
473
474 let grad = Gradient {
477 kind: SweepGradientPosition {
478 center: Point::new(p0.x, -p0.y),
479 start_angle: start_angle.to_radians(),
480 end_angle: end_angle.to_radians(),
481 }
482 .into(),
483 stops,
484 extend,
485 ..Default::default()
486 };
487
488 let paint_transform = self.cur_transform() * Affine::scale_non_uniform(1.0, -1.0);
489
490 self.painter.set_paint_transform(paint_transform);
491 self.painter.fill_gradient(fill_rect, grad);
492 }
493 };
494 }
495
496 fn push_layer(&mut self, composite_mode: CompositeMode) {
497 let blend_mode = convert_composite_mode(composite_mode);
498
499 if !self.skip_blend_layers {
500 self.painter.push_blend_layer(blend_mode);
501 self.stack.push(ColrStackEntry::BlendLayer);
502 }
503 }
504
505 fn pop_layer(&mut self) {
506 if !self.skip_blend_layers && self.pop_stack_entry(ColrStackEntry::BlendLayer) {
507 self.painter.pop_layer();
508 }
509 }
510}
511
512impl ColorPainter for GlyphInfoExtractor<'_, '_> {
513 fn push_transform(&mut self, t: Transform) {
514 self.transforms
515 .push(self.cur_transform() * convert_affine(t));
516 }
517
518 fn pop_transform(&mut self) {
519 self.transforms.pop();
520 }
521
522 fn push_clip_glyph(&mut self, glyph_id: GlyphId) {
523 let outline_bbox = {
524 let Some(outline) = self.get_outline(glyph_id) else {
525 return;
526 };
527 outline.bbox
528 };
529
530 self.push_clip_bbox(self.transform_rect(outline_bbox));
531 }
532
533 fn push_clip_box(&mut self, clip_box: BoundingBox<f32>) {
534 self.push_clip_bbox(self.transform_rect(convert_bounding_box(clip_box)));
535 }
536
537 fn pop_clip(&mut self) {
538 self.clip_stack.pop();
539 }
540
541 fn fill(&mut self, _brush: Brush<'_>) {}
542
543 fn push_layer(&mut self, composite_mode: CompositeMode) {
544 self.has_non_default_blend |=
545 convert_composite_mode(composite_mode) != BlendMode::default();
546 }
547
548 fn pop_layer(&mut self) {}
549}
550
551fn convert_composite_mode(composite_mode: CompositeMode) -> BlendMode {
552 match composite_mode {
553 CompositeMode::Clear => BlendMode::new(Mix::Normal, Compose::Clear),
554 CompositeMode::Src => BlendMode::new(Mix::Normal, Compose::Copy),
555 CompositeMode::Dest => BlendMode::new(Mix::Normal, Compose::Dest),
556 CompositeMode::SrcOver => BlendMode::new(Mix::Normal, Compose::SrcOver),
557 CompositeMode::DestOver => BlendMode::new(Mix::Normal, Compose::DestOver),
558 CompositeMode::SrcIn => BlendMode::new(Mix::Normal, Compose::SrcIn),
559 CompositeMode::DestIn => BlendMode::new(Mix::Normal, Compose::DestIn),
560 CompositeMode::SrcOut => BlendMode::new(Mix::Normal, Compose::SrcOut),
561 CompositeMode::DestOut => BlendMode::new(Mix::Normal, Compose::DestOut),
562 CompositeMode::SrcAtop => BlendMode::new(Mix::Normal, Compose::SrcAtop),
563 CompositeMode::DestAtop => BlendMode::new(Mix::Normal, Compose::DestAtop),
564 CompositeMode::Xor => BlendMode::new(Mix::Normal, Compose::Xor),
565 CompositeMode::Plus => BlendMode::new(Mix::Normal, Compose::Plus),
566 CompositeMode::Screen => BlendMode::new(Mix::Screen, Compose::SrcOver),
567 CompositeMode::Overlay => BlendMode::new(Mix::Overlay, Compose::SrcOver),
568 CompositeMode::Darken => BlendMode::new(Mix::Darken, Compose::SrcOver),
569 CompositeMode::Lighten => BlendMode::new(Mix::Lighten, Compose::SrcOver),
570 CompositeMode::ColorDodge => BlendMode::new(Mix::ColorDodge, Compose::SrcOver),
571 CompositeMode::ColorBurn => BlendMode::new(Mix::ColorBurn, Compose::SrcOver),
572 CompositeMode::HardLight => BlendMode::new(Mix::HardLight, Compose::SrcOver),
573 CompositeMode::SoftLight => BlendMode::new(Mix::SoftLight, Compose::SrcOver),
574 CompositeMode::Difference => BlendMode::new(Mix::Difference, Compose::SrcOver),
575 CompositeMode::Exclusion => BlendMode::new(Mix::Exclusion, Compose::SrcOver),
576 CompositeMode::Multiply => BlendMode::new(Mix::Multiply, Compose::SrcOver),
577 CompositeMode::HslHue => BlendMode::new(Mix::Hue, Compose::SrcOver),
578 CompositeMode::HslSaturation => BlendMode::new(Mix::Saturation, Compose::SrcOver),
579 CompositeMode::HslColor => BlendMode::new(Mix::Color, Compose::SrcOver),
580 CompositeMode::HslLuminosity => BlendMode::new(Mix::Luminosity, Compose::SrcOver),
581 CompositeMode::Unknown => BlendMode::default(),
582 }
583}
584
585fn convert_affine(transform: Transform) -> Affine {
586 Affine::new([
587 f64::from(transform.xx),
588 f64::from(transform.yx),
589 f64::from(transform.xy),
590 f64::from(transform.yy),
591 f64::from(transform.dx),
592 f64::from(transform.dy),
593 ])
594}
595
596fn convert_extend(extend: skrifa::color::Extend) -> Extend {
597 match extend {
598 skrifa::color::Extend::Pad => Extend::Pad,
599 skrifa::color::Extend::Repeat => Extend::Repeat,
600 skrifa::color::Extend::Reflect => Extend::Reflect,
601 skrifa::color::Extend::Unknown => Extend::Pad,
602 }
603}
604
605fn convert_point(point: skrifa::raw::types::Point<f32>) -> Point {
606 Point::new(f64::from(point.x), f64::from(point.y))
607}
608
609pub(crate) fn convert_bounding_box(rect: BoundingBox<f32>) -> Rect {
610 Rect::new(
611 f64::from(rect.x_min),
612 f64::from(rect.y_min),
613 f64::from(rect.x_max),
614 f64::from(rect.y_max),
615 )
616}