Skip to main content

ferroui_base/media/text_formatting/
formatted_text_source.rs

1use std::rc::Rc;
2
3use crate::media::text_formatting::unicode::GraphemeEnumerator;
4use crate::media::text_formatting::{ITextSource, TextCharacters, TextRun, TextRunProperties};
5use crate::utilities::{ReadOnlyMemory, ValueSpan};
6
7/// The text source of a formatted text: one string with default properties
8/// and optional property overrides for ranges of it.
9#[derive(Clone)]
10pub struct FormattedTextSource {
11    text: ReadOnlyMemory<u16>,
12    default_properties: Rc<dyn TextRunProperties>,
13    text_modifier: Option<Rc<[ValueSpan<Rc<dyn TextRunProperties>>]>>,
14}
15
16impl FormattedTextSource {
17    pub fn new(
18        text: ReadOnlyMemory<u16>,
19        default_properties: Rc<dyn TextRunProperties>,
20        text_modifier: Option<Rc<[ValueSpan<Rc<dyn TextRunProperties>>]>>,
21    ) -> Self {
22        Self { text, default_properties, text_modifier }
23    }
24
25    /// Creates a span of text run properties that has modifier applied.
26    ///
27    /// * `text` — the text to create the properties for.
28    /// * `first_text_source_index` — the first text source index.
29    /// * `default_properties` — the default text properties.
30    /// * `text_modifier` — the text properties modifier.
31    ///
32    /// Returns the created text style run.
33    pub fn create_text_style_run(
34        text: &[u16],
35        first_text_source_index: i32,
36        default_properties: &Rc<dyn TextRunProperties>,
37        text_modifier: Option<&[ValueSpan<Rc<dyn TextRunProperties>>]>,
38    ) -> ValueSpan<Rc<dyn TextRunProperties>> {
39        let text_length = text.len() as i32;
40
41        let text_modifier = match text_modifier {
42            Some(text_modifier) if !text_modifier.is_empty() => text_modifier,
43            _ => return ValueSpan::new(first_text_source_index, text_length, default_properties.clone()),
44        };
45
46        let mut current_properties = default_properties;
47
48        let mut i = 0usize;
49
50        let mut length = 0i32;
51
52        while i < text_modifier.len() {
53            let properties_override = &text_modifier[i];
54
55            let text_range = TextRange::new(properties_override.start(), properties_override.length());
56
57            if text_range.start + text_range.length <= first_text_source_index {
58                i += 1;
59                continue;
60            }
61
62            if text_range.start > first_text_source_index + text_length {
63                length = text_length;
64                break;
65            }
66
67            if text_range.start > first_text_source_index && **properties_override.value() != **current_properties {
68                length = (text_range.start - first_text_source_index).abs().min(text_length);
69
70                break;
71            }
72
73            length = 0.max(text_range.start + text_range.length - first_text_source_index);
74            current_properties = properties_override.value();
75            break;
76        }
77
78        if length < text_length && i == text_modifier.len() && **current_properties == **default_properties {
79            length = text_length;
80        }
81
82        if length == 0 && **current_properties != **default_properties {
83            current_properties = default_properties;
84            length = text_length;
85        }
86
87        length = Self::coerce_length(text, length);
88
89        ValueSpan::new(first_text_source_index, length, current_properties.clone())
90    }
91
92    fn coerce_length(text: &[u16], length: i32) -> i32 {
93        let mut final_length = 0i32;
94
95        let mut grapheme_enumerator = GraphemeEnumerator::new(text);
96
97        while let Some(grapheme) = grapheme_enumerator.move_next() {
98            final_length += grapheme.length() as i32;
99
100            if final_length >= length {
101                return final_length;
102            }
103        }
104
105        length.min(text.len() as i32)
106    }
107}
108
109impl ITextSource for FormattedTextSource {
110    fn get_text_run(&self, text_source_index: i32) -> Option<Rc<dyn TextRun>> {
111        if text_source_index < 0 || text_source_index as usize > self.text.len() {
112            return None;
113        }
114
115        let run_text = &self.text.span()[text_source_index as usize..];
116
117        if run_text.is_empty() {
118            return None;
119        }
120
121        let text_style_run = Self::create_text_style_run(
122            run_text,
123            text_source_index,
124            &self.default_properties,
125            self.text_modifier.as_deref(),
126        );
127
128        Some(Rc::new(TextCharacters::new(
129            self.text.slice(text_source_index as usize, text_style_run.length() as usize),
130            text_style_run.value().clone(),
131        )))
132    }
133}
134
135/// References a portion of a text buffer.
136///
137/// Upstream's `End`, `Take` and `Skip` members of this private type are
138/// unused and not ported.
139#[derive(Clone, Copy, Debug, PartialEq, Eq)]
140struct TextRange {
141    start: i32,
142    length: i32,
143}
144
145impl TextRange {
146    const fn new(start: i32, length: i32) -> Self {
147        Self { start, length }
148    }
149}
150
151#[cfg(test)]
152mod tests {
153    use super::*;
154    use crate::media::text_formatting::testing::{utf16, TextTestScope};
155    use crate::media::text_formatting::GenericTextRunProperties;
156    use crate::media::{BaselineAlignment, Brushes, IBrush, Typeface};
157
158    fn highlighted(typeface: &Typeface) -> Rc<dyn TextRunProperties> {
159        let background: Rc<dyn IBrush> = Brushes::aqua();
160
161        Rc::new(GenericTextRunProperties::with_all(
162            typeface.clone(),
163            12.0,
164            None,
165            None,
166            Some(background),
167            BaselineAlignment::Baseline,
168            None,
169            None,
170        ))
171    }
172
173    #[test]
174    fn get_text_run_with_two_text_style_overrides_should_generate_correct_first_run() {
175        let _scope = TextTestScope::new();
176
177        // Prepare a sample text: The two "He" at the beginning of each line should be displayed
178        // with other text run properties
179        let text = "Hello World\r\nHello";
180
181        let typeface = Typeface::default_typeface();
182
183        let default_text_run_properties: Rc<dyn TextRunProperties> =
184            Rc::new(GenericTextRunProperties::new(typeface.clone()));
185
186        let text_style_overrides: Rc<[ValueSpan<Rc<dyn TextRunProperties>>]> =
187            Rc::from(vec![ValueSpan::new(0, 2, highlighted(&typeface)), ValueSpan::new(13, 2, highlighted(&typeface))]);
188
189        let text_source = FormattedTextSource::new(utf16(text), default_text_run_properties, Some(text_style_overrides));
190
191        let text_run = text_source.get_text_run(0).unwrap();
192
193        assert_eq!(text_run.length(), 2);
194        assert_eq!(text_run.text().to_string_lossy(), "He");
195
196        // Additions: the runs after the first override.
197        let text_run = text_source.get_text_run(2).unwrap();
198
199        assert_eq!(text_run.text().to_string_lossy(), "llo World\r\n");
200
201        let text_run = text_source.get_text_run(13).unwrap();
202
203        assert_eq!(text_run.text().to_string_lossy(), "He");
204
205        let text_run = text_source.get_text_run(15).unwrap();
206
207        assert_eq!(text_run.text().to_string_lossy(), "llo");
208
209        assert!(text_source.get_text_run(18).is_none());
210        assert!(text_source.get_text_run(19).is_none());
211        assert!(text_source.get_text_run(-1).is_none());
212    }
213}