use vello_cpu::{
Pixmap, RenderContext, Resources,
kurbo::{BezPath, Circle, Point, Rect, Shape as KurboShape, Stroke as KurboStroke},
peniko::color::AlphaColor,
};
use crate::{
error::Result,
render::Renderer,
text::TextLayout,
visual::{Color, FillStrokeStyle, GradientDef, Stroke, StrokeStyle, Transform, VisualElement},
};
#[derive(Debug)]
pub struct PixmapRenderer {
ctx: RenderContext,
resources: Resources,
width: u32,
height: u32,
}
impl PixmapRenderer {
pub fn new(width: u32, height: u32) -> Self {
Self {
ctx: RenderContext::new(width as u16, height as u16),
resources: Resources::new(),
width,
height,
}
}
pub fn render(mut self, elements: &[VisualElement]) -> Result<Pixmap> {
self.render_elements(elements);
let mut pixmap = Pixmap::new(self.width as u16, self.height as u16);
self.ctx.render_to_pixmap(&mut self.resources, &mut pixmap);
Ok(pixmap)
}
fn color_to_vello(color: &Color) -> AlphaColor<vello_cpu::color::Srgb> {
AlphaColor::from_rgba8(color.r, color.g, color.b, color.a)
}
fn set_stroke_style(&mut self, stroke: &Stroke) {
let kurbo_stroke = KurboStroke::new(stroke.width);
self.ctx.set_stroke(kurbo_stroke);
}
}
impl Renderer for PixmapRenderer {
fn draw_rect(&mut self, rect: Rect, style: &FillStrokeStyle) {
if let Some(fill) = &style.fill {
let color = Self::color_to_vello(fill);
self.ctx.set_paint(color);
self.ctx.fill_rect(&rect);
}
if let Some(stroke) = &style.stroke {
let color = Self::color_to_vello(&stroke.color);
self.ctx.set_paint(color);
self.set_stroke_style(stroke);
self.ctx.stroke_rect(&rect);
}
}
fn draw_circle(&mut self, center: Point, radius: f64, style: &FillStrokeStyle) {
let circle = Circle::new(center, radius);
let path = circle.to_path(0.1);
if let Some(fill) = &style.fill {
let color = Self::color_to_vello(fill);
self.ctx.set_paint(color);
self.ctx.fill_path(&path);
}
if let Some(stroke) = &style.stroke {
let color = Self::color_to_vello(&stroke.color);
self.ctx.set_paint(color);
self.set_stroke_style(stroke);
self.ctx.stroke_path(&path);
}
}
fn draw_line(&mut self, start: Point, end: Point, style: &StrokeStyle) {
let color = Self::color_to_vello(&style.color);
self.ctx.set_paint(color);
self.ctx.set_stroke(KurboStroke::new(style.width));
let mut path = BezPath::new();
path.move_to(start);
path.line_to(end);
self.ctx.stroke_path(&path);
}
fn draw_polyline(&mut self, points: &[Point], style: &StrokeStyle) {
if points.len() < 2 {
return;
}
let mut path = BezPath::new();
path.move_to(points[0]);
for point in &points[1..] {
path.line_to(*point);
}
let color = Self::color_to_vello(&style.color);
self.ctx.set_paint(color);
self.ctx.set_stroke(KurboStroke::new(style.width));
self.ctx.stroke_path(&path);
}
fn draw_path(&mut self, path: &BezPath, style: &FillStrokeStyle) {
if let Some(fill) = &style.fill {
let color = Self::color_to_vello(fill);
self.ctx.set_paint(color);
self.ctx.fill_path(path);
}
if let Some(stroke) = &style.stroke {
let color = Self::color_to_vello(&stroke.color);
self.ctx.set_paint(color);
self.set_stroke_style(stroke);
self.ctx.stroke_path(path);
}
}
fn draw_gradient_path(
&mut self,
path: &BezPath,
gradient: &GradientDef,
stroke: Option<&Stroke>,
) {
use vello_cpu::{
kurbo::Point as KurboPoint,
peniko::{
ColorStop, ColorStops, Extend, Gradient, GradientKind, InterpolationAlphaSpace,
LinearGradientPosition,
color::{ColorSpaceTag, DynamicColor, HueDirection},
},
};
let stops: Vec<ColorStop> = gradient
.stops
.iter()
.map(|(offset, color)| ColorStop {
offset: *offset as f32,
color: DynamicColor::from_alpha_color(AlphaColor::from_rgba8(
color.r, color.g, color.b, color.a,
)),
})
.collect();
let bounds = path.bounding_box();
let peniko_gradient = Gradient {
kind: GradientKind::Linear(LinearGradientPosition {
start: KurboPoint::new(bounds.x0, bounds.y0),
end: KurboPoint::new(bounds.x1, bounds.y0),
}),
extend: Extend::Pad,
interpolation_cs: ColorSpaceTag::Srgb,
hue_direction: HueDirection::default(),
interpolation_alpha_space: InterpolationAlphaSpace::Premultiplied,
stops: ColorStops::from(stops.as_slice()),
};
self.ctx.set_paint(peniko_gradient);
self.ctx.fill_path(path);
if let Some(stroke) = stroke {
let color = Self::color_to_vello(&stroke.color);
self.ctx.set_paint(color);
self.set_stroke_style(stroke);
self.ctx.stroke_path(path);
}
}
fn draw_text(
&mut self,
_text: &str,
position: Point,
_color: Color,
_font_size: f64,
_font_family: &str,
rotation: f64,
layout: Option<&TextLayout>,
) {
use vello_cpu::kurbo::Affine;
let Some(layout) = layout else {
return;
};
let transform = Affine::translate((position.x, position.y)) * Affine::rotate(rotation);
for line in layout.lines() {
for item in line.items() {
match item {
parley::layout::PositionedLayoutItem::GlyphRun(glyph_run) => {
let run = glyph_run.run();
let font_data = run.font();
let run_font_size = run.font_size();
let glyphs: Vec<vello_cpu::Glyph> = glyph_run
.positioned_glyphs()
.map(|g| vello_cpu::Glyph {
id: g.id,
x: g.x,
y: g.y,
})
.collect();
if glyphs.is_empty() {
continue;
}
let brush = glyph_run.style().brush;
self.ctx.set_paint(brush.as_vello_color());
self.ctx
.glyph_run(&mut self.resources, font_data)
.font_size(run_font_size)
.glyph_transform(transform)
.fill_glyphs(glyphs.into_iter());
}
parley::layout::PositionedLayoutItem::InlineBox(_inline_box) => {
}
}
}
}
}
fn begin_group(&mut self, _transform: Option<&Transform>) {
}
fn end_group(&mut self) {
}
}