embedded_graphics/mono_font/
mono_text_style.rs

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/// Style properties for text using a monospaced font.
20///
21/// A `MonoTextStyle` can be applied to a [`Text`] object to define how the text is drawn.
22///
23/// Because `MonoTextStyle` has the [`non_exhaustive`] attribute, it cannot be created using a
24/// struct literal. To create a `MonoTextStyle` with a given text color and transparent
25/// background, use the [`new`] method. For more complex text styles, use the
26/// [`MonoTextStyleBuilder`].
27///
28/// [`Text`]: crate::text::Text
29/// [`non_exhaustive`]: https://blog.rust-lang.org/2019/12/19/Rust-1.40.0.html#[non_exhaustive]-structs,-enums,-and-variants
30/// [`new`]: MonoTextStyle::new()
31#[derive(Copy, Clone, Debug, PartialEq)]
32#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
33#[non_exhaustive]
34pub struct MonoTextStyle<'a, C> {
35    /// Text color.
36    pub text_color: Option<C>,
37
38    /// Background color.
39    pub background_color: Option<C>,
40
41    /// Underline color.
42    pub underline_color: DecorationColor<C>,
43
44    /// Strikethrough color.
45    pub strikethrough_color: DecorationColor<C>,
46
47    /// Font.
48    pub font: &'a MonoFont<'a>,
49}
50
51impl<'a, C> MonoTextStyle<'a, C>
52where
53    C: PixelColor,
54{
55    /// Creates a text style with transparent background.
56    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    /// Returns `true` if the style is transparent.
64    ///
65    /// Drawing a `Text` with a transparent `MonoTextStyle` will not draw any pixels.
66    ///
67    /// [`Text`]: super::text::Text
68    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                // Fill space between characters if background color is set.
147                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    /// Returns the vertical offset between the line position and the top edge of the bounding box.
170    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/// Text style builder for monospaced fonts.
321///
322/// Use this builder to create [`MonoTextStyle`]s for [`Text`].
323///
324/// # Examples
325///
326/// ## Render yellow text on a blue background
327///
328/// This uses the [`FONT_6X9`] font, but [other fonts] can also be used.
329///
330/// ```rust
331/// use embedded_graphics::{
332///     mono_font::{ascii::FONT_6X9, MonoTextStyle, MonoTextStyleBuilder},
333///     pixelcolor::Rgb565,
334///     prelude::*,
335///     text::Text,
336/// };
337///
338/// let style = MonoTextStyleBuilder::new()
339///     .font(&FONT_6X9)
340///     .text_color(Rgb565::YELLOW)
341///     .background_color(Rgb565::BLUE)
342///     .build();
343///
344/// let text = Text::new("Hello Rust!", Point::new(0, 0), style);
345/// ```
346///
347/// ## Transparent background
348///
349/// If a property is omitted, it will remain at its default value in the resulting
350/// `MonoTextStyle` returned by `.build()`. This example draws white text with no background at
351/// all.
352///
353/// ```rust
354/// use embedded_graphics::{
355///     mono_font::{ascii::FONT_6X9, MonoTextStyle, MonoTextStyleBuilder},
356///     pixelcolor::Rgb565,
357///     prelude::*,
358///     text::Text,
359/// };
360///
361/// let style = MonoTextStyleBuilder::new()
362///     .font(&FONT_6X9)
363///     .text_color(Rgb565::WHITE)
364///     .build();
365///
366/// let text = Text::new("Hello Rust!", Point::new(0, 0), style);
367/// ```
368///
369/// ## Modifying an existing style
370///
371/// The builder can also be used to modify an existing style.
372///
373/// ```
374/// use embedded_graphics::{
375///     mono_font::{ascii::{FONT_6X9, FONT_10X20}, MonoTextStyle, MonoTextStyleBuilder},
376///     pixelcolor::Rgb565,
377///     prelude::*,
378///     text::Text,
379/// };
380///
381/// let style = MonoTextStyle::new(&FONT_6X9, Rgb565::YELLOW);
382///
383/// let style_larger = MonoTextStyleBuilder::from(&style)
384///     .font(&FONT_10X20)
385///     .build();
386/// ```
387///
388/// [`FONT_6X9`]: crate::mono_font::ascii::FONT_6X9
389/// [other fonts]: super
390/// [`Text`]: crate::text::Text
391#[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    /// Creates a new text style builder.
402    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    /// Sets the font.
415    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    /// Enables underline using the text color.
428    pub const fn underline(mut self) -> Self {
429        self.style.underline_color = DecorationColor::TextColor;
430
431        self
432    }
433
434    /// Enables strikethrough using the text color.
435    pub const fn strikethrough(mut self) -> Self {
436        self.style.strikethrough_color = DecorationColor::TextColor;
437
438        self
439    }
440
441    /// Resets the text color to transparent.
442    pub const fn reset_text_color(mut self) -> Self {
443        self.style.text_color = None;
444
445        self
446    }
447
448    /// Resets the background color to transparent.
449    pub const fn reset_background_color(mut self) -> Self {
450        self.style.background_color = None;
451
452        self
453    }
454
455    /// Removes the underline decoration.
456    pub const fn reset_underline(mut self) -> Self {
457        self.style.underline_color = DecorationColor::None;
458
459        self
460    }
461
462    /// Removes the strikethrough decoration.
463    pub const fn reset_strikethrough(mut self) -> Self {
464        self.style.strikethrough_color = DecorationColor::None;
465
466        self
467    }
468
469    /// Sets the text color.
470    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    /// Sets the background color.
477    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    /// Enables underline with a custom color.
484    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    /// Enables strikethrough with a custom color.
491    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    /// Builds the text style.
498    ///
499    /// This method can only be called after a font was set by using the [`font`] method. All other
500    /// settings are optional and they will be set to their default value if they are missing.
501    ///
502    /// [`font`]: MonoTextStyleBuilder::font()
503    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", //
783            "WWWW", //
784            "WWWW", //
785            "WWWW", //
786            "WWWW", //
787            "WWWW", //
788            "WWWW", //
789            "WWWW", //
790            "WWWW", //
791        ]);
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            "   ", //
810            "   ", //
811            "   ", //
812            "   ", //
813            "RRR", //
814            "   ", //
815            "   ", //
816            "   ", //
817            "GGG", //
818        ]);
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", //
840            "WWWWWWWW", //
841            "WWWWWWWW", //
842            "WWWWWWWW", //
843            "RRRRRRRR", //
844            "WWWWWWWW", //
845            "WWWWWWWW", //
846            "WWWWWWWW", //
847            "GGGGGGGG", //
848        ]);
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            // `style` cannot be returned from this block, because if is limited by the lifetime of
1213            // `font`. But it should be possible to return `style2` because `FONT_6X9` is a const.
1214            let style2 = MonoTextStyleBuilder::from(&style).font(&FONT_6X9).build();
1215
1216            style2
1217        };
1218    }
1219}