1 | //! Text drawing. |
2 | //! |
3 | //! The [`Text`] drawable can be used to draw text on a draw target. To construct a [`Text`] object |
4 | //! at least a text string, position and character style are required. For advanced formatting |
5 | //! options an additional [`TextStyle`] object might be required. |
6 | //! |
7 | //! Text rendering in embedded-graphics is designed to be extendable by text renderers for different |
8 | //! font formats. To use a text renderer in an embedded-graphics project each renderer provides a |
9 | //! character style object. This object is used to set the appearance of characters, like the text |
10 | //! color or the used font. The available settings vary between different text renderer and are |
11 | //! documented in the text renderer documentation. |
12 | //! |
13 | //! See the [`renderer` module] docs for more information about implementing custom text renderers. |
14 | //! |
15 | //! Embedded-graphics includes a text renderer for monospaced fonts in the [`mono_font`] module. |
16 | //! Most examples will use this renderer and the associated [`MonoTextStyle`] character style. |
17 | //! But they should be easily adaptable to any external renderer listed in the |
18 | //! [external crates list]. |
19 | //! |
20 | //! # Text style |
21 | //! |
22 | //! In addition to styling the individual characters the [`Text`] drawable also contains a |
23 | //! [`TextStyle`] setting. The text style is used to set the alignment and line spacing of text |
24 | //! objects. |
25 | //! |
26 | //! The [`alignment`] setting sets the horizontal alignment of the text. With the default value |
27 | //! `Left` the text will be rendered to the right of the given text position. Analogously `Right` |
28 | //! aligned text will be rendered to the left of the given position. `Center`ed text will extend |
29 | //! equally to the left and right of the text position. |
30 | //! |
31 | //! The [`baseline`] setting defines the vertical alignment of the first line of text. With the default |
32 | //! setting of `Alphabetic` the glyphs will be drawn with their descenders below the given position. |
33 | //! This means that the bottom of glyphs without descender (like 'A') will be on the same Y |
34 | //! coordinate as the given position. The other baseline settings will position the glyphs relative |
35 | //! to the EM box, without considering the baseline. |
36 | //! |
37 | //! If the text contains multiple lines only the first line will be vertically aligned based on the |
38 | //! baseline setting. All following lines will be spaced relative to the first line, according to the [`line_height`] setting. |
39 | //! |
40 | //! # Examples |
41 | //! |
42 | //! ## Draw basic text |
43 | //! |
44 | //! ``` |
45 | //! use embedded_graphics::{ |
46 | //! mono_font::{ascii::FONT_6X10, MonoTextStyle}, |
47 | //! pixelcolor::Rgb565, |
48 | //! prelude::*, |
49 | //! text::Text, |
50 | //! }; |
51 | //! # use embedded_graphics::mock_display::MockDisplay; |
52 | //! # let mut display: MockDisplay<Rgb565> = MockDisplay::default(); |
53 | //! # display.set_allow_out_of_bounds_drawing(true); |
54 | //! |
55 | //! // Create a new character style |
56 | //! let style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); |
57 | //! |
58 | //! // Create a text at position (20, 30) and draw it using the previously defined style |
59 | //! Text::new("Hello Rust!" , Point::new(20, 30), style).draw(&mut display)?; |
60 | //! # Ok::<(), core::convert::Infallible>(()) |
61 | //! ``` |
62 | //! ## Draw centered text |
63 | //! |
64 | //! [`Text`] provides the [`with_alignment`] and [`with_baseline`] constructors to easily set |
65 | //! these commonly used settings without having to build a [`TextStyle`] object first. |
66 | //! |
67 | //! ``` |
68 | //! use embedded_graphics::{ |
69 | //! mono_font::{ascii::FONT_6X10, MonoTextStyle}, |
70 | //! pixelcolor::Rgb565, |
71 | //! prelude::*, |
72 | //! text::{Text, Alignment}, |
73 | //! }; |
74 | //! # use embedded_graphics::mock_display::MockDisplay; |
75 | //! # let mut display: MockDisplay<Rgb565> = MockDisplay::default(); |
76 | //! # display.set_allow_out_of_bounds_drawing(true); |
77 | //! |
78 | //! // Create a new character style |
79 | //! let style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); |
80 | //! |
81 | //! // Create a text at position (20, 30) and draw it using the previously defined style |
82 | //! Text::with_alignment( |
83 | //! "First line \nSecond line" , |
84 | //! Point::new(20, 30), |
85 | //! style, |
86 | //! Alignment::Center, |
87 | //! ) |
88 | //! .draw(&mut display)?; |
89 | //! # Ok::<(), core::convert::Infallible>(()) |
90 | //! ``` |
91 | //! |
92 | //! ## Draw text with `TextStyle` |
93 | //! |
94 | //! For more advanced text styles a [`TextStyle`] object can be build using the |
95 | //! [`TextStyleBuilder`] and then passed to the [`with_text_style`] constructor. |
96 | //! |
97 | //! ``` |
98 | //! use embedded_graphics::{ |
99 | //! mono_font::{ascii::FONT_6X10, MonoTextStyle}, |
100 | //! pixelcolor::Rgb565, |
101 | //! prelude::*, |
102 | //! text::{Alignment, LineHeight, Text, TextStyleBuilder}, |
103 | //! }; |
104 | //! # use embedded_graphics::mock_display::MockDisplay; |
105 | //! # let mut display: MockDisplay<Rgb565> = MockDisplay::default(); |
106 | //! # display.set_allow_out_of_bounds_drawing(true); |
107 | //! |
108 | //! // Create a new character style. |
109 | //! let character_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); |
110 | //! |
111 | //! // Create a new text style. |
112 | //! let text_style = TextStyleBuilder::new() |
113 | //! .alignment(Alignment::Center) |
114 | //! .line_height(LineHeight::Percent(150)) |
115 | //! .build(); |
116 | //! |
117 | //! // Create a text at position (20, 30) and draw it using the previously defined style. |
118 | //! Text::with_text_style( |
119 | //! "First line \nSecond line" , |
120 | //! Point::new(20, 30), |
121 | //! character_style, |
122 | //! text_style, |
123 | //! ) |
124 | //! .draw(&mut display)?; |
125 | //! # Ok::<(), core::convert::Infallible>(()) |
126 | //! ``` |
127 | //! |
128 | //! ## Combine different character styles |
129 | //! |
130 | //! The `draw` method for text drawables returns the position of the next character. This can be |
131 | //! used to combine text with different character styles on a single line of text. |
132 | //! |
133 | //! ``` |
134 | //! use embedded_graphics::{ |
135 | //! mono_font::{ascii::{FONT_6X10, FONT_10X20}, MonoTextStyle}, |
136 | //! pixelcolor::Rgb565, |
137 | //! prelude::*, |
138 | //! text::{Alignment, LineHeight, Text, TextStyleBuilder}, |
139 | //! }; |
140 | //! # use embedded_graphics::mock_display::MockDisplay; |
141 | //! # let mut display: MockDisplay<Rgb565> = MockDisplay::default(); |
142 | //! # display.set_allow_out_of_bounds_drawing(true); |
143 | //! |
144 | //! // Create a small and a large character style. |
145 | //! let small_style = MonoTextStyle::new(&FONT_6X10, Rgb565::WHITE); |
146 | //! let large_style = MonoTextStyle::new(&FONT_10X20, Rgb565::WHITE); |
147 | //! |
148 | //! // Draw the first text at (20, 30) using the small character style. |
149 | //! let next = Text::new("small " , Point::new(20, 30), small_style).draw(&mut display)?; |
150 | //! |
151 | //! // Draw the second text after the first text using the large character style. |
152 | //! let next = Text::new("large" , next, large_style).draw(&mut display)?; |
153 | //! # Ok::<(), core::convert::Infallible>(()) |
154 | //! ``` |
155 | //! |
156 | //! [`Text::new`]: TextStyle::new() |
157 | //! [`with_alignment`]: Text::with_alignment() |
158 | //! [`with_baseline`]: Text::with_baseline() |
159 | //! [`with_text_style`]: Text::with_text_style() |
160 | //! [`alignment`]: TextStyle::alignment |
161 | //! [`baseline`]: TextStyle::baseline |
162 | //! [`line_height`]: TextStyle::line_height |
163 | //! [`mono_font`]: super::mono_font |
164 | //! [`MonoTextStyle`]: super::mono_font::MonoTextStyle |
165 | //! [`renderer` module]: renderer |
166 | //! [external crates list]: super#additional-functions-provided-by-external-crates |
167 | |
168 | pub mod renderer; |
169 | mod text; |
170 | mod text_style; |
171 | |
172 | use embedded_graphics_core::prelude::PixelColor; |
173 | pub use text::Text; |
174 | pub use text_style::{TextStyle, TextStyleBuilder}; |
175 | |
176 | /// Text baseline. |
177 | #[derive (Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] |
178 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
179 | pub enum Baseline { |
180 | /// Top. |
181 | Top, |
182 | /// Bottom. |
183 | Bottom, |
184 | /// Middle. |
185 | Middle, |
186 | /// Alphabetic baseline. |
187 | Alphabetic, |
188 | } |
189 | |
190 | /// Horizontal text alignment. |
191 | #[derive (Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] |
192 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
193 | pub enum Alignment { |
194 | /// Left. |
195 | Left, |
196 | /// Center. |
197 | Center, |
198 | /// Right. |
199 | Right, |
200 | } |
201 | |
202 | /// Text decoration color. |
203 | #[derive (Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] |
204 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
205 | pub enum DecorationColor<C> { |
206 | /// No text decoration. |
207 | None, |
208 | /// Text decoration with the same color as the text. |
209 | TextColor, |
210 | /// Text decoration with a custom color. |
211 | Custom(C), |
212 | } |
213 | |
214 | impl<C: PixelColor> DecorationColor<C> { |
215 | /// Returns `true` if the decoration_color is `None`. |
216 | pub const fn is_none(&self) -> bool { |
217 | matches!(self, Self::None) |
218 | } |
219 | |
220 | /// Returns `true` if the decoration_color is `TextColor`. |
221 | pub const fn is_text_color(&self) -> bool { |
222 | matches!(self, Self::TextColor) |
223 | } |
224 | |
225 | /// Returns `true` if the decoration_color is `Custom`. |
226 | pub const fn is_custom(&self) -> bool { |
227 | matches!(self, Self::Custom(_)) |
228 | } |
229 | |
230 | pub(crate) const fn to_color(&self, text_color: Option<C>) -> Option<C> { |
231 | match self { |
232 | DecorationColor::TextColor => text_color, |
233 | DecorationColor::Custom(custom_color: &C) => Some(*custom_color), |
234 | DecorationColor::None => None, |
235 | } |
236 | } |
237 | } |
238 | |
239 | /// Text line height. |
240 | /// |
241 | /// The line height is defined as the vertical distance between the baseline of two adjacent lines |
242 | /// of text. |
243 | #[derive (Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] |
244 | #[cfg_attr (feature = "defmt" , derive(::defmt::Format))] |
245 | pub enum LineHeight { |
246 | /// Absolute line height in pixels. |
247 | Pixels(u32), |
248 | |
249 | /// Relative line height in percent of the default line height. |
250 | Percent(u32), |
251 | } |
252 | |
253 | impl LineHeight { |
254 | /// Converts the line height to an absolute pixel distance. |
255 | /// |
256 | /// # Examples |
257 | /// |
258 | /// ``` |
259 | /// use embedded_graphics::text::LineHeight; |
260 | /// |
261 | /// let relative_height = LineHeight::Percent(150); |
262 | /// assert_eq!(relative_height.to_absolute(20), 30); |
263 | /// ``` |
264 | pub const fn to_absolute(self, base_line_height: u32) -> u32 { |
265 | match self { |
266 | Self::Pixels(px: u32) => px, |
267 | Self::Percent(percent: u32) => base_line_height * percent / 100, |
268 | } |
269 | } |
270 | } |
271 | |
272 | impl Default for LineHeight { |
273 | fn default() -> Self { |
274 | Self::Percent(100) |
275 | } |
276 | } |
277 | |
278 | #[cfg (test)] |
279 | mod tests { |
280 | use super::*; |
281 | use crate::pixelcolor::BinaryColor; |
282 | |
283 | #[test ] |
284 | fn decoration_color_is_methods() { |
285 | let none = DecorationColor::<BinaryColor>::None; |
286 | assert!(none.is_none()); |
287 | assert!(!none.is_text_color()); |
288 | assert!(!none.is_custom()); |
289 | |
290 | let text_color = DecorationColor::<BinaryColor>::TextColor; |
291 | assert!(!text_color.is_none()); |
292 | assert!(text_color.is_text_color()); |
293 | assert!(!text_color.is_custom()); |
294 | |
295 | let custom = DecorationColor::Custom(BinaryColor::On); |
296 | assert!(!custom.is_none()); |
297 | assert!(!custom.is_text_color()); |
298 | assert!(custom.is_custom()); |
299 | } |
300 | |
301 | #[test ] |
302 | fn line_height_to_absolute() { |
303 | assert_eq!(LineHeight::Pixels(100).to_absolute(20), 100); |
304 | assert_eq!(LineHeight::Percent(100).to_absolute(20), 20); |
305 | assert_eq!(LineHeight::Percent(150).to_absolute(20), 30); |
306 | } |
307 | } |
308 | |