u8g2_fonts/content/
args.rs

1use core::{mem, ops::Range};
2
3use crate::{
4    font_reader::FontReader,
5    renderer::render_actions::compute_horizontal_glyph_dimensions,
6    utils::{FormatArgsReader, FormatArgsReaderInfallible, HorizontalRenderedDimensions},
7    Content, LookupError,
8};
9
10use super::LineDimensionsIterator;
11
12impl<'a> Content for core::fmt::Arguments<'a> {
13    fn for_each_char<F, E>(&self, mut func: F) -> Result<(), E>
14    where
15        F: FnMut(char) -> Result<(), E>,
16    {
17        FormatArgsReader::new(|ch| func(ch).map(|()| true)).process_args(*self)
18    }
19
20    fn for_each_char_infallible<F>(&self, func: F)
21    where
22        F: FnMut(char),
23    {
24        FormatArgsReaderInfallible::new(func).process_args(*self)
25    }
26
27    type LineDimensionsIter = ArgsLineDimensionsIterator<'a>;
28
29    fn line_dimensions_iterator(&self) -> ArgsLineDimensionsIterator<'a> {
30        ArgsLineDimensionsIterator::new(*self)
31    }
32}
33
34// Most strings will print only a single line.
35// Having a buffer of 5 should be fine for most embedded systems.
36// (5* sizeof(HorizontalRenderedDimensions)) should be in the range
37// of ~60 bytes.
38const NUM_BUFFERED_LINES: usize = 5;
39
40pub struct ArgsLineDimensionsIterator<'a> {
41    args: core::fmt::Arguments<'a>,
42    buffer_range: Range<usize>,
43    dimensions_buffer: [HorizontalRenderedDimensions; NUM_BUFFERED_LINES],
44    next_line: usize,
45    finished: bool,
46}
47
48impl<'a> ArgsLineDimensionsIterator<'a> {
49    pub fn new(args: core::fmt::Arguments<'a>) -> Self {
50        Self {
51            args,
52            buffer_range: 0..0,
53            dimensions_buffer: [(); NUM_BUFFERED_LINES]
54                .map(|()| HorizontalRenderedDimensions::empty()),
55            next_line: 0,
56            finished: false,
57        }
58    }
59
60    pub fn regenerate_buffer(
61        &mut self,
62        range_start: usize,
63        font: &FontReader,
64    ) -> Result<(), LookupError> {
65        let mut line_dimensions = HorizontalRenderedDimensions::empty();
66        let mut line_num: usize = 0;
67
68        FormatArgsReader::new(|ch| -> Result<bool, LookupError> {
69            if ch == '\n' {
70                let previous_line_dimensions =
71                    mem::replace(&mut line_dimensions, HorizontalRenderedDimensions::empty());
72
73                if let Some(array_pos) = line_num.checked_sub(range_start) {
74                    if let Some(cell) = self.dimensions_buffer.get_mut(array_pos) {
75                        // If we are in the correct range, set the value in the array
76                        *cell = previous_line_dimensions;
77                    }
78                }
79
80                line_num += 1;
81
82                if line_num >= range_start + self.dimensions_buffer.len() {
83                    // break if we are past the desired range
84                    return Ok(false);
85                }
86            } else if line_num >= range_start {
87                // Only compute dimensions if we are in a line that will be buffered
88                let dimensions =
89                    compute_horizontal_glyph_dimensions(ch, line_dimensions.advance, font)?;
90                line_dimensions.add(dimensions);
91            }
92
93            Ok(true)
94        })
95        .process_args(self.args)?;
96
97        // One last time, if format_args ran out and our last line didn't end with a newline
98        if let Some(array_pos) = line_num.checked_sub(range_start) {
99            if let Some(cell) = self.dimensions_buffer.get_mut(array_pos) {
100                // If we are in the correct range, set the value in the array
101                *cell = line_dimensions;
102
103                // We hit the end, store that so we don't continue in future
104                self.finished = true;
105                line_num += 1;
106            }
107        }
108
109        self.buffer_range = range_start..line_num;
110        assert!(self.buffer_range.len() <= self.dimensions_buffer.len());
111
112        Ok(())
113    }
114}
115
116impl LineDimensionsIterator for ArgsLineDimensionsIterator<'_> {
117    fn next(
118        &mut self,
119        font: &crate::font_reader::FontReader,
120    ) -> Result<HorizontalRenderedDimensions, LookupError> {
121        let next_line = self.next_line;
122        self.next_line += 1;
123
124        if !self.buffer_range.contains(&next_line) {
125            if self.finished {
126                return Ok(HorizontalRenderedDimensions::empty());
127            }
128
129            self.regenerate_buffer(next_line, font)?;
130            assert!(self.buffer_range.contains(&next_line));
131        }
132
133        Ok(self.dimensions_buffer[next_line - self.buffer_range.start].clone())
134    }
135}
136
137#[cfg(test)]
138mod tests {
139    extern crate std;
140    use core::fmt::Arguments;
141    use std::vec::Vec;
142
143    use crate::fonts;
144
145    use super::*;
146
147    #[test]
148    fn for_each_char_produces_correct_values() {
149        let mut content = Vec::new();
150
151        format_args!("{}", "abc")
152            .for_each_char(|e| {
153                content.push(e);
154                Result::<(), &'static str>::Ok(())
155            })
156            .unwrap();
157
158        assert_eq!(content, ['a', 'b', 'c']);
159    }
160
161    #[test]
162    fn for_each_char_infallible_produces_correct_values() {
163        let mut content = Vec::new();
164
165        format_args!("{}", "abc").for_each_char_infallible(|e| {
166            content.push(e);
167        });
168
169        assert_eq!(content, ['a', 'b', 'c']);
170    }
171
172    #[test]
173    fn for_each_char_propagates_error() {
174        let result = format_args!("{}", "abc").for_each_char(|_| Err("Failed!"));
175
176        assert_eq!(result, Err("Failed!"));
177    }
178
179    #[test]
180    fn get_newline_count_provides_correct_value() {
181        assert_eq!(format_args!("{}", "a\nbc\n").get_newline_count(), 2);
182        assert_eq!(format_args!("{}", "a\nbc").get_newline_count(), 1);
183        assert_eq!(format_args!("{}", "").get_newline_count(), 0);
184    }
185
186    #[test]
187    fn line_dimensions_iter_provides_correct_values() {
188        // Nested function to deal with format_args!()'s weird lifetimes
189        fn run_test(args: Arguments<'_>) {
190            let font = FontReader::new::<fonts::u8g2_font_u8glib_4_tf>();
191            let mut dims = args.line_dimensions_iterator();
192
193            assert_eq!(
194                dims.next(&font).unwrap(),
195                HorizontalRenderedDimensions {
196                    advance: 4,
197                    bounding_box_width: 3,
198                    bounding_box_offset: 0,
199                }
200            );
201            assert_eq!(
202                dims.next(&font).unwrap(),
203                HorizontalRenderedDimensions {
204                    advance: 7,
205                    bounding_box_width: 6,
206                    bounding_box_offset: 0,
207                }
208            );
209            assert_eq!(
210                dims.next(&font).unwrap(),
211                HorizontalRenderedDimensions::empty()
212            );
213            assert_eq!(
214                dims.next(&font).unwrap(),
215                HorizontalRenderedDimensions::empty()
216            );
217        }
218
219        run_test(format_args!("{}", "a\nbc\n"));
220    }
221
222    #[test]
223    fn line_dimensions_iter_errors_on_glyph_not_found() {
224        // Nested function to deal with format_args!()'s weird lifetimes
225        fn run_test(args: Arguments<'_>) {
226            let font = FontReader::new::<fonts::u8g2_font_u8glib_4_tf>();
227            let mut dims = args.line_dimensions_iterator();
228
229            assert!(matches!(
230                dims.next(&font),
231                Err(LookupError::GlyphNotFound('☃'))
232            ));
233        }
234
235        run_test(format_args!("{}", "☃"));
236    }
237
238    #[test]
239    fn line_dimensions_iter_creates_empty_array_when_out_of_range() {
240        // Nested function to deal with format_args!()'s weird lifetimes
241        fn run_test(args: Arguments<'_>) {
242            let font = FontReader::new::<fonts::u8g2_font_u8glib_4_tf>();
243            let mut dims = args.line_dimensions_iterator();
244
245            dims.regenerate_buffer(1000, &font).unwrap();
246            assert!(dims.buffer_range.is_empty());
247        }
248
249        run_test(format_args!("{}", "a"));
250    }
251}