use crate::{
ChSzTy, GlyphData, MultiMonoFont, MultiMonoFontList, char_size::CharSize,
draw_target::MultiMonoFontDrawTarget,
};
use embedded_graphics::{
draw_target::DrawTarget,
geometry::{Point, Size},
pixelcolor::{BinaryColor, PixelColor},
prelude::OriginDimensions,
primitives::Rectangle,
text::{
Baseline,
renderer::{CharacterStyle, TextMetrics, TextRenderer},
},
};
#[cfg(feature = "font-rawimg")]
use embedded_graphics::{Drawable, image::Image};
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum MultiMonoLineHeight {
Max,
Min,
Specify(ChSzTy),
}
const fn get_line_height<'a>(
fonts_height: MultiMonoLineHeight,
fonts: MultiMonoFontList<'a>,
) -> ChSzTy {
let mut idx = 0;
match fonts_height {
MultiMonoLineHeight::Max => {
let mut max = ChSzTy::MIN;
while idx < fonts.len() {
let h = fonts[idx].character_size.height as ChSzTy;
idx += 1;
if h > max {
max = h;
}
}
max
}
MultiMonoLineHeight::Min => {
let mut min = ChSzTy::MAX;
while idx < fonts.len() {
let h = fonts[idx].character_size.height as ChSzTy;
idx += 1;
if h < min {
min = h;
}
}
min
}
MultiMonoLineHeight::Specify(h) => h,
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
#[non_exhaustive]
pub struct MultiMonoTextStyle<'a, C> {
pub text_color: C,
pub background_color: Option<C>,
pub fonts: MultiMonoFontList<'a>,
pub line_height: ChSzTy,
}
impl<'a, C> MultiMonoTextStyle<'a, C>
where
C: PixelColor,
{
pub const fn new(font_list: MultiMonoFontList<'a>, text_color: C) -> Self {
MultiMonoTextStyleBuilder::new(text_color)
.font(font_list, MultiMonoLineHeight::Max)
.build()
}
pub const fn new_with_line_height(
font_list: MultiMonoFontList<'a>,
line_height: MultiMonoLineHeight,
text_color: C,
) -> Self {
MultiMonoTextStyleBuilder::new(text_color)
.font(font_list, line_height)
.build()
}
fn get_font_info(&self, c: char) -> &MultiMonoFont<'a> {
for font in self.fonts {
if font.glyph_mapping.contains(c) {
return font;
}
}
self.fonts[0]
}
fn draw_string_binary<D>(
&self,
text: &str,
position: Point,
baseline: Baseline,
mut target: D,
) -> Result<Point, D::Error>
where
D: DrawTarget<Color = BinaryColor>,
{
let mut next_pos = position;
let mut draw_pos;
for c in text.chars() {
let font = self.get_font_info(c);
draw_pos = next_pos - Point::new(0, self.baseline_offset(baseline, font));
match font.glyph_data {
#[cfg(feature = "font-rawimg")]
GlyphData::ImgRaw(img_raw) => {
let glyph = font.glyph_img(c, &img_raw);
Image::new(&glyph, draw_pos).draw(&mut target)?;
}
#[cfg(feature = "font-rle")]
GlyphData::RLE(rle_raw) => {
let glyph = font.glyph_rle(c, &rle_raw);
crate::glyph_reader::render_glyph_as_box_fill(
&Rectangle::new(draw_pos, font.character_size.size()),
glyph,
&mut target,
)?;
}
}
next_pos.x += font.character_size.width as i32;
if font.character_spacing > 0 {
draw_pos.x += font.character_size.width as i32;
if self.background_color.is_some() {
target.fill_solid(
&Rectangle::new(
draw_pos,
CharSize::new(font.character_spacing, font.character_size.height)
.size(),
),
BinaryColor::Off,
)?;
}
next_pos.x += font.character_spacing as i32;
}
}
Ok(next_pos)
}
fn baseline_offset(&self, baseline: Baseline, font: &MultiMonoFont<'a>) -> i32 {
match baseline {
Baseline::Top => 0,
Baseline::Bottom => font.character_size.height.saturating_sub(1) as i32,
Baseline::Middle => (font.character_size.height.saturating_sub(1) / 2) as i32,
Baseline::Alphabetic => font.baseline as i32,
}
}
}
impl<C> TextRenderer for MultiMonoTextStyle<'_, C>
where
C: PixelColor,
{
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>,
{
self.draw_string_binary(
text,
position,
baseline,
MultiMonoFontDrawTarget::new(target, self.text_color, self.background_color),
)
}
fn draw_whitespace<D>(
&self,
width: u32,
position: Point,
baseline: Baseline,
target: &mut D,
) -> Result<Point, D::Error>
where
D: DrawTarget<Color = Self::Color>,
{
let (offet_y, height) = match baseline {
Baseline::Top => (0, self.line_height),
Baseline::Bottom => (self.line_height.saturating_sub(1) as i32, self.line_height),
Baseline::Middle => (
(self.line_height.saturating_sub(1) / 2) as i32,
self.line_height,
),
Baseline::Alphabetic => (
self.fonts[0].baseline as i32,
self.fonts[0].character_size.height,
),
};
let position = position - Point::new(0, offet_y);
if width != 0
&& let Some(background_color) = self.background_color
{
target.fill_solid(
&Rectangle::new(position, Size::new(width, height as u32)),
background_color,
)?;
}
Ok(position + Point::new(width as i32, offet_y))
}
fn measure_string(&self, text: &str, position: Point, baseline: Baseline) -> TextMetrics {
let mut bb_width = 0;
let mut bb_height = 0;
let mut baseline_max = 0;
let mut font = self.fonts[0];
for c in text.chars() {
font = self.get_font_info(c);
bb_width += (font.character_size.width + font.character_spacing) as u32;
bb_height = bb_height.max(font.character_size.height as u32);
baseline_max = baseline_max.max(self.baseline_offset(baseline, font));
}
bb_width = bb_width.saturating_sub(font.character_spacing as u32);
let bb_size = Size::new(bb_width, bb_height);
let bb_position = position - Point::new(0, baseline_max);
TextMetrics {
bounding_box: Rectangle::new(bb_position, bb_size),
next_position: position + bb_size.x_axis(),
}
}
fn line_height(&self) -> u32 {
self.line_height as u32
}
}
impl<C> CharacterStyle for MultiMonoTextStyle<'_, C>
where
C: PixelColor,
{
type Color = C;
fn set_text_color(&mut self, text_color: Option<Self::Color>) {
if let Some(color) = text_color {
self.text_color = color;
}
}
fn set_background_color(&mut self, background_color: Option<Self::Color>) {
self.background_color = background_color;
}
}
#[derive(Copy, Clone, Debug)]
#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
pub struct MultiMonoTextStyleBuilder<'a, C> {
style: MultiMonoTextStyle<'a, C>,
}
impl<'a, C> MultiMonoTextStyleBuilder<'a, C>
where
C: PixelColor,
{
pub const fn new(text_color: C) -> Self {
Self {
style: MultiMonoTextStyle {
fonts: &[&super::NULL_FONT],
background_color: None,
text_color,
line_height: 0,
},
}
}
pub const fn font<'b>(
self,
font_list: &'b [&'b MultiMonoFont<'b>],
line_height: MultiMonoLineHeight,
) -> MultiMonoTextStyleBuilder<'b, C> {
let fonts = if font_list.is_empty() {
&[&crate::NULL_FONT]
} else {
font_list
};
let line_height = get_line_height(line_height, fonts);
let style = MultiMonoTextStyle {
fonts,
background_color: self.style.background_color,
text_color: self.style.text_color,
line_height,
};
MultiMonoTextStyleBuilder { style }
}
pub const fn reset_background_color(mut self) -> Self {
self.style.background_color = None;
self
}
pub const fn text_color(mut self, text_color: C) -> Self {
self.style.text_color = text_color;
self
}
pub const fn line_height(mut self, line_height: MultiMonoLineHeight) -> Self {
self.style.line_height = get_line_height(line_height, self.style.fonts);
self
}
pub const fn background_color(mut self, background_color: C) -> Self {
self.style.background_color = Some(background_color);
self
}
pub const fn build(self) -> MultiMonoTextStyle<'a, C> {
self.style
}
}
impl<'a, C> From<&MultiMonoTextStyle<'a, C>> for MultiMonoTextStyleBuilder<'a, C>
where
C: PixelColor,
{
fn from(style: &MultiMonoTextStyle<'a, C>) -> Self {
Self { style: *style }
}
}