Skip to main content

multi_mono_font/
static_text.rs

1use embedded_graphics::{
2    draw_target::DrawTarget,
3    geometry::Point,
4    prelude::{PixelColor, Primitive},
5    primitives::{PrimitiveStyle, Rectangle},
6    text::{renderer::TextRenderer, Alignment, Baseline},
7    transform::Transform,
8    Drawable,
9};
10
11/// StaticText drawable.
12///
13/// A text drawable can be used to draw text to a draw target.
14///
15/// See the [module-level documentation](super) for more information about text drawables and examples.
16#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug)]
17#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
18pub struct StaticText<'a, S> {
19    /// The string.
20    pub text: &'a str,
21
22    /// The position.
23    pub rectangle: Rectangle,
24
25    /// The character style.
26    pub character_style: S,
27
28    /// Horizontal text alignment.
29    pub alignment: Alignment,
30
31    /// Text baseline.
32    pub baseline: Baseline,
33}
34
35impl<'a, S> StaticText<'a, S> {
36    /// Creates a text drawable with the default text style.
37    pub const fn new(text: &'a str, rectangle: Rectangle, character_style: S) -> Self {
38        Self {
39            text,
40            rectangle,
41            character_style,
42            alignment: Alignment::Left,
43            baseline: Baseline::Alphabetic,
44        }
45    }
46
47    /// Creates a text drawable with the given alignment and baseline.
48    pub const fn with_style(
49        text: &'a str,
50        rectangle: Rectangle,
51        character_style: S,
52        alignment: Alignment,
53        baseline: Baseline,
54    ) -> Self {
55        Self {
56            text,
57            rectangle,
58            character_style,
59            alignment,
60            baseline,
61        }
62    }
63
64    pub fn fill_with_style<C, D>(
65        &self,
66        style: PrimitiveStyle<C>,
67        target: &mut D,
68    ) -> Result<Point, D::Error>
69    where
70        C: PixelColor,
71        D: DrawTarget<Color = C>,
72    {
73        let result = self.rectangle.into_styled(style).draw(target);
74        result.map(|_| {
75            Point::new(
76                self.rectangle.top_left.x + self.rectangle.size.width as i32,
77                self.rectangle.top_left.y,
78            )
79        })
80    }
81}
82
83impl<S: Clone> Transform for StaticText<'_, S> {
84    fn translate(&self, by: Point) -> Self {
85        Self {
86            rectangle: Rectangle::new(self.rectangle.top_left + by, self.rectangle.size),
87            ..self.clone()
88        }
89    }
90
91    fn translate_mut(&mut self, by: Point) -> &mut Self {
92        self.rectangle.top_left += by;
93
94        self
95    }
96}
97
98impl<S: TextRenderer> StaticText<'_, S> {
99    fn lines(&self) -> impl Iterator<Item = (&str, Point)> {
100        let line_feed = self.text.matches('\n').count() as i32;
101
102        let offset_y = self.character_style.line_height() as i32 * line_feed;
103        let mut position = self.rectangle.top_left;
104        let height = self.rectangle.size.height as i32;
105        match self.baseline {
106            Baseline::Top => {}
107            Baseline::Bottom | Baseline::Alphabetic => position.y += height - 1 - offset_y,
108            Baseline::Middle => position.y += (height - 1 - offset_y) / 2,
109        }
110
111        self.text.split('\n').map(move |line| {
112            let p = match self.alignment {
113                Alignment::Left => position,
114                Alignment::Right => {
115                    let metrics =
116                        self.character_style
117                            .measure_string(line, Point::zero(), self.baseline);
118                    position + Point::new(self.rectangle.size.width as i32, 0)
119                        - (metrics.next_position - Point::new(1, 0))
120                }
121                Alignment::Center => {
122                    let metrics =
123                        self.character_style
124                            .measure_string(line, Point::zero(), self.baseline);
125                    position + Point::new(self.rectangle.size.width as i32 / 2, 0)
126                        - metrics.next_position / 2
127                }
128            };
129
130            position.y += self.character_style.line_height() as i32;
131
132            // remove trailing '\r' for '\r\n' line endings
133            let len = line.len();
134            if len > 0 && line.as_bytes()[len - 1] == b'\r' {
135                (&line[0..len - 1], p)
136            } else {
137                (line, p)
138            }
139        })
140    }
141
142    pub fn clear<D>(&self, target: &mut D) -> Result<Point, D::Error>
143    where
144        D: DrawTarget<Color = S::Color>,
145    {
146        self.character_style.draw_whitespace(
147            self.rectangle.size.width,
148            self.rectangle.top_left,
149            self.baseline,
150            target,
151        )
152    }
153
154    pub fn clear_by_color<C, D>(&self, color: C, target: &mut D) -> Result<Point, D::Error>
155    where
156        C: PixelColor,
157        D: DrawTarget<Color = C>,
158    {
159        target.fill_solid(&self.rectangle, color).map(|_| {
160            Point::new(
161                self.rectangle.top_left.x + self.rectangle.size.width as i32,
162                self.rectangle.top_left.y,
163            )
164        })
165    }
166}
167
168impl<S: TextRenderer> Drawable for StaticText<'_, S> {
169    type Color = S::Color;
170    type Output = Point;
171
172    fn draw<D>(&self, target: &mut D) -> Result<Point, D::Error>
173    where
174        D: DrawTarget<Color = Self::Color>,
175    {
176        let mut next_position = Point::zero();
177        let size = &self.rectangle.size;
178        let left_x = self.rectangle.top_left.x;
179        let right_x = left_x + size.width as i32;
180
181        for (line, position) in self.lines() {
182            if position.x > left_x {
183                self.character_style.draw_whitespace(
184                    (position.x - left_x) as u32,
185                    Point::new(left_x, position.y),
186                    self.baseline,
187                    target,
188                )?;
189            }
190
191            next_position =
192                self.character_style
193                    .draw_string(line, position, self.baseline, target)?;
194
195            if next_position.x < right_x {
196                self.character_style.draw_whitespace(
197                    (right_x - next_position.x) as u32,
198                    next_position,
199                    self.baseline,
200                    target,
201                )?;
202                next_position.x = right_x
203            }
204        }
205
206        Ok(next_position)
207    }
208}