layout/flow/inline/
line_breaker.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::ops::Range;
6
7use icu_segmenter::LineSegmenter;
8
9pub(crate) struct LineBreaker {
10    linebreaks: Vec<usize>,
11    current_offset: usize,
12}
13
14impl LineBreaker {
15    pub(crate) fn new(string: &str) -> Self {
16        let line_segmenter = LineSegmenter::new_auto();
17        Self {
18            // From https://docs.rs/icu_segmenter/1.5.0/icu_segmenter/struct.LineSegmenter.html
19            // > For consistency with the grapheme, word, and sentence segmenters, there is always a
20            // > breakpoint returned at index 0, but this breakpoint is not a meaningful line break
21            // > opportunity.
22            //
23            // Skip this first line break opportunity, as it isn't interesting to us.
24            linebreaks: line_segmenter.segment_str(string).skip(1).collect(),
25            current_offset: 0,
26        }
27    }
28
29    pub(crate) fn advance_to_linebreaks_in_range(&mut self, text_range: Range<usize>) -> &[usize] {
30        let linebreaks_in_range = self.linebreaks_in_range_after_current_offset(text_range);
31        self.current_offset = linebreaks_in_range.end;
32        &self.linebreaks[linebreaks_in_range]
33    }
34
35    fn linebreaks_in_range_after_current_offset(&self, text_range: Range<usize>) -> Range<usize> {
36        assert!(text_range.start <= text_range.end);
37
38        let mut linebreaks_range = self.current_offset..self.linebreaks.len();
39
40        while self.linebreaks[linebreaks_range.start] < text_range.start &&
41            linebreaks_range.len() > 1
42        {
43            linebreaks_range.start += 1;
44        }
45
46        let mut ending_linebreak_index = linebreaks_range.start;
47        while self.linebreaks[ending_linebreak_index] < text_range.end &&
48            ending_linebreak_index < self.linebreaks.len() - 1
49        {
50            ending_linebreak_index += 1;
51        }
52        linebreaks_range.end = ending_linebreak_index;
53        linebreaks_range
54    }
55}
56
57#[test]
58fn test_linebreaker_ranges() {
59    let linebreaker = LineBreaker::new("abc def");
60    assert_eq!(linebreaker.linebreaks, [4, 7]);
61    assert_eq!(
62        linebreaker.linebreaks_in_range_after_current_offset(0..5),
63        0..1
64    );
65    // The last linebreak should not be included for the text range we are interested in.
66    assert_eq!(
67        linebreaker.linebreaks_in_range_after_current_offset(0..7),
68        0..1
69    );
70
71    let linebreaker = LineBreaker::new("abc d def");
72    assert_eq!(linebreaker.linebreaks, [4, 6, 9]);
73    assert_eq!(
74        linebreaker.linebreaks_in_range_after_current_offset(0..5),
75        0..1
76    );
77    assert_eq!(
78        linebreaker.linebreaks_in_range_after_current_offset(0..7),
79        0..2
80    );
81    assert_eq!(
82        linebreaker.linebreaks_in_range_after_current_offset(0..9),
83        0..2
84    );
85
86    assert_eq!(
87        linebreaker.linebreaks_in_range_after_current_offset(4..9),
88        0..2
89    );
90
91    std::panic::catch_unwind(|| {
92        let linebreaker = LineBreaker::new("abc def");
93        linebreaker.linebreaks_in_range_after_current_offset(5..2);
94    })
95    .expect_err("Reversed range should cause an assertion failure.");
96}
97
98#[test]
99fn test_linebreaker_stateful_advance() {
100    let mut linebreaker = LineBreaker::new("abc d def");
101    assert_eq!(linebreaker.linebreaks, [4, 6, 9]);
102    assert!(linebreaker.advance_to_linebreaks_in_range(0..7) == &[4, 6]);
103    assert!(linebreaker.advance_to_linebreaks_in_range(8..9).is_empty());
104
105    // We've already advanced, so a range from the beginning shouldn't affect things.
106    assert!(linebreaker.advance_to_linebreaks_in_range(0..9).is_empty());
107
108    linebreaker.current_offset = 0;
109
110    // Sending a value out of range shoudn't break things.
111    assert!(linebreaker.advance_to_linebreaks_in_range(0..999) == &[4, 6]);
112
113    linebreaker.current_offset = 0;
114
115    std::panic::catch_unwind(|| {
116        let mut linebreaker = LineBreaker::new("abc d def");
117        linebreaker.advance_to_linebreaks_in_range(2..0);
118    })
119    .expect_err("Reversed range should cause an assertion failure.");
120}