Skip to main content

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}