use crate::color::Srgb;
use crate::color::{AlphaColor, DynamicColor};
use crate::glyph::{ColorGlyph, OutlinePath};
use crate::kurbo::{Affine, BezPath, Point, Rect, Shape};
use crate::math::FloatExt;
use crate::peniko::{self, BlendMode, ColorStops, Compose, Extend, Gradient, Mix};
use alloc::boxed::Box;
use alloc::vec;
use alloc::vec::Vec;
use core::fmt::Debug;
use peniko::{LinearGradientPosition, RadialGradientPosition, SweepGradientPosition};
use skrifa::color::{Brush, ColorPainter, ColorStop, CompositeMode, Transform};
use skrifa::outline::DrawSettings;
use skrifa::raw::TableProvider;
use skrifa::raw::types::BoundingBox;
use skrifa::{GlyphId, MetadataProvider};
use smallvec::SmallVec;
pub trait ColrRenderer {
fn push_clip_layer(&mut self, clip: &BezPath);
fn push_blend_layer(&mut self, blend_mode: BlendMode);
fn fill_solid(&mut self, color: AlphaColor<Srgb>);
fn fill_gradient(&mut self, gradient: Gradient);
fn set_paint_transform(&mut self, affine: Affine);
fn pop_layer(&mut self);
}
pub struct ColrPainter<'a> {
transforms: Vec<Affine>,
color_glyph: Box<ColorGlyph<'a>>,
context_color: AlphaColor<Srgb>,
painter: &'a mut dyn ColrRenderer,
layer_count: u32,
}
impl Debug for ColrPainter<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ColrPainter()").finish()
}
}
impl<'a> ColrPainter<'a> {
pub fn new(
color_glyph: Box<ColorGlyph<'a>>,
context_color: AlphaColor<Srgb>,
painter: &'a mut impl ColrRenderer,
) -> Self {
Self {
transforms: vec![color_glyph.draw_transform],
color_glyph,
context_color,
painter,
layer_count: 0,
}
}
pub fn paint(&mut self) {
let color_glyph = self.color_glyph.skrifa_glyph.clone();
let location_ref = self.color_glyph.location;
let _ = color_glyph.paint(location_ref, self);
for _ in 0..self.layer_count {
self.painter.pop_layer();
}
}
fn cur_transform(&self) -> Affine {
self.transforms.last().copied().unwrap_or_default()
}
fn palette_index_to_color(&self, palette_index: u16, alpha: f32) -> Option<AlphaColor<Srgb>> {
if palette_index != u16::MAX {
let color = self
.color_glyph
.font_ref
.cpal()
.ok()?
.color_records_array()?
.ok()?[palette_index as usize];
Some(
AlphaColor::from_rgba8(color.red, color.green, color.blue, color.alpha)
.multiply_alpha(alpha),
)
} else {
Some(self.context_color.multiply_alpha(alpha))
}
}
fn convert_stops(&self, stops: &[ColorStop]) -> ColorStops {
let mut stops = stops
.iter()
.map(|s| {
let color = self
.palette_index_to_color(s.palette_index, s.alpha)
.unwrap_or(AlphaColor::BLACK);
peniko::ColorStop {
offset: s.offset,
color: DynamicColor::from_alpha_color(color),
}
})
.collect::<SmallVec<[peniko::ColorStop; 4]>>();
let first_stop = stops[0];
let last_stop = *stops.last().unwrap();
if first_stop.offset != 0.0 {
let mut new_stop = first_stop;
new_stop.offset = 0.0;
stops.insert(0, new_stop);
}
if last_stop.offset != 1.0 {
let mut new_stop = last_stop;
new_stop.offset = 1.0;
stops.push(new_stop);
}
while let Some(stop) = stops.get(stops.len() - 2).map(|s| s.offset) {
if (stop - 1.0).is_nearly_zero() {
stops.remove(stops.len() - 2);
} else {
break;
}
}
ColorStops(stops)
}
}
impl ColorPainter for ColrPainter<'_> {
fn push_transform(&mut self, t: Transform) {
let affine = Affine::new([
f64::from(t.xx),
f64::from(t.yx),
f64::from(t.xy),
f64::from(t.yy),
f64::from(t.dx),
f64::from(t.dy),
]);
self.transforms.push(self.cur_transform() * affine);
}
fn pop_transform(&mut self) {
self.transforms.pop();
}
fn push_clip_glyph(&mut self, glyph_id: GlyphId) {
let mut outline_builder = OutlinePath::new();
let outline_glyphs = self.color_glyph.font_ref.outline_glyphs();
let Some(outline_glyph) = outline_glyphs.get(glyph_id) else {
return;
};
let _ = outline_glyph.draw(
DrawSettings::unhinted(
skrifa::instance::Size::unscaled(),
self.color_glyph.location,
),
&mut outline_builder,
);
let finished = outline_builder.0;
let transformed = self.cur_transform() * finished;
self.painter.push_clip_layer(&transformed);
self.layer_count += 1;
}
fn push_clip_box(&mut self, clip_box: BoundingBox<f32>) {
let rect = Rect::new(
f64::from(clip_box.x_min),
f64::from(clip_box.y_min),
f64::from(clip_box.x_max),
f64::from(clip_box.y_max),
);
let transformed = self.cur_transform() * rect.to_path(0.1);
self.painter.push_clip_layer(&transformed);
self.layer_count += 1;
}
fn pop_clip(&mut self) {
self.painter.pop_layer();
self.layer_count -= 1;
}
fn fill(&mut self, brush: Brush<'_>) {
match brush {
Brush::Solid {
palette_index,
alpha,
} => {
let color = self
.palette_index_to_color(palette_index, alpha)
.unwrap_or(AlphaColor::BLACK);
self.painter.fill_solid(color);
}
Brush::LinearGradient {
p0,
p1,
color_stops,
extend,
} => {
let p0 = convert_point(p0);
let p1 = convert_point(p1);
let extend = convert_extend(extend);
let stops = self.convert_stops(color_stops);
if stops.len() == 1 {
self.painter.fill_solid(stops[0].color.to_alpha_color());
} else {
let grad = Gradient {
kind: LinearGradientPosition { start: p0, end: p1 }.into(),
stops,
extend,
..Default::default()
};
self.painter.set_paint_transform(self.cur_transform());
self.painter.fill_gradient(grad);
}
}
Brush::RadialGradient {
c0,
r0,
c1,
r1,
color_stops,
extend,
} => {
let p0 = convert_point(c0);
let p1 = convert_point(c1);
let extend = convert_extend(extend);
let stops = self.convert_stops(color_stops);
if r1 <= 0.0 || stops.len() == 1 {
self.painter.fill_solid(stops[0].color.to_alpha_color());
return;
}
let grad = Gradient {
kind: RadialGradientPosition {
start_center: p0,
start_radius: r0,
end_center: p1,
end_radius: r1,
}
.into(),
stops,
extend,
..Default::default()
};
self.painter.set_paint_transform(self.cur_transform());
self.painter.fill_gradient(grad);
}
Brush::SweepGradient {
c0,
start_angle,
mut end_angle,
color_stops,
extend,
} => {
let p0 = convert_point(c0);
let extend = convert_extend(extend);
let stops = self.convert_stops(color_stops);
if stops.len() == 1 {
self.painter.fill_solid(stops[0].color.to_alpha_color());
return;
}
if start_angle == end_angle {
match extend {
Extend::Pad => {
end_angle += 0.01;
}
_ => {
unreachable!()
}
}
}
let grad = Gradient {
kind: SweepGradientPosition {
center: Point::new(p0.x, -p0.y),
start_angle: start_angle.to_radians(),
end_angle: end_angle.to_radians(),
}
.into(),
stops,
extend,
..Default::default()
};
let paint_transform = self.cur_transform() * Affine::scale_non_uniform(1.0, -1.0);
self.painter.set_paint_transform(paint_transform);
self.painter.fill_gradient(grad);
}
};
}
fn push_layer(&mut self, composite_mode: CompositeMode) {
let blend_mode = match composite_mode {
CompositeMode::Clear => BlendMode::new(Mix::Normal, Compose::Clear),
CompositeMode::Src => BlendMode::new(Mix::Normal, Compose::Copy),
CompositeMode::Dest => BlendMode::new(Mix::Normal, Compose::Dest),
CompositeMode::SrcOver => BlendMode::new(Mix::Normal, Compose::SrcOver),
CompositeMode::DestOver => BlendMode::new(Mix::Normal, Compose::DestOver),
CompositeMode::SrcIn => BlendMode::new(Mix::Normal, Compose::SrcIn),
CompositeMode::DestIn => BlendMode::new(Mix::Normal, Compose::DestIn),
CompositeMode::SrcOut => BlendMode::new(Mix::Normal, Compose::SrcOut),
CompositeMode::DestOut => BlendMode::new(Mix::Normal, Compose::DestOut),
CompositeMode::SrcAtop => BlendMode::new(Mix::Normal, Compose::SrcAtop),
CompositeMode::DestAtop => BlendMode::new(Mix::Normal, Compose::DestAtop),
CompositeMode::Xor => BlendMode::new(Mix::Normal, Compose::Xor),
CompositeMode::Plus => BlendMode::new(Mix::Normal, Compose::Plus),
CompositeMode::Screen => BlendMode::new(Mix::Screen, Compose::SrcOver),
CompositeMode::Overlay => BlendMode::new(Mix::Overlay, Compose::SrcOver),
CompositeMode::Darken => BlendMode::new(Mix::Darken, Compose::SrcOver),
CompositeMode::Lighten => BlendMode::new(Mix::Lighten, Compose::SrcOver),
CompositeMode::ColorDodge => BlendMode::new(Mix::ColorDodge, Compose::SrcOver),
CompositeMode::ColorBurn => BlendMode::new(Mix::ColorBurn, Compose::SrcOver),
CompositeMode::HardLight => BlendMode::new(Mix::HardLight, Compose::SrcOver),
CompositeMode::SoftLight => BlendMode::new(Mix::SoftLight, Compose::SrcOver),
CompositeMode::Difference => BlendMode::new(Mix::Difference, Compose::SrcOver),
CompositeMode::Exclusion => BlendMode::new(Mix::Exclusion, Compose::SrcOver),
CompositeMode::Multiply => BlendMode::new(Mix::Multiply, Compose::SrcOver),
CompositeMode::HslHue => BlendMode::new(Mix::Hue, Compose::SrcOver),
CompositeMode::HslSaturation => BlendMode::new(Mix::Saturation, Compose::SrcOver),
CompositeMode::HslColor => BlendMode::new(Mix::Color, Compose::SrcOver),
CompositeMode::HslLuminosity => BlendMode::new(Mix::Luminosity, Compose::SrcOver),
CompositeMode::Unknown => BlendMode::new(Mix::Normal, Compose::SrcOver),
};
self.painter.push_blend_layer(blend_mode);
self.layer_count += 1;
}
fn pop_layer(&mut self) {
self.painter.pop_layer();
self.layer_count -= 1;
}
}
fn convert_extend(extend: skrifa::color::Extend) -> Extend {
match extend {
skrifa::color::Extend::Pad => Extend::Pad,
skrifa::color::Extend::Repeat => Extend::Repeat,
skrifa::color::Extend::Reflect => Extend::Reflect,
skrifa::color::Extend::Unknown => Extend::Pad,
}
}
fn convert_point(point: skrifa::raw::types::Point<f32>) -> Point {
Point::new(f64::from(point.x), f64::from(point.y))
}
pub(crate) fn convert_bounding_box(rect: BoundingBox<f32>) -> Rect {
Rect::new(
f64::from(rect.x_min),
f64::from(rect.y_min),
f64::from(rect.x_max),
f64::from(rect.y_max),
)
}