1use crate::{Color, MediaId, Point, Rect, Transform};
4
5#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct GradientStop {
8 pub offset: f64,
9 pub color: Color,
10}
11
12#[derive(Debug, Clone, PartialEq)]
14pub enum Paint {
15 Solid(Color),
16 Linear {
17 start: Point,
18 end: Point,
19 stops: Vec<GradientStop>,
20 extend: (bool, bool),
21 },
22 Radial {
23 center: Point,
24 radius: f64,
25 focal: Point,
26 stops: Vec<GradientStop>,
27 extend: (bool, bool),
28 },
29 Tile {
30 image: MediaId,
31 tile: Rect,
32 transform: Transform,
33 },
34}
35
36impl Paint {
37 pub fn linear(
39 start: Point,
40 end: Point,
41 stops: Vec<GradientStop>,
42 extend: (bool, bool),
43 ) -> Self {
44 if let [stop] = stops.as_slice() {
45 Self::Solid(stop.color)
46 } else {
47 Self::Linear {
48 start,
49 end,
50 stops,
51 extend,
52 }
53 }
54 }
55
56 pub fn radial(
58 center: Point,
59 radius: f64,
60 focal: Point,
61 stops: Vec<GradientStop>,
62 extend: (bool, bool),
63 ) -> Self {
64 if let [stop] = stops.as_slice() {
65 Self::Solid(stop.color)
66 } else {
67 Self::Radial {
68 center,
69 radius,
70 focal,
71 stops,
72 extend,
73 }
74 }
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
80pub enum LineCap {
81 #[default]
82 Butt,
83 Round,
84 Square,
85}
86
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
89pub enum LineJoin {
90 #[default]
91 Miter,
92 Round,
93 Bevel,
94}
95
96#[derive(Debug, Clone, PartialEq)]
98pub struct Stroke {
99 pub paint: Paint,
100 pub width: f64,
101 pub cap: LineCap,
102 pub join: LineJoin,
103 pub dash: Option<Vec<f64>>,
104}
105
106impl Stroke {
107 pub fn new(paint: Paint, width: f64) -> Self {
109 Self {
110 paint,
111 width,
112 cap: LineCap::Butt,
113 join: LineJoin::Miter,
114 dash: None,
115 }
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::{GradientStop, LineCap, LineJoin, Paint, Stroke};
122 use crate::{Color, MediaId, Point, Rect, Transform};
123
124 fn red() -> Color {
125 Color {
126 r: 1.0,
127 g: 0.0,
128 b: 0.0,
129 a: 1.0,
130 }
131 }
132
133 #[test]
134 fn single_stop_gradients_degrade_to_solid_at_construction() {
135 let stops = vec![GradientStop {
136 offset: 0.25,
137 color: red(),
138 }];
139 assert_eq!(
140 Paint::linear(
141 Point { x: 0.0, y: 0.0 },
142 Point { x: 1.0, y: 1.0 },
143 stops.clone(),
144 (false, false),
145 ),
146 Paint::Solid(red())
147 );
148 assert_eq!(
149 Paint::radial(
150 Point { x: 0.5, y: 0.5 },
151 1.0,
152 Point { x: 0.5, y: 0.5 },
153 stops,
154 (false, false),
155 ),
156 Paint::Solid(red())
157 );
158 }
159
160 #[test]
161 fn multiple_stop_gradients_preserve_their_geometry_and_stops() {
162 let stops = vec![
163 GradientStop {
164 offset: 0.0,
165 color: Color::BLACK,
166 },
167 GradientStop {
168 offset: 1.0,
169 color: Color::WHITE,
170 },
171 ];
172 let paint = Paint::linear(
173 Point { x: 0.0, y: 0.0 },
174 Point { x: 1.0, y: 1.0 },
175 stops.clone(),
176 (true, false),
177 );
178 assert!(matches!(
179 paint,
180 Paint::Linear {
181 stops: actual,
182 extend: (true, false),
183 ..
184 } if actual == stops
185 ));
186 }
187
188 #[test]
189 fn stroke_new_uses_pdf_defaults() {
190 let stroke = Stroke::new(Paint::Solid(red()), 2.0);
191 assert_eq!(stroke.cap, LineCap::Butt);
192 assert_eq!(stroke.join, LineJoin::Miter);
193 assert_eq!(stroke.dash, None);
194 }
195
196 #[test]
197 fn tile_paint_uses_a_media_id() {
198 let media_id = MediaId::from_bytes(b"tile");
199 let paint = Paint::Tile {
200 image: media_id,
201 tile: Rect {
202 x: 0.0,
203 y: 0.0,
204 width: 10.0,
205 height: 20.0,
206 },
207 transform: Transform::IDENTITY,
208 };
209 assert!(matches!(paint, Paint::Tile { image, .. } if image == media_id));
210 }
211}