Skip to main content

packed_font/
textrenderer.rs

1use embedded_graphics::{
2    draw_target::DrawTarget,
3    geometry::{Point, Size},
4    primitives::Rectangle,
5    text::{
6        Baseline,
7        renderer::{TextMetrics, TextRenderer},
8    },
9};
10
11use super::{PackedFont, UnpackStyle};
12
13pub struct CharacterStyle<'t, S> {
14    pub font: &'t PackedFont,
15    pub style: S,
16}
17
18impl<'t, S> CharacterStyle<'t, S> {
19    pub fn new(font: &'t PackedFont, style: S) -> Self {
20        Self { font, style }
21    }
22
23    pub fn apply_baseline(&self, position: Point, baseline: Baseline) -> Point {
24        let Point { x, mut y } = position;
25        let metrics = &self.font.metrics;
26        y += match baseline {
27            Baseline::Top => metrics.ascent as i32,
28            Baseline::Bottom => metrics.descent as i32,
29            Baseline::Middle => (metrics.ascent as i32 + metrics.descent as i32) / 2,
30            Baseline::Alphabetic => 0,
31        };
32        Point::new(x, y)
33    }
34}
35
36impl<'t, S: UnpackStyle> CharacterStyle<'t, S> {
37    pub fn draw_character<D>(
38        &self,
39        chr: char,
40        origin: Point,
41        target: &mut D,
42    ) -> Result<TextMetrics, D::Error>
43    where
44        D: DrawTarget<Color = S::Color>,
45    {
46        if let Some((metrics, height)) = self.font.render(chr, &self.style, origin, target)? {
47            let top_left = Point::new(origin.x, origin.y - self.font.metrics.ascent as i32);
48            let full_height = (self.font.metrics.ascent - self.font.metrics.descent) as u32;
49            if let Some(color) = self.style.background_color() {
50                if let Ok(left_bearing) = metrics.left_bearing.try_into() {
51                    target.fill_solid(
52                        &Rectangle::new(top_left, Size::new(left_bearing, full_height)),
53                        color,
54                    )?;
55                }
56                if self.font.metrics.ascent > metrics.top_bearing {
57                    target.fill_solid(
58                        &Rectangle::new(
59                            Point::new(top_left.x + metrics.left_bearing as i32, top_left.y),
60                            Size::new(
61                                metrics.width as u32,
62                                (self.font.metrics.ascent - metrics.top_bearing) as u32,
63                            ),
64                        ),
65                        color,
66                    )?;
67                }
68                let bottom_y_offset = height as i32 - metrics.top_bearing as i32;
69                let bottom_rest = -bottom_y_offset - self.font.metrics.descent as i32;
70                if bottom_rest > 0 {
71                    target.fill_solid(
72                        &Rectangle::new(
73                            Point::new(
74                                top_left.x + metrics.left_bearing as i32,
75                                origin.y + bottom_y_offset,
76                            ),
77                            Size::new(metrics.width as u32, bottom_rest as u32),
78                        ),
79                        color,
80                    )?;
81                }
82                let right_rest =
83                    metrics.advance as i32 - metrics.left_bearing as i32 - metrics.width as i32;
84                if right_rest > 0 {
85                    target.fill_solid(
86                        &Rectangle::new(
87                            Point::new(
88                                top_left.x + metrics.left_bearing as i32 + metrics.width as i32,
89                                top_left.y,
90                            ),
91                            Size::new(right_rest as u32, full_height),
92                        ),
93                        color,
94                    )?;
95                }
96            }
97
98            let next_position = Point::new(origin.x + metrics.advance as i32, origin.y);
99            let size = Size::new(metrics.advance as u32, full_height);
100            let bounding_box = Rectangle::new(top_left, size);
101            Ok(TextMetrics {
102                bounding_box,
103                next_position,
104            })
105        } else {
106            let bounding_box = Rectangle::new(origin, Size::new(0, 0));
107            Ok(TextMetrics {
108                bounding_box,
109                next_position: origin,
110            })
111        }
112    }
113
114    pub fn measure_character(&self, chr: char) -> Size {
115        let full_height = (self.font.metrics.ascent - self.font.metrics.descent) as u32;
116        let Some((metrics, _)) = self.font.get_metrics_and_data(chr) else {
117            return Size::zero();
118        };
119        let width = metrics.advance.max(0) as u32;
120        Size::new(width, full_height)
121    }
122}
123
124impl<S> TextRenderer for CharacterStyle<'_, S>
125where
126    S: UnpackStyle,
127{
128    type Color = S::Color;
129
130    fn draw_string<D>(
131        &self,
132        text: &str,
133        position: Point,
134        baseline: Baseline,
135        target: &mut D,
136    ) -> Result<Point, D::Error>
137    where
138        D: DrawTarget<Color = Self::Color>,
139    {
140        let mut pos = self.apply_baseline(position, baseline);
141
142        for chr in text.chars() {
143            pos = self.draw_character(chr, pos, target)?.next_position;
144        }
145
146        Ok(Point::new(pos.x, position.y))
147    }
148
149    fn draw_whitespace<D>(
150        &self,
151        width: u32,
152        position: Point,
153        baseline: Baseline,
154        target: &mut D,
155    ) -> Result<Point, D::Error>
156    where
157        D: DrawTarget<Color = Self::Color>,
158    {
159        let pos = self.apply_baseline(position, baseline);
160        let height = self.line_height();
161        if let Some(color) = self.style.background_color() {
162            target.fill_solid(&Rectangle::new(pos, Size::new(width, height)), color)?;
163        }
164        Ok(Point::new(pos.x + width as i32, pos.y))
165    }
166
167    fn measure_string(&self, text: &str, position: Point, baseline: Baseline) -> TextMetrics {
168        let pos = self.apply_baseline(position, baseline);
169        let full_height = (self.font.metrics.ascent - self.font.metrics.descent) as u32;
170        let mut total_width = 0;
171
172        let top_left = Point::new(pos.x, pos.y - self.font.metrics.ascent as i32);
173
174        for chr in text.chars() {
175            if let Some((metrics, _)) = self.font.get_metrics_and_data(chr) {
176                total_width += metrics.advance as i32;
177            }
178        }
179
180        let width = total_width.max(0) as u32;
181
182        TextMetrics {
183            next_position: Point::new(position.x + total_width, position.y),
184            bounding_box: Rectangle::new(top_left, Size::new(width, full_height)),
185        }
186    }
187
188    fn line_height(&self) -> u32 {
189        (self.font.metrics.ascent as i32 - self.font.metrics.descent as i32
190            + self.font.metrics.leading as i32) as u32
191    }
192}