//! Monospaced bitmap fonts.
//!
//! This module contains support for drawing monospaced bitmap fonts and provides
//! several [built-in fonts].
//!
//! Additional custom fonts can be added by the application or other crates. This
//! is demonstrated in the `text-custom-font` example in the [examples repository].
//!
//! # Examples
//!
//! The [`text` module] contains examples how these fonts can be used in an application.
//!
//! # Built-in fonts
//!
//! Each built-in font is provided in different glyph subsets. The ASCII variant is the smallest
//! subset which saves memory in embedded applications, but only covers all characters of the English
//! language. The ISO 8859 subsets support a wide range of languages, see
//! [Wikipedia](https://en.wikipedia.org/wiki/ISO/IEC_8859#The_parts_of_ISO/IEC_8859) for a list of
//! languages.
//!
//! The table below shows the ASCII variant of the built-in fonts. See the [subset modules](#modules) for
//! an overview of the complete character set included in the other variants.
//!
// WARNING: The table between START-FONT-TABLE and END-FONT-TABLE is generated.
// Use `just convert-fonts` to update the table.
//START-FONT-TABLE-ASCII
//! | Type | Screenshot | | Type | Screenshot |
//! |------|------------|-|------|------------|
//! | `FONT_4X6` |  | | `FONT_7X13_ITALIC` |  |
//! | `FONT_5X7` |  | | `FONT_7X14` |  |
//! | `FONT_5X8` |  | | `FONT_7X14_BOLD` |  |
//! | `FONT_6X9` |  | | `FONT_8X13` |  |
//! | `FONT_6X10` |  | | `FONT_8X13_BOLD` |  |
//! | `FONT_6X12` |  | | `FONT_8X13_ITALIC` |  |
//! | `FONT_6X13` |  | | `FONT_9X15` |  |
//! | `FONT_6X13_BOLD` |  | | `FONT_9X15_BOLD` |  |
//! | `FONT_6X13_ITALIC` |  | | `FONT_9X18` |  |
//! | `FONT_7X13` |  | | `FONT_9X18_BOLD` |  |
//! | `FONT_7X13_BOLD` |  | | `FONT_10X20` |  |
//END-FONT-TABLE
//!
//! [built-in fonts]: #built-in-fonts
//! [`text` module]: ../text/index.html#examples
//! [`MonoTextStyle`]: struct.MonoTextStyle.html
//! [examples repository]: https://github.com/embedded-graphics/examples
mod draw_target;
mod generated;
pub mod mapping;
mod mono_text_style;
use core::fmt;
pub use generated::*;
pub use mono_text_style::{MonoTextStyle, MonoTextStyleBuilder};
use crate::{
geometry::{OriginDimensions, Point, Size},
image::{ImageRaw, SubImage},
mono_font::mapping::GlyphMapping,
pixelcolor::BinaryColor,
primitives::Rectangle,
};
/// Monospaced bitmap font.
///
/// See the [module documentation] for more information about using fonts.
///
/// [module documentation]: index.html
#[derive(Clone, Copy)]
pub struct MonoFont<'a> {
/// Raw image data containing the font.
pub image: ImageRaw<'a, BinaryColor>,
/// Size of a single character in pixel.
pub character_size: Size,
/// Spacing between characters.
///
/// The spacing defines how many empty pixels are added horizontally between adjacent characters
/// on a single line of text.
pub character_spacing: u32,
/// The baseline.
///
/// Offset from the top of the glyph bounding box to the baseline.
pub baseline: u32,
/// Strikethrough decoration dimensions.
pub strikethrough: DecorationDimensions,
/// Underline decoration dimensions.
pub underline: DecorationDimensions,
/// Glyph mapping.
pub glyph_mapping: &'a dyn GlyphMapping,
}
impl MonoFont<'_> {
/// Returns a subimage for a glyph.
pub(crate) fn glyph(&self, c: char) -> SubImage<'_, ImageRaw<BinaryColor>> {
let glyphs_per_row = self.image.size().width / self.character_size.width;
// Char _code_ offset from first char, most often a space
// E.g. first char = ' ' (32), target char = '!' (33), offset = 33 - 32 = 1
let glyph_index = self.glyph_mapping.index(c) as u32;
let row = glyph_index / glyphs_per_row;
// Top left corner of character, in pixels
let char_x = (glyph_index - (row * glyphs_per_row)) * self.character_size.width;
let char_y = row * self.character_size.height;
SubImage::new_unchecked(
&self.image,
Rectangle::new(
Point::new(char_x as i32, char_y as i32),
self.character_size,
),
)
}
}
impl PartialEq for MonoFont<'_> {
#[allow(trivial_casts)]
fn eq(&self, other: &Self) -> bool {
self.image == other.image
&& self.character_size == other.character_size
&& self.character_spacing == other.character_spacing
&& self.baseline == other.baseline
&& self.strikethrough == other.strikethrough
&& self.underline == other.underline
&& core::ptr::eq(
self.glyph_mapping as *const dyn GlyphMapping as *const u8,
other.glyph_mapping as *const dyn GlyphMapping as *const u8,
)
}
}
impl fmt::Debug for MonoFont<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("MonoFont")
.field("image", &self.image)
.field("character_size", &self.character_size)
.field("character_spacing", &self.character_spacing)
.field("baseline", &self.baseline)
.field("strikethrough", &self.strikethrough)
.field("underline", &self.underline)
.field("glyph_mapping", &"?")
// MSRV 1.53.0: use `finish_non_exhaustive`
.finish()
}
}
/// Decoration dimensions.
///
/// `DecorationDimensions` is used to specify the position and height of underline and strikethrough
/// decorations in [`MonoFont`]s.
///
/// [`MonoFont`]: struct.MonoFont.html
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct DecorationDimensions {
/// Offset from the top of the character to the top of the decoration.
pub offset: u32,
/// Height of the decoration.
pub height: u32,
}
impl DecorationDimensions {
/// Creates new decoration dimensions.
pub const fn new(offset: u32, height: u32) -> Self {
Self { offset, height }
}
/// Creates a new default strikethrough decoration for the given glyph height.
pub const fn default_strikethrough(glyph_height: u32) -> Self {
// MSRV 1.47: Use `saturating_sub` to remove possible panic.
Self {
offset: (glyph_height - 1) / 2,
height: 1,
}
}
/// Creates a new default underline decoration for the given glyph height.
pub const fn default_underline(glyph_height: u32) -> Self {
Self {
offset: glyph_height + 1,
height: 1,
}
}
fn to_rectangle(&self, position: Point, width: u32) -> Rectangle {
let top_left = position + Size::new(0, self.offset);
let size = Size::new(width, self.height);
Rectangle::new(top_left, size)
}
}
const NULL_FONT: MonoFont = MonoFont {
image: ImageRaw::new_binary(&[], 1),
character_size: Size::zero(),
character_spacing: 0,
baseline: 0,
strikethrough: DecorationDimensions::new(0, 0),
underline: DecorationDimensions::new(0, 0),
glyph_mapping: &mapping::ASCII,
};
#[cfg(test)]
pub(crate) mod tests {
use super::*;
use crate::{
geometry::Point,
mock_display::MockDisplay,
mono_font::MonoTextStyleBuilder,
pixelcolor::BinaryColor,
text::{Baseline, Text},
Drawable,
};
/// Draws a text using the given font and checks it against the expected pattern.
// MSRV: Add `track_caller` attribute for rust version >= 1.46.0
// #[track_caller]
pub fn assert_text_from_pattern(text: &str, font: &MonoFont, pattern: &[&str]) {
let style = MonoTextStyleBuilder::new()
.font(font)
.text_color(BinaryColor::On)
.build();
let mut display = MockDisplay::new();
Text::with_baseline(text, Point::zero(), style, Baseline::Top)
.draw(&mut display)
.unwrap();
display.assert_pattern(pattern);
}
/// Test if the baseline constant is set correctly.
///
/// This test assumes that the character `A` is on the baseline.
pub fn test_baseline(font: &MonoFont) {
let style = MonoTextStyleBuilder::new()
.font(font)
.text_color(BinaryColor::On)
.build();
// Draw 'A' character to determine it's baseline
let mut display = MockDisplay::new();
Text::with_baseline("A", Point::zero(), style, Baseline::Top)
.draw(&mut display)
.unwrap();
let baseline = display.affected_area().bottom_right().unwrap().y as u32;
assert_eq!(font.baseline, baseline);
}
#[test]
fn baseline() {
test_baseline(&ascii::FONT_4X6);
test_baseline(&ascii::FONT_5X7);
test_baseline(&ascii::FONT_5X8);
test_baseline(&ascii::FONT_6X10);
test_baseline(&ascii::FONT_6X12);
test_baseline(&ascii::FONT_6X13_BOLD);
test_baseline(&ascii::FONT_6X13);
test_baseline(&ascii::FONT_6X13_ITALIC);
test_baseline(&ascii::FONT_6X9);
test_baseline(&ascii::FONT_7X13_BOLD);
test_baseline(&ascii::FONT_7X13);
test_baseline(&ascii::FONT_7X13_ITALIC);
test_baseline(&ascii::FONT_7X14_BOLD);
test_baseline(&ascii::FONT_7X14);
test_baseline(&ascii::FONT_8X13_BOLD);
test_baseline(&ascii::FONT_8X13);
test_baseline(&ascii::FONT_8X13_ITALIC);
test_baseline(&ascii::FONT_9X15_BOLD);
test_baseline(&ascii::FONT_9X15);
test_baseline(&ascii::FONT_9X18_BOLD);
test_baseline(&ascii::FONT_9X18);
test_baseline(&ascii::FONT_10X20);
}
}