1use 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 pub byte_range: Range<usize>,
19 pub trailing_whitespace_bytes: usize,
21 pub(crate) glyph_range: Range<usize>,
22 trailing_whitespace: Length,
23 pub(crate) text_width: Length, }
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 ¶graph[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 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 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 self.fragments.break_anywhere = true;
153 continue;
154 } else {
155 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 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 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 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 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 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 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 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 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}