bitmap_font/
lib.rs

1#![cfg_attr(not(test), no_std)]
2#![allow(clippy::tabs_in_doc_comments)]
3#![warn(rust_2018_idioms, unreachable_pub)]
4#![forbid(unsafe_code)]
5
6//! This crate provides bitmap fonts for the [`embedded-graphics`] crate. Those don't only look
7//! better than the [built-in fonts](embedded_graphics::mono_font) by using the good-looking
8//! [Tamzen font](https://github.com/sunaku/tamzen-font) over a font that renders `.` like a `+`,
9//! but also allow scaling fonts by pixel-doubling them, giving you two font sizes for the flash
10//! size requirements of the smaller one.
11//!
12//! See the [`tamzen`] module for a list of all included fonts.
13//!
14//! # Usage
15//!
16//! ```rust
17//! use bitmap_font::{tamzen::FONT_8x15, BitmapFont, TextStyle};
18//! use embedded_graphics::{pixelcolor::BinaryColor, prelude::*, text::Text};
19//! # use core::convert::Infallible;
20//! # struct Display;
21//! # impl OriginDimensions for Display {
22//! #   fn size(&self) -> Size { unimplemented!() }
23//! # }
24//! # impl DrawTarget for Display {
25//! #   type Color = BinaryColor;
26//! #   type Error = Infallible;
27//! #   fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Infallible>
28//! #   where I: IntoIterator<Item = Pixel<BinaryColor>>
29//! #   { Ok(()) }
30//! # }
31//! # fn main() -> Result<(), Infallible> {
32//! # let mut display = Display;
33//!
34//! // Draw text 'Hello World!' with the top left corner being the origin
35//! let text = Text::new(
36//! 	"Hello World!",
37//! 	Point::zero(),
38//! 	TextStyle::new(&FONT_8x15, BinaryColor::On)
39//! );
40//! text.draw(&mut display)?;
41//! # Ok(())
42//! # }
43//! ```
44//!
45//!  [`embedded-graphics`]: embedded_graphics
46
47use core::num::NonZeroU8;
48use embedded_graphics::{
49	draw_target::DrawTarget,
50	geometry::{Dimensions, OriginDimensions, Point, Size},
51	image::{ImageDrawable, ImageRaw},
52	mono_font::mapping::GlyphMapping,
53	pixelcolor::BinaryColor,
54	primitives::Rectangle,
55	text::{
56		renderer::{TextMetrics, TextRenderer},
57		Baseline
58	},
59	Pixel
60};
61
62pub mod tamzen;
63
64/// Stores the font bitmap and some additional info for each font.
65#[derive(Clone, Copy)]
66pub struct BitmapFont<'a> {
67	/// The raw bitmap data for the font.
68	bitmap: ImageRaw<'a, BinaryColor>,
69
70	/// The char to glyph mapping.
71	glyph_mapping: &'a dyn GlyphMapping,
72
73	/// The size of each character in the raw bitmap data.
74	size: Size,
75
76	/// The amount of pixels to draw per each font pixel. Set to `2` for
77	/// pixel-double font.
78	pixels: NonZeroU8
79}
80
81impl<'a> BitmapFont<'a> {
82	/// Return the width of each character.
83	pub const fn width(self) -> u32 {
84		self.size.width * self.pixels.get() as u32
85	}
86
87	/// Return the height of each character.
88	pub const fn height(self) -> u32 {
89		self.size.height * self.pixels.get() as u32
90	}
91
92	/// Draw a glyph to the `target` with `color` at position `pos`.
93	pub fn draw_glyph<D>(&self, idx: u32, target: &mut D, color: BinaryColor, pos: Point) -> Result<(), D::Error>
94	where
95		D: DrawTarget<Color = BinaryColor>
96	{
97		let char_per_row = self.bitmap.size().width / self.size.width;
98		let row = idx / char_per_row;
99
100		// index in the raw bitmap
101		let char_x = (idx - (row * char_per_row)) * self.size.width;
102		let char_y = row * self.size.height;
103		let area = Rectangle::new(Point::new(char_x as _, char_y as _), self.size);
104
105		// draw the glyph, suitably pixel-doubled
106		let mut pixel_doubling_target = PixelDoublingDrawTarget {
107			target,
108			color,
109			offset: pos,
110			pixels: self.pixels
111		};
112		self.bitmap.draw_sub_image(&mut pixel_doubling_target, &area)?;
113
114		Ok(())
115	}
116
117	/// Returns a pixel-double version of this font.
118	pub const fn pixel_double(mut self) -> Self {
119		// unwrap: if x is not zero, x*2 is also not zero
120		self.pixels = match NonZeroU8::new(self.pixels.get() * 2) {
121			Some(px) => px,
122			None => unreachable!()
123		};
124		self
125	}
126}
127
128/// The equivalent of [`MonoTextStyle`][embedded_graphics::mono_font::MonoTextStyle] for [`BitmapFont`].
129#[derive(Clone, Copy)]
130#[non_exhaustive]
131pub struct TextStyle<'a> {
132	pub font: &'a BitmapFont<'a>,
133	pub color: BinaryColor
134}
135
136impl<'a> TextStyle<'a> {
137	pub fn new(font: &'a BitmapFont<'a>, color: BinaryColor) -> Self {
138		Self { font, color }
139	}
140}
141
142impl<'a> TextRenderer for TextStyle<'a> {
143	type Color = BinaryColor;
144
145	fn draw_string<D>(&self, text: &str, mut pos: Point, _: Baseline, target: &mut D) -> Result<Point, D::Error>
146	where
147		D: DrawTarget<Color = Self::Color>
148	{
149		for c in text.chars() {
150			let glyph_idx = self.font.glyph_mapping.index(c) as u32;
151			self.font.draw_glyph(glyph_idx, target, self.color, pos)?;
152			pos += Size::new(self.font.width(), 0);
153		}
154		Ok(pos)
155	}
156
157	fn draw_whitespace<D>(&self, width: u32, pos: Point, _: Baseline, _: &mut D) -> Result<Point, D::Error>
158	where
159		D: DrawTarget<Color = Self::Color>
160	{
161		// we don't draw background nor text decorations
162		Ok(pos + Size::new(width * self.font.width(), 0))
163	}
164
165	fn measure_string(&self, text: &str, pos: Point, _: Baseline) -> TextMetrics {
166		let size = Size::new(self.font.width() * text.len() as u32, self.font.height());
167		TextMetrics {
168			bounding_box: Rectangle::new(pos, size),
169			next_position: pos + Size::new(size.width, 0)
170		}
171	}
172
173	fn line_height(&self) -> u32 {
174		self.font.height()
175	}
176}
177
178/// A pixel-doubling draw target. This works by intercepting all drawing operations, and doubling
179/// all pixels from the point of the origin. **This means you need to carefully select your
180/// origin!** All drawing operations, after being pixel-doubled (i.e. multiplied by the amount
181/// of pixels specified), will be offseted to map your custom origin to the `target`'s origin.
182///
183/// Also, this target draws `color` for all pixels that are on, and nothing for all pixels
184/// that are off.
185struct PixelDoublingDrawTarget<'a, D: DrawTarget<Color = BinaryColor>> {
186	target: &'a mut D,
187	color: BinaryColor,
188	offset: Point,
189	pixels: NonZeroU8
190}
191
192impl<'a, D> Dimensions for PixelDoublingDrawTarget<'a, D>
193where
194	D: DrawTarget<Color = BinaryColor>
195{
196	fn bounding_box(&self) -> Rectangle {
197		let mut bb = self.target.bounding_box();
198		bb.top_left -= self.offset;
199		bb.top_left /= self.pixels.get().into();
200		bb.size /= self.pixels.get().into();
201		bb
202	}
203}
204
205impl<'a, D> DrawTarget for PixelDoublingDrawTarget<'a, D>
206where
207	D: DrawTarget<Color = BinaryColor>
208{
209	type Color = BinaryColor;
210	type Error = D::Error;
211
212	fn draw_iter<I>(&mut self, pixel_iter: I) -> Result<(), Self::Error>
213	where
214		I: IntoIterator<Item = Pixel<BinaryColor>>
215	{
216		let color = self.color;
217		let offset = self.offset;
218		let pixels = self.pixels.get();
219		self.target.draw_iter(
220			pixel_iter
221				.into_iter()
222				.filter(|pixel| pixel.1 == BinaryColor::On)
223				.flat_map(|pixel| PixelDoublingIterator::new(pixel, pixels).map(|pixel| Pixel(pixel.0 + offset, color)))
224		)
225	}
226}
227
228struct PixelDoublingIterator {
229	color: BinaryColor,
230	pos: Point,
231	// pixels traveled in x direction
232	x: u8,
233	// remaining after this one
234	remaining_x: u8,
235	// remaining including this one
236	remaining_y: u8
237}
238
239impl PixelDoublingIterator {
240	fn new(pixel: Pixel<BinaryColor>, pixels: u8) -> Self {
241		Self {
242			color: pixel.1,
243			pos: pixel.0 * pixels as _,
244			x: 0,
245			remaining_x: pixels - 1,
246			remaining_y: pixels
247		}
248	}
249}
250
251impl Iterator for PixelDoublingIterator {
252	type Item = Pixel<BinaryColor>;
253
254	fn next(&mut self) -> Option<Pixel<BinaryColor>> {
255		if self.remaining_y == 0 {
256			return None;
257		}
258
259		let pixel = Pixel(self.pos, self.color);
260		if self.remaining_x > 0 {
261			self.remaining_x -= 1;
262			self.x += 1;
263			self.pos.x += 1;
264		} else {
265			self.pos.x -= self.x as i32;
266			self.remaining_x = self.x;
267			self.x = 0;
268
269			self.remaining_y -= 1;
270			self.pos.y += 1;
271		}
272		Some(pixel)
273	}
274}
275
276#[cfg(test)]
277mod tests {
278	use super::*;
279
280	fn px(x: i32, y: i32) -> Pixel<BinaryColor> {
281		Pixel(Point::new(x, y), BinaryColor::On)
282	}
283
284	#[test]
285	fn pixel_doubling_iterator() {
286		assert_eq!(PixelDoublingIterator::new(px(0, 0), 1).collect::<Vec<_>>(), vec![px(0, 0)]);
287		assert_eq!(PixelDoublingIterator::new(px(1, 2), 1).collect::<Vec<_>>(), vec![px(1, 2)]);
288
289		assert_eq!(PixelDoublingIterator::new(px(0, 0), 2).collect::<Vec<_>>(), vec![
290			px(0, 0),
291			px(1, 0),
292			px(0, 1),
293			px(1, 1)
294		]);
295		assert_eq!(PixelDoublingIterator::new(px(1, 2), 2).collect::<Vec<_>>(), vec![
296			px(2, 4),
297			px(3, 4),
298			px(2, 5),
299			px(3, 5)
300		]);
301
302		assert_eq!(PixelDoublingIterator::new(px(0, 0), 3).collect::<Vec<_>>(), vec![
303			px(0, 0),
304			px(1, 0),
305			px(2, 0),
306			px(0, 1),
307			px(1, 1),
308			px(2, 1),
309			px(0, 2),
310			px(1, 2),
311			px(2, 2)
312		]);
313		assert_eq!(PixelDoublingIterator::new(px(1, 2), 3).collect::<Vec<_>>(), vec![
314			px(3, 6),
315			px(4, 6),
316			px(5, 6),
317			px(3, 7),
318			px(4, 7),
319			px(5, 7),
320			px(3, 8),
321			px(4, 8),
322			px(5, 8)
323		]);
324	}
325}