1use core::convert::TryFrom;
2
3use embedded_graphics::{
4 prelude::*,
5 primitives::{Rectangle, StyledDrawable},
6 text::{
7 renderer::{CharacterStyle, TextMetrics, TextRenderer},
8 Baseline,
9 },
10};
11
12use crate::{Digit, Segments};
13
14#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
20#[non_exhaustive]
21pub struct SevenSegmentStyle<C> {
22 pub digit_size: Size,
24
25 pub digit_spacing: u32,
27
28 pub segment_width: u32,
30
31 pub segment_color: Option<C>,
33
34 pub inactive_segment_color: Option<C>,
36}
37
38impl<C: PixelColor> SevenSegmentStyle<C> {
39 pub(crate) fn state_color(&self, state: bool) -> Option<C> {
41 if state {
42 self.segment_color
43 } else {
44 self.inactive_segment_color
45 }
46 }
47
48 fn baseline_offset(&self, baseline: Baseline) -> u32 {
50 let bottom = self.digit_size.height.saturating_sub(1);
51
52 match baseline {
53 Baseline::Top => 0,
54 Baseline::Bottom | Baseline::Alphabetic => bottom,
55 Baseline::Middle => bottom / 2,
56 }
57 }
58}
59
60impl<C: PixelColor> CharacterStyle for SevenSegmentStyle<C> {
61 type Color = C;
62
63 fn set_text_color(&mut self, text_color: Option<Self::Color>) {
64 self.segment_color = text_color;
65 }
66}
67
68impl<C: PixelColor> TextRenderer for SevenSegmentStyle<C> {
69 type Color = C;
70
71 fn draw_string<D>(
72 &self,
73 text: &str,
74 mut position: Point,
75 baseline: Baseline,
76 target: &mut D,
77 ) -> Result<Point, D::Error>
78 where
79 D: DrawTarget<Color = C>,
80 {
81 position -= Size::new(0, self.baseline_offset(baseline));
82
83 for c in text.chars() {
84 if let Ok(segments) = Segments::try_from(c) {
85 position = Digit::new(segments, position).draw_styled(self, target)?;
86 } else if c == ':' {
87 if let Some(color) = self.segment_color {
88 let dy = self.digit_size.height / 3;
89
90 let mut rect = Rectangle::new(
91 position + Size::new(0, dy - self.segment_width / 2),
92 Size::new(self.segment_width, self.segment_width),
93 );
94 target.fill_solid(&rect, color)?;
95
96 rect.top_left += Size::new(0, dy);
97 target.fill_solid(&rect, color)?;
98 }
99
100 position += Size::new(self.segment_width + self.digit_spacing, 0);
101 } else if c == '.' {
102 if let Some(color) = self.segment_color {
103 let rect = Rectangle::new(
104 position + Size::new(0, self.digit_size.height - self.segment_width),
105 Size::new(self.segment_width, self.segment_width),
106 );
107 target.fill_solid(&rect, color)?;
108 }
109
110 position += Size::new(self.segment_width + self.digit_spacing, 0);
111 } else {
112 position += self.digit_size.x_axis() + Size::new(self.digit_spacing, 0);
113 }
114 }
115
116 position += Size::new(0, self.baseline_offset(baseline));
117
118 Ok(position)
119 }
120
121 fn draw_whitespace<D>(
122 &self,
123 width: u32,
124 position: Point,
125 _baseline: Baseline,
126 _target: &mut D,
127 ) -> Result<Point, D::Error>
128 where
129 D: DrawTarget<Color = C>,
130 {
131 Ok(position + Size::new(width, 0))
132 }
133
134 fn measure_string(&self, text: &str, position: Point, baseline: Baseline) -> TextMetrics {
135 let width = text
136 .chars()
137 .map(|c| {
138 let width = if c == '.' || c == ':' {
139 self.segment_width
140 } else {
141 self.digit_size.width
142 };
143
144 width + self.digit_spacing
145 })
146 .sum::<u32>()
147 .saturating_sub(self.digit_spacing);
148
149 let bounding_box = Rectangle::new(
150 position - Size::new(0, self.baseline_offset(baseline)),
151 Size::new(width, self.digit_size.height),
152 );
153 let next_position = position + Size::new(width, 0);
154
155 TextMetrics {
156 bounding_box,
157 next_position,
158 }
159 }
160
161 fn line_height(&self) -> u32 {
162 self.digit_size.height + self.digit_spacing
163 }
164}
165
166#[cfg(test)]
167mod tests {
168 use super::*;
169 use crate::SevenSegmentStyleBuilder;
170 use embedded_graphics::{mock_display::MockDisplay, pixelcolor::BinaryColor, text::Text};
171
172 fn test_digits(
173 character_style: SevenSegmentStyle<BinaryColor>,
174 digits: &str,
175 expected_pattern: &[&str],
176 ) {
177 let mut display = MockDisplay::new();
178
179 Text::with_baseline(digits, Point::zero(), character_style, Baseline::Top)
180 .draw(&mut display)
181 .unwrap();
182
183 display.assert_pattern(expected_pattern);
184 }
185
186 #[test]
187 fn digits_1px_9px() {
188 let style = SevenSegmentStyleBuilder::new()
189 .digit_size(Size::new(5, 9))
190 .digit_spacing(1)
191 .segment_width(1)
192 .segment_color(BinaryColor::On)
193 .build();
194
195 test_digits(
196 style,
197 "0123456789",
198 &[
199 " ### ### ### ### ### ### ### ### ",
200 "# # # # # # # # # # # # # #",
201 "# # # # # # # # # # # # # #",
202 "# # # # # # # # # # # # # #",
203 " ### ### ### ### ### ### ### ",
204 "# # # # # # # # # # # # #",
205 "# # # # # # # # # # # # #",
206 "# # # # # # # # # # # # #",
207 " ### ### ### ### ### ### ### ",
208 ],
209 );
210 }
211
212 #[test]
213 fn digits_1px_10px() {
214 let style = SevenSegmentStyleBuilder::new()
215 .digit_size(Size::new(5, 10))
216 .digit_spacing(1)
217 .segment_width(1)
218 .segment_color(BinaryColor::On)
219 .build();
220
221 test_digits(
222 style,
223 "0123456789",
224 &[
225 " ### ### ### ### ### ### ### ### ",
226 "# # # # # # # # # # # # # #",
227 "# # # # # # # # # # # # # #",
228 "# # # # # # # # # # # # # #",
229 " ### ### ### ### ### ### ### ",
230 "# # # # # # # # # # # # #",
231 "# # # # # # # # # # # # #",
232 "# # # # # # # # # # # # #",
233 "# # # # # # # # # # # # #",
234 " ### ### ### ### ### ### ### ",
235 ],
236 );
237 }
238
239 #[test]
240 fn digits_2px_12px() {
241 let style = SevenSegmentStyleBuilder::new()
242 .digit_size(Size::new(7, 12))
243 .digit_spacing(1)
244 .segment_width(2)
245 .segment_color(BinaryColor::On)
246 .build();
247
248 test_digits(
249 style,
250 "01234",
251 &[
252 " ### ### ### ",
253 " ### ### ### ",
254 "## ## ## ## ## ## ##",
255 "## ## ## ## ## ## ##",
256 "## ## ## ## ## ## ##",
257 " ### ### ### ",
258 " ### ### ### ",
259 "## ## ## ## ## ##",
260 "## ## ## ## ## ##",
261 "## ## ## ## ## ##",
262 " ### ### ### ",
263 " ### ### ### ",
264 ],
265 );
266
267 test_digits(
268 style,
269 "56789",
270 &[
271 " ### ### ### ### ### ",
272 " ### ### ### ### ### ",
273 "## ## ## ## ## ## ##",
274 "## ## ## ## ## ## ##",
275 "## ## ## ## ## ## ##",
276 " ### ### ### ### ",
277 " ### ### ### ### ",
278 " ## ## ## ## ## ## ##",
279 " ## ## ## ## ## ## ##",
280 " ## ## ## ## ## ## ##",
281 " ### ### ### ### ",
282 " ### ### ### ### ",
283 ],
284 );
285 }
286
287 #[test]
288 fn digits_2px_13px() {
289 let style = SevenSegmentStyleBuilder::new()
290 .digit_size(Size::new(7, 13))
291 .digit_spacing(1)
292 .segment_width(2)
293 .segment_color(BinaryColor::On)
294 .build();
295
296 test_digits(
297 style,
298 "01234",
299 &[
300 " ### ### ### ",
301 " ### ### ### ",
302 "## ## ## ## ## ## ##",
303 "## ## ## ## ## ## ##",
304 "## ## ## ## ## ## ##",
305 " ### ### ### ",
306 " ### ### ### ",
307 "## ## ## ## ## ##",
308 "## ## ## ## ## ##",
309 "## ## ## ## ## ##",
310 "## ## ## ## ## ##",
311 " ### ### ### ",
312 " ### ### ### ",
313 ],
314 );
315
316 test_digits(
317 style,
318 "56789",
319 &[
320 " ### ### ### ### ### ",
321 " ### ### ### ### ### ",
322 "## ## ## ## ## ## ##",
323 "## ## ## ## ## ## ##",
324 "## ## ## ## ## ## ##",
325 " ### ### ### ### ",
326 " ### ### ### ### ",
327 " ## ## ## ## ## ## ##",
328 " ## ## ## ## ## ## ##",
329 " ## ## ## ## ## ## ##",
330 " ## ## ## ## ## ## ##",
331 " ### ### ### ### ",
332 " ### ### ### ### ",
333 ],
334 );
335 }
336
337 #[test]
338 fn digits_3px_15px() {
339 let style = SevenSegmentStyleBuilder::new()
340 .digit_size(Size::new(9, 15))
341 .digit_spacing(1)
342 .segment_width(3)
343 .segment_color(BinaryColor::On)
344 .build();
345
346 test_digits(
347 style,
348 "01234",
349 &[
350 " ### ### ### ",
351 " ##### ##### ##### ",
352 " # ### # # ### # ### # # # ",
353 "### ### ### ### ### ### ###",
354 "### ### ### ### ### ### ###",
355 "### ### ### ### ### ### ###",
356 " # # # ### # ### # # ### # ",
357 " ##### ##### ##### ",
358 " # # # # ### ### # ### # ",
359 "### ### ### ### ### ###",
360 "### ### ### ### ### ###",
361 "### ### ### ### ### ###",
362 " # ### # # # ### ### # # ",
363 " ##### ##### ##### ",
364 " ### ### ### ",
365 ],
366 );
367
368 test_digits(
369 style,
370 "56789",
371 &[
372 " ### ### ### ### ### ",
373 " ##### ##### ##### ##### ##### ",
374 " # ### # ### ### # # ### # # ### # ",
375 "### ### ### ### ### ### ###",
376 "### ### ### ### ### ### ###",
377 "### ### ### ### ### ### ###",
378 " # ### # ### # # ### # # ### # ",
379 " ##### ##### ##### ##### ",
380 " ### # # ### # # # ### # ### # ",
381 " ### ### ### ### ### ### ###",
382 " ### ### ### ### ### ### ###",
383 " ### ### ### ### ### ### ###",
384 " ### # # ### # # # ### # ### # ",
385 " ##### ##### ##### ##### ",
386 " ### ### ### ### ",
387 ],
388 );
389 }
390
391 #[test]
392 fn digits_3px_16px() {
393 let style = SevenSegmentStyleBuilder::new()
394 .digit_size(Size::new(9, 16))
395 .digit_spacing(1)
396 .segment_width(3)
397 .segment_color(BinaryColor::On)
398 .build();
399
400 test_digits(
401 style,
402 "01234",
403 &[
404 " ### ### ### ",
405 " ##### ##### ##### ",
406 " # ### # # ### # ### # # # ",
407 "### ### ### ### ### ### ###",
408 "### ### ### ### ### ### ###",
409 "### ### ### ### ### ### ###",
410 " # # # ### # ### # # ### # ",
411 " ##### ##### ##### ",
412 " # # # # ### ### # ### # ",
413 "### ### ### ### ### ###",
414 "### ### ### ### ### ###",
415 "### ### ### ### ### ###",
416 "### ### ### ### ### ###",
417 " # ### # # # ### ### # # ",
418 " ##### ##### ##### ",
419 " ### ### ### ",
420 ],
421 );
422
423 test_digits(
424 style,
425 "56789",
426 &[
427 " ### ### ### ### ### ",
428 " ##### ##### ##### ##### ##### ",
429 " # ### # ### ### # # ### # # ### # ",
430 "### ### ### ### ### ### ###",
431 "### ### ### ### ### ### ###",
432 "### ### ### ### ### ### ###",
433 " # ### # ### # # ### # # ### # ",
434 " ##### ##### ##### ##### ",
435 " ### # # ### # # # ### # ### # ",
436 " ### ### ### ### ### ### ###",
437 " ### ### ### ### ### ### ###",
438 " ### ### ### ### ### ### ###",
439 " ### ### ### ### ### ### ###",
440 " ### # # ### # # # ### # ### # ",
441 " ##### ##### ##### ##### ",
442 " ### ### ### ### ",
443 ],
444 );
445 }
446
447 #[test]
448 fn chaining() {
449 let style1 = SevenSegmentStyleBuilder::new()
450 .digit_size(Size::new(5, 9))
451 .digit_spacing(1)
452 .segment_width(1)
453 .segment_color(BinaryColor::On)
454 .build();
455
456 let style2 = SevenSegmentStyleBuilder::from(&style1)
457 .digit_size(Size::new(7, 11))
458 .segment_color(BinaryColor::Off)
459 .build();
460
461 let mut display = MockDisplay::new();
462 let p = Point::new(0, 10);
463 let next = Text::new("12", p, style1).draw(&mut display).unwrap();
464 Text::new("3", next, style2).draw(&mut display).unwrap();
465
466 display.assert_pattern(&[
467 " ..... ",
468 " .",
469 " ### .",
470 " # # .",
471 " # # .",
472 " # # ..... ",
473 " ### .",
474 " # # .",
475 " # # .",
476 " # # .",
477 " ### ..... ",
478 ])
479 }
480
481 #[test]
482 fn chaining_with_colon() {
483 let style1 = SevenSegmentStyleBuilder::new()
484 .digit_size(Size::new(5, 9))
485 .digit_spacing(1)
486 .segment_width(1)
487 .segment_color(BinaryColor::On)
488 .build();
489
490 let style2 = SevenSegmentStyleBuilder::from(&style1)
491 .digit_size(Size::new(7, 11))
492 .segment_color(BinaryColor::Off)
493 .build();
494
495 let mut display = MockDisplay::new();
496 let p = Point::new(0, 10);
497 let next = Text::new("1:2", p, style1).draw(&mut display).unwrap();
498 Text::new("3", next, style2).draw(&mut display).unwrap();
499
500 display.assert_pattern(&[
501 " ..... ",
502 " .",
503 " ### .",
504 " # # .",
505 " # # .",
506 " # # # ..... ",
507 " ### .",
508 " # # .",
509 " # # # .",
510 " # # .",
511 " ### ..... ",
512 ])
513 }
514
515 #[test]
516 fn multiple_lines() {
517 let style = SevenSegmentStyleBuilder::new()
518 .digit_size(Size::new(5, 9))
519 .digit_spacing(2)
520 .segment_width(1)
521 .segment_color(BinaryColor::On)
522 .build();
523
524 test_digits(
525 style,
526 "12\n3",
527 &[
528 " ### ",
529 " # #",
530 " # #",
531 " # #",
532 " ### ",
533 " # # ",
534 " # # ",
535 " # # ",
536 " ### ",
537 " ",
538 " ",
539 " ### ",
540 " # ",
541 " # ",
542 " # ",
543 " ### ",
544 " # ",
545 " # ",
546 " # ",
547 " ### ",
548 ],
549 );
550 }
551
552 fn test_baseline(baseline: Baseline, expected_pattern: &[&str]) {
553 let style = SevenSegmentStyleBuilder::new()
554 .digit_size(Size::new(5, 9))
555 .digit_spacing(2)
556 .segment_width(1)
557 .segment_color(BinaryColor::On)
558 .build();
559
560 let mut display = MockDisplay::new();
561 Text::with_baseline("8", Point::new(0, 8), style, baseline)
562 .draw(&mut display)
563 .unwrap();
564
565 display.assert_pattern(expected_pattern);
566 }
567
568 #[test]
569 fn baseline_top() {
570 test_baseline(
571 Baseline::Top,
572 &[
573 " ", " ", " ", " ", " ", " ", " ", " ", " ### ", "# #", "# #", "# #", " ### ", "# #", "# #", "# #", " ### ", ],
591 );
592 }
593
594 #[test]
595 fn baseline_middle() {
596 test_baseline(
597 Baseline::Middle,
598 &[
599 " ", " ", " ", " ", " ### ", "# #", "# #", "# #", " ### ", "# #", "# #", "# #", " ### ", ],
613 );
614 }
615
616 #[test]
617 fn baseline_bottom() {
618 test_baseline(
619 Baseline::Bottom,
620 &[
621 " ### ", "# #", "# #", "# #", " ### ", "# #", "# #", "# #", " ### ", ],
631 );
632 }
633
634 #[test]
635 fn baseline_alphabetic() {
636 test_baseline(
637 Baseline::Alphabetic,
638 &[
639 " ### ", "# #", "# #", "# #", " ### ", "# #", "# #", "# #", " ### ", ],
649 );
650 }
651
652 #[test]
653 fn measure_string() {
654 let style = SevenSegmentStyleBuilder::new()
655 .digit_size(Size::new(7, 12))
656 .digit_spacing(1)
657 .segment_width(2)
658 .segment_color(BinaryColor::On)
659 .build();
660
661 let position = Point::new(1, 2);
662
663 let metrics = style.measure_string("12", position, Baseline::Top);
664 assert_eq!(
665 metrics.bounding_box,
666 Rectangle::new(
667 position,
668 style.digit_size.component_mul(Size::new(2, 1)) + Size::new(style.digit_spacing, 0)
669 )
670 );
671 assert_eq!(
672 metrics.next_position,
673 position + metrics.bounding_box.size.x_axis()
674 );
675 }
676
677 #[test]
678 fn measure_string_with_colon() {
679 let style = SevenSegmentStyleBuilder::new()
680 .digit_size(Size::new(7, 12))
681 .digit_spacing(1)
682 .segment_width(2)
683 .segment_color(BinaryColor::On)
684 .build();
685
686 let position = Point::new(1, 2);
687
688 let metrics = style.measure_string("1:2", position, Baseline::Top);
689 assert_eq!(
690 metrics.bounding_box,
691 Rectangle::new(
692 position,
693 style.digit_size.component_mul(Size::new(2, 1))
694 + Size::new(style.digit_spacing * 2 + style.segment_width, 0)
695 )
696 );
697 assert_eq!(
698 metrics.next_position,
699 position + metrics.bounding_box.size.x_axis()
700 );
701 }
702
703 #[test]
704 fn invalid_char() {
705 let style = SevenSegmentStyleBuilder::new()
706 .digit_size(Size::new(5, 9))
707 .digit_spacing(1)
708 .segment_width(1)
709 .segment_color(BinaryColor::On)
710 .build();
711
712 test_digits(
713 style,
714 "0W1",
715 &[
716 " ### ",
717 "# # #",
718 "# # #",
719 "# # #",
720 " ",
721 "# # #",
722 "# # #",
723 "# # #",
724 " ### ",
725 ],
726 );
727 }
728}