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#[cfg(test)]
58mod test {
59    use super::*;
60
61    #[test]
62    fn test_linebreaker_ranges() {
63        let linebreaker = LineBreaker::new("abc def");
64        assert_eq!(linebreaker.linebreaks, [4, 7]);
65        assert_eq!(
66            linebreaker.linebreaks_in_range_after_current_offset(0..5),
67            0..1
68        );
69        // The last linebreak should not be included for the text range we are interested in.
70        assert_eq!(
71            linebreaker.linebreaks_in_range_after_current_offset(0..7),
72            0..1
73        );
74
75        let linebreaker = LineBreaker::new("abc d def");
76        assert_eq!(linebreaker.linebreaks, [4, 6, 9]);
77        assert_eq!(
78            linebreaker.linebreaks_in_range_after_current_offset(0..5),
79            0..1
80        );
81        assert_eq!(
82            linebreaker.linebreaks_in_range_after_current_offset(0..7),
83            0..2
84        );
85        assert_eq!(
86            linebreaker.linebreaks_in_range_after_current_offset(0..9),
87            0..2
88        );
89
90        assert_eq!(
91            linebreaker.linebreaks_in_range_after_current_offset(4..9),
92            0..2
93        );
94
95        std::panic::catch_unwind(|| {
96            let linebreaker = LineBreaker::new("abc def");
97            linebreaker.linebreaks_in_range_after_current_offset(5..2);
98        })
99        .expect_err("Reversed range should cause an assertion failure.");
100    }
101
102    #[test]
103    fn test_linebreaker_stateful_advance() {
104        let mut linebreaker = LineBreaker::new("abc d def");
105        assert_eq!(linebreaker.linebreaks, [4, 6, 9]);
106        assert!(linebreaker.advance_to_linebreaks_in_range(0..7) == &[4, 6]);
107        assert!(linebreaker.advance_to_linebreaks_in_range(8..9).is_empty());
108
109        // We've already advanced, so a range from the beginning shouldn't affect things.
110        assert!(linebreaker.advance_to_linebreaks_in_range(0..9).is_empty());
111
112        linebreaker.current_offset = 0;
113
114        // Sending a value out of range shouldn't break things.
115        assert!(linebreaker.advance_to_linebreaks_in_range(0..999) == &[4, 6]);
116
117        linebreaker.current_offset = 0;
118
119        std::panic::catch_unwind(|| {
120            let mut linebreaker = LineBreaker::new("abc d def");
121            linebreaker.advance_to_linebreaks_in_range(2..0);
122        })
123        .expect_err("Reversed range should cause an assertion failure.");
124    }
125}