1#![cfg_attr(not(feature = "std"), no_std)]
37
38#[cfg(not(feature = "std"))]
39extern crate alloc;
40
41#[cfg(not(feature = "std"))]
42use num_traits::float::FloatCore;
43
44#[cfg(feature = "std")]
45use std as stdlib;
46
47#[cfg(not(feature = "std"))]
48mod stdlib {
49 pub use ::alloc::vec;
50 pub use core::*;
51}
52
53use stdlib::{f32, vec::Vec};
54
55use embedded_graphics::{
56 draw_target::DrawTarget,
57 pixelcolor::Rgb888,
58 prelude::*,
59 primitives::Rectangle,
60 text::{
61 renderer::{CharacterStyle, TextMetrics, TextRenderer},
62 Baseline, DecorationColor,
63 },
64};
65
66use rusttype::Font;
67
68#[derive(Debug, Clone)]
71pub enum AntiAliasing<C> {
72 BackgroundColor,
76 SolidColor(C),
78 None,
80}
81
82#[derive(Debug, Clone)]
87pub struct FontTextStyle<C> {
88 pub text_color: Option<C>,
90
91 pub background_color: Option<C>,
93
94 pub anti_aliasing: AntiAliasing<C>,
96
97 pub underline_color: DecorationColor<C>,
99
100 pub strikethrough_color: DecorationColor<C>,
102
103 pub font_size: u32,
105
106 font: Font<'static>,
108}
109
110impl<C: PixelColor> FontTextStyle<C> {
111 pub fn new(font: Font<'static>, text_color: C, font_size: u32) -> Self {
113 FontTextStyleBuilder::new(font)
114 .text_color(text_color)
115 .font_size(font_size)
116 .build()
117 }
118
119 fn resolve_decoration_color(&self, color: DecorationColor<C>) -> Option<C> {
121 match color {
122 DecorationColor::None => None,
123 DecorationColor::TextColor => self.text_color,
124 DecorationColor::Custom(c) => Some(c),
125 }
126 }
127
128 fn draw_background<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 width == 0 {
138 return Ok(());
139 }
140
141 if let Some(background_color) = self.background_color {
142 target.fill_solid(
143 &Rectangle::new(position, Size::new(width, self.font_size)),
144 background_color,
145 )?;
146 }
147
148 Ok(())
149 }
150
151 fn draw_strikethrough<D>(
152 &self,
153 width: u32,
154 position: Point,
155 target: &mut D,
156 ) -> Result<(), D::Error>
157 where
158 D: DrawTarget<Color = C>,
159 {
160 if let Some(strikethrough_color) = self.resolve_decoration_color(self.strikethrough_color) {
161 let top_left = position + Point::new(0, self.font_size as i32 / 2);
162 let size = Size::new(width, self.font_size / 30 + 1);
164
165 target.fill_solid(&Rectangle::new(top_left, size), strikethrough_color)?;
166 }
167
168 Ok(())
169 }
170
171 fn draw_underline<D>(&self, width: u32, position: Point, target: &mut D) -> Result<(), D::Error>
172 where
173 D: DrawTarget<Color = C>,
174 {
175 if let Some(underline_color) = self.resolve_decoration_color(self.underline_color) {
176 let top_left = position + Point::new(0, self.font_size as i32);
177 let size = Size::new(width, self.font_size / 30 + 1);
179
180 target.fill_solid(&Rectangle::new(top_left, size), underline_color)?;
181 }
182
183 Ok(())
184 }
185}
186
187impl<C: PixelColor> CharacterStyle for FontTextStyle<C> {
188 type Color = C;
189
190 fn set_text_color(&mut self, text_color: Option<Self::Color>) {
191 self.text_color = text_color;
192 }
193
194 fn set_background_color(&mut self, background_color: Option<Self::Color>) {
195 self.background_color = background_color;
196 if background_color.is_some() {
197 self.anti_aliasing = AntiAliasing::BackgroundColor;
199 }
200 }
201
202 fn set_underline_color(&mut self, underline_color: DecorationColor<Self::Color>) {
203 self.underline_color = underline_color;
204 }
205
206 fn set_strikethrough_color(&mut self, strikethrough_color: DecorationColor<Self::Color>) {
207 self.strikethrough_color = strikethrough_color;
208 }
209}
210
211impl<C> TextRenderer for FontTextStyle<C>
212where
213 C: PixelColor + Into<Rgb888> + From<Rgb888> + stdlib::fmt::Debug,
214{
215 type Color = C;
216
217 fn draw_string<D>(
218 &self,
219 text: &str,
220 position: Point,
221 _baseline: Baseline,
222 target: &mut D,
223 ) -> Result<Point, D::Error>
224 where
225 D: DrawTarget<Color = Self::Color>,
226 {
227 let scale = rusttype::Scale::uniform(self.font_size as f32);
228
229 let v_metrics = self.font.v_metrics(scale);
230 let offset = rusttype::point(0.0, v_metrics.ascent);
231
232 let glyphs: Vec<rusttype::PositionedGlyph> =
233 self.font.layout(text, scale, offset).collect();
234
235 let width = glyphs
236 .iter()
237 .rev()
238 .filter_map(|g| {
239 g.pixel_bounding_box()
240 .map(|b| b.min.x as f32 + g.unpositioned().h_metrics().advance_width)
241 })
242 .next()
243 .unwrap_or(0.0)
244 .ceil() as i32;
245
246 let height = self.font_size as i32;
247
248 let mut pixels = Vec::new();
249
250 if let Some(text_color) = self.text_color {
251 for g in glyphs.iter() {
252 if let Some(bb) = g.pixel_bounding_box() {
253 g.draw(|off_x, off_y, v| {
254 let off_x = off_x as i32 + bb.min.x;
255 let off_y = off_y as i32 + bb.min.y;
256 if off_x >= 0 && off_x < width as i32 && off_y >= 0 && off_y < height as i32
258 {
259 let text_a = (v * 255.0) as u32;
260
261 let bg_color = match self.anti_aliasing {
262 AntiAliasing::BackgroundColor => self.background_color,
263 AntiAliasing::SolidColor(c) => Some(c),
264 AntiAliasing::None => None,
265 };
266 match bg_color {
267 None => if text_a > 127 {
268 pixels.push(Pixel(
269 Point::new(position.x + off_x, position.y + off_y),
270 text_color
271 ));
272 }
273 Some(color) => {
274 let a = text_a as u16;
275 let fg = text_color.into();
276 let bg = color.into();
277 let new_r = (a * fg.r() as u16 + (255-a) * bg.r() as u16) / 255;
279 let new_g = (a * fg.g() as u16 + (255-a) * bg.g() as u16) / 255;
280 let new_b = (a * fg.b() as u16 + (255-a) * bg.b() as u16) / 255;
281
282 pixels.push(Pixel(
283 Point::new(position.x + off_x, position.y + off_y),
284 Rgb888::new(new_r as u8, new_g as u8, new_b as u8).into(),
285 ));
286 }
287 }
288 }
289 });
290 }
291 }
292 }
293
294 self.draw_background(width as u32, position, target)?;
295 target.draw_iter(pixels)?;
296 self.draw_strikethrough(width as u32, position, target)?;
297 self.draw_underline(width as u32, position, target)?;
298
299 Ok(position + Point::new(width, 0))
300 }
301
302 fn draw_whitespace<D>(
303 &self,
304 width: u32,
305 position: Point,
306 _baseline: Baseline,
307 target: &mut D,
308 ) -> Result<Point, D::Error>
309 where
310 D: DrawTarget<Color = Self::Color>,
311 {
312 self.draw_background(width, position, target)?;
313 self.draw_strikethrough(width, position, target)?;
314 self.draw_underline(width, position, target)?;
315
316 Ok(position + Size::new(width, 0))
317 }
318
319 fn measure_string(&self, text: &str, position: Point, _baseline: Baseline) -> TextMetrics {
320 let scale = rusttype::Scale::uniform(self.font_size as f32);
321 let v_metrics = self.font.v_metrics(scale);
322 let offset = rusttype::point(0.0, v_metrics.ascent);
323
324 let glyphs: Vec<rusttype::PositionedGlyph> =
325 self.font.layout(text, scale, offset).collect();
326
327 let width = glyphs
328 .iter()
329 .rev()
330 .map(|g| g.position().x as f32 + g.unpositioned().h_metrics().advance_width)
331 .next()
332 .unwrap_or(0.0)
333 .ceil() as f64;
334
335 let size = Size::new(width as u32, self.font_size);
336
337 TextMetrics {
338 bounding_box: Rectangle::new(position, size),
339 next_position: position + size.x_axis(),
340 }
341 }
342
343 fn line_height(&self) -> u32 {
344 self.font_size
345 }
346}
347
348pub struct FontTextStyleBuilder<C: PixelColor> {
352 style: FontTextStyle<C>,
353}
354
355impl<C: PixelColor> FontTextStyleBuilder<C> {
356 pub fn new(font: Font<'static>) -> Self {
358 Self {
359 style: FontTextStyle {
360 font,
361 background_color: None,
362 anti_aliasing: AntiAliasing::None,
363 font_size: 12,
364 text_color: None,
365 underline_color: DecorationColor::None,
366 strikethrough_color: DecorationColor::None,
367 },
368 }
369 }
370
371 pub fn font_size(mut self, font_size: u32) -> Self {
373 self.style.font_size = font_size;
374 self
375 }
376
377 pub fn underline(mut self) -> Self {
379 self.style.underline_color = DecorationColor::TextColor;
380 self
381 }
382
383 pub fn strikethrough(mut self) -> Self {
385 self.style.strikethrough_color = DecorationColor::TextColor;
386 self
387 }
388
389 pub fn text_color(mut self, text_color: C) -> Self {
391 self.style.text_color = Some(text_color);
392 self.style.anti_aliasing = AntiAliasing::BackgroundColor;
393 self
394 }
395
396 pub fn background_color(mut self, background_color: C) -> Self {
398 self.style.background_color = Some(background_color);
399 self
400 }
401
402 pub fn anti_aliasing_color(mut self, background_color: C) -> Self {
404 self.style.anti_aliasing = AntiAliasing::SolidColor(background_color);
405 self
406 }
407
408 pub fn underline_with_color(mut self, underline_color: C) -> Self {
410 self.style.underline_color = DecorationColor::Custom(underline_color);
411 self
412 }
413
414 pub fn strikethrough_with_color(mut self, strikethrough_color: C) -> Self {
416 self.style.strikethrough_color = DecorationColor::Custom(strikethrough_color);
417
418 self
419 }
420
421 pub fn build(self) -> FontTextStyle<C> {
423 self.style
424 }
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430 use embedded_graphics::pixelcolor::Rgb888;
431
432 }