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;
9use servo_base::text::Utf8CodeUnits;
10
11pub(crate) struct LineBreaker {
12    linebreaks: Vec<Utf8CodeUnits>,
13    current_linebreak_offset: usize,
14    /// Whether or not the line breaker should include a break at this starting index.
15    additional_break_at_start: Option<Utf8CodeUnits>,
16}
17
18impl LineBreaker {
19    pub(crate) fn new(string: &str, options: LineBreakOptions<'_>) -> Self {
20        let line_segmenter = LineSegmenter::new_auto(options);
21        Self {
22            // From https://docs.rs/icu_segmenter/1.5.0/icu_segmenter/struct.LineSegmenter.html
23            // > For consistency with the grapheme, word, and sentence segmenters, there is always a
24            // > breakpoint returned at index 0, but this breakpoint is not a meaningful line break
25            // > opportunity.
26            //
27            // Skip this first line break opportunity, as it isn't interesting to us.
28            linebreaks: line_segmenter
29                .segment_str(string)
30                .skip(1)
31                .map(|offset| Utf8CodeUnits(offset as u32))
32                .collect(),
33            current_linebreak_offset: 0,
34            additional_break_at_start: None,
35        }
36    }
37
38    pub(crate) fn advance_to_linebreaks_in_range(
39        &mut self,
40        text_range: Range<Utf8CodeUnits>,
41    ) -> &[Utf8CodeUnits] {
42        let linebreaks_in_range = self.linebreaks_in_range_after_current_offset(text_range);
43        self.current_linebreak_offset = linebreaks_in_range.end;
44        &self.linebreaks[linebreaks_in_range]
45    }
46
47    fn linebreaks_in_range_after_current_offset(
48        &self,
49        text_range: Range<Utf8CodeUnits>,
50    ) -> Range<usize> {
51        assert!(text_range.start <= text_range.end);
52
53        let mut linebreaks_range = self.current_linebreak_offset..self.linebreaks.len();
54
55        while self.linebreaks[linebreaks_range.start] < text_range.start &&
56            linebreaks_range.len() > 1
57        {
58            linebreaks_range.start += 1;
59        }
60
61        let mut ending_linebreak_index = linebreaks_range.start;
62        while self.linebreaks[ending_linebreak_index] < text_range.end &&
63            ending_linebreak_index < self.linebreaks.len() - 1
64        {
65            ending_linebreak_index += 1;
66        }
67        linebreaks_range.end = ending_linebreak_index;
68        linebreaks_range
69    }
70
71    pub(crate) fn set_additional_break_at_start(&mut self, index: Utf8CodeUnits) {
72        debug_assert!(self.additional_break_at_start.is_none());
73        self.additional_break_at_start = Some(index);
74    }
75
76    pub(crate) fn take_additional_break_at_start(&mut self) -> Option<Utf8CodeUnits> {
77        self.additional_break_at_start.take()
78    }
79}
80
81#[cfg(test)]
82mod test {
83    use super::*;
84
85    fn linebreaks_in_range_after_current_offset(
86        linebreaker: &LineBreaker,
87        range: Range<u32>,
88    ) -> Range<usize> {
89        linebreaker.linebreaks_in_range_after_current_offset(
90            Utf8CodeUnits(range.start)..Utf8CodeUnits(range.end),
91        )
92    }
93
94    #[test]
95    fn test_linebreaker_ranges() {
96        let linebreaker = LineBreaker::new("abc def", LineBreakOptions::default());
97        assert_eq!(linebreaker.linebreaks, [Utf8CodeUnits(4), Utf8CodeUnits(7)]);
98        assert_eq!(
99            linebreaks_in_range_after_current_offset(&linebreaker, 0..5),
100            0..1
101        );
102        // The last linebreak should not be included for the text range we are interested in.
103        assert_eq!(
104            linebreaks_in_range_after_current_offset(&linebreaker, 0..7),
105            0..1
106        );
107
108        let linebreaker = LineBreaker::new("abc d def", LineBreakOptions::default());
109        assert_eq!(
110            linebreaker.linebreaks,
111            [Utf8CodeUnits(4), Utf8CodeUnits(6), Utf8CodeUnits(9)]
112        );
113        assert_eq!(
114            linebreaks_in_range_after_current_offset(&linebreaker, 0..5),
115            0..1
116        );
117        assert_eq!(
118            linebreaks_in_range_after_current_offset(&linebreaker, 0..7),
119            0..2
120        );
121        assert_eq!(
122            linebreaks_in_range_after_current_offset(&linebreaker, 0..9),
123            0..2
124        );
125
126        assert_eq!(
127            linebreaks_in_range_after_current_offset(&linebreaker, 4..9),
128            0..2
129        );
130
131        std::panic::catch_unwind(|| {
132            let linebreaker = LineBreaker::new("abc def", LineBreakOptions::default());
133            linebreaks_in_range_after_current_offset(&linebreaker, 5..2);
134        })
135        .expect_err("Reversed range should cause an assertion failure.");
136    }
137
138    fn advance_to_linebreaks_in_range(
139        linebreaker: &mut LineBreaker,
140        range: Range<u32>,
141    ) -> &[Utf8CodeUnits] {
142        linebreaker
143            .advance_to_linebreaks_in_range(Utf8CodeUnits(range.start)..Utf8CodeUnits(range.end))
144    }
145
146    #[test]
147    fn test_linebreaker_stateful_advance() {
148        let mut linebreaker = LineBreaker::new("abc d def", LineBreakOptions::default());
149        assert_eq!(
150            linebreaker.linebreaks,
151            [Utf8CodeUnits(4), Utf8CodeUnits(6), Utf8CodeUnits(9)]
152        );
153        assert!(
154            advance_to_linebreaks_in_range(&mut linebreaker, 0..7) ==
155                &[Utf8CodeUnits(4), Utf8CodeUnits(6)]
156        );
157        assert!(advance_to_linebreaks_in_range(&mut linebreaker, 8..9).is_empty());
158
159        // We've already advanced, so a range from the beginning shouldn't affect things.
160        assert!(advance_to_linebreaks_in_range(&mut linebreaker, 0..9).is_empty());
161
162        linebreaker.current_linebreak_offset = 0;
163
164        // Sending a value out of range shouldn't break things.
165        assert!(
166            advance_to_linebreaks_in_range(&mut linebreaker, 0..999) ==
167                &[Utf8CodeUnits(4), Utf8CodeUnits(6)]
168        );
169
170        linebreaker.current_linebreak_offset = 0;
171
172        std::panic::catch_unwind(|| {
173            let mut linebreaker = LineBreaker::new("abc d def", LineBreakOptions::default());
174            advance_to_linebreaks_in_range(&mut linebreaker, 2..0);
175        })
176        .expect_err("Reversed range should cause an assertion failure.");
177    }
178}