1use crate::{
4 geometry::{Dimensions, Point},
5 primitives::{
6 common::StrokeOffset,
7 line::thick_points::{ParallelLineType, ParallelsIterator},
8 PointsIter, Primitive, Rectangle,
9 },
10 transform::Transform,
11};
12use az::SaturatingAs;
13
14mod bresenham;
15pub(in crate::primitives) mod intersection_params;
16mod points;
17mod styled;
18mod thick_points;
19
20pub use points::Points;
21pub use styled::StyledPixelsIterator;
22
23#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Default)]
49#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
50pub struct Line {
51 pub start: Point,
53
54 pub end: Point,
56}
57
58impl Primitive for Line {}
59
60impl PointsIter for Line {
61 type Iter = Points;
62
63 fn points(&self) -> Self::Iter {
64 Points::new(self)
65 }
66}
67
68impl Dimensions for Line {
69 fn bounding_box(&self) -> Rectangle {
70 Rectangle::with_corners(self.start, self.end)
71 }
72}
73
74impl Line {
75 pub const fn new(start: Point, end: Point) -> Self {
77 Self { start, end }
78 }
79
80 pub const fn with_delta(start: Point, delta: Point) -> Self {
90 let end = Point::new(start.x + delta.x, start.y + delta.y);
92
93 Self { start, end }
94 }
95
96 fn perpendicular(&self) -> Self {
101 let delta = self.end - self.start;
102 let delta = Point::new(delta.y, -delta.x);
103
104 Line::new(self.start, self.start + delta)
105 }
106
107 pub(in crate::primitives) fn extents(
111 &self,
112 thickness: u32,
113 stroke_offset: StrokeOffset,
114 ) -> (Line, Line) {
115 let mut it = ParallelsIterator::new(self, thickness.saturating_as(), stroke_offset);
116 let reduce =
117 it.parallel_parameters.position_step.major + it.parallel_parameters.position_step.minor;
118
119 let mut left = (self.start, ParallelLineType::Normal);
120 let mut right = (self.start, ParallelLineType::Normal);
121
122 match stroke_offset {
123 #[allow(clippy::while_let_loop)]
124 StrokeOffset::None => loop {
125 if let Some((bresenham, reduce)) = it.next() {
126 right = (bresenham.point, reduce);
127 } else {
128 break;
129 }
130
131 if let Some((bresenham, reduce)) = it.next() {
132 left = (bresenham.point, reduce);
133 } else {
134 break;
135 }
136 },
137 StrokeOffset::Left => {
138 if let Some((bresenham, reduce)) = it.last() {
139 left = (bresenham.point, reduce);
140 }
141 }
142 StrokeOffset::Right => {
143 if let Some((bresenham, reduce)) = it.last() {
144 right = (bresenham.point, reduce);
145 }
146 }
147 };
148
149 let left_start = left.0;
150 let right_start = right.0;
151
152 let delta = self.end - self.start;
153
154 let left_line = Line::new(
155 left_start,
156 left_start + delta
157 - match left.1 {
158 ParallelLineType::Normal => Point::zero(),
159 ParallelLineType::Extra => reduce,
160 },
161 );
162
163 let right_line = Line::new(
164 right_start,
165 right_start + delta
166 - match right.1 {
167 ParallelLineType::Normal => Point::zero(),
168 ParallelLineType::Extra => reduce,
169 },
170 );
171 (left_line, right_line)
172 }
173
174 pub fn midpoint(&self) -> Point {
176 self.start + (self.end - self.start) / 2
177 }
178
179 pub fn delta(&self) -> Point {
181 self.end - self.start
182 }
183}
184
185impl Transform for Line {
186 fn translate(&self, by: Point) -> Self {
199 Self {
200 start: self.start + by,
201 end: self.end + by,
202 }
203 }
204
205 fn translate_mut(&mut self, by: Point) -> &mut Self {
217 self.start += by;
218 self.end += by;
219
220 self
221 }
222}
223
224#[cfg(test)]
226mod tests {
227 use super::*;
228 use crate::{
229 geometry::Size, mock_display::MockDisplay, pixelcolor::BinaryColor,
230 primitives::PrimitiveStyle, Drawable, Pixel,
231 };
232 use arrayvec::ArrayVec;
233
234 #[test]
235 fn bounding_box() {
236 let start = Point::new(10, 10);
237 let end = Point::new(19, 29);
238
239 let line: Line = Line::new(start, end);
240 let backwards_line: Line = Line::new(end, start);
241
242 assert_eq!(
243 line.bounding_box(),
244 Rectangle::new(start, Size::new(10, 20))
245 );
246 assert_eq!(
247 backwards_line.bounding_box(),
248 Rectangle::new(start, Size::new(10, 20))
249 );
250 }
251
252 #[test]
253 fn no_stroke_width_no_line() {
254 let start = Point::new(2, 3);
255 let end = Point::new(3, 2);
256
257 let line =
258 Line::new(start, end).into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 0));
259
260 assert!(line.pixels().eq(core::iter::empty()));
261 }
262
263 #[test]
264 fn thick_line_octant_1() {
265 let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
266
267 Line::new(Point::new(2, 2), Point::new(20, 8))
268 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 5))
269 .draw(&mut display)
270 .unwrap();
271
272 display.assert_pattern(&[
273 " # ",
274 " ##### ",
275 " ######## ",
276 " ########### ",
277 " ############### ",
278 " ############### ",
279 " ############### ",
280 " ########### ",
281 " ######## ",
282 " ##### ",
283 " # ",
284 ]);
285 }
286
287 #[test]
288 fn thick_line_2px() {
289 let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
290
291 Line::new(Point::new(2, 2), Point::new(10, 2))
293 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 2))
294 .draw(&mut display)
295 .unwrap();
296
297 Line::new(Point::new(2, 5), Point::new(2, 10))
299 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::Off, 2))
300 .draw(&mut display)
301 .unwrap();
302
303 display.assert_pattern(&[
304 " ",
305 " ######### ",
306 " ######### ",
307 " ",
308 " ",
309 " .. ",
310 " .. ",
311 " .. ",
312 " .. ",
313 " .. ",
314 " .. ",
315 ]);
316 }
317
318 #[test]
320 fn diagonal() {
321 let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
322
323 Line::new(Point::new(3, 2), Point::new(10, 9))
324 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 7))
325 .draw(&mut display)
326 .unwrap();
327
328 display.assert_pattern(&[
329 " # ",
330 " ### ",
331 " ##### ",
332 " ####### ",
333 " ######### ",
334 " ######### ",
335 " ######### ",
336 " ######### ",
337 " ####### ",
338 " ##### ",
339 " ### ",
340 " # ",
341 ]);
342 }
343
344 #[test]
345 fn thick_line_3px() {
346 let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
347
348 Line::new(Point::new(2, 2), Point::new(10, 2))
350 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 3))
351 .draw(&mut display)
352 .unwrap();
353
354 Line::new(Point::new(2, 5), Point::new(2, 10))
356 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::Off, 3))
357 .draw(&mut display)
358 .unwrap();
359
360 display.assert_pattern(&[
361 " ",
362 " ######### ",
363 " ######### ",
364 " ######### ",
365 " ",
366 " ... ",
367 " ... ",
368 " ... ",
369 " ... ",
370 " ... ",
371 " ... ",
372 ]);
373 }
374
375 #[test]
376 fn thick_line_0px() {
377 let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
378
379 Line::new(Point::new(2, 2), Point::new(2, 2))
380 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 3))
381 .draw(&mut display)
382 .unwrap();
383
384 display.assert_pattern(&[
385 " ", " #", " #", " #", ]);
390 }
391
392 #[test]
393 fn event_width_offset() {
394 let mut display: MockDisplay<BinaryColor> = MockDisplay::new();
395
396 Line::new(Point::new(2, 3), Point::new(10, 3))
398 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 4))
399 .draw(&mut display)
400 .unwrap();
401
402 Line::new(Point::new(2, 9), Point::new(10, 8))
404 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 4))
405 .draw(&mut display)
406 .unwrap();
407
408 display.assert_pattern(&[
409 " ",
410 " ######### ",
411 " ######### ",
412 " ######### ",
413 " ######### ",
414 " ",
415 " #### ",
416 " ######### ",
417 " ######### ",
418 " ######### ",
419 " ##### ",
420 ]);
421 }
422
423 #[test]
424 fn points_iter() {
425 let line = Line::new(Point::new(10, 10), Point::new(20, 30));
426
427 let styled_points: ArrayVec<_, 32> = line
428 .clone()
429 .into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
430 .pixels()
431 .map(|Pixel(p, _)| p)
432 .collect();
433
434 let points: ArrayVec<_, 32> = line.points().collect();
435
436 assert_eq!(points, styled_points);
437 }
438
439 #[test]
440 fn perpendicular() {
441 assert_eq!(
442 Line::new(Point::zero(), Point::new(10, 0)).perpendicular(),
443 Line::new(Point::zero(), Point::new(0, -10))
444 );
445
446 assert_eq!(
447 Line::new(Point::new(10, 20), Point::new(20, 10)).perpendicular(),
448 Line::new(Point::new(10, 20), Point::new(0, 10))
449 );
450
451 assert_eq!(
452 Line::new(Point::zero(), Point::new(0, -10)).perpendicular(),
453 Line::new(Point::zero(), Point::new(-10, 0))
454 );
455 }
456
457 #[test]
458 fn extents() {
459 let line = Line::new(Point::new(10, 50), Point::new(10, 0));
460 let (l, r) = line.extents(11, StrokeOffset::None);
461
462 assert_eq!(l, line.translate(Point::new(-5, 0)));
463 assert_eq!(r, line.translate(Point::new(5, 0)));
464 }
465
466 #[test]
467 fn extents_zero_thickness() {
468 let line = Line::new(Point::new(10, 20), Point::new(20, 10));
469
470 let (l, r) = line.extents(0, StrokeOffset::None);
471
472 assert_eq!(l, line);
473 assert_eq!(r, line);
474 }
475}