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