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 if self.num_emitted_lines >= max_lines {
104 return None;
105 }
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 if 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
172 self.fragments = fragments;
173 self.current_line.add_fragment(&fragment);
174
175 if fragment.trailing_mandatory_break {
176 break Some(core::mem::take(&mut self.current_line));
177 }
178 };
179
180 if next_line.is_none()
182 && (!self.current_line.byte_range.is_empty() || self.num_emitted_lines == 0)
183 {
184 next_line = Some(core::mem::take(&mut self.current_line));
185 }
186
187 if next_line.is_some() {
188 self.num_emitted_lines += 1;
189 }
190
191 next_line
192 }
193}
194
195#[cfg(test)]
196use super::{FixedTestFont, TextLayout};
197
198#[test]
199fn test_empty_line_break() {
200 let font = FixedTestFont;
201 let text = "";
202 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
203 let lines = TextLineBreaker::<FixedTestFont>::new(
204 text,
205 &shape_buffer,
206 Some(50.),
207 None,
208 TextWrap::WordWrap,
209 )
210 .collect::<std::vec::Vec<_>>();
211 assert_eq!(lines.len(), 1);
212 assert_eq!(lines[0].line_text(text), "");
213}
214
215#[test]
216fn test_basic_line_break_char_wrap() {
217 let font = FixedTestFont;
219 let text = "Hello World";
220 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
221 let lines = TextLineBreaker::<FixedTestFont>::new(
222 text,
223 &shape_buffer,
224 Some(80.),
225 None,
226 TextWrap::CharWrap,
227 )
228 .collect::<std::vec::Vec<_>>();
229 assert_eq!(lines.len(), 2);
230 assert_eq!(lines[0].line_text(text), "Hello Wo");
231 assert_eq!(lines[1].line_text(text), "rld");
232}
233
234#[test]
235fn test_basic_line_break() {
236 let font = FixedTestFont;
237 let text = "Hello World";
238 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
239 let lines = TextLineBreaker::<FixedTestFont>::new(
240 text,
241 &shape_buffer,
242 Some(50.),
243 None,
244 TextWrap::WordWrap,
245 )
246 .collect::<std::vec::Vec<_>>();
247 assert_eq!(lines.len(), 2);
248 assert_eq!(lines[0].line_text(text), "Hello");
249 assert_eq!(lines[1].line_text(text), "World");
250}
251
252#[test]
253fn test_basic_line_break_max_lines() {
254 let font = FixedTestFont;
255 let text = "Hello World";
256 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
257 let lines = TextLineBreaker::<FixedTestFont>::new(
258 text,
259 &shape_buffer,
260 Some(50.),
261 Some(1),
262 TextWrap::WordWrap,
263 )
264 .collect::<std::vec::Vec<_>>();
265 assert_eq!(lines.len(), 1);
266 assert_eq!(lines[0].line_text(text), "Hello");
267}
268
269#[test]
270fn test_linebreak_trailing_space() {
271 let font = FixedTestFont;
272 let text = "Hello ";
273 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
274 let lines = TextLineBreaker::<FixedTestFont>::new(
275 text,
276 &shape_buffer,
277 Some(50.),
278 None,
279 TextWrap::WordWrap,
280 )
281 .collect::<std::vec::Vec<_>>();
282 assert_eq!(lines.len(), 1);
283 assert_eq!(lines[0].line_text(text), "Hello");
284}
285
286#[test]
287fn test_forced_break() {
288 let font = FixedTestFont;
289 let text = "Hello\nWorld";
290 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
291 let lines =
292 TextLineBreaker::<FixedTestFont>::new(text, &shape_buffer, None, None, TextWrap::WordWrap)
293 .collect::<std::vec::Vec<_>>();
294 assert_eq!(lines.len(), 2);
295 assert_eq!(lines[0].line_text(text), "Hello");
296 assert_eq!(lines[1].line_text(text), "World");
297}
298
299#[test]
300fn test_forced_break_multi() {
301 let font = FixedTestFont;
302 let text = "Hello\n\n\nWorld";
303 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
304 let lines =
305 TextLineBreaker::<FixedTestFont>::new(text, &shape_buffer, None, None, TextWrap::WordWrap)
306 .collect::<std::vec::Vec<_>>();
307 assert_eq!(lines.len(), 4);
308 assert_eq!(lines[0].line_text(text), "Hello");
309 assert_eq!(lines[1].line_text(text), "");
310 assert_eq!(lines[2].line_text(text), "");
311 assert_eq!(lines[3].line_text(text), "World");
312}
313
314#[test]
315fn test_forced_break_multi_char_wrap() {
316 let font = FixedTestFont;
317 let text = "Hello\n\n\nWorld";
318 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
319 let lines = TextLineBreaker::<FixedTestFont>::new(
320 text,
321 &shape_buffer,
322 Some(30.),
323 None,
324 TextWrap::CharWrap,
325 )
326 .collect::<std::vec::Vec<_>>();
327 assert_eq!(lines.len(), 6);
328 assert_eq!(lines[0].line_text(text), "Hel");
329 assert_eq!(lines[1].line_text(text), "lo");
330 assert_eq!(lines[2].line_text(text), "");
331 assert_eq!(lines[3].line_text(text), "");
332 assert_eq!(lines[4].line_text(text), "Wor");
333 assert_eq!(lines[5].line_text(text), "ld");
334}
335
336#[test]
337fn test_forced_break_max_lines() {
338 let font = FixedTestFont;
339 let text = "Hello\n\n\nWorld";
340 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
341 let lines = TextLineBreaker::<FixedTestFont>::new(
342 text,
343 &shape_buffer,
344 None,
345 Some(2),
346 TextWrap::WordWrap,
347 )
348 .collect::<std::vec::Vec<_>>();
349 assert_eq!(lines.len(), 2);
350 assert_eq!(lines[0].line_text(text), "Hello");
351 assert_eq!(lines[1].line_text(text), "");
352}
353
354#[test]
355fn test_nbsp_break() {
356 let font = FixedTestFont;
357 let text = "Ok Hello\u{00a0}World";
358 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
359 let lines = TextLineBreaker::<FixedTestFont>::new(
360 text,
361 &shape_buffer,
362 Some(110.),
363 None,
364 TextWrap::WordWrap,
365 )
366 .collect::<std::vec::Vec<_>>();
367 assert_eq!(lines.len(), 2);
368 assert_eq!(lines[0].line_text(text), "Ok");
369 assert_eq!(lines[1].line_text(text), "Hello\u{00a0}World");
370}
371
372#[test]
373fn test_single_line_multi_break_opportunity() {
374 let font = FixedTestFont;
375 let text = "a b c";
376 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
377 let lines =
378 TextLineBreaker::<FixedTestFont>::new(text, &shape_buffer, None, None, TextWrap::WordWrap)
379 .collect::<std::vec::Vec<_>>();
380 assert_eq!(lines.len(), 1);
381 assert_eq!(lines[0].line_text(text), "a b c");
382}
383
384#[test]
385fn test_basic_line_break_anywhere_fallback() {
386 let font = FixedTestFont;
387 let text = "HelloWorld";
388 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
389 let lines = TextLineBreaker::<FixedTestFont>::new(
390 text,
391 &shape_buffer,
392 Some(50.),
393 None,
394 TextWrap::WordWrap,
395 )
396 .collect::<std::vec::Vec<_>>();
397 assert_eq!(lines.len(), 2);
398 assert_eq!(lines[0].line_text(text), "Hello");
399 assert_eq!(lines[1].line_text(text), "World");
400}
401
402#[test]
403fn test_basic_line_break_anywhere_fallback_multi_line() {
404 let font = FixedTestFont;
405 let text = "HelloWorld\nHelloWorld";
406 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
407 let lines = TextLineBreaker::<FixedTestFont>::new(
408 text,
409 &shape_buffer,
410 Some(50.),
411 None,
412 TextWrap::WordWrap,
413 )
414 .collect::<std::vec::Vec<_>>();
415 assert_eq!(lines.len(), 4);
416 assert_eq!(lines[0].line_text(text), "Hello");
417 assert_eq!(lines[1].line_text(text), "World");
418 assert_eq!(lines[2].line_text(text), "Hello");
419 assert_eq!(lines[3].line_text(text), "World");
420}
421
422#[test]
423fn test_basic_line_break_anywhere_fallback_multi_line_char_wrap() {
424 let font = FixedTestFont;
425 let text = "HelloWorld\nHelloWorld";
426 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
427 let lines = TextLineBreaker::<FixedTestFont>::new(
428 text,
429 &shape_buffer,
430 Some(50.),
431 None,
432 TextWrap::CharWrap,
433 )
434 .collect::<std::vec::Vec<_>>();
435 assert_eq!(lines.len(), 4);
436 assert_eq!(lines[0].line_text(text), "Hello");
437 assert_eq!(lines[1].line_text(text), "World");
438 assert_eq!(lines[2].line_text(text), "Hello");
439 assert_eq!(lines[3].line_text(text), "World");
440}
441
442#[test]
443fn test_basic_line_break_anywhere_fallback_multi_line_v2() {
444 let font = FixedTestFont;
445 let text = "HelloW orldHellow";
446 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
447 let lines = TextLineBreaker::<FixedTestFont>::new(
448 text,
449 &shape_buffer,
450 Some(50.),
451 None,
452 TextWrap::WordWrap,
453 )
454 .collect::<std::vec::Vec<_>>();
455 assert_eq!(lines.len(), 4);
456 assert_eq!(lines[0].line_text(text), "Hello");
457 assert_eq!(lines[1].line_text(text), "W");
458 assert_eq!(lines[2].line_text(text), "orldH");
459 assert_eq!(lines[3].line_text(text), "ellow");
460}
461
462#[test]
463fn test_basic_line_break_anywhere_fallback_max_lines() {
464 let font = FixedTestFont;
465 let text = "HelloW orldHellow";
466 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
467 let lines = TextLineBreaker::<FixedTestFont>::new(
468 text,
469 &shape_buffer,
470 Some(50.),
471 Some(3),
472 TextWrap::WordWrap,
473 )
474 .collect::<std::vec::Vec<_>>();
475 assert_eq!(lines.len(), 3);
476 assert_eq!(lines[0].line_text(text), "Hello");
477 assert_eq!(lines[1].line_text(text), "W");
478 assert_eq!(lines[2].line_text(text), "orldH");
479}
480
481#[test]
482fn test_basic_line_break_space() {
483 let font = FixedTestFont;
485 let text = "H W";
486 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
487 let lines = TextLineBreaker::<FixedTestFont>::new(
488 text,
489 &shape_buffer,
490 Some(25.),
491 None,
492 TextWrap::WordWrap,
493 )
494 .collect::<std::vec::Vec<_>>();
495 assert_eq!(lines.len(), 2);
496 assert_eq!(lines[0].line_text(text), "H");
497 assert_eq!(lines[1].line_text(text), "W");
498}
499
500#[test]
501fn test_basic_line_break_space_char_wrap() {
502 let font = FixedTestFont;
504 let text = "H W";
505 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
506 let lines = TextLineBreaker::<FixedTestFont>::new(
507 text,
508 &shape_buffer,
509 Some(25.),
510 None,
511 TextWrap::CharWrap,
512 )
513 .collect::<std::vec::Vec<_>>();
514 assert_eq!(lines.len(), 2);
515 assert_eq!(lines[0].line_text(text), "H");
516 assert_eq!(lines[1].line_text(text), "W");
517}
518
519#[test]
520fn test_basic_line_break_space_v2() {
521 let font = FixedTestFont;
523 let text = "B B W";
524 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
525 let lines = TextLineBreaker::<FixedTestFont>::new(
526 text,
527 &shape_buffer,
528 Some(45.),
529 None,
530 TextWrap::WordWrap,
531 )
532 .collect::<std::vec::Vec<_>>();
533 assert_eq!(lines.len(), 2);
534 assert_eq!(lines[0].line_text(text), "B B");
535 assert_eq!(lines[1].line_text(text), "W");
536}
537
538#[test]
539fn test_basic_line_break_space_v3() {
540 let font = FixedTestFont;
542 let text = "H W";
543 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
544 let lines = TextLineBreaker::<FixedTestFont>::new(
545 text,
546 &shape_buffer,
547 Some(15.),
548 None,
549 TextWrap::WordWrap,
550 )
551 .collect::<std::vec::Vec<_>>();
552 assert_eq!(lines.len(), 2);
553 assert_eq!(lines[0].line_text(text), "H");
554 assert_eq!(lines[1].line_text(text), "W");
555}
556
557#[test]
558fn test_basic_line_break_space_v4() {
559 let font = FixedTestFont;
561 let text = "H W H ";
562 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
563 let lines = TextLineBreaker::<FixedTestFont>::new(
564 text,
565 &shape_buffer,
566 Some(65.),
567 None,
568 TextWrap::WordWrap,
569 )
570 .collect::<std::vec::Vec<_>>();
571 assert_eq!(lines.len(), 1);
572 assert_eq!(lines[0].line_text(text), "H W H");
573}
574
575#[test]
576fn test_line_width_with_whitespace() {
577 let font = FixedTestFont;
578 let text = "Hello World";
579 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
580 let lines = TextLineBreaker::<FixedTestFont>::new(
581 text,
582 &shape_buffer,
583 Some(200.),
584 None,
585 TextWrap::WordWrap,
586 )
587 .collect::<std::vec::Vec<_>>();
588 assert_eq!(lines.len(), 1);
589 assert_eq!(lines[0].text_width, text.len() as f32 * 10.);
590}
591
592#[test]
593fn zero_width() {
594 let font = FixedTestFont;
595 let text = "He\nHe o";
596 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
597 let lines = TextLineBreaker::<FixedTestFont>::new(
598 text,
599 &shape_buffer,
600 Some(0.0001),
601 None,
602 TextWrap::WordWrap,
603 )
604 .map(|t| t.line_text(text))
605 .collect::<std::vec::Vec<_>>();
606 assert_eq!(lines, ["H", "e", "", "H", "e", "o"]);
607}
608
609#[test]
610fn zero_width_char_wrap() {
611 let font = FixedTestFont;
612 let text = "He\nHe o";
613 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
614 let lines = TextLineBreaker::<FixedTestFont>::new(
615 text,
616 &shape_buffer,
617 Some(0.0001),
618 None,
619 TextWrap::CharWrap,
620 )
621 .map(|t| t.line_text(text))
622 .collect::<std::vec::Vec<_>>();
623 assert_eq!(lines, ["H", "e", "", "H", "e", "o"]);
624}
625
626#[test]
627fn char_wrap_sentences() {
628 let font = FixedTestFont;
629 let text = "Hello world\nHow are you?";
630 let shape_buffer = ShapeBuffer::new(&TextLayout { font: &font, letter_spacing: None }, text);
631 let lines = TextLineBreaker::<FixedTestFont>::new(
632 text,
633 &shape_buffer,
634 Some(80.),
635 None,
636 TextWrap::CharWrap,
637 )
638 .map(|t| t.line_text(text))
639 .collect::<std::vec::Vec<_>>();
640 assert_eq!(lines, ["Hello wo", "rld", "How are", "you?"]);
641}