cranpose_ui/text/
annotated_string.rs1use std::ops::Range;
2
3use crate::{ParagraphStyle, SpanStyle};
4
5#[derive(Debug, Clone, PartialEq, Default)]
9pub struct AnnotatedString {
10 pub text: String,
11 pub span_styles: Vec<RangeStyle<SpanStyle>>,
12 pub paragraph_styles: Vec<RangeStyle<ParagraphStyle>>,
13}
14
15#[derive(Debug, Clone, PartialEq)]
17pub struct RangeStyle<T> {
18 pub item: T,
19 pub range: Range<usize>,
20}
21
22impl AnnotatedString {
23 pub fn new(text: String) -> Self {
24 Self {
25 text,
26 span_styles: vec![],
27 paragraph_styles: vec![],
28 }
29 }
30
31 pub fn builder() -> Builder {
32 Builder::new()
33 }
34
35 pub fn len(&self) -> usize {
36 self.text.len()
37 }
38
39 pub fn is_empty(&self) -> bool {
40 self.text.is_empty()
41 }
42
43 pub fn span_boundaries(&self) -> Vec<usize> {
45 let mut boundaries = vec![0, self.text.len()];
46 for span in &self.span_styles {
47 boundaries.push(span.range.start);
48 boundaries.push(span.range.end);
49 }
50 boundaries.sort_unstable();
51 boundaries.dedup();
52 boundaries
53 .into_iter()
54 .filter(|&b| b <= self.text.len() && self.text.is_char_boundary(b))
55 .collect()
56 }
57
58 pub fn span_styles_hash(&self) -> u64 {
60 use std::hash::{Hash, Hasher};
61 let mut hasher = std::collections::hash_map::DefaultHasher::new();
62 hasher.write_usize(self.span_styles.len());
63 for span in &self.span_styles {
64 hasher.write_usize(span.range.start);
65 hasher.write_usize(span.range.end);
66
67 let dummy = crate::text::TextStyle {
69 span_style: span.item.clone(),
70 ..Default::default()
71 };
72 hasher.write_u64(dummy.measurement_hash());
73
74 if let Some(c) = &span.item.color {
76 hasher.write_u32(c.0.to_bits());
77 hasher.write_u32(c.1.to_bits());
78 hasher.write_u32(c.2.to_bits());
79 hasher.write_u32(c.3.to_bits());
80 }
81 if let Some(bg) = &span.item.background {
82 hasher.write_u32(bg.0.to_bits());
83 hasher.write_u32(bg.1.to_bits());
84 hasher.write_u32(bg.2.to_bits());
85 hasher.write_u32(bg.3.to_bits());
86 }
87 if let Some(d) = &span.item.text_decoration {
88 d.hash(&mut hasher);
89 }
90 }
91 hasher.finish()
92 }
93
94 pub fn subsequence(&self, range: std::ops::Range<usize>) -> Self {
97 if range.is_empty() {
98 return Self::new(String::new());
99 }
100
101 let start = range.start.min(self.text.len());
102 let end = range.end.max(start).min(self.text.len());
103
104 if start == end {
105 return Self::new(String::new());
106 }
107
108 let mut new_spans = Vec::new();
109 for span in &self.span_styles {
110 let intersection_start = span.range.start.max(start);
111 let intersection_end = span.range.end.min(end);
112 if intersection_start < intersection_end {
113 new_spans.push(RangeStyle {
114 item: span.item.clone(),
115 range: (intersection_start - start)..(intersection_end - start),
116 });
117 }
118 }
119
120 let mut new_paragraphs = Vec::new();
121 for span in &self.paragraph_styles {
122 let intersection_start = span.range.start.max(start);
123 let intersection_end = span.range.end.min(end);
124 if intersection_start < intersection_end {
125 new_paragraphs.push(RangeStyle {
126 item: span.item.clone(),
127 range: (intersection_start - start)..(intersection_end - start),
128 });
129 }
130 }
131
132 Self {
133 text: self.text[start..end].to_string(),
134 span_styles: new_spans,
135 paragraph_styles: new_paragraphs,
136 }
137 }
138}
139
140impl From<String> for AnnotatedString {
141 fn from(text: String) -> Self {
142 Self::new(text)
143 }
144}
145
146impl From<&str> for AnnotatedString {
147 fn from(text: &str) -> Self {
148 Self::new(text.to_owned())
149 }
150}
151
152impl From<&String> for AnnotatedString {
153 fn from(text: &String) -> Self {
154 Self::new(text.clone())
155 }
156}
157
158impl From<&mut String> for AnnotatedString {
159 fn from(text: &mut String) -> Self {
160 Self::new(text.clone())
161 }
162}
163
164#[derive(Debug, Default, Clone)]
166pub struct Builder {
167 text: String,
168 span_styles: Vec<MutableRange<SpanStyle>>,
169 paragraph_styles: Vec<MutableRange<ParagraphStyle>>,
170 style_stack: Vec<StyleStackRecord>,
171}
172
173#[derive(Debug, Clone)]
174struct MutableRange<T> {
175 item: T,
176 start: usize,
177 end: usize,
178}
179
180#[derive(Debug, Clone)]
181struct StyleStackRecord {
182 style_type: StyleType,
183 index: usize,
184}
185
186#[derive(Debug, Clone, Copy, PartialEq, Eq)]
187enum StyleType {
188 Span,
189 Paragraph,
190}
191
192impl Builder {
193 pub fn new() -> Self {
194 Self::default()
195 }
196
197 pub fn append(mut self, text: &str) -> Self {
199 self.text.push_str(text);
200 self
201 }
202
203 pub fn push_style(mut self, style: SpanStyle) -> Self {
207 let index = self.span_styles.len();
208 self.span_styles.push(MutableRange {
209 item: style,
210 start: self.text.len(),
211 end: usize::MAX,
212 });
213 self.style_stack.push(StyleStackRecord {
214 style_type: StyleType::Span,
215 index,
216 });
217 self
218 }
219
220 pub fn push_paragraph_style(mut self, style: ParagraphStyle) -> Self {
222 let index = self.paragraph_styles.len();
223 self.paragraph_styles.push(MutableRange {
224 item: style,
225 start: self.text.len(),
226 end: usize::MAX,
227 });
228 self.style_stack.push(StyleStackRecord {
229 style_type: StyleType::Paragraph,
230 index,
231 });
232 self
233 }
234
235 pub fn pop(mut self) -> Self {
237 if let Some(record) = self.style_stack.pop() {
238 match record.style_type {
239 StyleType::Span => {
240 self.span_styles[record.index].end = self.text.len();
241 }
242 StyleType::Paragraph => {
243 self.paragraph_styles[record.index].end = self.text.len();
244 }
245 }
246 }
247 self
248 }
249
250 pub fn to_annotated_string(mut self) -> AnnotatedString {
252 while let Some(record) = self.style_stack.pop() {
254 match record.style_type {
255 StyleType::Span => {
256 self.span_styles[record.index].end = self.text.len();
257 }
258 StyleType::Paragraph => {
259 self.paragraph_styles[record.index].end = self.text.len();
260 }
261 }
262 }
263
264 AnnotatedString {
265 text: self.text,
266 span_styles: self
267 .span_styles
268 .into_iter()
269 .map(|s| RangeStyle {
270 item: s.item,
271 range: s.start..s.end,
272 })
273 .collect(),
274 paragraph_styles: self
275 .paragraph_styles
276 .into_iter()
277 .map(|s| RangeStyle {
278 item: s.item,
279 range: s.start..s.end,
280 })
281 .collect(),
282 }
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289
290 #[test]
291 fn test_builder_span() {
292 let span1 = SpanStyle {
293 alpha: Some(0.5),
294 ..Default::default()
295 };
296
297 let span2 = SpanStyle {
298 alpha: Some(1.0),
299 ..Default::default()
300 };
301
302 let annotated = AnnotatedString::builder()
303 .append("Hello ")
304 .push_style(span1.clone())
305 .append("World")
306 .push_style(span2.clone())
307 .append("!")
308 .pop()
309 .pop()
310 .to_annotated_string();
311
312 assert_eq!(annotated.text, "Hello World!");
313 assert_eq!(annotated.span_styles.len(), 2);
314 assert_eq!(annotated.span_styles[0].range, 6..12);
315 assert_eq!(annotated.span_styles[0].item, span1);
316 assert_eq!(annotated.span_styles[1].range, 11..12);
317 assert_eq!(annotated.span_styles[1].item, span2);
318 }
319}