use deckcraft_color::Rgba;
use deckcraft_fonts::FontFace;
use kurbo::{Affine, BezPath, Point, Rect};
use skrifa::color::{Brush, ColorPainter, ColorStop, CompositeMode, Extend, Transform};
use skrifa::raw::types::BoundingBox;
use skrifa::{GlyphId, MetadataProvider};
use vello_cpu::{RenderContext, peniko};
const MAX_DEPTH: usize = 64;
pub fn paint(
ctx: &mut RenderContext,
face: &FontFace,
gid: u32,
at: Affine,
scale: f64,
fg: Rgba,
alpha: f64,
) -> bool {
let Some(font) = face.skrifa() else {
return false;
};
let glyphs = font.color_glyphs();
let Some(glyph) = glyphs.get(GlyphId::new(gid)) else {
return false;
};
let palette: Vec<Rgba> = font
.color_palettes()
.get(0)
.map(|p| {
p.colors()
.iter()
.map(|c| Rgba {
r: c.red,
g: c.green,
b: c.blue,
a: c.alpha,
})
.collect()
})
.unwrap_or_default();
let base = at * Affine::scale(scale) * Affine::scale_non_uniform(1.0, -1.0);
let mut p = Painter {
ctx,
face,
base,
stack: vec![Affine::IDENTITY],
palette,
fg,
opened: Vec::new(),
};
p.ctx
.push_layer(None, None, Some(alpha.clamp(0.0, 1.0) as f32), None, None);
let ok = glyph.paint(face.location(), &mut p).is_ok();
while let Some(open) = p.opened.pop() {
if open {
p.ctx.pop_layer();
}
}
p.ctx.pop_layer();
ok
}
struct Painter<'a> {
ctx: &'a mut RenderContext,
face: &'a FontFace,
base: Affine,
stack: Vec<Affine>,
palette: Vec<Rgba>,
fg: Rgba,
opened: Vec<bool>,
}
fn affine(t: &Transform) -> Affine {
Affine::new([
t.xx as f64,
t.yx as f64,
t.xy as f64,
t.yy as f64,
t.dx as f64,
t.dy as f64,
])
}
fn pt(p: skrifa::raw::types::Point<f32>) -> Point {
Point::new(p.x as f64, p.y as f64)
}
fn extend(e: Extend) -> peniko::Extend {
match e {
Extend::Repeat => peniko::Extend::Repeat,
Extend::Reflect => peniko::Extend::Reflect,
_ => peniko::Extend::Pad,
}
}
fn blend(m: CompositeMode) -> peniko::BlendMode {
use peniko::{Compose, Mix};
let (mix, compose) = match m {
CompositeMode::Clear => (Mix::Normal, Compose::Clear),
CompositeMode::Src => (Mix::Normal, Compose::Copy),
CompositeMode::Dest => (Mix::Normal, Compose::Dest),
CompositeMode::DestOver => (Mix::Normal, Compose::DestOver),
CompositeMode::SrcIn => (Mix::Normal, Compose::SrcIn),
CompositeMode::DestIn => (Mix::Normal, Compose::DestIn),
CompositeMode::SrcOut => (Mix::Normal, Compose::SrcOut),
CompositeMode::DestOut => (Mix::Normal, Compose::DestOut),
CompositeMode::SrcAtop => (Mix::Normal, Compose::SrcAtop),
CompositeMode::DestAtop => (Mix::Normal, Compose::DestAtop),
CompositeMode::Xor => (Mix::Normal, Compose::Xor),
CompositeMode::Plus => (Mix::Normal, Compose::Plus),
CompositeMode::Screen => (Mix::Screen, Compose::SrcOver),
CompositeMode::Overlay => (Mix::Overlay, Compose::SrcOver),
CompositeMode::Darken => (Mix::Darken, Compose::SrcOver),
CompositeMode::Lighten => (Mix::Lighten, Compose::SrcOver),
CompositeMode::ColorDodge => (Mix::ColorDodge, Compose::SrcOver),
CompositeMode::ColorBurn => (Mix::ColorBurn, Compose::SrcOver),
CompositeMode::HardLight => (Mix::HardLight, Compose::SrcOver),
CompositeMode::SoftLight => (Mix::SoftLight, Compose::SrcOver),
CompositeMode::Difference => (Mix::Difference, Compose::SrcOver),
CompositeMode::Exclusion => (Mix::Exclusion, Compose::SrcOver),
CompositeMode::Multiply => (Mix::Multiply, Compose::SrcOver),
CompositeMode::HslHue => (Mix::Hue, Compose::SrcOver),
CompositeMode::HslSaturation => (Mix::Saturation, Compose::SrcOver),
CompositeMode::HslColor => (Mix::Color, Compose::SrcOver),
CompositeMode::HslLuminosity => (Mix::Luminosity, Compose::SrcOver),
_ => (Mix::Normal, Compose::SrcOver),
};
peniko::BlendMode::new(mix, compose)
}
impl Painter<'_> {
fn current(&self) -> Affine {
self.base * self.stack.last().copied().unwrap_or(Affine::IDENTITY)
}
fn color(&self, index: u16, alpha: f32) -> peniko::Color {
let c = if index == 0xFFFF {
self.fg
} else {
self.palette.get(index as usize).copied().unwrap_or(self.fg)
};
let a = (c.a as f64 * (alpha as f64).clamp(0.0, 1.0))
.round()
.clamp(0.0, 255.0) as u8;
peniko::Color::from_rgba8(c.r, c.g, c.b, a)
}
fn stops(&self, stops: &[ColorStop]) -> Vec<peniko::ColorStop> {
stops
.iter()
.map(|s| peniko::ColorStop {
offset: s.offset.clamp(0.0, 1.0),
color: self.color(s.palette_index, s.alpha).into(),
})
.collect()
}
fn outline(&self, gid: GlyphId) -> BezPath {
let p = deckcraft_fonts::FontDb::global().outline(self.face, gid.to_u32());
Affine::scale_non_uniform(1.0, -1.0) * (*p).clone()
}
fn push_clip(&mut self, path: &BezPath) {
let open = self.opened.len() < MAX_DEPTH;
if open {
self.ctx.set_transform(self.current());
self.ctx.push_clip_layer(path);
}
self.opened.push(open);
}
fn pop(&mut self) {
if self.opened.pop() == Some(true) {
self.ctx.pop_layer();
}
}
fn cover(&mut self, paint: impl Into<vello_cpu::PaintType>) {
self.ctx.set_transform(self.current());
self.ctx.set_paint(paint);
self.ctx.fill_rect(&Rect::new(-1.0e5, -1.0e5, 1.0e5, 1.0e5));
}
}
impl ColorPainter for Painter<'_> {
fn push_transform(&mut self, t: Transform) {
let next = self.stack.last().copied().unwrap_or(Affine::IDENTITY) * affine(&t);
self.stack.push(next);
}
fn pop_transform(&mut self) {
if self.stack.len() > 1 {
self.stack.pop();
}
}
fn push_clip_glyph(&mut self, gid: GlyphId) {
let path = self.outline(gid);
self.push_clip(&path);
}
fn push_clip_box(&mut self, b: BoundingBox<f32>) {
let r = Rect::new(
b.x_min as f64,
b.y_min as f64,
b.x_max as f64,
b.y_max as f64,
);
self.push_clip(&kurbo::Shape::to_path(&r, 0.1));
}
fn pop_clip(&mut self) {
self.pop();
}
fn fill(&mut self, brush: Brush<'_>) {
match brush {
Brush::Solid {
palette_index,
alpha,
} => {
let c = self.color(palette_index, alpha);
self.cover(c);
}
Brush::LinearGradient {
p0,
p1,
color_stops,
extend: e,
} => {
let g = peniko::Gradient::new_linear(pt(p0), pt(p1))
.with_stops(self.stops(color_stops).as_slice())
.with_extend(extend(e));
self.cover(g);
}
Brush::RadialGradient {
c0,
r0,
c1,
r1,
color_stops,
extend: e,
} => {
let g = peniko::Gradient::new_two_point_radial(
pt(c0),
r0.max(0.0),
pt(c1),
r1.max(0.0),
)
.with_stops(self.stops(color_stops).as_slice())
.with_extend(extend(e));
self.cover(g);
}
Brush::SweepGradient {
c0,
start_angle,
end_angle,
color_stops,
extend: e,
} => {
let g = peniko::Gradient::new_sweep(
pt(c0),
start_angle.to_radians(),
end_angle.to_radians(),
)
.with_stops(self.stops(color_stops).as_slice())
.with_extend(extend(e));
self.cover(g);
}
}
}
fn push_layer(&mut self, mode: CompositeMode) {
let open = self.opened.len() < MAX_DEPTH;
if open {
self.ctx.push_blend_layer(blend(mode));
}
self.opened.push(open);
}
fn pop_layer(&mut self) {
self.pop();
}
}