embedded_graphics_profont/font.rs
1use embedded_graphics::{
2 draw_target::DrawTarget,
3 geometry::Point,
4 pixelcolor::PixelColor,
5 Drawable,
6};
7
8use crate::renderer;
9
10/// Text anchor point for positioning.
11///
12/// Defines which point of the text bounding box should be placed at the given position.
13/// This allows precise control over text alignment without calculating dimensions manually.
14///
15/// # Examples
16///
17/// - `TopLeft`: Position is at top-left corner (default)
18/// - `MiddleCenter`: Position is at the center of the text
19/// - `BottomRight`: Position is at bottom-right corner
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum Anchor {
22 /// Position text so the given point is at its top-left corner.
23 TopLeft,
24 /// Position text so the given point is at its top-center.
25 TopCenter,
26 /// Position text so the given point is at its top-right corner.
27 TopRight,
28 /// Position text so the given point is at its middle-left.
29 MiddleLeft,
30 /// Position text so the given point is at its center.
31 MiddleCenter,
32 /// Position text so the given point is at its middle-right.
33 MiddleRight,
34 /// Position text so the given point is at its bottom-left corner.
35 BottomLeft,
36 /// Position text so the given point is at its bottom-center.
37 BottomCenter,
38 /// Position text so the given point is at its bottom-right corner.
39 BottomRight,
40}
41
42impl Default for Anchor {
43 fn default() -> Self {
44 Anchor::TopLeft
45 }
46}
47
48/// Trait for types that support anchoring.
49///
50/// Allows configuring the anchor point for text and character positioning.
51///
52/// # Examples
53///
54/// ```ignore
55/// use embedded_graphics_profont::{Text, Anchor, WithAnchor};
56/// use embedded_graphics::geometry::Point;
57///
58/// let text = Text::new("Hello", Point::new(100, 100), &font, color)
59/// .with_anchor(Anchor::MiddleCenter);
60/// ```
61pub trait WithAnchor: Sized {
62 /// Set the anchor point for positioning this object.
63 fn with_anchor(self, anchor: Anchor) -> Self;
64}
65
66/// A bitmap font definition.
67///
68/// Contains all data needed to render text: glyph lookup table, bitmap data,
69/// and metadata about the font.
70///
71/// # Fields
72///
73/// - `lookup_table`: Array of glyph entries, one per character in the supported range
74/// - `data`: Raw bitmap data for all glyphs, packed as bits
75/// - `ascii_begin`: First character code in the supported range (e.g., 32 for space)
76/// - `ascii_end`: Last character code in the supported range (e.g., 126 for tilde)
77/// - `max_height`: Height of all glyphs in pixels
78/// - `proportional_width`: Whether glyphs have variable widths (vs fixed-width)
79///
80/// # Examples
81///
82/// ```ignore
83/// let font = Font::new(
84/// &LOOKUP_TABLE, // &[GlyphEntry]
85/// &FONT_DATA, // &[u8]
86/// 32, // ASCII start
87/// 126, // ASCII end
88/// 8, // Height
89/// true, // Proportional
90/// );
91/// ```
92pub struct Font {
93 /// Lookup table mapping characters to glyph metadata.
94 pub lookup_table: &'static [GlyphEntry],
95 /// Packed bitmap data for all glyphs.
96 pub data: &'static [u8],
97 /// First character code supported by this font.
98 pub ascii_begin: u32,
99 /// Last character code supported by this font.
100 pub ascii_end: u32,
101 /// Maximum height of glyphs in this font (in pixels).
102 pub max_height: u8,
103 /// Whether this font uses proportional widths (vs monospace).
104 #[allow(dead_code)]
105 pub proportional_width: bool,
106}
107
108/// Metadata for a single glyph in a font.
109///
110/// Used as entries in the font's lookup table to locate and describe each character's bitmap.
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub struct GlyphEntry {
113 /// Width of this glyph in pixels.
114 pub width: u8,
115 /// Byte offset in font data where this glyph's bitmap starts.
116 pub offset: u16,
117}
118
119/// A drawable text string with configurable positioning and styling.
120///
121/// Implements the `Drawable` trait from `embedded-graphics`, allowing it to be
122/// rendered on any `DrawTarget`.
123///
124/// # Fields (Internal)
125///
126/// - `text`: String to render
127/// - `position`: Position for the anchor point
128/// - `font`: Font to use for rendering
129/// - `color`: Pixel color for the text
130/// - `anchor`: Which point of the text bounds the position refers to
131/// - `tracking`: Extra pixel spacing between characters
132///
133/// # Examples
134///
135/// ```ignore
136/// use embedded_graphics::geometry::Point;
137/// use embedded_graphics_profont::{Text, Anchor, WithAnchor};
138///
139/// // Simple left-aligned text
140/// let text = Text::new("Hello", Point::new(0, 0), &font, Rgb565::WHITE);
141/// target.draw(&text)?;
142///
143/// // Centered text with letter spacing
144/// let text = Text::new("Spaced", Point::new(64, 32), &font, color)
145/// .with_anchor(Anchor::MiddleCenter)
146/// .with_tracking(2);
147/// target.draw(&text)?;
148/// ```
149pub struct Text<'a, C> {
150 text: &'a str,
151 position: Point,
152 font: &'a Font,
153 color: C,
154 anchor: Anchor,
155 tracking: i32,
156}
157
158/// A single drawable character with configurable positioning.
159///
160/// Like `Text`, this implements `Drawable` for rendering on `DrawTarget`.
161///
162/// Useful for rendering individual characters with positioning and anchor support.
163///
164/// # Examples
165///
166/// ```ignore
167/// use embedded_graphics_profont::{Character, Anchor, WithAnchor};
168/// use embedded_graphics::geometry::Point;
169///
170/// let ch = Character::new('A', Point::new(10, 10), &font, color)
171/// .with_anchor(Anchor::MiddleCenter);
172/// target.draw(&ch)?;
173/// ```
174pub struct Character<'a, C> {
175 ch: char,
176 position: Point,
177 font: &'a Font,
178 color: C,
179 anchor: Anchor,
180}
181
182impl Font {
183 /// Create a new font definition.
184 ///
185 /// # Arguments
186 ///
187 /// * `lookup_table` - Array of glyph entries, one per supported character
188 /// * `data` - Raw bitmap data for all glyphs
189 /// * `ascii_begin` - First supported character code
190 /// * `ascii_end` - Last supported character code (inclusive)
191 /// * `max_height` - Height of all glyphs in pixels
192 /// * `proportional_width` - Whether glyphs have variable widths
193 ///
194 /// # Examples
195 ///
196 /// ```ignore
197 /// let font = Font::new(
198 /// &GLYPHS, // 95 entries for ASCII 32-126
199 /// &FONT_DATA, // Binary bitmap data
200 /// 32, // Space
201 /// 126, // Tilde
202 /// 8, // 8-pixel height
203 /// false, // Monospace
204 /// );
205 /// ```
206 pub const fn new(
207 lookup_table: &'static [GlyphEntry],
208 data: &'static [u8],
209 ascii_begin: u32,
210 ascii_end: u32,
211 max_height: u8,
212 proportional_width: bool,
213 ) -> Self {
214 Self {
215 lookup_table,
216 data,
217 ascii_begin,
218 ascii_end,
219 max_height,
220 proportional_width,
221 }
222 }
223
224 /// Get the glyph metadata for a character.
225 ///
226 /// Returns `None` if the character is not supported by this font.
227 #[inline]
228 pub fn get_glyph(&self, c: char) -> Option<&GlyphEntry> {
229 let code = c as u32;
230 if code >= self.ascii_begin && code <= self.ascii_end {
231 let index = (code - self.ascii_begin) as usize;
232 self.lookup_table.get(index)
233 } else {
234 None
235 }
236 }
237
238 /// Get the bitmap data for a glyph.
239 ///
240 /// Data is stored as packed bits: 8 bits per byte, where each set bit
241 /// represents a pixel that should be drawn.
242 pub fn glyph_data(&self, entry: &GlyphEntry) -> &[u8] {
243 let start = entry.offset as usize;
244 let bytes_per_row = (entry.width as usize + 7) / 8;
245 let end = start + (bytes_per_row * self.max_height as usize);
246 &self.data[start..end]
247 }
248
249 /// Measure the pixel width of a text string.
250 ///
251 /// Returns the total width including any unsupported characters (which contribute 0 width)
252 /// and the specified tracking value.
253 ///
254 /// # Arguments
255 ///
256 /// * `text` - The string to measure
257 /// * `tracking` - Extra spacing to add between characters in pixels
258 ///
259 /// # Examples
260 ///
261 /// ```ignore
262 /// let width = font.measure_str("Hello", 0); // No extra spacing
263 /// let width = font.measure_str("Hello", 2); // 2 pixels between chars
264 /// ```
265 pub fn measure_str(&self, text: &str, tracking: i32) -> u32 {
266 let mut total_width: i32 = 0;
267 let mut count = 0;
268
269 for c in text.chars() {
270 if let Some(entry) = self.get_glyph(c) {
271 total_width += entry.width as i32 + tracking;
272 count += 1;
273 }
274 }
275
276 if count > 0 && tracking > 0 {
277 total_width -= tracking;
278 }
279
280 total_width.max(0) as u32
281 }
282}
283
284impl<'a, C> Text<'a, C>
285where
286 C: PixelColor,
287{
288 /// Create a new text drawable.
289 ///
290 /// The text will be positioned at the given point using the default
291 /// `TopLeft` anchor. Use `with_anchor()` to change the anchor point.
292 ///
293 /// # Arguments
294 ///
295 /// * `text` - String to render
296 /// * `position` - Position of the anchor point
297 /// * `font` - Font to use
298 /// * `color` - Pixel color for the text
299 pub fn new(text: &'a str, position: Point, font: &'a Font, color: C) -> Self {
300 Self {
301 text,
302 position,
303 font,
304 color,
305 anchor: Anchor::TopLeft,
306 tracking: 0,
307 }
308 }
309
310 /// Set the spacing between characters in pixels.
311 ///
312 /// # Arguments
313 ///
314 /// * `tracking` - Extra pixels to add between characters (can be negative)
315 ///
316 /// # Examples
317 ///
318 /// ```ignore
319 /// let text = Text::new("Hello", Point::new(0, 0), &font, color)
320 /// .with_tracking(2); // 2 pixels between each character
321 /// ```
322 pub fn with_tracking(mut self, tracking: i32) -> Self {
323 self.tracking = tracking;
324 self
325 }
326}
327
328impl<'a, C: PixelColor> WithAnchor for Text<'a, C> {
329 fn with_anchor(mut self, anchor: Anchor) -> Self {
330 self.anchor = anchor;
331 self
332 }
333}
334
335impl<'a, C> Character<'a, C>
336where
337 C: PixelColor,
338{
339 /// Create a new character drawable.
340 ///
341 /// The character will be positioned at the given point using the default
342 /// `TopLeft` anchor. Use `with_anchor()` to change the anchor point.
343 ///
344 /// If the character is not supported by the font, it will simply not render.
345 ///
346 /// # Arguments
347 ///
348 /// * `ch` - Character to render
349 /// * `position` - Position of the anchor point
350 /// * `font` - Font to use
351 /// * `color` - Pixel color for the character
352 pub fn new(ch: char, position: Point, font: &'a Font, color: C) -> Self {
353 Self {
354 ch,
355 position,
356 font,
357 color,
358 anchor: Anchor::TopLeft,
359 }
360 }
361}
362
363impl<'a, C: PixelColor> WithAnchor for Character<'a, C> {
364 fn with_anchor(mut self, anchor: Anchor) -> Self {
365 self.anchor = anchor;
366 self
367 }
368}
369
370impl<'a, C> Drawable for Text<'a, C>
371where
372 C: PixelColor,
373{
374 type Color = C;
375 type Output = ();
376
377 fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
378 where
379 D: DrawTarget<Color = Self::Color>,
380 {
381 let text_width = self.font.measure_str(self.text, self.tracking) as i32;
382 let text_height = self.font.max_height as i32;
383
384 let (x, y) = match self.anchor {
385 Anchor::TopLeft => (
386 self.position.x,
387 self.position.y
388 ),
389 Anchor::TopCenter => (
390 self.position.x - (text_width / 2),
391 self.position.y
392 ),
393 Anchor::TopRight => (
394 self.position.x - text_width,
395 self.position.y
396 ),
397 Anchor::MiddleLeft => (
398 self.position.x,
399 self.position.y - (text_height / 2)
400 ),
401 Anchor::MiddleCenter => (
402 self.position.x - (text_width / 2),
403 self.position.y - (text_height / 2),
404 ),
405 Anchor::MiddleRight => (
406 self.position.x - text_width,
407 self.position.y - (text_height / 2),
408 ),
409 Anchor::BottomLeft => (
410 self.position.x,
411 self.position.y - text_height
412 ),
413 Anchor::BottomCenter => (
414 self.position.x - (text_width / 2),
415 self.position.y - text_height
416 ),
417 Anchor::BottomRight => (
418 self.position.x - text_width,
419 self.position.y - text_height
420 ),
421 };
422
423 renderer::draw_str(
424 target,
425 self.text,
426 Point::new(x, y),
427 self.font,
428 self.color,
429 self.tracking,
430 )?;
431
432 Ok(())
433 }
434}
435
436impl<'a, C> Drawable for Character<'a, C>
437where
438 C: PixelColor,
439{
440 type Color = C;
441 type Output = ();
442
443 fn draw<D>(&self, target: &mut D) -> Result<Self::Output, D::Error>
444 where
445 D: DrawTarget<Color = Self::Color>,
446 {
447 let char_width = match self.font.get_glyph(self.ch) {
448 Some(entry) => entry.width as i32,
449 None => return Ok(()),
450 };
451 let char_height = self.font.max_height as i32;
452
453 let (x, y) = match self.anchor {
454 Anchor::TopLeft => (
455 self.position.x,
456 self.position.y
457 ),
458 Anchor::TopCenter => (
459 self.position.x - (char_width / 2),
460 self.position.y
461 ),
462 Anchor::TopRight => (
463 self.position.x - char_width,
464 self.position.y
465 ),
466 Anchor::MiddleLeft => (
467 self.position.x,
468 self.position.y - (char_height / 2)
469 ),
470 Anchor::MiddleCenter => (
471 self.position.x - (char_width / 2),
472 self.position.y - (char_height / 2),
473 ),
474 Anchor::MiddleRight => (
475 self.position.x - char_width,
476 self.position.y - (char_height / 2),
477 ),
478 Anchor::BottomLeft => (
479 self.position.x,
480 self.position.y - char_height
481 ),
482 Anchor::BottomCenter => (
483 self.position.x - (char_width / 2),
484 self.position.y - char_height,
485 ),
486 Anchor::BottomRight => (
487 self.position.x - char_width,
488 self.position.y - char_height
489 ),
490 };
491
492 renderer::draw_char(target, self.ch, Point::new(x, y), self.font, self.color)?;
493
494 Ok(())
495 }
496}