Skip to main content

kas_text/
util.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4//     https://www.apache.org/licenses/LICENSE-2.0
5
6//! Utility types and traits
7
8use icu_properties::{CodePointMapData, props::LineBreak};
9use icu_segmenter::{LineSegmenter, iterators::LineBreakIterator, scaffold::Utf8};
10use std::ops::Range;
11
12/// Describes the state-of-preparation of a [`TextDisplay`][crate::TextDisplay]
13#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Ord, PartialOrd, Hash)]
14pub enum Status {
15    /// Nothing done yet
16    #[default]
17    New,
18    /// As [`Self::LevelRuns`], except these need resizing
19    ResizeLevelRuns,
20    /// Source text has been broken into level runs
21    LevelRuns,
22    /// Line wrapping and horizontal alignment is done
23    Wrapped,
24    /// The text is ready for display
25    Ready,
26}
27
28impl Status {
29    /// True if status is `Status::Ready`
30    #[inline]
31    pub fn is_ready(&self) -> bool {
32        *self == Status::Ready
33    }
34}
35
36/// An iterator over a `Vec` which clones elements
37pub struct OwningVecIter<T: Clone> {
38    v: Vec<T>,
39    i: usize,
40}
41
42impl<T: Clone> OwningVecIter<T> {
43    /// Construct from a `Vec`
44    pub fn new(v: Vec<T>) -> Self {
45        let i = 0;
46        OwningVecIter { v, i }
47    }
48}
49
50impl<T: Clone> Iterator for OwningVecIter<T> {
51    type Item = T;
52    fn next(&mut self) -> Option<Self::Item> {
53        if self.i < self.v.len() {
54            let item = self.v[self.i].clone();
55            self.i += 1;
56            Some(item)
57        } else {
58            None
59        }
60    }
61
62    fn size_hint(&self) -> (usize, Option<usize>) {
63        let len = self.v.len() - self.i;
64        (len, Some(len))
65    }
66}
67
68impl<T: Clone> ExactSizeIterator for OwningVecIter<T> {}
69impl<T: Clone> std::iter::FusedIterator for OwningVecIter<T> {}
70
71/// Returns `true` when `text` ends with a hard break, assuming that it ends
72/// with a valid line break.
73///
74/// This filter is copied from icu_segmenter docs.
75pub(crate) fn ends_with_hard_break(text: &str) -> bool {
76    text.chars().next_back().is_some_and(|c| {
77        matches!(
78            CodePointMapData::<LineBreak>::new().get(c),
79            LineBreak::MandatoryBreak
80                | LineBreak::CarriageReturn
81                | LineBreak::LineFeed
82                | LineBreak::NextLine
83        )
84    })
85}
86
87/// Iterator over lines / paragraphs within the text
88///
89/// This iterator splits the input text into a sequence of "lines" at mandatory
90/// breaks (see [TR14#BK](https://www.unicode.org/reports/tr14/#BK)).
91/// The resulting slices cover the whole input text in order without overlap.
92pub struct LineIterator<'a> {
93    break_iter: LineBreakIterator<'static, 'a, Utf8>,
94    text: &'a str,
95    start: usize,
96}
97
98impl<'a> LineIterator<'a> {
99    /// Construct
100    #[inline]
101    pub fn new(text: &'a str) -> Self {
102        let segmenter = LineSegmenter::new_auto(Default::default());
103        let mut break_iter = segmenter.segment_str(text);
104        assert_eq!(break_iter.next(), Some(0)); // the iterator always reports a break at 0
105        LineIterator {
106            break_iter,
107            text,
108            start: 0,
109        }
110    }
111}
112
113impl<'a> Iterator for LineIterator<'a> {
114    type Item = Range<usize>;
115
116    fn next(&mut self) -> Option<Self::Item> {
117        while let Some(index) = self.break_iter.next() {
118            if ends_with_hard_break(&self.text[..index]) || index == self.text.len() {
119                let range = self.start..index;
120                self.start = index;
121                return Some(range);
122            }
123        }
124        None
125    }
126}