line_wrapper.rs

  1use crate::{px, FontId, FontRun, Pixels, PlatformTextSystem};
  2use collections::HashMap;
  3use std::{iter, sync::Arc};
  4
  5pub struct LineWrapper {
  6    platform_text_system: Arc<dyn PlatformTextSystem>,
  7    pub(crate) font_id: FontId,
  8    pub(crate) font_size: Pixels,
  9    cached_ascii_char_widths: [Option<Pixels>; 128],
 10    cached_other_char_widths: HashMap<char, Pixels>,
 11}
 12
 13impl LineWrapper {
 14    pub const MAX_INDENT: u32 = 256;
 15
 16    pub fn new(
 17        font_id: FontId,
 18        font_size: Pixels,
 19        text_system: Arc<dyn PlatformTextSystem>,
 20    ) -> Self {
 21        Self {
 22            platform_text_system: text_system,
 23            font_id,
 24            font_size,
 25            cached_ascii_char_widths: [None; 128],
 26            cached_other_char_widths: HashMap::default(),
 27        }
 28    }
 29
 30    pub fn wrap_line<'a>(
 31        &'a mut self,
 32        line: &'a str,
 33        wrap_width: Pixels,
 34    ) -> impl Iterator<Item = Boundary> + 'a {
 35        let mut width = px(0.);
 36        let mut first_non_whitespace_ix = None;
 37        let mut indent = None;
 38        let mut last_candidate_ix = 0;
 39        let mut last_candidate_width = px(0.);
 40        let mut last_wrap_ix = 0;
 41        let mut prev_c = '\0';
 42        let mut char_indices = line.char_indices();
 43        iter::from_fn(move || {
 44            for (ix, c) in char_indices.by_ref() {
 45                if c == '\n' {
 46                    continue;
 47                }
 48
 49                if prev_c == ' ' && c != ' ' && first_non_whitespace_ix.is_some() {
 50                    last_candidate_ix = ix;
 51                    last_candidate_width = width;
 52                }
 53
 54                if c != ' ' && first_non_whitespace_ix.is_none() {
 55                    first_non_whitespace_ix = Some(ix);
 56                }
 57
 58                let char_width = self.width_for_char(c);
 59                width += char_width;
 60                if width > wrap_width && ix > last_wrap_ix {
 61                    if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
 62                    {
 63                        indent = Some(
 64                            Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
 65                        );
 66                    }
 67
 68                    if last_candidate_ix > 0 {
 69                        last_wrap_ix = last_candidate_ix;
 70                        width -= last_candidate_width;
 71                        last_candidate_ix = 0;
 72                    } else {
 73                        last_wrap_ix = ix;
 74                        width = char_width;
 75                    }
 76
 77                    if let Some(indent) = indent {
 78                        width += self.width_for_char(' ') * indent as f32;
 79                    }
 80
 81                    return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
 82                }
 83                prev_c = c;
 84            }
 85
 86            None
 87        })
 88    }
 89
 90    #[inline(always)]
 91    fn width_for_char(&mut self, c: char) -> Pixels {
 92        if (c as u32) < 128 {
 93            if let Some(cached_width) = self.cached_ascii_char_widths[c as usize] {
 94                cached_width
 95            } else {
 96                let width = self.compute_width_for_char(c);
 97                self.cached_ascii_char_widths[c as usize] = Some(width);
 98                width
 99            }
100        } else {
101            if let Some(cached_width) = self.cached_other_char_widths.get(&c) {
102                *cached_width
103            } else {
104                let width = self.compute_width_for_char(c);
105                self.cached_other_char_widths.insert(c, width);
106                width
107            }
108        }
109    }
110
111    fn compute_width_for_char(&self, c: char) -> Pixels {
112        let mut buffer = [0; 4];
113        let buffer = c.encode_utf8(&mut buffer);
114        self.platform_text_system
115            .layout_line(
116                buffer,
117                self.font_size,
118                &[FontRun {
119                    len: 1,
120                    font_id: self.font_id,
121                }],
122            )
123            .width
124    }
125}
126
127#[derive(Copy, Clone, Debug, PartialEq, Eq)]
128pub struct Boundary {
129    pub ix: usize,
130    pub next_indent: u32,
131}
132
133impl Boundary {
134    fn new(ix: usize, next_indent: u32) -> Self {
135        Self { ix, next_indent }
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142    use crate::{font, TestAppContext, TestDispatcher};
143    use rand::prelude::*;
144
145    #[test]
146    fn test_wrap_line() {
147        let dispatcher = TestDispatcher::new(StdRng::seed_from_u64(0));
148        let cx = TestAppContext::new(dispatcher);
149
150        cx.update(|cx| {
151            let text_system = cx.text_system().clone();
152            let mut wrapper = LineWrapper::new(
153                text_system.font_id(&font("Courier")).unwrap(),
154                px(16.),
155                text_system.platform_text_system.clone(),
156            );
157            assert_eq!(
158                wrapper
159                    .wrap_line("aa bbb cccc ddddd eeee", px(72.))
160                    .collect::<Vec<_>>(),
161                &[
162                    Boundary::new(7, 0),
163                    Boundary::new(12, 0),
164                    Boundary::new(18, 0)
165                ],
166            );
167            assert_eq!(
168                wrapper
169                    .wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0))
170                    .collect::<Vec<_>>(),
171                &[
172                    Boundary::new(4, 0),
173                    Boundary::new(11, 0),
174                    Boundary::new(18, 0)
175                ],
176            );
177            assert_eq!(
178                wrapper
179                    .wrap_line("     aaaaaaa", px(72.))
180                    .collect::<Vec<_>>(),
181                &[
182                    Boundary::new(7, 5),
183                    Boundary::new(9, 5),
184                    Boundary::new(11, 5),
185                ]
186            );
187            assert_eq!(
188                wrapper
189                    .wrap_line("                            ", px(72.))
190                    .collect::<Vec<_>>(),
191                &[
192                    Boundary::new(7, 0),
193                    Boundary::new(14, 0),
194                    Boundary::new(21, 0)
195                ]
196            );
197            assert_eq!(
198                wrapper
199                    .wrap_line("          aaaaaaaaaaaaaa", px(72.))
200                    .collect::<Vec<_>>(),
201                &[
202                    Boundary::new(7, 0),
203                    Boundary::new(14, 3),
204                    Boundary::new(18, 3),
205                    Boundary::new(22, 3),
206                ]
207            );
208        });
209    }
210
211    // todo!("move this to a test on TextSystem::layout_text")
212    // todo! repeat this test
213    // #[test]
214    // fn test_wrap_shaped_line() {
215    //     App::test().run(|cx| {
216    //         let text_system = cx.text_system().clone();
217
218    //         let normal = TextRun {
219    //             len: 0,
220    //             font: font("Helvetica"),
221    //             color: Default::default(),
222    //             underline: Default::default(),
223    //         };
224    //         let bold = TextRun {
225    //             len: 0,
226    //             font: font("Helvetica").bold(),
227    //             color: Default::default(),
228    //             underline: Default::default(),
229    //         };
230
231    //         impl TextRun {
232    //             fn with_len(&self, len: usize) -> Self {
233    //                 let mut this = self.clone();
234    //                 this.len = len;
235    //                 this
236    //             }
237    //         }
238
239    //         let text = "aa bbb cccc ddddd eeee".into();
240    //         let lines = text_system
241    //             .layout_text(
242    //                 &text,
243    //                 px(16.),
244    //                 &[
245    //                     normal.with_len(4),
246    //                     bold.with_len(5),
247    //                     normal.with_len(6),
248    //                     bold.with_len(1),
249    //                     normal.with_len(7),
250    //                 ],
251    //                 None,
252    //             )
253    //             .unwrap();
254    //         let line = &lines[0];
255
256    //         let mut wrapper = LineWrapper::new(
257    //             text_system.font_id(&normal.font).unwrap(),
258    //             px(16.),
259    //             text_system.platform_text_system.clone(),
260    //         );
261    //         assert_eq!(
262    //             wrapper
263    //                 .wrap_shaped_line(&text, &line, px(72.))
264    //                 .collect::<Vec<_>>(),
265    //             &[
266    //                 ShapedBoundary {
267    //                     run_ix: 1,
268    //                     glyph_ix: 3
269    //                 },
270    //                 ShapedBoundary {
271    //                     run_ix: 2,
272    //                     glyph_ix: 3
273    //                 },
274    //                 ShapedBoundary {
275    //                     run_ix: 4,
276    //                     glyph_ix: 2
277    //                 }
278    //             ],
279    //         );
280    //     });
281    // }
282}