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