1use 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 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<
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 ¶graph[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 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 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 self.fragments.break_anywhere = true;
149 continue;
150 } else {
151 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 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 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 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 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 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 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 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}