line_wrapper.rs

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