1use crate::Color;
2use std::rc::Rc;
3use std::sync::Arc;
4
5#[derive(Clone, Debug)]
8pub struct TextLayoutResult {
9 pub line_count: usize,
11 pub width_px: f32,
13 pub height_px: f32,
15}
16
17pub trait VisualTransformation: std::fmt::Debug + Send + Sync + 'static {
20 fn filter(&self, text: &str) -> TransformedText;
24}
25
26pub struct TransformedText {
28 pub text: String,
30 pub offset_map: Rc<dyn Fn(usize) -> usize>,
32}
33
34impl Clone for TransformedText {
35 fn clone(&self) -> Self {
36 Self {
37 text: self.text.clone(),
38 offset_map: self.offset_map.clone(),
39 }
40 }
41}
42
43impl std::fmt::Debug for TransformedText {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 f.debug_struct("TransformedText")
46 .field("text", &self.text)
47 .finish()
48 }
49}
50
51#[derive(Clone, Copy, Debug)]
53pub struct NoVisualTransformation;
54
55impl VisualTransformation for NoVisualTransformation {
56 fn filter(&self, text: &str) -> TransformedText {
57 let len = text.len();
58 TransformedText {
59 text: text.to_string(),
60 offset_map: Rc::new(move |offset| offset.min(len)),
61 }
62 }
63}
64
65#[derive(Clone, Copy, Debug)]
67pub struct PasswordVisualTransformation {
68 pub mask_char: char,
70}
71
72impl Default for PasswordVisualTransformation {
73 fn default() -> Self {
74 Self { mask_char: '*' }
75 }
76}
77
78impl VisualTransformation for PasswordVisualTransformation {
79 fn filter(&self, text: &str) -> TransformedText {
80 let masked: String = text.chars().map(|_| self.mask_char).collect();
81 let len = text.len();
82 TransformedText {
83 text: masked,
84 offset_map: Rc::new(move |offset| offset.min(len)),
85 }
86 }
87}
88
89pub fn original_offset_to_display(original: &str, display: &str, original_byte: usize) -> usize {
93 let char_idx = original[..original_byte.min(original.len())]
94 .chars()
95 .count();
96 display
97 .char_indices()
98 .nth(char_idx)
99 .map(|(i, _)| i)
100 .unwrap_or(display.len())
101}
102
103#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
105pub enum KeyboardType {
106 #[default]
107 Text,
108 Ascii,
109 Number,
110 Phone,
111 Email,
112 Uri,
113 Decimal,
114}
115
116#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
118pub enum ImeAction {
119 #[default]
120 Unspecified,
121 None,
122 Go,
123 Search,
124 Send,
125 Next,
126 Done,
127 Previous,
128}
129
130#[derive(Debug, Clone, Copy, PartialEq)]
131pub struct SpanStyle {
132 pub color: Option<Color>,
133 pub font_size: Option<f32>,
134}
135
136impl SpanStyle {
137 pub const fn default() -> Self {
138 Self {
139 color: None,
140 font_size: None,
141 }
142 }
143
144 pub fn color(mut self, c: Color) -> Self {
145 self.color = Some(c);
146 self
147 }
148
149 pub fn font_size(mut self, px: f32) -> Self {
150 self.font_size = Some(px);
151 self
152 }
153}
154
155impl Default for SpanStyle {
156 fn default() -> Self {
157 Self::default()
158 }
159}
160
161#[derive(Debug, Clone, PartialEq)]
163pub struct TextSpan {
164 pub start: usize,
166 pub end: usize,
168 pub style: SpanStyle,
169}
170
171#[derive(Debug, Clone)]
175pub struct AnnotatedString {
176 pub text: String,
177 pub spans: Arc<[TextSpan]>,
178}
179
180impl AnnotatedString {
181 pub fn new(text: impl Into<String>, spans: Vec<TextSpan>) -> Self {
182 let text = text.into();
183 Self {
184 text,
185 spans: spans.into(),
186 }
187 }
188
189 pub fn as_str(&self) -> &str {
190 &self.text
191 }
192}
193
194impl From<String> for AnnotatedString {
195 fn from(text: String) -> Self {
196 Self {
197 text,
198 spans: Arc::from([]),
199 }
200 }
201}
202
203impl From<&str> for AnnotatedString {
204 fn from(text: &str) -> Self {
205 Self {
206 text: text.to_string(),
207 spans: Arc::from([]),
208 }
209 }
210}
211
212#[derive(Default)]
214pub struct AnnotatedStringBuilder {
215 text: String,
216 spans: Vec<TextSpan>,
217}
218
219impl AnnotatedStringBuilder {
220 pub fn new() -> Self {
221 Self::default()
222 }
223
224 pub fn push(&mut self, text: &str) -> &mut Self {
226 self.text.push_str(text);
227 self
228 }
229
230 pub fn push_with_style(&mut self, text: &str, style: SpanStyle) -> &mut Self {
232 let start = self.text.len();
233 self.text.push_str(text);
234 let end = self.text.len();
235 if start < end {
236 self.spans.push(TextSpan { start, end, style });
237 }
238 self
239 }
240
241 pub fn push_color(&mut self, text: &str, color: Color) -> &mut Self {
243 self.push_with_style(text, SpanStyle::default().color(color))
244 }
245
246 pub fn add_style(&mut self, start: usize, end: usize, style: SpanStyle) -> &mut Self {
248 if start < end && end <= self.text.len() {
249 self.spans.push(TextSpan { start, end, style });
250 }
251 self
252 }
253
254 pub fn build(&mut self) -> AnnotatedString {
255 let text = std::mem::take(&mut self.text);
256 self.spans.sort_by_key(|s| s.start);
257 let mut merged: Vec<TextSpan> = Vec::new();
259 for span in std::mem::take(&mut self.spans) {
260 if let Some(last) = merged.last_mut()
261 && last.end == span.start
262 && last.style == span.style
263 {
264 last.end = span.end;
265 continue;
266 }
267 merged.push(span);
268 }
269 AnnotatedString {
270 text,
271 spans: merged.into(),
272 }
273 }
274}
275
276#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
278pub enum TextAlign {
279 Left,
280 Right,
281 Center,
282 Justify,
283 Start,
284 End,
285 Unspecified,
286}
287
288impl Default for TextAlign {
289 fn default() -> Self {
290 TextAlign::Unspecified
291 }
292}
293
294#[derive(Clone, Copy, Debug, PartialEq)]
296pub struct FontWeight(pub u16);
297
298impl FontWeight {
299 pub const THIN: FontWeight = FontWeight(100);
300 pub const EXTRA_LIGHT: FontWeight = FontWeight(200);
301 pub const LIGHT: FontWeight = FontWeight(300);
302 pub const NORMAL: FontWeight = FontWeight(400);
303 pub const MEDIUM: FontWeight = FontWeight(500);
304 pub const SEMI_BOLD: FontWeight = FontWeight(600);
305 pub const BOLD: FontWeight = FontWeight(700);
306 pub const EXTRA_BOLD: FontWeight = FontWeight(800);
307 pub const BLACK: FontWeight = FontWeight(900);
308}
309
310impl Default for FontWeight {
311 fn default() -> Self {
312 FontWeight::NORMAL
313 }
314}
315
316#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
318pub enum FontStyle {
319 Normal,
320 Italic,
321}
322
323impl Default for FontStyle {
324 fn default() -> Self {
325 FontStyle::Normal
326 }
327}
328
329#[derive(Clone, Copy, Debug, PartialEq)]
331pub struct TextDecoration {
332 pub underline: bool,
333 pub strikethrough: bool,
334 pub color: Option<Color>,
335}
336
337impl Default for TextDecoration {
338 fn default() -> Self {
339 Self {
340 underline: false,
341 strikethrough: false,
342 color: None,
343 }
344 }
345}
346
347pub fn build_annotated_string(b: impl FnOnce(&mut AnnotatedStringBuilder)) -> AnnotatedString {
349 let mut builder = AnnotatedStringBuilder::new();
350 b(&mut builder);
351 builder.build()
352}