Skip to main content

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