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