1use yog_data::Schema;
6
7use crate::color::Color;
8use crate::image::Image;
9use crate::mask::Mask;
10use crate::math::Vec2;
11use crate::path::{self, Seg};
12use crate::raster::Canvas;
13use crate::rect::Rect;
14
15#[derive(Schema, Debug, Clone, PartialEq)]
17#[data(tag = "op")]
18pub enum Op {
19 Fill(Fill),
21 Stroke(Stroke),
23 Image(ImageOp),
25 Mask(MaskOp),
27}
28
29#[derive(Schema, Debug, Clone, PartialEq)]
30pub struct Fill {
31 pub path: Vec<Seg>,
32 pub color: Option<Color>,
34}
35
36#[derive(Schema, Debug, Clone, PartialEq)]
37pub struct Stroke {
38 pub path: Vec<Seg>,
39 pub width: f32,
41 pub color: Option<Color>,
43}
44
45#[derive(Schema, Debug, Clone, PartialEq)]
47pub struct ImageOp {
48 pub rect: Rect,
49 pub image: Image,
50 #[data(default)]
53 pub tinted: bool,
54}
55
56#[derive(Schema, Debug, Clone, PartialEq)]
59pub struct MaskOp {
60 pub rect: Rect,
61 pub mask: Mask,
62 pub color: Option<Color>,
63}
64
65pub fn bounds(ops: &[Op]) -> Option<Rect> {
68 ops.iter()
69 .filter_map(|op| match op {
70 Op::Fill(f) => path::bounds(&f.path, 0.0),
71 Op::Stroke(s) => path::bounds(&s.path, s.width / 2.0),
72 Op::Image(i) => Some(i.rect),
73 Op::Mask(m) => Some(m.rect),
74 })
75 .reduce(|a, b| a.union(&b))
76}
77
78impl Canvas {
79 pub fn draw(&mut self, ops: &[Op], to_px: impl Fn(Vec2) -> Vec2, scale: f32, current: Color) {
83 for op in ops {
84 match op {
85 Op::Fill(f) => {
86 self.fill(&path::flatten(&f.path, &to_px), f.color.unwrap_or(current))
87 }
88 Op::Stroke(s) => self.stroke(
89 &path::flatten(&s.path, &to_px),
90 s.width * scale,
91 s.color.unwrap_or(current),
92 ),
93 Op::Image(i) => {
94 self.image(
95 &i.image,
96 px_box(i.rect, &to_px),
97 i.tinted.then_some(current),
98 );
99 }
100 Op::Mask(m) => {
101 self.mask(&m.mask, px_box(m.rect, &to_px), m.color.unwrap_or(current))
102 }
103 }
104 }
105 }
106}
107
108fn px_box(r: Rect, to_px: &impl Fn(Vec2) -> Vec2) -> Rect {
110 Rect::from_corners(
111 to_px(Vec2::new(r.x, r.y)),
112 to_px(Vec2::new(r.right(), r.bottom())),
113 )
114}