line_wrapper.rs

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