ferroui_base/media/text_formatting/
formatted_text_source.rs1use 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#[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 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#[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 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 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}