liecharts 0.1.0-beta

A Rust charting library with PNG and SVG rendering support
Documentation
//! Pixmap 渲染器 - 使用 vello_cpu 渲染为位图

use crate::error::Result;
use crate::visual::{Color, FillStrokeStyle, GradientDef, Stroke, StrokeStyle, Transform, VisualElement};
use crate::render::Renderer;
use crate::text::TextLayout;
use vello_cpu::{Pixmap, Resources};
use vello_cpu::RenderContext;
use vello_cpu::kurbo::Shape as KurboShape;
use vello_cpu::kurbo::{BezPath, Circle, Point, Rect, Stroke as KurboStroke};
use vello_cpu::peniko::color::AlphaColor;

/// Vello CPU 渲染器,输出 Pixmap
///
/// 实现 Renderer trait,将 VisualElement 渲染为位图
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,
        }
    }

    /// 渲染视觉元素序列并输出 Pixmap
    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)
    }

    /// 将 Stroke 转换为 KurboStroke 并设置到渲染上下文
    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::peniko::{Gradient, Extend};
        use vello_cpu::peniko::{GradientKind, LinearGradientPosition, ColorStops, ColorStop, InterpolationAlphaSpace};
        use vello_cpu::peniko::color::{DynamicColor, ColorSpaceTag, HueDirection};
        use vello_cpu::kurbo::Point as KurboPoint;

        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;
        };

        // position 是组件已计算好的文本块左上角
        // glyph 坐标是 layout 内绝对坐标,直接平移+旋转即可
        let transform = Affine::translate((position.x, position.y))
            * Affine::rotate(rotation);

        // 遍历布局中的每一行和每个 glyph run
        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();

                        // 将 parley 的 glyph 转换为 vello_cpu 的 glyph
                        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.0);

                        // 使用 vello_cpu 渲染 glyph run
                        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>) {
        // vello_cpu 暂不支持变换组,直接渲染子元素
        // 未来可以在这里保存/恢复渲染状态
    }

    fn end_group(&mut self) {
        // 恢复渲染状态
    }
}