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}