Skip to main content

i_slint_core/textlayout/
linebreaker.rs

1// Copyright © SixtyFPS GmbH <info@slint.dev>
2// SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-Slint-Royalty-free-2.0 OR LicenseRef-Slint-Software-3.0
3
4// cSpell: ignore ellow orld Hellow
5
6use core::ops::Range;
7
8use euclid::num::Zero;
9
10use crate::items::TextWrap;
11
12use super::fragments::{TextFragment, TextFragmentIterator};
13use super::{CheckedAdd, ShapeBuffer, TextShaper};
14
15#[derive(Clone, Default, Debug)]
16pub struct TextLine<Length: Default + Clone> {
17    // The range excludes trailing whitespace
18    pub byte_range: Range<usize>,
19    // number of bytes in text after byte_range occupied by trailing whitespace
20    pub trailing_whitespace_bytes: usize,
21    pub(crate) glyph_range: Range<usize>,
22    trailing_whitespace: Length,
23    pub(crate) text_width: Length, // with as occupied by the glyphs
24}
25
26impl<Length: Default + Copy + Clone + Zero + CheckedAdd + core::cmp::PartialOrd> TextLine<Length> {
27    pub fn line_text<'a>(&self, paragraph: &'a str) -> &'a str {
28        &paragraph[self.byte_range.clone()]
29    }
30
31    pub fn width_including_trailing_whitespace(&self) -> Length {
32        if self.text_width > Length::zero() {
33            self.text_width.saturating_add(self.trailing_whitespace)
34        } else {
35            Length::zero()
36        }
37    }
38
39    pub fn is_empty(&self) -> bool {
40        self.byte_range.is_empty()
41    }
42}
43
44impl<Length: Clone + Copy + Default + CheckedAdd + core::ops::AddAssign> TextLine<Length> {
45    pub fn add_fragment(&mut self, fragment: &TextFragment<Length>) {
46        if self.byte_range.is_empty() {
47            self.byte_range = fragment.byte_range.clone();
48        } else if !fragment.byte_range.is_empty() {
49            self.byte_range.end = fragment.byte_range.end;
50        }
51        if self.glyph_range.is_empty() {
52            self.glyph_range = fragment.glyph_range.clone();
53        } else {
54            self.glyph_range.end = fragment.glyph_range.end;
55        }
56        if !fragment.byte_range.is_empty() {
57            self.text_width = self.text_width.saturating_add(self.trailing_whitespace);
58            self.trailing_whitespace = Length::default();
59            self.trailing_whitespace_bytes = 0;
60        }
61        self.text_width = self.text_width.saturating_add(fragment.width);
62        self.trailing_whitespace =
63            self.trailing_whitespace.saturating_add(fragment.trailing_whitespace_width);
64        self.trailing_whitespace_bytes += fragment.trailing_whitespace_bytes;
65    }
66}
67
68pub struct TextLineBreaker<'a, Font: TextShaper> {
69    fragments: TextFragmentIterator<'a, Font::Length>,
70    available_width: Option<Font::Length>,
71    current_line: TextLine<Font::Length>,
72    num_emitted_lines: usize,
73    mandatory_line_break_on_next_iteration: bool,
74    max_lines: Option<usize>,
75    text_wrap: TextWrap,
76    done: bool,
77}
78
79impl<'a, Font: TextShaper> TextLineBreaker<'a, Font> {
80    pub fn new(
81        text: &'a str,
82        shape_buffer: &'a ShapeBuffer<Font::Length>,
83        available_width: Option<Font::Length>,
84        max_lines: Option<usize>,
85        text_wrap: TextWrap,
86    ) -> Self {
87        Self {
88            fragments: TextFragmentIterator::new(text, shape_buffer),
89            available_width,
90            current_line: Default::default(),
91            num_emitted_lines: 0,
92            mandatory_line_break_on_next_iteration: false,
93            max_lines,
94            text_wrap,
95            done: false,
96        }
97    }
98}
99
100impl<Font: TextShaper> Iterator for TextLineBreaker<'_, Font> {
101    type Item = TextLine<Font::Length>;
102
103    fn next(&mut self) -> Option<Self::Item> {
104        if self.done {
105            return None;
106        }
107
108        if let Some(max_lines) = self.max_lines
109            && self.num_emitted_lines >= max_lines
110        {
111            return None;
112        }
113
114        if core::mem::take(&mut self.mandatory_line_break_on_next_iteration) {
115            self.num_emitted_lines += 1;
116            return Some(core::mem::take(&mut self.current_line));
117        }
118
119        self.fragments.break_anywhere = false;
120
121        let mut next_line = loop {
122            // Clone the fragment iterator so that we can roll back in case we must break down the first
123            // word with `break_anywhere = true`.
124            let mut fragments = self.fragments.clone();
125
126            let fragment = match fragments.next() {
127                Some(fragment) => fragment,
128                None => {
129                    break None;
130                }
131            };
132
133            // As trailing_mandatory_break is only set if break_anywhere is false, the fragment must
134            // be first processed with break_anywhere = false and if no mandatory break is found, the
135            // loop is re-run with break_anywhere = true when using CharWrap.
136            if self.text_wrap == TextWrap::CharWrap
137                && !fragment.trailing_mandatory_break
138                && !self.fragments.break_anywhere
139            {
140                self.fragments.break_anywhere = true;
141                continue;
142            }
143
144            if let Some(available_width) = self.available_width
145                && self.current_line.width_including_trailing_whitespace() + fragment.width
146                    > available_width
147            {
148                if self.current_line.is_empty() {
149                    if !self.fragments.break_anywhere {
150                        // Try again but break anywhere this time. self.fragments is cloned at the beginning
151                        // of the loop.
152                        self.fragments.break_anywhere = true;
153                        continue;
154                    } else {
155                        // Even if we allow to break anywhere, there is still no room for the next fragment.
156                        // Just use it anywhere otherwise we would return many empty lines
157                        self.fragments = fragments;
158                        self.current_line.add_fragment(&fragment);
159                        break Some(core::mem::take(&mut self.current_line));
160                    }
161                }
162
163                let next_line = core::mem::take(&mut self.current_line);
164                self.mandatory_line_break_on_next_iteration = fragment.trailing_mandatory_break;
165
166                if self.text_wrap != TextWrap::CharWrap
167                    && !fragments.break_anywhere
168                    && fragment.width < available_width
169                {
170                    self.current_line.add_fragment(&fragment);
171                    self.fragments = fragments;
172                }
173
174                break Some(next_line);
175            };
176
177            self.fragments = fragments;
178            self.current_line.add_fragment(&fragment);
179
180            // The fragment reached the width the coordinate type can represent; the rest of the
181            // line is off any viewport, so emit what we have and stop.
182            if self.fragments.truncated {
183                self.done = true;
184                break Some(core::mem::take(&mut self.current_line));
185            }
186
187            if fragment.trailing_mandatory_break {
188                break Some(core::mem::take(&mut self.current_line));
189            }
190        };
191
192        // Emit at least one single line
193        if next_line.is_none()
194            && (!self.current_line.byte_range.is_empty() || self.num_emitted_lines == 0)
195        {
196            next_line = Some(core::mem::take(&mut self.current_line));
197        }
198
199        if next_line.is_some() {
200            self.num_emitted_lines += 1;
201        }
202
203        next_line
204    }
205}
206
207#[cfg(test)]
208use super::{FixedTestFont, TextLayout};
209
210#[test]
211fn test_empty_line_break() {
212    let font = FixedTestFont;
213    let text = "";
214    let shape_buffer = ShapeBuffer::new(
215        &TextLayout { font: &font, letter_spacing: None, line_height: None },
216        text,
217    );
218    let lines = TextLineBreaker::<FixedTestFont>::new(
219        text,
220        &shape_buffer,
221        Some(50.),
222        None,
223        TextWrap::WordWrap,
224    )
225    .collect::<std::vec::Vec<_>>();
226    assert_eq!(lines.len(), 1);
227    assert_eq!(lines[0].line_text(text), "");
228}
229
230#[test]
231fn test_basic_line_break_char_wrap() {
232    // The available width is half-way into the next word
233    let font = FixedTestFont;
234    let text = "Hello World";
235    let shape_buffer = ShapeBuffer::new(
236        &TextLayout { font: &font, letter_spacing: None, line_height: None },
237        text,
238    );
239    let lines = TextLineBreaker::<FixedTestFont>::new(
240        text,
241        &shape_buffer,
242        Some(80.),
243        None,
244        TextWrap::CharWrap,
245    )
246    .collect::<std::vec::Vec<_>>();
247    assert_eq!(lines.len(), 2);
248    assert_eq!(lines[0].line_text(text), "Hello Wo");
249    assert_eq!(lines[1].line_text(text), "rld");
250}
251
252#[test]
253fn test_basic_line_break() {
254    let font = FixedTestFont;
255    let text = "Hello World";
256    let shape_buffer = ShapeBuffer::new(
257        &TextLayout { font: &font, letter_spacing: None, line_height: None },
258        text,
259    );
260    let lines = TextLineBreaker::<FixedTestFont>::new(
261        text,
262        &shape_buffer,
263        Some(50.),
264        None,
265        TextWrap::WordWrap,
266    )
267    .collect::<std::vec::Vec<_>>();
268    assert_eq!(lines.len(), 2);
269    assert_eq!(lines[0].line_text(text), "Hello");
270    assert_eq!(lines[1].line_text(text), "World");
271}
272
273#[test]
274fn test_basic_line_break_max_lines() {
275    let font = FixedTestFont;
276    let text = "Hello World";
277    let shape_buffer = ShapeBuffer::new(
278        &TextLayout { font: &font, letter_spacing: None, line_height: None },
279        text,
280    );
281    let lines = TextLineBreaker::<FixedTestFont>::new(
282        text,
283        &shape_buffer,
284        Some(50.),
285        Some(1),
286        TextWrap::WordWrap,
287    )
288    .collect::<std::vec::Vec<_>>();
289    assert_eq!(lines.len(), 1);
290    assert_eq!(lines[0].line_text(text), "Hello");
291}
292
293#[test]
294fn test_linebreak_trailing_space() {
295    let font = FixedTestFont;
296    let text = "Hello              ";
297    let shape_buffer = ShapeBuffer::new(
298        &TextLayout { font: &font, letter_spacing: None, line_height: None },
299        text,
300    );
301    let lines = TextLineBreaker::<FixedTestFont>::new(
302        text,
303        &shape_buffer,
304        Some(50.),
305        None,
306        TextWrap::WordWrap,
307    )
308    .collect::<std::vec::Vec<_>>();
309    assert_eq!(lines.len(), 1);
310    assert_eq!(lines[0].line_text(text), "Hello");
311}
312
313#[test]
314fn test_forced_break() {
315    let font = FixedTestFont;
316    let text = "Hello\nWorld";
317    let shape_buffer = ShapeBuffer::new(
318        &TextLayout { font: &font, letter_spacing: None, line_height: None },
319        text,
320    );
321    let lines =
322        TextLineBreaker::<FixedTestFont>::new(text, &shape_buffer, None, None, TextWrap::WordWrap)
323            .collect::<std::vec::Vec<_>>();
324    assert_eq!(lines.len(), 2);
325    assert_eq!(lines[0].line_text(text), "Hello");
326    assert_eq!(lines[1].line_text(text), "World");
327}
328
329#[test]
330fn test_forced_break_multi() {
331    let font = FixedTestFont;
332    let text = "Hello\n\n\nWorld";
333    let shape_buffer = ShapeBuffer::new(
334        &TextLayout { font: &font, letter_spacing: None, line_height: None },
335        text,
336    );
337    let lines =
338        TextLineBreaker::<FixedTestFont>::new(text, &shape_buffer, None, None, TextWrap::WordWrap)
339            .collect::<std::vec::Vec<_>>();
340    assert_eq!(lines.len(), 4);
341    assert_eq!(lines[0].line_text(text), "Hello");
342    assert_eq!(lines[1].line_text(text), "");
343    assert_eq!(lines[2].line_text(text), "");
344    assert_eq!(lines[3].line_text(text), "World");
345}
346
347#[test]
348fn test_forced_break_multi_char_wrap() {
349    let font = FixedTestFont;
350    let text = "Hello\n\n\nWorld";
351    let shape_buffer = ShapeBuffer::new(
352        &TextLayout { font: &font, letter_spacing: None, line_height: None },
353        text,
354    );
355    let lines = TextLineBreaker::<FixedTestFont>::new(
356        text,
357        &shape_buffer,
358        Some(30.),
359        None,
360        TextWrap::CharWrap,
361    )
362    .collect::<std::vec::Vec<_>>();
363    assert_eq!(lines.len(), 6);
364    assert_eq!(lines[0].line_text(text), "Hel");
365    assert_eq!(lines[1].line_text(text), "lo");
366    assert_eq!(lines[2].line_text(text), "");
367    assert_eq!(lines[3].line_text(text), "");
368    assert_eq!(lines[4].line_text(text), "Wor");
369    assert_eq!(lines[5].line_text(text), "ld");
370}
371
372#[test]
373fn test_forced_break_max_lines() {
374    let font = FixedTestFont;
375    let text = "Hello\n\n\nWorld";
376    let shape_buffer = ShapeBuffer::new(
377        &TextLayout { font: &font, letter_spacing: None, line_height: None },
378        text,
379    );
380    let lines = TextLineBreaker::<FixedTestFont>::new(
381        text,
382        &shape_buffer,
383        None,
384        Some(2),
385        TextWrap::WordWrap,
386    )
387    .collect::<std::vec::Vec<_>>();
388    assert_eq!(lines.len(), 2);
389    assert_eq!(lines[0].line_text(text), "Hello");
390    assert_eq!(lines[1].line_text(text), "");
391}
392
393#[test]
394fn test_nbsp_break() {
395    let font = FixedTestFont;
396    let text = "Ok Hello\u{00a0}World";
397    let shape_buffer = ShapeBuffer::new(
398        &TextLayout { font: &font, letter_spacing: None, line_height: None },
399        text,
400    );
401    let lines = TextLineBreaker::<FixedTestFont>::new(
402        text,
403        &shape_buffer,
404        Some(110.),
405        None,
406        TextWrap::WordWrap,
407    )
408    .collect::<std::vec::Vec<_>>();
409    assert_eq!(lines.len(), 2);
410    assert_eq!(lines[0].line_text(text), "Ok");
411    assert_eq!(lines[1].line_text(text), "Hello\u{00a0}World");
412}
413
414#[test]
415fn test_single_line_multi_break_opportunity() {
416    let font = FixedTestFont;
417    let text = "a b c";
418    let shape_buffer = ShapeBuffer::new(
419        &TextLayout { font: &font, letter_spacing: None, line_height: None },
420        text,
421    );
422    let lines =
423        TextLineBreaker::<FixedTestFont>::new(text, &shape_buffer, None, None, TextWrap::WordWrap)
424            .collect::<std::vec::Vec<_>>();
425    assert_eq!(lines.len(), 1);
426    assert_eq!(lines[0].line_text(text), "a b c");
427}
428
429#[test]
430fn test_basic_line_break_anywhere_fallback() {
431    let font = FixedTestFont;
432    let text = "HelloWorld";
433    let shape_buffer = ShapeBuffer::new(
434        &TextLayout { font: &font, letter_spacing: None, line_height: None },
435        text,
436    );
437    let lines = TextLineBreaker::<FixedTestFont>::new(
438        text,
439        &shape_buffer,
440        Some(50.),
441        None,
442        TextWrap::WordWrap,
443    )
444    .collect::<std::vec::Vec<_>>();
445    assert_eq!(lines.len(), 2);
446    assert_eq!(lines[0].line_text(text), "Hello");
447    assert_eq!(lines[1].line_text(text), "World");
448}
449
450#[test]
451fn test_basic_line_break_anywhere_fallback_multi_line() {
452    let font = FixedTestFont;
453    let text = "HelloWorld\nHelloWorld";
454    let shape_buffer = ShapeBuffer::new(
455        &TextLayout { font: &font, letter_spacing: None, line_height: None },
456        text,
457    );
458    let lines = TextLineBreaker::<FixedTestFont>::new(
459        text,
460        &shape_buffer,
461        Some(50.),
462        None,
463        TextWrap::WordWrap,
464    )
465    .collect::<std::vec::Vec<_>>();
466    assert_eq!(lines.len(), 4);
467    assert_eq!(lines[0].line_text(text), "Hello");
468    assert_eq!(lines[1].line_text(text), "World");
469    assert_eq!(lines[2].line_text(text), "Hello");
470    assert_eq!(lines[3].line_text(text), "World");
471}
472
473#[test]
474fn test_basic_line_break_anywhere_fallback_multi_line_char_wrap() {
475    let font = FixedTestFont;
476    let text = "HelloWorld\nHelloWorld";
477    let shape_buffer = ShapeBuffer::new(
478        &TextLayout { font: &font, letter_spacing: None, line_height: None },
479        text,
480    );
481    let lines = TextLineBreaker::<FixedTestFont>::new(
482        text,
483        &shape_buffer,
484        Some(50.),
485        None,
486        TextWrap::CharWrap,
487    )
488    .collect::<std::vec::Vec<_>>();
489    assert_eq!(lines.len(), 4);
490    assert_eq!(lines[0].line_text(text), "Hello");
491    assert_eq!(lines[1].line_text(text), "World");
492    assert_eq!(lines[2].line_text(text), "Hello");
493    assert_eq!(lines[3].line_text(text), "World");
494}
495
496#[test]
497fn test_basic_line_break_anywhere_fallback_multi_line_v2() {
498    let font = FixedTestFont;
499    let text = "HelloW orldHellow";
500    let shape_buffer = ShapeBuffer::new(
501        &TextLayout { font: &font, letter_spacing: None, line_height: None },
502        text,
503    );
504    let lines = TextLineBreaker::<FixedTestFont>::new(
505        text,
506        &shape_buffer,
507        Some(50.),
508        None,
509        TextWrap::WordWrap,
510    )
511    .collect::<std::vec::Vec<_>>();
512    assert_eq!(lines.len(), 4);
513    assert_eq!(lines[0].line_text(text), "Hello");
514    assert_eq!(lines[1].line_text(text), "W");
515    assert_eq!(lines[2].line_text(text), "orldH");
516    assert_eq!(lines[3].line_text(text), "ellow");
517}
518
519#[test]
520fn test_basic_line_break_anywhere_fallback_max_lines() {
521    let font = FixedTestFont;
522    let text = "HelloW orldHellow";
523    let shape_buffer = ShapeBuffer::new(
524        &TextLayout { font: &font, letter_spacing: None, line_height: None },
525        text,
526    );
527    let lines = TextLineBreaker::<FixedTestFont>::new(
528        text,
529        &shape_buffer,
530        Some(50.),
531        Some(3),
532        TextWrap::WordWrap,
533    )
534    .collect::<std::vec::Vec<_>>();
535    assert_eq!(lines.len(), 3);
536    assert_eq!(lines[0].line_text(text), "Hello");
537    assert_eq!(lines[1].line_text(text), "W");
538    assert_eq!(lines[2].line_text(text), "orldH");
539}
540
541#[test]
542fn test_basic_line_break_space() {
543    // The available width is half-way into the trailing "W"
544    let font = FixedTestFont;
545    let text = "H W";
546    let shape_buffer = ShapeBuffer::new(
547        &TextLayout { font: &font, letter_spacing: None, line_height: None },
548        text,
549    );
550    let lines = TextLineBreaker::<FixedTestFont>::new(
551        text,
552        &shape_buffer,
553        Some(25.),
554        None,
555        TextWrap::WordWrap,
556    )
557    .collect::<std::vec::Vec<_>>();
558    assert_eq!(lines.len(), 2);
559    assert_eq!(lines[0].line_text(text), "H");
560    assert_eq!(lines[1].line_text(text), "W");
561}
562
563#[test]
564fn test_basic_line_break_space_char_wrap() {
565    // The available width is half-way into the trailing "W"
566    let font = FixedTestFont;
567    let text = "H W";
568    let shape_buffer = ShapeBuffer::new(
569        &TextLayout { font: &font, letter_spacing: None, line_height: None },
570        text,
571    );
572    let lines = TextLineBreaker::<FixedTestFont>::new(
573        text,
574        &shape_buffer,
575        Some(25.),
576        None,
577        TextWrap::CharWrap,
578    )
579    .collect::<std::vec::Vec<_>>();
580    assert_eq!(lines.len(), 2);
581    assert_eq!(lines[0].line_text(text), "H");
582    assert_eq!(lines[1].line_text(text), "W");
583}
584
585#[test]
586fn test_basic_line_break_space_v2() {
587    // The available width is half-way into the trailing "W"
588    let font = FixedTestFont;
589    let text = "B B W";
590    let shape_buffer = ShapeBuffer::new(
591        &TextLayout { font: &font, letter_spacing: None, line_height: None },
592        text,
593    );
594    let lines = TextLineBreaker::<FixedTestFont>::new(
595        text,
596        &shape_buffer,
597        Some(45.),
598        None,
599        TextWrap::WordWrap,
600    )
601    .collect::<std::vec::Vec<_>>();
602    assert_eq!(lines.len(), 2);
603    assert_eq!(lines[0].line_text(text), "B B");
604    assert_eq!(lines[1].line_text(text), "W");
605}
606
607#[test]
608fn test_basic_line_break_space_v3() {
609    // The available width is half-way into the trailing "W"
610    let font = FixedTestFont;
611    let text = "H   W";
612    let shape_buffer = ShapeBuffer::new(
613        &TextLayout { font: &font, letter_spacing: None, line_height: None },
614        text,
615    );
616    let lines = TextLineBreaker::<FixedTestFont>::new(
617        text,
618        &shape_buffer,
619        Some(15.),
620        None,
621        TextWrap::WordWrap,
622    )
623    .collect::<std::vec::Vec<_>>();
624    assert_eq!(lines.len(), 2);
625    assert_eq!(lines[0].line_text(text), "H");
626    assert_eq!(lines[1].line_text(text), "W");
627}
628
629#[test]
630fn test_basic_line_break_space_v4() {
631    // The available width is half-way into the trailing space
632    let font = FixedTestFont;
633    let text = "H W  H  ";
634    let shape_buffer = ShapeBuffer::new(
635        &TextLayout { font: &font, letter_spacing: None, line_height: None },
636        text,
637    );
638    let lines = TextLineBreaker::<FixedTestFont>::new(
639        text,
640        &shape_buffer,
641        Some(65.),
642        None,
643        TextWrap::WordWrap,
644    )
645    .collect::<std::vec::Vec<_>>();
646    assert_eq!(lines.len(), 1);
647    assert_eq!(lines[0].line_text(text), "H W  H");
648}
649
650#[test]
651fn test_line_width_with_whitespace() {
652    let font = FixedTestFont;
653    let text = "Hello World";
654    let shape_buffer = ShapeBuffer::new(
655        &TextLayout { font: &font, letter_spacing: None, line_height: None },
656        text,
657    );
658    let lines = TextLineBreaker::<FixedTestFont>::new(
659        text,
660        &shape_buffer,
661        Some(200.),
662        None,
663        TextWrap::WordWrap,
664    )
665    .collect::<std::vec::Vec<_>>();
666    assert_eq!(lines.len(), 1);
667    assert_eq!(lines[0].text_width, text.len() as f32 * 10.);
668}
669
670#[test]
671fn zero_width() {
672    let font = FixedTestFont;
673    let text = "He\nHe o";
674    let shape_buffer = ShapeBuffer::new(
675        &TextLayout { font: &font, letter_spacing: None, line_height: None },
676        text,
677    );
678    let lines = TextLineBreaker::<FixedTestFont>::new(
679        text,
680        &shape_buffer,
681        Some(0.0001),
682        None,
683        TextWrap::WordWrap,
684    )
685    .map(|t| t.line_text(text))
686    .collect::<std::vec::Vec<_>>();
687    assert_eq!(lines, ["H", "e", "", "H", "e", "o"]);
688}
689
690#[test]
691fn zero_width_char_wrap() {
692    let font = FixedTestFont;
693    let text = "He\nHe o";
694    let shape_buffer = ShapeBuffer::new(
695        &TextLayout { font: &font, letter_spacing: None, line_height: None },
696        text,
697    );
698    let lines = TextLineBreaker::<FixedTestFont>::new(
699        text,
700        &shape_buffer,
701        Some(0.0001),
702        None,
703        TextWrap::CharWrap,
704    )
705    .map(|t| t.line_text(text))
706    .collect::<std::vec::Vec<_>>();
707    assert_eq!(lines, ["H", "e", "", "H", "e", "o"]);
708}
709
710#[test]
711fn char_wrap_sentences() {
712    let font = FixedTestFont;
713    let text = "Hello world\nHow are you?";
714    let shape_buffer = ShapeBuffer::new(
715        &TextLayout { font: &font, letter_spacing: None, line_height: None },
716        text,
717    );
718    let lines = TextLineBreaker::<FixedTestFont>::new(
719        text,
720        &shape_buffer,
721        Some(80.),
722        None,
723        TextWrap::CharWrap,
724    )
725    .map(|t| t.line_text(text))
726    .collect::<std::vec::Vec<_>>();
727    assert_eq!(lines, ["Hello wo", "rld", "How are", "you?"]);
728}