1use crate::{
4 parser::{ChangeTextStyle, Parser},
5 plugin::{PluginMarker as Plugin, PluginWrapper, ProcessingState},
6 rendering::{
7 cursor::LineCursor,
8 line_iter::{ElementHandler, LineElementParser, LineEndType},
9 },
10 style::TextBoxStyle,
11 utils::{str_width, str_width_and_left_offset},
12};
13use embedded_graphics::{
14 draw_target::DrawTarget,
15 geometry::Point,
16 prelude::{PixelColor, Size},
17 primitives::Rectangle,
18 text::{
19 renderer::{CharacterStyle, TextRenderer},
20 Baseline, DecorationColor,
21 },
22};
23
24impl<C> ChangeTextStyle<C>
25where
26 C: PixelColor,
27{
28 pub(crate) fn apply<S: CharacterStyle<Color = C>>(self, text_renderer: &mut S) {
29 match self {
30 ChangeTextStyle::Reset => {
31 text_renderer.set_text_color(None);
32 text_renderer.set_background_color(None);
33 text_renderer.set_underline_color(DecorationColor::None);
34 text_renderer.set_strikethrough_color(DecorationColor::None);
35 }
36 ChangeTextStyle::TextColor(color) => text_renderer.set_text_color(color),
37 ChangeTextStyle::BackgroundColor(color) => text_renderer.set_background_color(color),
38 ChangeTextStyle::Underline(color) => text_renderer.set_underline_color(color),
39 ChangeTextStyle::Strikethrough(color) => text_renderer.set_strikethrough_color(color),
40 }
41 }
42}
43
44pub(crate) struct StyledLineRenderer<'a, 'b, 'c, S, M>
46where
47 S: TextRenderer + Clone,
48 M: Plugin<'a, <S as TextRenderer>::Color>,
49{
50 pub(crate) cursor: LineCursor,
51 pub(crate) state: &'c mut LineRenderState<'a, 'b, S, M>,
52 pub(crate) style: &'c TextBoxStyle,
53}
54
55#[derive(Clone)]
56pub(crate) struct LineRenderState<'a, 'b, S, M>
57where
58 S: TextRenderer + Clone,
59 M: Plugin<'a, S::Color>,
60{
61 pub parser: Parser<'a, S::Color>,
62 pub text_renderer: S,
63 pub end_type: LineEndType,
64 pub plugin: &'b PluginWrapper<'a, M, S::Color>,
65}
66
67struct RenderElementHandler<'a, 'b, F, D, M>
68where
69 F: TextRenderer,
70 D: DrawTarget<Color = F::Color>,
71{
72 text_renderer: &'b mut F,
73 display: &'b mut D,
74 pos: Point,
75 plugin: &'b PluginWrapper<'a, M, F::Color>,
76}
77
78impl<'a, 'b, F, D, M> RenderElementHandler<'a, 'b, F, D, M>
79where
80 F: CharacterStyle + TextRenderer,
81 D: DrawTarget<Color = <F as TextRenderer>::Color>,
82 M: Plugin<'a, <F as TextRenderer>::Color>,
83{
84 fn post_print(&mut self, width: u32, st: &str) -> Result<(), D::Error> {
85 let bounds = Rectangle::new(self.pos, Size::new(width, self.text_renderer.line_height()));
86
87 self.pos += Point::new(width as i32, 0);
88
89 self.plugin
90 .post_render(self.display, self.text_renderer, Some(st), bounds)
91 }
92}
93
94impl<'a, 'c, F, D, M> ElementHandler for RenderElementHandler<'a, 'c, F, D, M>
95where
96 F: CharacterStyle + TextRenderer,
97 D: DrawTarget<Color = <F as TextRenderer>::Color>,
98 M: Plugin<'a, <F as TextRenderer>::Color>,
99{
100 type Error = D::Error;
101 type Color = <F as CharacterStyle>::Color;
102
103 fn measure(&self, st: &str) -> u32 {
104 str_width(self.text_renderer, st)
105 }
106
107 fn measure_width_and_left_offset(&self, st: &str) -> (u32, u32) {
108 str_width_and_left_offset(self.text_renderer, st)
109 }
110
111 fn whitespace(&mut self, st: &str, _space_count: u32, width: u32) -> Result<(), Self::Error> {
112 if width > 0 {
113 self.text_renderer
114 .draw_whitespace(width, self.pos, Baseline::Top, self.display)?;
115 }
116
117 self.post_print(width, st)
118 }
119
120 fn printed_characters(&mut self, st: &str, width: Option<u32>) -> Result<(), Self::Error> {
121 let render_width =
122 self.text_renderer
123 .draw_string(st, self.pos, Baseline::Top, self.display)?;
124
125 let width = width.unwrap_or((render_width - self.pos).x as u32);
126
127 self.post_print(width, st)
128 }
129
130 fn move_cursor(&mut self, by: i32) -> Result<(), Self::Error> {
131 self.pos += Point::new(by, 0);
133 Ok(())
134 }
135
136 fn change_text_style(
137 &mut self,
138 change: ChangeTextStyle<<F as CharacterStyle>::Color>,
139 ) -> Result<(), Self::Error> {
140 change.apply(self.text_renderer);
141 Ok(())
142 }
143}
144
145impl<'a, 'b, 'c, F, M> StyledLineRenderer<'a, 'b, 'c, F, M>
146where
147 F: TextRenderer<Color = <F as CharacterStyle>::Color> + CharacterStyle,
148 M: Plugin<'a, <F as TextRenderer>::Color> + Plugin<'a, <F as CharacterStyle>::Color>,
149{
150 #[inline]
151 pub(crate) fn draw<D>(mut self, display: &mut D) -> Result<(), D::Error>
152 where
153 D: DrawTarget<Color = <F as CharacterStyle>::Color>,
154 {
155 let LineRenderState {
156 ref mut parser,
157 ref mut text_renderer,
158 plugin,
159 ..
160 } = self.state;
161
162 let lm = {
163 let mut cloned_parser = parser.clone();
165 let measure_plugin = plugin.clone();
166 measure_plugin.set_state(ProcessingState::Measure);
167 self.style.measure_line(
168 &measure_plugin,
169 text_renderer,
170 &mut cloned_parser,
171 self.cursor.line_width(),
172 )
173 };
174
175 let (left, space_config) = self.style.alignment.place_line(text_renderer, lm);
176
177 self.cursor.move_cursor(left).ok();
178
179 let mut render_element_handler = RenderElementHandler {
180 text_renderer,
181 display,
182 pos: self.cursor.pos(),
183 plugin: *plugin,
184 };
185 let end_type =
186 LineElementParser::new(parser, plugin, self.cursor, space_config, self.style)
187 .process(&mut render_element_handler)?;
188
189 if end_type == LineEndType::EndOfText {
190 let end_pos = render_element_handler.pos;
191 plugin.post_render(
192 display,
193 text_renderer,
194 None,
195 Rectangle::new(end_pos, Size::new(0, text_renderer.line_height())),
196 )?;
197 }
198
199 self.state.end_type = end_type;
200
201 Ok(())
202 }
203}
204
205#[cfg(test)]
206mod test {
207 use crate::{
208 parser::Parser,
209 plugin::{NoPlugin, PluginWrapper},
210 rendering::{
211 cursor::LineCursor,
212 line::{LineRenderState, StyledLineRenderer},
213 line_iter::LineEndType,
214 },
215 style::{TabSize, TextBoxStyle, TextBoxStyleBuilder},
216 utils::test::size_for,
217 };
218 use embedded_graphics::{
219 geometry::Point,
220 mock_display::MockDisplay,
221 mono_font::{ascii::FONT_6X9, MonoTextStyleBuilder},
222 pixelcolor::BinaryColor,
223 primitives::Rectangle,
224 text::renderer::{CharacterStyle, TextRenderer},
225 };
226
227 fn test_rendered_text<'a, S>(
228 text: &'a str,
229 bounds: Rectangle,
230 character_style: S,
231 style: TextBoxStyle,
232 pattern: &[&str],
233 ) where
234 S: TextRenderer<Color = <S as CharacterStyle>::Color> + CharacterStyle,
235 <S as CharacterStyle>::Color: embedded_graphics::mock_display::ColorMapping,
236 {
237 let parser = Parser::parse(text);
238 let cursor = LineCursor::new(
239 bounds.size.width,
240 TabSize::Spaces(4).into_pixels(&character_style),
241 );
242
243 let plugin = PluginWrapper::new(NoPlugin::new());
244
245 let mut state = LineRenderState {
246 parser,
247 text_renderer: character_style,
248 end_type: LineEndType::EndOfText,
249 plugin: &plugin,
250 };
251
252 let renderer = StyledLineRenderer {
253 cursor,
254 state: &mut state,
255 style: &style,
256 };
257 let mut display = MockDisplay::new();
258 display.set_allow_overdraw(true);
259
260 renderer.draw(&mut display).unwrap();
261
262 display.assert_pattern(pattern);
263 }
264
265 #[test]
266 fn simple_render() {
267 let character_style = MonoTextStyleBuilder::new()
268 .font(&FONT_6X9)
269 .text_color(BinaryColor::On)
270 .background_color(BinaryColor::Off)
271 .build();
272
273 let style = TextBoxStyleBuilder::new().build();
274
275 test_rendered_text(
276 "Some sample text",
277 Rectangle::new(Point::zero(), size_for(&FONT_6X9, 7, 1)),
278 character_style,
279 style,
280 &[
281 "........................",
282 "..##....................",
283 ".#..#...................",
284 "..#.....##..##.#....##..",
285 "...#...#..#.#.#.#..#.##.",
286 ".#..#..#..#.#.#.#..##...",
287 "..##....##..#...#...###.",
288 "........................",
289 "........................",
290 ],
291 );
292 }
293
294 #[test]
295 fn simple_render_nbsp() {
296 let character_style = MonoTextStyleBuilder::new()
297 .font(&FONT_6X9)
298 .text_color(BinaryColor::On)
299 .background_color(BinaryColor::Off)
300 .build();
301
302 let style = TextBoxStyleBuilder::new().build();
303
304 test_rendered_text(
305 "Some\u{A0}sample text",
306 Rectangle::new(Point::zero(), size_for(&FONT_6X9, 7, 1)),
307 character_style,
308 style,
309 &[
310 "..........................................",
311 "..##......................................",
312 ".#..#.....................................",
313 "..#.....##..##.#....##..........###...###.",
314 "...#...#..#.#.#.#..#.##........##....#..#.",
315 ".#..#..#..#.#.#.#..##............##..#..#.",
316 "..##....##..#...#...###........###....###.",
317 "..........................................",
318 "..........................................",
319 ],
320 );
321 }
322
323 #[test]
324 fn simple_render_first_word_not_wrapped() {
325 let character_style = MonoTextStyleBuilder::new()
326 .font(&FONT_6X9)
327 .text_color(BinaryColor::On)
328 .background_color(BinaryColor::Off)
329 .build();
330
331 let style = TextBoxStyleBuilder::new().build();
332
333 test_rendered_text(
334 "Some sample text",
335 Rectangle::new(Point::zero(), size_for(&FONT_6X9, 2, 1)),
336 character_style,
337 style,
338 &[
339 "............",
340 "..##........",
341 ".#..#.......",
342 "..#.....##..",
343 "...#...#..#.",
344 ".#..#..#..#.",
345 "..##....##..",
346 "............",
347 "............",
348 ],
349 );
350 }
351
352 #[test]
353 fn newline_stops_render() {
354 let character_style = MonoTextStyleBuilder::new()
355 .font(&FONT_6X9)
356 .text_color(BinaryColor::On)
357 .background_color(BinaryColor::Off)
358 .build();
359
360 let style = TextBoxStyleBuilder::new().build();
361
362 test_rendered_text(
363 "Some \nsample text",
364 Rectangle::new(Point::zero(), size_for(&FONT_6X9, 7, 1)),
365 character_style,
366 style,
367 &[
368 "........................",
369 "..##....................",
370 ".#..#...................",
371 "..#.....##..##.#....##..",
372 "...#...#..#.#.#.#..#.##.",
373 ".#..#..#..#.#.#.#..##...",
374 "..##....##..#...#...###.",
375 "........................",
376 "........................",
377 ],
378 );
379 }
380}