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