Skip to main content

yog_graphics/
draw.rs

1//! Drawings: what to paint, in order — paths filled or stroked, images —
2//! in a drawing's own units (`y` up). Glyphs, icons and shapes are
3//! drawings; a [`Canvas`] paints them.
4
5use yog_data::Schema;
6
7use crate::color::Color;
8use crate::image::Image;
9use crate::math::Vec2;
10use crate::path::{self, Seg};
11use crate::raster::Canvas;
12use crate::rect::Rect;
13
14/// One step of a drawing.
15#[derive(Schema, Debug, Clone, PartialEq)]
16#[data(tag = "op")]
17pub enum Op {
18    /// A path's inside painted (non-zero winding).
19    Fill(Fill),
20    /// A path's outline painted, round-joined and capped.
21    Stroke(Stroke),
22    /// An image in a box.
23    Image(ImageOp),
24}
25
26#[derive(Schema, Debug, Clone, PartialEq)]
27pub struct Fill {
28    pub path: Vec<Seg>,
29    /// None: the current color (what the drawing is drawn in — a text's).
30    pub color: Option<Color>,
31}
32
33#[derive(Schema, Debug, Clone, PartialEq)]
34pub struct Stroke {
35    pub path: Vec<Seg>,
36    /// How wide, in the drawing's units.
37    pub width: f32,
38    /// None: the current color.
39    pub color: Option<Color>,
40}
41
42/// `image` in box `rect` (`rect.x, rect.y` its bottom-left corner).
43#[derive(Schema, Debug, Clone, PartialEq)]
44pub struct ImageOp {
45    pub rect: Rect,
46    pub image: Image,
47    /// Whether the current color tints it (an image in one color, white
48    /// to take the current one whole); else it keeps its colors.
49    #[data(default)]
50    pub tinted: bool,
51}
52
53/// What `ops` cover (control points included); `None` when they draw
54/// nothing.
55pub fn bounds(ops: &[Op]) -> Option<Rect> {
56    ops.iter()
57        .filter_map(|op| match op {
58            Op::Fill(f) => path::bounds(&f.path, 0.0),
59            Op::Stroke(s) => path::bounds(&s.path, s.width / 2.0),
60            Op::Image(i) => Some(i.rect),
61        })
62        .reduce(|a, b| a.union(&b))
63}
64
65impl Canvas {
66    /// `ops` painted: each point through `to_px` (the drawing's units to
67    /// the canvas's pixels, `scale` pixels a unit), parts without a color
68    /// in `current`.
69    pub fn draw(&mut self, ops: &[Op], to_px: impl Fn(Vec2) -> Vec2, scale: f32, current: Color) {
70        for op in ops {
71            match op {
72                Op::Fill(f) => {
73                    self.fill(&path::flatten(&f.path, &to_px), f.color.unwrap_or(current))
74                }
75                Op::Stroke(s) => self.stroke(
76                    &path::flatten(&s.path, &to_px),
77                    s.width * scale,
78                    s.color.unwrap_or(current),
79                ),
80                Op::Image(i) => {
81                    let r = i.rect;
82                    let dst = Rect::from_corners(
83                        to_px(Vec2::new(r.x, r.y)),
84                        to_px(Vec2::new(r.right(), r.bottom())),
85                    );
86                    self.image(&i.image, dst, i.tinted.then_some(current));
87                }
88            }
89        }
90    }
91}