1use embedded_graphics::{
2 draw_target::DrawTarget,
3 geometry::{Point, Size},
4 image::Image,
5 pixelcolor::{BinaryColor, PixelColor},
6 prelude::OriginDimensions,
7 primitives::Rectangle,
8 text::{
9 renderer::{CharacterStyle, TextMetrics, TextRenderer},
10 Baseline,
11 },
12 Drawable,
13};
14
15use crate::{
16 char_size::CharSize, draw_target::MultiMonoFontDrawTarget, ChSzTy, MultiMonoFont,
17 MultiMonoFontList,
18};
19
20#[derive(Copy, Clone, Debug, PartialEq)]
21pub enum MultiMonoLineHeight {
22 Max,
23 Min,
24 Specify(ChSzTy),
25}
26
27const fn get_line_height<'a>(
28 fonts_height: MultiMonoLineHeight,
29 fonts: MultiMonoFontList<'a>,
30) -> ChSzTy {
31 let mut idx = 0;
32 match fonts_height {
33 MultiMonoLineHeight::Max => {
34 let mut max = ChSzTy::MIN;
35 while idx < fonts.len() {
36 let h = fonts[idx].character_size.height as ChSzTy;
37 idx += 1;
38 if h > max {
39 max = h;
40 }
41 }
42 max
43 }
44 MultiMonoLineHeight::Min => {
45 let mut min = ChSzTy::MAX;
46 while idx < fonts.len() {
47 let h = fonts[idx].character_size.height as ChSzTy;
48 idx += 1;
49 if h < min {
50 min = h;
51 }
52 }
53 min
54 }
55 MultiMonoLineHeight::Specify(h) => h,
56 }
57}
58
59#[derive(Copy, Clone, Debug, PartialEq)]
72#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
73#[non_exhaustive]
74pub struct MultiMonoTextStyle<'a, C> {
75 pub text_color: C,
77
78 pub background_color: Option<C>,
80
81 pub fonts: MultiMonoFontList<'a>,
83
84 pub line_height: ChSzTy,
86}
87
88impl<'a, C> MultiMonoTextStyle<'a, C>
89where
90 C: PixelColor,
91{
92 pub const fn new(font_list: MultiMonoFontList<'a>, text_color: C) -> Self {
94 MultiMonoTextStyleBuilder::new(text_color)
95 .font(font_list, MultiMonoLineHeight::Max)
96 .build()
97 }
98
99 pub const fn new_with_line_height(
100 font_list: MultiMonoFontList<'a>,
101 line_height: MultiMonoLineHeight,
102 text_color: C,
103 ) -> Self {
104 MultiMonoTextStyleBuilder::new(text_color)
105 .font(font_list, line_height)
106 .build()
107 }
108
109 fn get_font_info(&self, c: char) -> &MultiMonoFont<'a> {
110 for font in self.fonts {
111 if font.glyph_mapping.contains(c) {
112 return font;
113 }
114 }
115
116 self.fonts[0]
117 }
118
119 fn draw_string_binary<D>(
120 &self,
121 text: &str,
122 position: Point,
123 baseline: Baseline,
124 mut target: D,
125 ) -> Result<Point, D::Error>
126 where
127 D: DrawTarget<Color = BinaryColor>,
128 {
129 let mut next_pos = position;
130 let mut draw_pos;
131
132 for c in text.chars() {
133 let font = self.get_font_info(c);
134 let glyph = font.glyph(c);
135 draw_pos = next_pos - Point::new(0, self.baseline_offset(baseline, font));
136 Image::new(&glyph, draw_pos).draw(&mut target)?;
137 next_pos.x += font.character_size.width as i32;
138 if font.character_spacing > 0 {
139 draw_pos.x += font.character_size.width as i32;
140 if self.background_color.is_some() {
141 target.fill_solid(
142 &Rectangle::new(
143 draw_pos,
144 CharSize::new(font.character_spacing, font.character_size.height)
145 .size(),
146 ),
147 BinaryColor::Off,
148 )?;
149 }
150 next_pos.x += font.character_spacing as i32;
151 }
152 }
153
154 Ok(next_pos)
155 }
156
157 fn baseline_offset(&self, baseline: Baseline, font: &MultiMonoFont<'a>) -> i32 {
159 match baseline {
160 Baseline::Top => 0,
161 Baseline::Bottom => font.character_size.height.saturating_sub(1) as i32,
162 Baseline::Middle => (font.character_size.height.saturating_sub(1) / 2) as i32,
163 Baseline::Alphabetic => font.baseline as i32,
164 }
165 }
166}
167
168impl<C> TextRenderer for MultiMonoTextStyle<'_, C>
169where
170 C: PixelColor,
171{
172 type Color = C;
173
174 fn draw_string<D>(
175 &self,
176 text: &str,
177 position: Point,
178 baseline: Baseline,
179 target: &mut D,
180 ) -> Result<Point, D::Error>
181 where
182 D: DrawTarget<Color = Self::Color>,
183 {
184 self.draw_string_binary(
185 text,
186 position,
187 baseline,
188 MultiMonoFontDrawTarget::new(target, self.text_color, self.background_color),
189 )
190 }
191
192 fn draw_whitespace<D>(
193 &self,
194 width: u32,
195 position: Point,
196 baseline: Baseline,
197 target: &mut D,
198 ) -> Result<Point, D::Error>
199 where
200 D: DrawTarget<Color = Self::Color>,
201 {
202 let (offet_y, height) = match baseline {
203 Baseline::Top => (0, self.line_height),
204 Baseline::Bottom => (self.line_height.saturating_sub(1) as i32, self.line_height),
205 Baseline::Middle => (
206 (self.line_height.saturating_sub(1) / 2) as i32,
207 self.line_height,
208 ),
209 Baseline::Alphabetic => (
210 self.fonts[0].baseline as i32,
211 self.fonts[0].character_size.height,
212 ),
213 };
214 let position = position - Point::new(0, offet_y);
215
216 if width != 0 {
217 if let Some(background_color) = self.background_color {
218 target.fill_solid(
219 &Rectangle::new(position, Size::new(width, height as u32)),
220 background_color,
221 )?;
222 }
223 }
224
225 Ok(position + Point::new(width as i32, offet_y))
226 }
227
228 fn measure_string(&self, text: &str, position: Point, baseline: Baseline) -> TextMetrics {
229 let mut bb_width = 0;
230 let mut bb_height = 0;
231 let mut baseline_max = 0;
232 let mut font = self.fonts[0];
233 for c in text.chars() {
234 font = self.get_font_info(c);
235 bb_width += (font.character_size.width + font.character_spacing) as u32;
236 bb_height = bb_height.max(font.character_size.height as u32);
237
238 baseline_max = baseline_max.max(self.baseline_offset(baseline, font));
239 }
240 bb_width = bb_width.saturating_sub(font.character_spacing as u32);
241
242 let bb_size = Size::new(bb_width, bb_height);
243
244 let bb_position = position - Point::new(0, baseline_max);
245 TextMetrics {
246 bounding_box: Rectangle::new(bb_position, bb_size),
247 next_position: position + bb_size.x_axis(),
248 }
249 }
250
251 fn line_height(&self) -> u32 {
252 self.line_height as u32
253 }
254}
255
256impl<C> CharacterStyle for MultiMonoTextStyle<'_, C>
257where
258 C: PixelColor,
259{
260 type Color = C;
261
262 fn set_text_color(&mut self, text_color: Option<Self::Color>) {
263 if let Some(color) = text_color {
264 self.text_color = color;
265 }
266 }
267
268 fn set_background_color(&mut self, background_color: Option<Self::Color>) {
269 self.background_color = background_color;
270 }
271}
272
273#[derive(Copy, Clone, Debug)]
345#[cfg_attr(feature = "defmt", derive(::defmt::Format))]
346pub struct MultiMonoTextStyleBuilder<'a, C> {
347 style: MultiMonoTextStyle<'a, C>,
348}
349
350impl<'a, C> MultiMonoTextStyleBuilder<'a, C>
351where
352 C: PixelColor,
353{
354 pub const fn new(text_color: C) -> Self {
356 Self {
357 style: MultiMonoTextStyle {
358 fonts: &[&super::NULL_FONT],
359 background_color: None,
360 text_color,
361 line_height: 0,
362 },
363 }
364 }
365
366 pub const fn font<'b>(
368 self,
369 font_list: &'b [&'b MultiMonoFont<'b>],
370 line_height: MultiMonoLineHeight,
371 ) -> MultiMonoTextStyleBuilder<'b, C> {
372 let fonts = if font_list.is_empty() {
373 &[&crate::NULL_FONT]
374 } else {
375 font_list
376 };
377 let line_height = get_line_height(line_height, fonts);
378 let style = MultiMonoTextStyle {
379 fonts,
380 background_color: self.style.background_color,
381 text_color: self.style.text_color,
382 line_height,
383 };
384
385 MultiMonoTextStyleBuilder { style }
386 }
387
388 pub const fn reset_background_color(mut self) -> Self {
390 self.style.background_color = None;
391
392 self
393 }
394
395 pub const fn text_color(mut self, text_color: C) -> Self {
397 self.style.text_color = text_color;
398
399 self
400 }
401
402 pub const fn line_height(mut self, line_height: MultiMonoLineHeight) -> Self {
404 self.style.line_height = get_line_height(line_height, self.style.fonts);
405
406 self
407 }
408
409 pub const fn background_color(mut self, background_color: C) -> Self {
411 self.style.background_color = Some(background_color);
412
413 self
414 }
415
416 pub const fn build(self) -> MultiMonoTextStyle<'a, C> {
423 self.style
424 }
425}
426
427impl<'a, C> From<&MultiMonoTextStyle<'a, C>> for MultiMonoTextStyleBuilder<'a, C>
428where
429 C: PixelColor,
430{
431 fn from(style: &MultiMonoTextStyle<'a, C>) -> Self {
432 Self { style: *style }
433 }
434}