| 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 | |