1use crate::{
2 draw_target::DrawTarget,
3 geometry::{Point, Size},
4 image::Image,
5 mono_font::{
6 draw_target::{Background, Both, Foreground, MonoFontDrawTarget},
7 MonoFont,
8 },
9 pixelcolor::{BinaryColor, PixelColor},
10 primitives::Rectangle,
11 text::{
12 renderer::{CharacterStyle, TextMetrics, TextRenderer},
13 Baseline, DecorationColor,
14 },
15 Drawable,
16};
17use az::SaturatingAs;
18
19#[derive(Copy, Clone, Debug, PartialEq)]
32#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
33#[non_exhaustive]
34pub struct MonoTextStyle<'a, C> {
35 pub text_color: Option<C>,
37
38 pub background_color: Option<C>,
40
41 pub underline_color: DecorationColor<C>,
43
44 pub strikethrough_color: DecorationColor<C>,
46
47 pub font: &'a MonoFont<'a>,
49}
50
51impl<'a, C> MonoTextStyle<'a, C>
52where
53 C: PixelColor,
54{
55 pub const fn new(font: &'a MonoFont<'a>, text_color: C) -> Self {
57 MonoTextStyleBuilder::new()
58 .font(font)
59 .text_color(text_color)
60 .build()
61 }
62
63 pub fn is_transparent(&self) -> bool {
69 self.text_color.is_none()
70 && self.background_color.is_none()
71 && self.underline_color.is_none()
72 && self.strikethrough_color.is_none()
73 }
74
75 fn line_elements<'t>(
76 &self,
77 mut position: Point,
78 text: &'t str,
79 ) -> impl Iterator<Item = (Point, LineElement)> + 't {
80 let char_width = self.font.character_size.width as i32;
81 let spacing_width = self.font.character_spacing as i32;
82
83 let mut chars = text.chars();
84 let mut next_char = chars.next();
85 let mut add_spacing = false;
86
87 core::iter::from_fn(move || {
88 if add_spacing {
89 let p = position;
90 position.x += spacing_width;
91
92 add_spacing = false;
93
94 Some((p, LineElement::Spacing))
95 } else if let Some(c) = next_char {
96 let p = position;
97 position.x += char_width;
98
99 next_char = chars.next();
100 add_spacing = next_char.is_some();
101
102 Some((p, LineElement::Char(c)))
103 } else {
104 Some((position, LineElement::Done))
105 }
106 })
107 }
108
109 fn draw_decorations<D>(
110 &self,
111 width: u32,
112 position: Point,
113 target: &mut D,
114 ) -> Result<(), D::Error>
115 where
116 D: DrawTarget<Color = C>,
117 {
118 if let Some(color) = self.strikethrough_color.to_color(self.text_color) {
119 let rect = self.font.strikethrough.to_rectangle(position, width);
120 target.fill_solid(&rect, color)?;
121 }
122
123 if let Some(color) = self.underline_color.to_color(self.text_color) {
124 let rect = self.font.underline.to_rectangle(position, width);
125 target.fill_solid(&rect, color)?;
126 }
127
128 Ok(())
129 }
130
131 fn draw_string_binary<D>(
132 &self,
133 text: &str,
134 position: Point,
135 mut target: D,
136 ) -> Result<Point, D::Error>
137 where
138 D: DrawTarget<Color = BinaryColor>,
139 {
140 for (p, element) in self.line_elements(position, text) {
141 match element {
142 LineElement::Char(c) => {
143 let glyph = self.font.glyph(c);
144 Image::new(&glyph, p).draw(&mut target)?;
145 }
146 LineElement::Spacing if self.font.character_spacing > 0 => {
148 if self.background_color.is_some() {
149 target.fill_solid(
150 &Rectangle::new(
151 p,
152 Size::new(
153 self.font.character_spacing,
154 self.font.character_size.height,
155 ),
156 ),
157 BinaryColor::Off,
158 )?;
159 }
160 }
161 LineElement::Spacing => {}
162 LineElement::Done => return Ok(p),
163 }
164 }
165
166 Ok(position)
167 }
168
169 fn baseline_offset(&self, baseline: Baseline) -> i32 {
171 match baseline {
172 Baseline::Top => 0,
173 Baseline::Bottom => self
174 .font
175 .character_size
176 .height
177 .saturating_sub(1)
178 .saturating_as(),
179 Baseline::Middle => {
180 (self.font.character_size.height.saturating_sub(1) / 2).saturating_as()
181 }
182 Baseline::Alphabetic => self.font.baseline.saturating_as(),
183 }
184 }
185}
186
187impl<C> TextRenderer for MonoTextStyle<'_, C>
188where
189 C: PixelColor,
190{
191 type Color = C;
192
193 fn draw_string<D>(
194 &self,
195 text: &str,
196 position: Point,
197 baseline: Baseline,
198 target: &mut D,
199 ) -> Result<Point, D::Error>
200 where
201 D: DrawTarget<Color = Self::Color>,
202 {
203 let position = position - Point::new(0, self.baseline_offset(baseline));
204
205 let next = match (self.text_color, self.background_color) {
206 (Some(text_color), Some(background_color)) => self.draw_string_binary(
207 text,
208 position,
209 MonoFontDrawTarget::new(target, Both(text_color, background_color)),
210 )?,
211 (Some(text_color), None) => self.draw_string_binary(
212 text,
213 position,
214 MonoFontDrawTarget::new(target, Foreground(text_color)),
215 )?,
216 (None, Some(background_color)) => self.draw_string_binary(
217 text,
218 position,
219 MonoFontDrawTarget::new(target, Background(background_color)),
220 )?,
221 (None, None) => {
222 let dx = (self.font.character_size.width + self.font.character_spacing)
223 * text.chars().count() as u32;
224
225 position + Size::new(dx, 0)
226 }
227 };
228
229 if next.x > position.x {
230 let width = (next.x - position.x) as u32;
231 self.draw_decorations(width, position, target)?;
232 }
233
234 Ok(next + Point::new(0, self.baseline_offset(baseline)))
235 }
236
237 fn draw_whitespace<D>(
238 &self,
239 width: u32,
240 position: Point,
241 baseline: Baseline,
242 target: &mut D,
243 ) -> Result<Point, D::Error>
244 where
245 D: DrawTarget<Color = Self::Color>,
246 {
247 let position = position - Point::new(0, self.baseline_offset(baseline));
248
249 if width != 0 {
250 if let Some(background_color) = self.background_color {
251 target.fill_solid(
252 &Rectangle::new(position, Size::new(width, self.font.character_size.height)),
253 background_color,
254 )?;
255 }
256
257 self.draw_decorations(width, position, target)?;
258 }
259
260 Ok(position + Point::new(width.saturating_as(), self.baseline_offset(baseline)))
261 }
262
263 fn measure_string(&self, text: &str, position: Point, baseline: Baseline) -> TextMetrics {
264 let bb_position = position - Point::new(0, self.baseline_offset(baseline));
265
266 let bb_width = (text.chars().count() as u32
267 * (self.font.character_size.width + self.font.character_spacing))
268 .saturating_sub(self.font.character_spacing);
269
270 let bb_height = if self.underline_color != DecorationColor::None {
271 self.font.underline.height + self.font.underline.offset
272 } else {
273 self.font.character_size.height
274 };
275
276 let bb_size = Size::new(bb_width, bb_height);
277
278 TextMetrics {
279 bounding_box: Rectangle::new(bb_position, bb_size),
280 next_position: position + bb_size.x_axis(),
281 }
282 }
283
284 fn line_height(&self) -> u32 {
285 self.font.character_size.height
286 }
287}
288
289impl<C> CharacterStyle for MonoTextStyle<'_, C>
290where
291 C: PixelColor,
292{
293 type Color = C;
294
295 fn set_text_color(&mut self, text_color: Option<Self::Color>) {
296 self.text_color = text_color;
297 }
298
299 fn set_background_color(&mut self, background_color: Option<Self::Color>) {
300 self.background_color = background_color;
301 }
302
303 fn set_underline_color(&mut self, underline_color: DecorationColor<Self::Color>) {
304 self.underline_color = underline_color;
305 }
306
307 fn set_strikethrough_color(&mut self, strikethrough_color: DecorationColor<Self::Color>) {
308 self.strikethrough_color = strikethrough_color;
309 }
310}
311
312#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
313#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
314enum LineElement {
315 Char(char),
316 Spacing,
317 Done,
318}
319
320#[derive(Copy, Clone, Debug)]
392#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
393pub struct MonoTextStyleBuilder<'a, C> {
394 style: MonoTextStyle<'a, C>,
395}
396
397impl<'a, C> MonoTextStyleBuilder<'a, C>
398where
399 C: PixelColor,
400{
401 pub const fn new() -> Self {
403 Self {
404 style: MonoTextStyle {
405 font: &super::NULL_FONT,
406 background_color: None,
407 text_color: None,
408 underline_color: DecorationColor::None,
409 strikethrough_color: DecorationColor::None,
410 },
411 }
412 }
413
414 pub const fn font<'b>(self, font: &'b MonoFont<'b>) -> MonoTextStyleBuilder<'b, C> {
416 let style = MonoTextStyle {
417 font,
418 background_color: self.style.background_color,
419 text_color: self.style.text_color,
420 underline_color: self.style.underline_color,
421 strikethrough_color: self.style.strikethrough_color,
422 };
423
424 MonoTextStyleBuilder { style }
425 }
426
427 pub const fn underline(mut self) -> Self {
429 self.style.underline_color = DecorationColor::TextColor;
430
431 self
432 }
433
434 pub const fn strikethrough(mut self) -> Self {
436 self.style.strikethrough_color = DecorationColor::TextColor;
437
438 self
439 }
440
441 pub const fn reset_text_color(mut self) -> Self {
443 self.style.text_color = None;
444
445 self
446 }
447
448 pub const fn reset_background_color(mut self) -> Self {
450 self.style.background_color = None;
451
452 self
453 }
454
455 pub const fn reset_underline(mut self) -> Self {
457 self.style.underline_color = DecorationColor::None;
458
459 self
460 }
461
462 pub const fn reset_strikethrough(mut self) -> Self {
464 self.style.strikethrough_color = DecorationColor::None;
465
466 self
467 }
468
469 pub const fn text_color(mut self, text_color: C) -> Self {
471 self.style.text_color = Some(text_color);
472
473 self
474 }
475
476 pub const fn background_color(mut self, background_color: C) -> Self {
478 self.style.background_color = Some(background_color);
479
480 self
481 }
482
483 pub const fn underline_with_color(mut self, underline_color: C) -> Self {
485 self.style.underline_color = DecorationColor::Custom(underline_color);
486
487 self
488 }
489
490 pub const fn strikethrough_with_color(mut self, strikethrough_color: C) -> Self {
492 self.style.strikethrough_color = DecorationColor::Custom(strikethrough_color);
493
494 self
495 }
496
497 pub const fn build(self) -> MonoTextStyle<'a, C> {
504 self.style
505 }
506}
507
508impl<'a, C> From<&MonoTextStyle<'a, C>> for MonoTextStyleBuilder<'a, C>
509where
510 C: PixelColor,
511{
512 fn from(style: &MonoTextStyle<'a, C>) -> Self {
513 Self { style: *style }
514 }
515}
516
517#[cfg(test)]
518mod tests {
519 use super::*;
520 use crate::{
521 geometry::Dimensions,
522 image::ImageRaw,
523 mock_display::MockDisplay,
524 mono_font::{
525 ascii::{FONT_10X20, FONT_6X9},
526 iso_8859_1::FONT_6X9 as FONT_6X9_LATIN1,
527 mapping,
528 tests::*,
529 DecorationDimensions,
530 },
531 pixelcolor::{BinaryColor, Rgb888, RgbColor},
532 text::Text,
533 Drawable,
534 };
535
536 const SPACED_FONT: MonoFont = MonoFont {
537 character_spacing: 5,
538 underline: DecorationDimensions::new(9, 1),
539 ..FONT_6X9
540 };
541
542 const UNDERLINED_WHITE_STYLE: MonoTextStyle<Rgb888> = MonoTextStyleBuilder::new()
543 .font(&FONT_6X9)
544 .text_color(Rgb888::WHITE)
545 .underline()
546 .build();
547
548 #[test]
549 fn builder_default() {
550 assert_eq!(
551 MonoTextStyleBuilder::<BinaryColor>::new()
552 .font(&FONT_10X20)
553 .build(),
554 MonoTextStyle {
555 font: &FONT_10X20,
556 text_color: None,
557 background_color: None,
558 underline_color: DecorationColor::None,
559 strikethrough_color: DecorationColor::None,
560 }
561 );
562 }
563
564 #[test]
565 fn builder_text_color() {
566 assert_eq!(
567 MonoTextStyleBuilder::new()
568 .font(&FONT_10X20)
569 .text_color(BinaryColor::On)
570 .build(),
571 MonoTextStyle::new(&FONT_10X20, BinaryColor::On)
572 );
573 }
574
575 #[test]
576 fn builder_background_color() {
577 assert_eq!(
578 MonoTextStyleBuilder::new()
579 .font(&FONT_10X20)
580 .background_color(BinaryColor::On)
581 .build(),
582 {
583 let mut style = MonoTextStyleBuilder::new().font(&FONT_10X20).build();
584
585 style.text_color = None;
586 style.background_color = Some(BinaryColor::On);
587
588 style
589 }
590 );
591 }
592
593 #[test]
594 fn builder_resets() {
595 let base = MonoTextStyleBuilder::new()
596 .font(&FONT_10X20)
597 .text_color(BinaryColor::On)
598 .background_color(BinaryColor::On)
599 .underline()
600 .strikethrough();
601
602 assert_eq!(
603 base.reset_text_color().build(),
604 MonoTextStyleBuilder::new()
605 .font(&FONT_10X20)
606 .background_color(BinaryColor::On)
607 .underline()
608 .strikethrough()
609 .build()
610 );
611
612 assert_eq!(
613 base.reset_background_color().build(),
614 MonoTextStyleBuilder::new()
615 .font(&FONT_10X20)
616 .text_color(BinaryColor::On)
617 .underline()
618 .strikethrough()
619 .build()
620 );
621
622 assert_eq!(
623 base.reset_underline().build(),
624 MonoTextStyleBuilder::new()
625 .font(&FONT_10X20)
626 .text_color(BinaryColor::On)
627 .background_color(BinaryColor::On)
628 .strikethrough()
629 .build()
630 );
631
632 assert_eq!(
633 base.reset_strikethrough().build(),
634 MonoTextStyleBuilder::new()
635 .font(&FONT_10X20)
636 .text_color(BinaryColor::On)
637 .background_color(BinaryColor::On)
638 .underline()
639 .build()
640 );
641 }
642
643 #[test]
644 fn underline_text_color() {
645 let mut display = MockDisplay::new();
646 Text::new("ABC", Point::new(0, 6), UNDERLINED_WHITE_STYLE)
647 .draw(&mut display)
648 .unwrap();
649
650 display.assert_pattern(&[
651 " ",
652 " W WWWW WW ",
653 " W W W W W W ",
654 "W W WWWW W ",
655 "WWWWW W W W ",
656 "W W W W W W ",
657 "W W WWWW WW ",
658 " ",
659 "WWWWWWWWWWWWWWWWWW",
660 ]);
661 }
662
663 #[test]
664 fn underline_text_color_with_alignment() {
665 let mut display = MockDisplay::new();
666 Text::with_baseline(
667 "ABC",
668 Point::new(0, 6),
669 UNDERLINED_WHITE_STYLE,
670 Baseline::Middle,
671 )
672 .draw(&mut display)
673 .unwrap();
674
675 display.assert_pattern(&[
676 " ",
677 " ",
678 " ",
679 " W WWWW WW ",
680 " W W W W W W ",
681 "W W WWWW W ",
682 "WWWWW W W W ",
683 "W W W W W W ",
684 "W W WWWW WW ",
685 " ",
686 "WWWWWWWWWWWWWWWWWW",
687 ]);
688 }
689
690 #[test]
691 fn underline_custom_color() {
692 let style = MonoTextStyleBuilder::new()
693 .font(&FONT_6X9)
694 .text_color(Rgb888::WHITE)
695 .underline_with_color(Rgb888::RED)
696 .build();
697
698 let mut display = MockDisplay::new();
699 Text::new("ABC", Point::new(0, 6), style)
700 .draw(&mut display)
701 .unwrap();
702
703 display.assert_pattern(&[
704 " ",
705 " W WWWW WW ",
706 " W W W W W W ",
707 "W W WWWW W ",
708 "WWWWW W W W ",
709 "W W W W W W ",
710 "W W WWWW WW ",
711 " ",
712 "RRRRRRRRRRRRRRRRRR",
713 ]);
714 }
715
716 #[test]
717 fn strikethrough_text_color() {
718 let style = MonoTextStyleBuilder::new()
719 .font(&FONT_6X9)
720 .text_color(Rgb888::WHITE)
721 .strikethrough()
722 .build();
723
724 let mut display = MockDisplay::new();
725 display.set_allow_overdraw(true);
726
727 Text::new("ABC", Point::new(0, 6), style)
728 .draw(&mut display)
729 .unwrap();
730
731 display.assert_pattern(&[
732 " ",
733 " W WWWW WW ",
734 " W W W W W W ",
735 "W W WWWW W ",
736 "WWWWWWWWWWWWWWWWWW",
737 "W W W W W W ",
738 "W W WWWW WW ",
739 ]);
740 }
741
742 #[test]
743 fn strikethrough_custom_color() {
744 let style = MonoTextStyleBuilder::new()
745 .font(&FONT_6X9)
746 .text_color(Rgb888::WHITE)
747 .strikethrough_with_color(Rgb888::RED)
748 .build();
749
750 let mut display = MockDisplay::new();
751 display.set_allow_overdraw(true);
752
753 Text::new("ABC", Point::new(0, 6), style)
754 .draw(&mut display)
755 .unwrap();
756
757 display.assert_pattern(&[
758 " ",
759 " W WWWW WW ",
760 " W W W W W W ",
761 "W W WWWW W ",
762 "RRRRRRRRRRRRRRRRRR",
763 "W W W W W W ",
764 "W W WWWW WW ",
765 ]);
766 }
767
768 #[test]
769 fn whitespace_background() {
770 let style = MonoTextStyleBuilder::new()
771 .font(&FONT_6X9)
772 .text_color(Rgb888::YELLOW)
773 .background_color(Rgb888::WHITE)
774 .build();
775
776 let mut display = MockDisplay::new();
777 style
778 .draw_whitespace(4, Point::zero(), Baseline::Top, &mut display)
779 .unwrap();
780
781 display.assert_pattern(&[
782 "WWWW", "WWWW", "WWWW", "WWWW", "WWWW", "WWWW", "WWWW", "WWWW", "WWWW", ]);
792 }
793
794 #[test]
795 fn whitespace_decorations() {
796 let style = MonoTextStyleBuilder::new()
797 .font(&FONT_6X9)
798 .text_color(Rgb888::YELLOW)
799 .underline_with_color(Rgb888::GREEN)
800 .strikethrough_with_color(Rgb888::RED)
801 .build();
802
803 let mut display = MockDisplay::new();
804 style
805 .draw_whitespace(3, Point::zero(), Baseline::Top, &mut display)
806 .unwrap();
807
808 display.assert_pattern(&[
809 " ", " ", " ", " ", "RRR", " ", " ", " ", "GGG", ]);
819 }
820
821 #[test]
822 fn whitespace_background_and_decorations() {
823 let style = MonoTextStyleBuilder::new()
824 .font(&FONT_6X9)
825 .text_color(Rgb888::YELLOW)
826 .background_color(Rgb888::WHITE)
827 .underline_with_color(Rgb888::GREEN)
828 .strikethrough_with_color(Rgb888::RED)
829 .build();
830
831 let mut display = MockDisplay::new();
832 display.set_allow_overdraw(true);
833
834 style
835 .draw_whitespace(8, Point::zero(), Baseline::Top, &mut display)
836 .unwrap();
837
838 display.assert_pattern(&[
839 "WWWWWWWW", "WWWWWWWW", "WWWWWWWW", "WWWWWWWW", "RRRRRRRR", "WWWWWWWW", "WWWWWWWW", "WWWWWWWW", "GGGGGGGG", ]);
849 }
850
851 #[test]
852 fn character_spacing() {
853 assert_text_from_pattern(
854 "##",
855 &SPACED_FONT,
856 &[
857 " ",
858 " # # # # ",
859 " # # # # ",
860 "##### ##### ",
861 " # # # # ",
862 "##### ##### ",
863 " # # # # ",
864 " # # # # ",
865 ],
866 );
867 }
868
869 #[test]
870 fn character_spacing_with_background() {
871 let character_style = MonoTextStyleBuilder::new()
872 .font(&SPACED_FONT)
873 .text_color(BinaryColor::On)
874 .background_color(BinaryColor::Off)
875 .build();
876
877 let mut display = MockDisplay::new();
878 Text::with_baseline("##", Point::zero(), character_style, Baseline::Top)
879 .draw(&mut display)
880 .unwrap();
881
882 display.assert_pattern(&[
883 ".................",
884 ".#.#........#.#..",
885 ".#.#........#.#..",
886 "#####......#####.",
887 ".#.#........#.#..",
888 "#####......#####.",
889 ".#.#........#.#..",
890 ".#.#........#.#..",
891 ".................",
892 ]);
893 }
894
895 #[test]
896 fn character_spacing_decorations() {
897 let character_style = MonoTextStyleBuilder::new()
898 .font(&SPACED_FONT)
899 .text_color(Rgb888::WHITE)
900 .underline_with_color(Rgb888::GREEN)
901 .strikethrough_with_color(Rgb888::RED)
902 .build();
903
904 let mut display = MockDisplay::new();
905 display.set_allow_overdraw(true);
906
907 Text::with_baseline("##", Point::zero(), character_style, Baseline::Top)
908 .draw(&mut display)
909 .unwrap();
910
911 display.assert_pattern(&[
912 " ",
913 " W W W W ",
914 " W W W W ",
915 "WWWWW WWWWW ",
916 "RRRRRRRRRRRRRRRRR",
917 "WWWWW WWWWW ",
918 " W W W W ",
919 " W W W W ",
920 " ",
921 "GGGGGGGGGGGGGGGGG",
922 ]);
923 }
924
925 #[test]
926 fn character_spacing_dimensions() {
927 let style = MonoTextStyleBuilder::new()
928 .font(&SPACED_FONT)
929 .text_color(BinaryColor::On)
930 .build();
931
932 assert_eq!(
933 Text::with_baseline("#", Point::zero(), style, Baseline::Top).bounding_box(),
934 Rectangle::new(Point::zero(), Size::new(6, 9)),
935 );
936
937 assert_eq!(
938 Text::with_baseline("##", Point::zero(), style, Baseline::Top).bounding_box(),
939 Rectangle::new(Point::zero(), Size::new(6 * 2 + 5, 9)),
940 );
941 assert_eq!(
942 Text::with_baseline("###", Point::zero(), style, Baseline::Top).bounding_box(),
943 Rectangle::new(Point::zero(), Size::new(6 * 3 + 5 * 2, 9)),
944 );
945 }
946
947 #[test]
948 fn underlined_character_dimensions() {
949 let style = MonoTextStyleBuilder::new()
950 .font(&SPACED_FONT)
951 .text_color(BinaryColor::On)
952 .underline()
953 .build();
954
955 assert_eq!(
956 Text::with_baseline("#", Point::zero(), style, Baseline::Top).bounding_box(),
957 Rectangle::new(Point::zero(), Size::new(6, 10)),
958 );
959 }
960
961 #[test]
962 fn control_characters() {
963 let style = MonoTextStyle::new(&FONT_6X9, BinaryColor::On);
964
965 let mut display = MockDisplay::new();
966 style
967 .draw_string("A\t\n\rB", Point::zero(), Baseline::Top, &mut display)
968 .unwrap();
969
970 let mut expected = MockDisplay::new();
971 style
972 .draw_string("A???B", Point::zero(), Baseline::Top, &mut expected)
973 .unwrap();
974
975 display.assert_eq(&expected);
976 }
977
978 #[test]
979 fn character_style() {
980 let mut style = MonoTextStyle::new(&FONT_6X9, BinaryColor::On);
981 CharacterStyle::set_text_color(&mut style, None);
982 CharacterStyle::set_background_color(&mut style, Some(BinaryColor::On));
983 CharacterStyle::set_underline_color(&mut style, DecorationColor::TextColor);
984 CharacterStyle::set_strikethrough_color(
985 &mut style,
986 DecorationColor::Custom(BinaryColor::On),
987 );
988
989 assert_eq!(
990 style,
991 MonoTextStyle {
992 text_color: None,
993 background_color: Some(BinaryColor::On),
994 underline_color: DecorationColor::TextColor,
995 strikethrough_color: DecorationColor::Custom(BinaryColor::On),
996 font: &FONT_6X9,
997 }
998 );
999 }
1000
1001 #[test]
1002 fn draw_string_return_value() {
1003 let style = MonoTextStyle::new(&FONT_6X9, BinaryColor::On);
1004 let start = Point::new(10, 20);
1005 let expected_next = start + Point::new(2 * 6, 0);
1006
1007 for baseline in [
1008 Baseline::Top,
1009 Baseline::Middle,
1010 Baseline::Alphabetic,
1011 Baseline::Bottom,
1012 ]
1013 .iter()
1014 {
1015 let mut display = MockDisplay::new();
1016 let next = style
1017 .draw_string("AB", start, *baseline, &mut display)
1018 .unwrap();
1019
1020 assert_eq!(
1021 next, expected_next,
1022 "Unexpected next point for {:?}: {:?} (expected {:?})",
1023 baseline, next, expected_next
1024 );
1025 }
1026 }
1027
1028 #[test]
1029 fn draw_whitespace_return_value() {
1030 let style = MonoTextStyle::new(&FONT_6X9, BinaryColor::On);
1031 let start = Point::new(10, 20);
1032 let expected_next = start + Point::new(15, 0);
1033
1034 for baseline in [
1035 Baseline::Top,
1036 Baseline::Middle,
1037 Baseline::Alphabetic,
1038 Baseline::Bottom,
1039 ]
1040 .iter()
1041 {
1042 let mut display = MockDisplay::new();
1043 let next = style
1044 .draw_whitespace(15, start, *baseline, &mut display)
1045 .unwrap();
1046
1047 assert_eq!(
1048 next, expected_next,
1049 "Unexpected next point for {:?}: {:?} (expected {:?})",
1050 baseline, next, expected_next
1051 );
1052 }
1053 }
1054
1055 #[test]
1056 fn latin1_text_dimensions_one_line() {
1057 let position = Point::new(5, 5);
1058
1059 let style = MonoTextStyleBuilder::<BinaryColor>::new()
1060 .font(&FONT_6X9_LATIN1)
1061 .build();
1062 let text = Text::with_baseline("123°§£", position, style, Baseline::Top);
1063
1064 assert_eq!(
1065 text.bounding_box(),
1066 Rectangle::new(
1067 position,
1068 FONT_6X9_LATIN1
1069 .character_size
1070 .component_mul(Size::new(6, 1))
1071 )
1072 );
1073
1074 let mut display = MockDisplay::new();
1075 let next = text.draw(&mut display).unwrap();
1076
1077 assert_eq!(next, position + FONT_6X9_LATIN1.character_size.x_axis() * 6);
1078 }
1079
1080 #[test]
1081 fn transparent_text_dimensions_one_line() {
1082 let position = Point::new(5, 5);
1083
1084 let style = MonoTextStyleBuilder::<BinaryColor>::new()
1085 .font(&FONT_6X9)
1086 .build();
1087 let text = Text::with_baseline("123", position, style, Baseline::Top);
1088
1089 assert_eq!(
1090 text.bounding_box(),
1091 Rectangle::new(
1092 position,
1093 FONT_6X9.character_size.component_mul(Size::new(3, 1))
1094 )
1095 );
1096
1097 let mut display = MockDisplay::new();
1098 let next = text.draw(&mut display).unwrap();
1099
1100 assert_eq!(next, position + FONT_6X9.character_size.x_axis() * 3);
1101 }
1102
1103 #[test]
1104 fn transparent_text_dimensions_one_line_spaced() {
1105 let position = Point::new(5, 5);
1106
1107 let style = MonoTextStyleBuilder::<BinaryColor>::new()
1108 .font(&SPACED_FONT)
1109 .build();
1110 let text = Text::with_baseline("123", position, style, Baseline::Top);
1111
1112 assert_eq!(
1113 text.bounding_box(),
1114 Rectangle::new(
1115 position,
1116 SPACED_FONT.character_size.component_mul(Size::new(3, 1))
1117 + Size::new(SPACED_FONT.character_spacing, 0) * 2
1118 )
1119 );
1120
1121 let mut display = MockDisplay::new();
1122 let next = text.draw(&mut display).unwrap();
1123
1124 assert_eq!(
1125 next,
1126 position
1127 + (SPACED_FONT.character_size.x_axis()
1128 + Size::new(SPACED_FONT.character_spacing, 0))
1129 * 3
1130 );
1131 }
1132
1133 #[test]
1134 fn transparent_text_dimensions_two_lines() {
1135 let position = Point::new(5, 5);
1136
1137 let style = MonoTextStyleBuilder::<BinaryColor>::new()
1138 .font(&FONT_6X9)
1139 .build();
1140 let text = Text::with_baseline("123\n1", position, style, Baseline::Top);
1141
1142 assert_eq!(
1143 text.bounding_box(),
1144 Rectangle::new(
1145 position,
1146 FONT_6X9.character_size.component_mul(Size::new(3, 2))
1147 )
1148 );
1149
1150 let mut display = MockDisplay::new();
1151 let next = text.draw(&mut display).unwrap();
1152
1153 assert_eq!(next, position + FONT_6X9.character_size);
1154 }
1155
1156 #[test]
1157 fn elements_iter() {
1158 let style = MonoTextStyle::new(&SPACED_FONT, BinaryColor::On);
1159
1160 let mut iter = style.line_elements(Point::new(10, 20), "");
1161 assert_eq!(iter.next(), Some((Point::new(10, 20), LineElement::Done)));
1162
1163 let mut iter = style.line_elements(Point::new(10, 20), "a");
1164 assert_eq!(
1165 iter.next(),
1166 Some((Point::new(10, 20), LineElement::Char('a')))
1167 );
1168 assert_eq!(iter.next(), Some((Point::new(16, 20), LineElement::Done)));
1169
1170 let mut iter = style.line_elements(Point::new(10, 20), "abc");
1171 assert_eq!(
1172 iter.next(),
1173 Some((Point::new(10, 20), LineElement::Char('a')))
1174 );
1175 assert_eq!(
1176 iter.next(),
1177 Some((Point::new(16, 20), LineElement::Spacing))
1178 );
1179 assert_eq!(
1180 iter.next(),
1181 Some((Point::new(21, 20), LineElement::Char('b')))
1182 );
1183 assert_eq!(
1184 iter.next(),
1185 Some((Point::new(27, 20), LineElement::Spacing))
1186 );
1187 assert_eq!(
1188 iter.next(),
1189 Some((Point::new(32, 20), LineElement::Char('c')))
1190 );
1191 assert_eq!(iter.next(), Some((Point::new(38, 20), LineElement::Done)));
1192 }
1193
1194 #[test]
1195 fn builder_change_font() {
1196 let _style = {
1197 let font = MonoFont {
1198 image: ImageRaw::new(&[1, 2, 3], 1),
1199 character_size: Size::new(1, 2),
1200 character_spacing: 0,
1201 baseline: 0,
1202 strikethrough: DecorationDimensions::default_strikethrough(2),
1203 underline: DecorationDimensions::default_underline(2),
1204 glyph_mapping: &mapping::ASCII,
1205 };
1206
1207 let style = MonoTextStyleBuilder::new()
1208 .font(&font)
1209 .text_color(BinaryColor::On)
1210 .build();
1211
1212 let style2 = MonoTextStyleBuilder::from(&style).font(&FONT_6X9).build();
1215
1216 style2
1217 };
1218 }
1219}