1#![cfg_attr(not(feature = "std"), no_std)]
28
29#[cfg(not(feature = "std"))]
30extern crate alloc;
31
32#[cfg(not(feature = "std"))]
33use num_traits::float::FloatCore;
34
35#[cfg(feature = "std")]
36use std as stdlib;
37
38#[cfg(not(feature = "std"))]
39mod stdlib {
40 pub use ::alloc::vec;
41 pub use core::*;
42}
43
44use stdlib::{f32, vec::Vec};
45
46use embedded_graphics::{
47 draw_target::DrawTarget,
48 pixelcolor::Rgb888,
49 prelude::*,
50 primitives::Rectangle,
51 text::{
52 renderer::{CharacterStyle, TextMetrics, TextRenderer},
53 Baseline, DecorationColor,
54 },
55};
56
57use rusttype::Font;
58
59#[derive(Debug, Clone)]
64pub struct FontTextStyle<C: PixelColor> {
65 pub text_color: Option<C>,
67
68 pub background_color: Option<C>,
70
71 pub aliasing_filter: Option<u8>,
73
74 pub underline_color: DecorationColor<C>,
76
77 pub strikethrough_color: DecorationColor<C>,
79
80 pub font_size: u32,
82
83 font: Font<'static>,
85}
86
87impl<C: PixelColor> FontTextStyle<C> {
88 pub fn new(font: Font<'static>, text_color: C, font_size: u32) -> Self {
90 FontTextStyleBuilder::new(font)
91 .text_color(text_color)
92 .font_size(font_size)
93 .build()
94 }
95
96 fn resolve_decoration_color(&self, color: DecorationColor<C>) -> Option<C> {
98 match color {
99 DecorationColor::None => None,
100 DecorationColor::TextColor => self.text_color,
101 DecorationColor::Custom(c) => Some(c),
102 }
103 }
104
105 fn draw_background<D>(
106 &self,
107 width: u32,
108 position: Point,
109 target: &mut D,
110 ) -> Result<(), D::Error>
111 where
112 D: DrawTarget<Color = C>,
113 {
114 if width == 0 {
115 return Ok(());
116 }
117
118 if let Some(background_color) = self.background_color {
119 target.fill_solid(
120 &Rectangle::new(position, Size::new(width, self.font_size)),
121 background_color,
122 )?;
123 }
124
125 Ok(())
126 }
127
128 fn draw_strikethrough<D>(
129 &self,
130 width: u32,
131 position: Point,
132 target: &mut D,
133 ) -> Result<(), D::Error>
134 where
135 D: DrawTarget<Color = C>,
136 {
137 if let Some(strikethrough_color) = self.resolve_decoration_color(self.strikethrough_color) {
138 let top_left = position + Point::new(0, self.font_size as i32 / 2);
139 let size = Size::new(width, self.font_size / 30 + 1);
141
142 target.fill_solid(&Rectangle::new(top_left, size), strikethrough_color)?;
143 }
144
145 Ok(())
146 }
147
148 fn draw_underline<D>(&self, width: u32, position: Point, target: &mut D) -> Result<(), D::Error>
149 where
150 D: DrawTarget<Color = C>,
151 {
152 if let Some(underline_color) = self.resolve_decoration_color(self.underline_color) {
153 let top_left = position + Point::new(0, self.font_size as i32);
154 let size = Size::new(width, self.font_size / 30 + 1);
156
157 target.fill_solid(&Rectangle::new(top_left, size), underline_color)?;
158 }
159
160 Ok(())
161 }
162}
163
164impl<C: PixelColor> CharacterStyle for FontTextStyle<C> {
165 type Color = C;
166
167 fn set_text_color(&mut self, text_color: Option<Self::Color>) {
168 self.text_color = text_color;
169 }
170
171 fn set_background_color(&mut self, background_color: Option<Self::Color>) {
172 self.background_color = background_color;
173 }
174
175 fn set_underline_color(&mut self, underline_color: DecorationColor<Self::Color>) {
176 self.underline_color = underline_color;
177 }
178
179 fn set_strikethrough_color(&mut self, strikethrough_color: DecorationColor<Self::Color>) {
180 self.strikethrough_color = strikethrough_color;
181 }
182}
183
184impl<C> TextRenderer for FontTextStyle<C>
185where
186 C: PixelColor + Into<Rgb888> + From<Rgb888> + stdlib::fmt::Debug,
187{
188 type Color = C;
189
190 fn draw_string<D>(
191 &self,
192 text: &str,
193 position: Point,
194 _baseline: Baseline,
195 target: &mut D,
196 ) -> Result<Point, D::Error>
197 where
198 D: DrawTarget<Color = Self::Color>,
199 {
200 let scale = rusttype::Scale::uniform(self.font_size as f32);
201
202 let v_metrics = self.font.v_metrics(scale);
203 let offset = rusttype::point(0.0, v_metrics.ascent);
204
205 let glyphs: Vec<rusttype::PositionedGlyph> =
206 self.font.layout(text, scale, offset).collect();
207
208 let width = glyphs
209 .iter()
210 .rev()
211 .filter_map(|g| {
212 g.pixel_bounding_box()
213 .map(|b| b.min.x as f32 + g.unpositioned().h_metrics().advance_width)
214 })
215 .next()
216 .unwrap_or(0.0)
217 .ceil() as i32;
218
219 let height = self.font_size as i32;
220
221 let mut pixels = Vec::new();
222
223 if let Some(text_color) = self.text_color {
224 for g in glyphs.iter() {
225 if let Some(bb) = g.pixel_bounding_box() {
226 g.draw(|off_x, off_y, v| {
227 let off_x = off_x as i32 + bb.min.x;
228 let off_y = off_y as i32 + bb.min.y;
229 if off_x >= 0 && off_x < width as i32 && off_y >= 0 && off_y < height as i32
231 {
232 let c = (v * 255.0) as u32;
233
234 let (text_r, text_g, text_b, text_a) =
235 u32_to_rgba(c << 24 | (pixel_color_to_u32(text_color) & 0xFFFFFF));
236
237 let (new_r, new_g, new_b) = rgba_background_to_rgb(
238 text_r,
239 text_g,
240 text_b,
241 text_a,
242 self.background_color,
243 );
244
245 if self.aliasing_filter.is_none() && text_a > 0 {
246 pixels.push(Pixel(
247 Point::new(position.x + off_x, position.y + off_y),
248 Rgb888::new(new_r, new_g, new_b).into(),
249 ));
250 } else if let Some(filter) = self.aliasing_filter {
251 if text_a >= filter {
252 pixels.push(Pixel(
253 Point::new(position.x + off_x, position.y + off_y),
254 Rgb888::new(text_r, text_g, text_b).into(),
255 ));
256 }
257 }
258 }
259 });
260 }
261 }
262 }
263
264 self.draw_background(width as u32, position, target)?;
265 target.draw_iter(pixels)?;
266 self.draw_strikethrough(width as u32, position, target)?;
267 self.draw_underline(width as u32, position, target)?;
268
269 Ok(position + Point::new(width, 0))
270 }
271
272 fn draw_whitespace<D>(
273 &self,
274 width: u32,
275 position: Point,
276 _baseline: Baseline,
277 target: &mut D,
278 ) -> Result<Point, D::Error>
279 where
280 D: DrawTarget<Color = Self::Color>,
281 {
282 self.draw_background(width, position, target)?;
283 self.draw_strikethrough(width, position, target)?;
284 self.draw_underline(width, position, target)?;
285
286 Ok(position + Size::new(width, 0))
287 }
288
289 fn measure_string(&self, text: &str, position: Point, _baseline: Baseline) -> TextMetrics {
290 let scale = rusttype::Scale::uniform(self.font_size as f32);
291 let v_metrics = self.font.v_metrics(scale);
292 let offset = rusttype::point(0.0, v_metrics.ascent);
293
294 let glyphs: Vec<rusttype::PositionedGlyph> =
295 self.font.layout(text, scale, offset).collect();
296
297 let width = glyphs
298 .iter()
299 .rev()
300 .map(|g| g.position().x as f32 + g.unpositioned().h_metrics().advance_width)
301 .next()
302 .unwrap_or(0.0)
303 .ceil() as f64;
304
305 let size = Size::new(width as u32, self.font_size);
306
307 TextMetrics {
308 bounding_box: Rectangle::new(position, size),
309 next_position: position + size.x_axis(),
310 }
311 }
312
313 fn line_height(&self) -> u32 {
314 self.font_size
315 }
316}
317
318pub struct FontTextStyleBuilder<C: PixelColor> {
322 style: FontTextStyle<C>,
323}
324
325impl<C: PixelColor> FontTextStyleBuilder<C> {
326 pub fn new(font: Font<'static>) -> Self {
328 Self {
329 style: FontTextStyle {
330 font,
331 background_color: None,
332 aliasing_filter: None,
333 font_size: 12,
334 text_color: None,
335 underline_color: DecorationColor::None,
336 strikethrough_color: DecorationColor::None,
337 },
338 }
339 }
340
341 pub fn font_size(mut self, font_size: u32) -> Self {
343 self.style.font_size = font_size;
344 self
345 }
346
347 pub fn underline(mut self) -> Self {
349 self.style.underline_color = DecorationColor::TextColor;
350 self
351 }
352
353 pub fn strikethrough(mut self) -> Self {
355 self.style.strikethrough_color = DecorationColor::TextColor;
356 self
357 }
358
359 pub fn text_color(mut self, text_color: C) -> Self {
361 self.style.text_color = Some(text_color);
362 self
363 }
364
365 pub fn background_color(mut self, background_color: C) -> Self {
367 self.style.background_color = Some(background_color);
368 self
369 }
370
371 pub fn aliasing_filter(mut self, alpha_filter: u8) -> Self {
374 self.style.aliasing_filter = Some(alpha_filter);
375 self
376 }
377
378 pub fn underline_with_color(mut self, underline_color: C) -> Self {
380 self.style.underline_color = DecorationColor::Custom(underline_color);
381 self
382 }
383
384 pub fn strikethrough_with_color(mut self, strikethrough_color: C) -> Self {
386 self.style.strikethrough_color = DecorationColor::Custom(strikethrough_color);
387
388 self
389 }
390
391 pub fn build(self) -> FontTextStyle<C> {
393 self.style
394 }
395}
396
397fn pixel_color_to_u32<C: Into<Rgb888>>(color: C) -> u32 {
398 let color = color.into();
399
400 0xFF000000 | ((color.r() as u32) << 16) | ((color.g() as u32) << 8) | (color.b() as u32)
401}
402
403fn u32_to_rgba(color: u32) -> (u8, u8, u8, u8) {
404 (
405 ((color & 0x00FF0000) >> 16) as u8,
406 ((color & 0x0000FF00) >> 8) as u8,
407 (color & 0x000000FF) as u8,
408 ((color & 0xFF000000) >> 24) as u8,
409 )
410}
411
412fn rgba_to_rgb(r: u8, g: u8, b: u8, a: u8) -> (u8, u8, u8) {
413 let alpha = a as f32 / 255.;
414
415 (
416 (r as f32 * alpha).ceil() as u8,
417 (g as f32 * alpha).ceil() as u8,
418 (b as f32 * alpha).ceil() as u8,
419 )
420}
421
422fn rgba_background_to_rgb<C: Into<Rgb888>>(
423 r: u8,
424 g: u8,
425 b: u8,
426 a: u8,
427 background_color: Option<C>,
428) -> (u8, u8, u8) {
429 if let Some(background_color) = background_color {
430 let background_color_data = pixel_color_to_u32(background_color);
431 let (br, bg, bb, ba) = u32_to_rgba(background_color_data);
432 let (br, bg, bb) = rgba_to_rgb(br, bg, bb, ba);
433
434 let alpha = a as f32 / 255.;
435 let b_alpha = 1. - alpha;
436
437 return (
439 ((r as f32 * alpha) + br as f32 * b_alpha).ceil() as u8,
440 ((g as f32 * alpha) + bg as f32 * b_alpha).ceil() as u8,
441 ((b as f32 * alpha) + bb as f32 * b_alpha).ceil() as u8,
442 );
443 }
444
445 rgba_to_rgb(r, g, b, a)
447}
448
449#[cfg(test)]
450mod tests {
451 use super::*;
452 use embedded_graphics::pixelcolor::Rgb888;
453
454 #[test]
455 fn test_pixel_color_to_u32() {
456 assert_eq!(4294967295, pixel_color_to_u32(Rgb888::WHITE));
457 assert_eq!(4278190080, pixel_color_to_u32(Rgb888::BLACK));
458 }
459
460 #[test]
461 fn test_u32_to_rgba() {
462 assert_eq!((255, 255, 255, 255), u32_to_rgba(4294967295));
463 assert_eq!((0, 0, 0, 255), u32_to_rgba(4278190080));
464 }
465
466 #[test]
467 fn test_rgba_to_rgb() {
468 assert_eq!((255, 255, 255), rgba_to_rgb(255, 255, 255, 255));
469 assert_eq!((100, 100, 100), rgba_to_rgb(255, 255, 255, 100));
470 }
471
472 #[test]
473 fn test_rgba_background_to_rgb() {
474 assert_eq!(
475 (255, 255, 255),
476 rgba_background_to_rgb::<Rgb888>(255, 255, 255, 255, None)
477 );
478 assert_eq!(
479 (100, 100, 100),
480 rgba_background_to_rgb(255, 255, 255, 100, Some(Rgb888::BLACK))
481 );
482 }
483}