line_wrapper.rs

  1use super::FontId;
  2use crate::{px, Line, Pixels, PlatformTextSystem, RunStyle, ShapedBoundary};
  3use collections::HashMap;
  4use std::{iter, sync::Arc};
  5
  6pub struct LineWrapper {
  7    text_system: Arc<dyn PlatformTextSystem>,
  8    pub(crate) font_id: FontId,
  9    pub(crate) font_size: Pixels,
 10    cached_ascii_char_widths: [Option<Pixels>; 128],
 11    cached_other_char_widths: HashMap<char, Pixels>,
 12}
 13
 14impl LineWrapper {
 15    pub const MAX_INDENT: u32 = 256;
 16
 17    pub fn new(
 18        font_id: FontId,
 19        font_size: Pixels,
 20        text_system: Arc<dyn PlatformTextSystem>,
 21    ) -> Self {
 22        Self {
 23            text_system,
 24            font_id,
 25            font_size,
 26            cached_ascii_char_widths: [None; 128],
 27            cached_other_char_widths: HashMap::default(),
 28        }
 29    }
 30
 31    pub fn wrap_line<'a>(
 32        &'a mut self,
 33        line: &'a str,
 34        wrap_width: Pixels,
 35    ) -> impl Iterator<Item = Boundary> + 'a {
 36        let mut width = px(0.);
 37        let mut first_non_whitespace_ix = None;
 38        let mut indent = None;
 39        let mut last_candidate_ix = 0;
 40        let mut last_candidate_width = px(0.);
 41        let mut last_wrap_ix = 0;
 42        let mut prev_c = '\0';
 43        let mut char_indices = line.char_indices();
 44        iter::from_fn(move || {
 45            for (ix, c) in char_indices.by_ref() {
 46                if c == '\n' {
 47                    continue;
 48                }
 49
 50                if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
 51                    last_candidate_ix = ix;
 52                    last_candidate_width = width;
 53                }
 54
 55                if c != ' ' && first_non_whitespace_ix.is_none() {
 56                    first_non_whitespace_ix = Some(ix);
 57                }
 58
 59                let char_width = self.width_for_char(c);
 60                width += char_width;
 61                if width > wrap_width && ix > last_wrap_ix {
 62                    if let (None, Some(first_non_whitespace_ix)) = (indent, first_non_whitespace_ix)
 63                    {
 64                        indent = Some(
 65                            Self::MAX_INDENT.min((first_non_whitespace_ix - last_wrap_ix) as u32),
 66                        );
 67                    }
 68
 69                    if last_candidate_ix > 0 {
 70                        last_wrap_ix = last_candidate_ix;
 71                        width -= last_candidate_width;
 72                        last_candidate_ix = 0;
 73                    } else {
 74                        last_wrap_ix = ix;
 75                        width = char_width;
 76                    }
 77
 78                    if let Some(indent) = indent {
 79                        width += self.width_for_char(' ') * indent as f32;
 80                    }
 81
 82                    return Some(Boundary::new(last_wrap_ix, indent.unwrap_or(0)));
 83                }
 84                prev_c = c;
 85            }
 86
 87            None
 88        })
 89    }
 90
 91    pub fn wrap_shaped_line<'a>(
 92        &'a mut self,
 93        str: &'a str,
 94        line: &'a Line,
 95        wrap_width: Pixels,
 96    ) -> impl Iterator<Item = ShapedBoundary> + 'a {
 97        let mut first_non_whitespace_ix = None;
 98        let mut last_candidate_ix = None;
 99        let mut last_candidate_x = px(0.);
