#![cfg_attr(not(feature = "std"), no_std)]
#[cfg(not(feature = "std"))]
extern crate alloc;
#[cfg(not(feature = "std"))]
use num_traits::float::FloatCore;
#[cfg(feature = "std")]
use std as stdlib;
#[cfg(not(feature = "std"))]
mod stdlib {
pub use ::alloc::vec;
pub use core::*;
}
use stdlib::{f32, vec::Vec};
use embedded_graphics::{
draw_target::DrawTarget,
pixelcolor::Rgb888,
prelude::*,
primitives::Rectangle,
text::{
renderer::{CharacterStyle, TextMetrics, TextRenderer},
Baseline, DecorationColor,
},
};
use rusttype::Font;
#[derive(Debug, Clone)]
pub enum AntiAliasing<C> {
BackgroundColor,
SolidColor(C),
None,
}
#[derive(Debug, Clone)]
pub struct FontTextStyle<C> {
pub text_color: Option<C>,
pub background_color: Option<C>,
pub anti_aliasing: AntiAliasing<C>,
pub underline_color: DecorationColor<C>,
pub strikethrough_color: DecorationColor<C>,
pub font_size: u32,
font: Font<'static>,
}
impl<C: PixelColor> FontTextStyle<C> {
pub fn new(font: Font<'static>, text_color: C, font_size: u32) -> Self {
FontTextStyleBuilder::new(font)
.text_color(text_color)
.font_size(font_size)
.build()
}
fn resolve_decoration_color(&self, color: DecorationColor<C>) -> Option<C> {
match color {
DecorationColor::None => None,
DecorationColor::TextColor => self.text_color,
DecorationColor::Custom(c) => Some(c),
}
}
fn draw_background<D>(
&self,
width: u32,
position: Point,
target: &mut D,
) -> Result<(), D::Error>
where
D: DrawTarget<Color = C>,
{
if width == 0 {
return Ok(());
}
if let Some(background_color) = self.background_color {
target.fill_solid(
&Rectangle::new(position, Size::new(width, self.font_size)),
background_color,
)?;
}
Ok(())
}
fn draw_strikethrough<D>(
&self,
width: u32,
position: Point,
target: &mut D,
) -> Result<(), D::Error>
where
D: DrawTarget<Color = C>,
{
if let Some(strikethrough_color) = self.resolve_decoration_color(self.strikethrough_color) {
let top_left = position + Point::new(0, self.font_size as i32 / 2);
let size = Size::new(width, self.font_size / 30 + 1);
target.fill_solid(&Rectangle::new(top_left, size), strikethrough_color)?;
}
Ok(())
}
fn draw_underline<D>(&self, width: u32, position: Point, target: &mut D) -> Result<(), D::Error>
where
D: DrawTarget<Color = C>,
{
if let Some(underline_color) = self.resolve_decoration_color(self.underline_color) {
let top_left = position + Point::new(0, self.font_size as i32);
let size = Size::new(width, self.font_size / 30 + 1);
target.fill_solid(&Rectangle::new(top_left, size), underline_color)?;
}
Ok(())
}
}
impl<C: PixelColor> CharacterStyle for FontTextStyle<C> {
type Color = C;
fn set_text_color(&mut self, text_color: Option<Self::Color>) {
self.text_color = text_color;
}
fn set_background_color(&mut self, background_color: Option<Self::Color>) {
self.background_color = background_color;
if background_color.is_some() {
self.anti_aliasing = AntiAliasing::BackgroundColor;
}
}
fn set_underline_color(&mut self, underline_color: DecorationColor<Self::Color>) {
self.underline_color = underline_color;
}
fn set_strikethrough_color(&mut self, strikethrough_color: DecorationColor<Self::Color>) {
self.strikethrough_color = strikethrough_color;
}
}
impl<C> TextRenderer for FontTextStyle<C>
where
C: PixelColor + Into<Rgb888> + From<Rgb888> + stdlib::fmt::Debug,
{
type Color = C;
fn draw_string<D>(
&self,
text: &str,
position: Point,
_baseline: Baseline,
target: &mut D,
) -> Result<Point, D::Error>
where
D: DrawTarget<Color = Self::Color>,
{
let scale = rusttype::Scale::uniform(self.font_size as f32);
let v_metrics = self.font.v_metrics(scale);
let offset = rusttype::point(0.0, v_metrics.ascent);
let glyphs: Vec<rusttype::PositionedGlyph> =
self.font.layout(text, scale, offset).collect();
let width = glyphs
.iter()
.rev()
.filter_map(|g| {
g.pixel_bounding_box()
.map(|b| b.min.x as f32 + g.unpositioned().h_metrics().advance_width)
})
.next()
.unwrap_or(0.0)
.ceil() as i32;
let height = self.font_size as i32;
let mut pixels = Vec::new();
if let Some(text_color) = self.text_color {
for g in glyphs.iter() {
if let Some(bb) = g.pixel_bounding_box() {
g.draw(|off_x, off_y, v| {
let off_x = off_x as i32 + bb.min.x;
let off_y = off_y as i32 + bb.min.y;
if off_x >= 0 && off_x < width as i32 && off_y >= 0 && off_y < height as i32
{
let text_a = (v * 255.0) as u32;
let bg_color = match self.anti_aliasing {
AntiAliasing::BackgroundColor => self.background_color,
AntiAliasing::SolidColor(c) => Some(c),
AntiAliasing::None => None,
};
match bg_color {
None => if text_a > 127 {
pixels.push(Pixel(
Point::new(position.x + off_x, position.y + off_y),
text_color
));
}
Some(color) => {
let a = text_a as u16;
let fg = text_color.into();
let bg = color.into();
let new_r = (a * fg.r() as u16 + (255-a) * bg.r() as u16) / 255;
let new_g = (a * fg.g() as u16 + (255-a) * bg.g() as u16) / 255;
let new_b = (a * fg.b() as u16 + (255-a) * bg.b() as u16) / 255;
pixels.push(Pixel(
Point::new(position.x + off_x, position.y + off_y),
Rgb888::new(new_r as u8, new_g as u8, new_b as u8).into(),
));
}
}
}
});
}
}
}
self.draw_background(width as u32, position, target)?;
target.draw_iter(pixels)?;
self.draw_strikethrough(width as u32, position, target)?;
self.draw_underline(width as u32, position, target)?;
Ok(position + Point::new(width, 0))
}
fn draw_whitespace<D>(
&self,
width: u32,
position: Point,
_baseline: Baseline,
target: &mut D,
) -> Result<Point, D::Error>
where
D: DrawTarget<Color = Self::Color>,
{
self.draw_background(width, position, target)?;
self.draw_strikethrough(width, position, target)?;
self.draw_underline(width, position, target)?;
Ok(position + Size::new(width, 0))
}
fn measure_string(&self, text: &str, position: Point, _baseline: Baseline) -> TextMetrics {
let scale = rusttype::Scale::uniform(self.font_size as f32);
let v_metrics = self.font.v_metrics(scale);
let offset = rusttype::point(0.0, v_metrics.ascent);
let glyphs: Vec<rusttype::PositionedGlyph> =
self.font.layout(text, scale, offset).collect();
let width = glyphs
.iter()
.rev()
.map(|g| g.position().x as f32 + g.unpositioned().h_metrics().advance_width)
.next()
.unwrap_or(0.0)
.ceil() as f64;
let size = Size::new(width as u32, self.font_size);
TextMetrics {
bounding_box: Rectangle::new(position, size),
next_position: position + size.x_axis(),
}
}
fn line_height(&self) -> u32 {
self.font_size
}
}
pub struct FontTextStyleBuilder<C: PixelColor> {
style: FontTextStyle<C>,
}
impl<C: PixelColor> FontTextStyleBuilder<C> {
pub fn new(font: Font<'static>) -> Self {
Self {
style: FontTextStyle {
font,
background_color: None,
anti_aliasing: AntiAliasing::None,
font_size: 12,
text_color: None,
underline_color: DecorationColor::None,
strikethrough_color: DecorationColor::None,
},
}
}
pub fn font_size(mut self, font_size: u32) -> Self {
self.style.font_size = font_size;
self
}
pub fn underline(mut self) -> Self {
self.style.underline_color = DecorationColor::TextColor;
self
}
pub fn strikethrough(mut self) -> Self {
self.style.strikethrough_color = DecorationColor::TextColor;
self
}
pub fn text_color(mut self, text_color: C) -> Self {
self.style.text_color = Some(text_color);
self.style.anti_aliasing = AntiAliasing::BackgroundColor;
self
}
pub fn background_color(mut self, background_color: C) -> Self {
self.style.background_color = Some(background_color);
self
}
pub fn anti_aliasing_color(mut self, background_color: C) -> Self {
self.style.anti_aliasing = AntiAliasing::SolidColor(background_color);
self
}
pub fn underline_with_color(mut self, underline_color: C) -> Self {
self.style.underline_color = DecorationColor::Custom(underline_color);
self
}
pub fn strikethrough_with_color(mut self, strikethrough_color: C) -> Self {
self.style.strikethrough_color = DecorationColor::Custom(strikethrough_color);
self
}
pub fn build(self) -> FontTextStyle<C> {
self.style
}
}
#[cfg(test)]
mod tests {
use super::*;
use embedded_graphics::pixelcolor::Rgb888;
}