100        let mut last_wrap_ix = ShapedBoundary {
101            run_ix: 0,
102            glyph_ix: 0,
103        };
104        let mut last_wrap_x = px(0.);
105        let mut prev_c = '\0';
106        let mut glyphs = line
107            .runs()
108            .iter()
109            .enumerate()
110            .flat_map(move |(run_ix, run)| {
111                run.glyphs()
112                    .iter()
113                    .enumerate()
114                    .map(move |(glyph_ix, glyph)| {
115                        let character = str[glyph.index..].chars().next().unwrap();
116                        (
117                            ShapedBoundary { run_ix, glyph_ix },
118                            character,
119                            glyph.position.x,
120                        )
121                    })
122            })
123            .peekable();
124
125        iter::from_fn(move || {
126            while let Some((ix, c, x)) = glyphs.next() {
127                if c == '\n' {
128                    continue;
129                }
130
131                if self.is_boundary(prev_c, c) && first_non_whitespace_ix.is_some() {
132                    last_candidate_ix = Some(ix);
133                    last_candidate_x = x;
134                }
135
136                if c != ' ' && first_non_whitespace_ix.is_none() {
137                    first_non_whitespace_ix = Some(ix);
138                }
139
140                let next_x = glyphs.peek().map_or(line.width(), |(_, _, x)| *x);
141                let width = next_x - last_wrap_x;
142                if width > wrap_width && ix > last_wrap_ix {
143                    if let Some(last_candidate_ix) = last_candidate_ix.take() {
144                        last_wrap_ix = last_candidate_ix;
145                        last_wrap_x = last_candidate_x;
146                    } else {
147                        last_wrap_ix = ix;
148                        last_wrap_x = x;
149                    }
150
151                    return Some(last_wrap_ix);
152                }
153                prev_c = c;
154            }
155
156            None
157        })
158    }
159
160    fn is_boundary(&self, prev: char, next: char) -> bool {
161        (prev == ' ') && (next != ' ')
162    }
163
164    #[inline(always)]
165    fn width_for_char(&mut self, c: char) -> Pixels {
166        if (c as u32) < 128 {
167            if let Some(cached_width) = self.cached_ascii_char_widths[c as usize] {
168                cached_width
169            } else {
170                let width = self.compute_width_for_char(c);
171                self.cached_ascii_char_widths[c as usize] = Some(width);
172                width
173            }
174        } else {
175            if let Some(cached_width) = self.cached_other_char_widths.get(&c) {
176                *cached_width
177            } else {
178                let width = self.compute_width_for_char(c);
179                self.cached_other_char_widths.insert(c, width);
180                width
181            }
182        }
183    }
184
185    fn compute_width_for_char(&self, c: char) -> Pixels {
186        self.text_system
187            .layout_line(
188                &c.to_string(),
189                self.font_size,
190                &[(
191                    1,
192                    RunStyle {
193                        font_id: self.font_id,
194                        color: Default::default(),
195                        underline: Default::default(),
196                    },
197                )],
198            )
199            .width
200    }
201}
202
203#[derive(Copy, Clone, Debug, PartialEq, Eq)]
204pub struct Boundary {
205    pub ix: usize,
206    pub next_indent: u32,
207}
208
209impl Boundary {
210    fn new(ix: usize, next_indent: u32) -> Self {
211        Self { ix, next_indent }
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use crate::{AppContext, FontWeight};
219
220    #[test]
221    fn test_wrap_line() {
222        let cx = AppContext::test();
223
224        let text_system = cx.text_system().clone();
225        let family = text_system
226            .load_font_family(&["Courier"], &Default::default())
227            .unwrap();
228        let font_id = text_system
229            .select_font(family, Default::default(), Default::default())
230            .unwrap();
231
232        let mut wrapper =
233            LineWrapper::new(font_id, px(16.), text_system.platform_text_system.clone());
234        assert_eq!(
235            wrapper
236                .wrap_line("aa bbb cccc ddddd eeee", px(72.))
237                .collect::<Vec<_>>(),
238            &[
239                Boundary::new(7, 0),
240                Boundary::new(12, 0),
241                Boundary::new(18, 0)
242            ],
243        );
244        assert_eq!(
245            wrapper
246                .wrap_line("aaa aaaaaaaaaaaaaaaaaa", px(72.0))
247                .collect::<Vec<_>>(),
248            &[
249                Boundary::new(4, 0),
250                Boundary::new(11, 0),
251                Boundary::new(18, 0)
252            ],
253        );
254        assert_eq!(
255            wrapper
256                .wrap_line("     aaaaaaa", px(72.))
257                .collect::<Vec<_>>(),
258            &[
259                Boundary::new(7, 5),
260                Boundary::new(9, 5),
261                Boundary::new(11, 5),
262            ]
263        );
264        assert_eq!(
265            wrapper
266                .wrap_line("                            ", px(72.))
267                .collect::<Vec<_>>(),
268            &[
269                Boundary::new(7, 0),
270                Boundary::new(14, 0),
271                Boundary::new(21, 0)
272            ]
273        );
274        assert_eq!(
275            wrapper
276                .wrap_line("          aaaaaaaaaaaaaa", px(72.))
277                .collect::<Vec<_>>(),
278            &[
279                Boundary::new(7, 0),
280                Boundary::new(14, 3),
281                Boundary::new(18, 3),
282                Boundary::new(22, 3),
283            ]
284        );
285    }
286
287    // todo! repeat this test
288    #[test]
289    fn test_wrap_shaped_line() {
290        let cx = AppContext::test();
291        let text_system = cx.text_system().clone();
292
293        let family = text_system
294            .load_font_family(&["Helvetica"], &Default::default())
295            .unwrap();
296        let font_id = text_system
297            .select_font(family, Default::default(), Default::default())
298            .unwrap();
299        let normal = RunStyle {
300            font_id,
301            color: Default::default(),
302            underline: Default::default(),
303        };
304        let bold = RunStyle {
305            font_id: text_system
306                .select_font(family, FontWeight::BOLD, Default::default())
307                .unwrap(),
308            color: Default::default(),
309            underline: Default::default(),
310        };
311
312        let text = "aa bbb cccc ddddd eeee";
313        let line = text_system.layout_str(
314            text,
315            px(16.),
316            &[
317                (4, normal.clone()),
318                (5, bold.clone()),
319                (6, normal.clone()),
320                (1, bold),
321                (7, normal),
322            ],
323        );
324
325        let mut wrapper =
326            LineWrapper::new(font_id, px(16.), text_system.platform_text_system.clone());
327        assert_eq!(
328            wrapper
329                .wrap_shaped_line(text, &line, px(72.))
330                .collect::<Vec<_>>(),
331            &[
332                ShapedBoundary {
333                    run_ix: 1,
334                    glyph_ix: 3
335                },
336                ShapedBoundary {
337                    run_ix: 2,
338                    glyph_ix: 3
339                },
340                ShapedBoundary {
341                    run_ix: 4,
342                    glyph_ix: 2
343                }
344            ],
345        );
346    }
347}