Skip to main content

freya_core/elements/
paragraph.rs

1//! [paragraph()] makes it possible to render rich text with different styles. Its a more customizable API than [crate::elements::label].
2
3use std::{
4    any::Any,
5    borrow::Cow,
6    cell::RefCell,
7    fmt::{
8        Debug,
9        Display,
10    },
11    rc::Rc,
12};
13
14use freya_engine::prelude::{
15    FontStyle,
16    Paint,
17    PaintStyle,
18    ParagraphBuilder,
19    ParagraphStyle,
20    RectHeightStyle,
21    RectWidthStyle,
22    SkParagraph,
23    SkRect,
24    TextStyle,
25};
26use rustc_hash::FxHashMap;
27use torin::prelude::Size2D;
28
29use crate::{
30    data::{
31        AccessibilityData,
32        CursorStyleData,
33        EffectData,
34        LayoutData,
35        StyleState,
36        TextStyleData,
37        TextStyleState,
38    },
39    diff_key::DiffKey,
40    element::{
41        Element,
42        ElementExt,
43        EventHandlerType,
44        LayoutContext,
45        RenderContext,
46    },
47    events::name::EventName,
48    layers::Layer,
49    prelude::{
50        AccessibilityExt,
51        Color,
52        ContainerExt,
53        EventHandlersExt,
54        KeyExt,
55        LayerExt,
56        LayoutExt,
57        MaybeExt,
58        TextAlign,
59        TextStyleExt,
60        VerticalAlign,
61    },
62    style::cursor::{
63        CursorMode,
64        CursorStyle,
65    },
66    text_cache::CachedParagraph,
67    tree::DiffModifies,
68};
69
70/// [paragraph()] makes it possible to render rich text with different styles. Its a more customizable API than [crate::elements::label].
71///
72/// See the available methods in [Paragraph].
73///
74/// ```rust
75/// # use freya::prelude::*;
76/// fn app() -> impl IntoElement {
77///     paragraph()
78///         .span(Span::new("Hello").font_size(24.0))
79///         .span(Span::new("World").font_size(16.0))
80/// }
81/// ```
82pub fn paragraph() -> Paragraph {
83    Paragraph {
84        key: DiffKey::None,
85        element: ParagraphElement::default(),
86    }
87}
88
89pub struct ParagraphHolderInner {
90    pub paragraph: Rc<SkParagraph>,
91    pub scale_factor: f64,
92}
93
94#[derive(Clone)]
95pub struct ParagraphHolder(pub Rc<RefCell<Option<ParagraphHolderInner>>>);
96
97impl PartialEq for ParagraphHolder {
98    fn eq(&self, other: &Self) -> bool {
99        Rc::ptr_eq(&self.0, &other.0)
100    }
101}
102
103impl Debug for ParagraphHolder {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        f.write_str("ParagraphHolder")
106    }
107}
108
109impl Default for ParagraphHolder {
110    fn default() -> Self {
111        Self(Rc::new(RefCell::new(None)))
112    }
113}
114
115#[derive(PartialEq, Clone)]
116pub struct ParagraphElement {
117    pub layout: LayoutData,
118    pub spans: Vec<Span<'static>>,
119    pub accessibility: AccessibilityData,
120    pub text_style_data: TextStyleData,
121    pub cursor_style_data: CursorStyleData,
122    pub event_handlers: FxHashMap<EventName, EventHandlerType>,
123    pub sk_paragraph: ParagraphHolder,
124    pub cursor_index: Option<usize>,
125    pub highlights: Vec<(usize, usize)>,
126    pub max_lines: Option<usize>,
127    pub line_height: Option<f32>,
128    pub relative_layer: Layer,
129    pub cursor_style: CursorStyle,
130    pub cursor_mode: CursorMode,
131    pub vertical_align: VerticalAlign,
132}
133
134impl Default for ParagraphElement {
135    fn default() -> Self {
136        let mut accessibility = AccessibilityData::default();
137        accessibility.builder.set_role(accesskit::Role::Paragraph);
138        Self {
139            layout: Default::default(),
140            spans: Default::default(),
141            accessibility,
142            text_style_data: Default::default(),
143            cursor_style_data: Default::default(),
144            event_handlers: Default::default(),
145            sk_paragraph: Default::default(),
146            cursor_index: Default::default(),
147            highlights: Default::default(),
148            max_lines: Default::default(),
149            line_height: Default::default(),
150            relative_layer: Default::default(),
151            cursor_style: CursorStyle::default(),
152            cursor_mode: CursorMode::default(),
153            vertical_align: VerticalAlign::default(),
154        }
155    }
156}
157
158impl Display for ParagraphElement {
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        f.write_str(
161            &self
162                .spans
163                .iter()
164                .map(|s| s.text.clone())
165                .collect::<Vec<_>>()
166                .join("\n"),
167        )
168    }
169}
170
171impl ElementExt for ParagraphElement {
172    fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
173        let Some(paragraph) = (other.as_ref() as &dyn Any).downcast_ref::<ParagraphElement>()
174        else {
175            return false;
176        };
177        self != paragraph
178    }
179
180    fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
181        let Some(paragraph) = (other.as_ref() as &dyn Any).downcast_ref::<ParagraphElement>()
182        else {
183            return DiffModifies::all();
184        };
185
186        let mut diff = DiffModifies::empty();
187
188        if self.spans != paragraph.spans {
189            diff.insert(DiffModifies::STYLE);
190            diff.insert(DiffModifies::LAYOUT);
191        }
192
193        if self.accessibility != paragraph.accessibility {
194            diff.insert(DiffModifies::ACCESSIBILITY);
195        }
196
197        if self.relative_layer != paragraph.relative_layer {
198            diff.insert(DiffModifies::LAYER);
199        }
200
201        if self.text_style_data != paragraph.text_style_data {
202            diff.insert(DiffModifies::STYLE);
203        }
204
205        if self.event_handlers != paragraph.event_handlers {
206            diff.insert(DiffModifies::EVENT_HANDLERS);
207        }
208
209        if self.cursor_index != paragraph.cursor_index
210            || self.highlights != paragraph.highlights
211            || self.cursor_mode != paragraph.cursor_mode
212            || self.cursor_style != paragraph.cursor_style
213            || self.cursor_style_data != paragraph.cursor_style_data
214            || self.vertical_align != paragraph.vertical_align
215        {
216            diff.insert(DiffModifies::STYLE);
217        }
218
219        if self.text_style_data != paragraph.text_style_data
220            || self.line_height != paragraph.line_height
221            || self.max_lines != paragraph.max_lines
222        {
223            diff.insert(DiffModifies::TEXT_STYLE);
224            diff.insert(DiffModifies::LAYOUT);
225        }
226
227        if self.layout != paragraph.layout {
228            diff.insert(DiffModifies::STYLE);
229            diff.insert(DiffModifies::LAYOUT);
230        }
231
232        diff
233    }
234
235    fn layout(&'_ self) -> Cow<'_, LayoutData> {
236        Cow::Borrowed(&self.layout)
237    }
238    fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
239        None
240    }
241
242    fn style(&'_ self) -> Cow<'_, StyleState> {
243        Cow::Owned(StyleState::default())
244    }
245
246    fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
247        Cow::Borrowed(&self.text_style_data)
248    }
249
250    fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
251        Cow::Borrowed(&self.accessibility)
252    }
253
254    fn layer(&self) -> Layer {
255        self.relative_layer
256    }
257
258    fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
259        let cached_paragraph = CachedParagraph {
260            text_style_state: context.text_style_state,
261            spans: &self.spans,
262            max_lines: self.max_lines,
263            line_height: self.line_height,
264            width: context.area_size.width,
265        };
266        let paragraph = context
267            .text_cache
268            .utilize(context.node_id, &cached_paragraph)
269            .unwrap_or_else(|| {
270                let mut paragraph_style = ParagraphStyle::default();
271                let mut text_style = TextStyle::default();
272
273                let mut font_families = context.text_style_state.font_families.clone();
274                font_families.extend_from_slice(context.fallback_fonts);
275
276                text_style.set_color(context.text_style_state.color);
277                text_style.set_font_size(
278                    f32::from(context.text_style_state.font_size) * context.scale_factor as f32,
279                );
280                text_style.set_font_families(&font_families);
281                text_style.set_font_style(FontStyle::new(
282                    context.text_style_state.font_weight.into(),
283                    context.text_style_state.font_width.into(),
284                    context.text_style_state.font_slant.into(),
285                ));
286
287                if context.text_style_state.text_height.needs_custom_height() {
288                    text_style.set_height_override(true);
289                    text_style.set_half_leading(true);
290                }
291
292                if let Some(line_height) = self.line_height {
293                    text_style.set_height_override(true);
294                    text_style.set_height(line_height);
295                }
296
297                for text_shadow in context.text_style_state.text_shadows.iter() {
298                    text_style.add_shadow((*text_shadow).into());
299                }
300
301                if let Some(ellipsis) = context.text_style_state.text_overflow.get_ellipsis() {
302                    paragraph_style.set_ellipsis(ellipsis);
303                }
304
305                paragraph_style.set_text_style(&text_style);
306                paragraph_style.set_max_lines(self.max_lines);
307                paragraph_style.set_text_align(context.text_style_state.text_align.into());
308
309                let mut paragraph_builder =
310                    ParagraphBuilder::new(&paragraph_style, &*context.font_collection);
311
312                for span in &self.spans {
313                    let text_style_state =
314                        TextStyleState::from_data(context.text_style_state, &span.text_style_data);
315                    let mut text_style = TextStyle::new();
316                    let mut font_families = context.text_style_state.font_families.clone();
317                    font_families.extend_from_slice(context.fallback_fonts);
318
319                    for text_shadow in text_style_state.text_shadows.iter() {
320                        text_style.add_shadow((*text_shadow).into());
321                    }
322
323                    text_style.set_color(text_style_state.color);
324                    text_style.set_font_size(
325                        f32::from(text_style_state.font_size) * context.scale_factor as f32,
326                    );
327                    text_style.set_font_families(&font_families);
328                    text_style.set_font_style(FontStyle::new(
329                        text_style_state.font_weight.into(),
330                        text_style_state.font_width.into(),
331                        text_style_state.font_slant.into(),
332                    ));
333                    text_style.set_decoration_type(text_style_state.text_decoration.into());
334                    if let Some(line_height) = self.line_height {
335                        text_style.set_height_override(true);
336                        text_style.set_height(line_height);
337                    }
338                    paragraph_builder.push_style(&text_style);
339                    paragraph_builder.add_text(&span.text);
340                }
341
342                let mut paragraph = paragraph_builder.build();
343                paragraph.layout(
344                    if self.max_lines == Some(1)
345                        && context.text_style_state.text_align == TextAlign::default()
346                        && !paragraph_style.ellipsized()
347                    {
348                        f32::MAX
349                    } else {
350                        context.area_size.width + 1.0
351                    },
352                );
353                context
354                    .text_cache
355                    .insert(context.node_id, &cached_paragraph, paragraph)
356            });
357
358        let size = Size2D::new(paragraph.longest_line(), paragraph.height());
359
360        self.sk_paragraph
361            .0
362            .borrow_mut()
363            .replace(ParagraphHolderInner {
364                paragraph,
365                scale_factor: context.scale_factor,
366            });
367
368        Some((size, Rc::new(())))
369    }
370
371    fn should_hook_measurement(&self) -> bool {
372        true
373    }
374
375    fn should_measure_inner_children(&self) -> bool {
376        false
377    }
378
379    fn events_handlers(&'_ self) -> Option<Cow<'_, FxHashMap<EventName, EventHandlerType>>> {
380        Some(Cow::Borrowed(&self.event_handlers))
381    }
382
383    fn render(&self, context: RenderContext) {
384        let paragraph = self.sk_paragraph.0.borrow();
385        let ParagraphHolderInner { paragraph, .. } = paragraph.as_ref().unwrap();
386        let visible_area = context.layout_node.visible_area();
387
388        let cursor_area = match self.cursor_mode {
389            CursorMode::Fit => visible_area,
390            CursorMode::Expanded => context.layout_node.area,
391        };
392
393        let paragraph_height = paragraph.height();
394        let area_height = visible_area.height();
395        let vertical_offset = match self.vertical_align {
396            VerticalAlign::Start => 0.0,
397            VerticalAlign::Center => (area_height - paragraph_height).max(0.0) / 2.0,
398        };
399
400        let cursor_vertical_offset = match self.cursor_mode {
401            CursorMode::Fit => vertical_offset,
402            CursorMode::Expanded => 0.0,
403        };
404        let cursor_vertical_size_offset = match self.cursor_mode {
405            CursorMode::Fit => 0.,
406            CursorMode::Expanded => vertical_offset * 2.,
407        };
408
409        // Draw highlights
410        for (from, to) in self.highlights.iter() {
411            if from == to {
412                continue;
413            }
414            let (from, to) = { if from < to { (from, to) } else { (to, from) } };
415            let rects = paragraph.get_rects_for_range(
416                *from..*to,
417                RectHeightStyle::Tight,
418                RectWidthStyle::Tight,
419            );
420
421            let mut highlights_paint = Paint::default();
422            highlights_paint.set_anti_alias(true);
423            highlights_paint.set_style(PaintStyle::Fill);
424            highlights_paint.set_color(self.cursor_style_data.highlight_color);
425
426            if rects.is_empty() && *from == 0 {
427                let avg_line_height =
428                    paragraph.height() / paragraph.get_line_metrics().len().max(1) as f32;
429                let rect = SkRect::new(
430                    cursor_area.min_x(),
431                    cursor_area.min_y() + cursor_vertical_offset,
432                    cursor_area.min_x() + 6.,
433                    cursor_area.min_y() + avg_line_height + cursor_vertical_size_offset,
434                );
435
436                context.canvas.draw_rect(rect, &highlights_paint);
437            }
438
439            for rect in rects {
440                let rect = SkRect::new(
441                    cursor_area.min_x() + rect.rect.left,
442                    cursor_area.min_y() + rect.rect.top + cursor_vertical_offset,
443                    cursor_area.min_x() + rect.rect.right.max(6.),
444                    cursor_area.min_y() + rect.rect.bottom + cursor_vertical_size_offset,
445                );
446                context.canvas.draw_rect(rect, &highlights_paint);
447            }
448        }
449
450        // We exclude those highlights that on the same start and end (e.g the user just started dragging)
451        let visible_highlights = self
452            .highlights
453            .iter()
454            .filter(|highlight| highlight.0 != highlight.1)
455            .count()
456            > 0;
457
458        // Draw block cursor behind text if needed
459        if let Some(cursor_index) = self.cursor_index
460            && self.cursor_style == CursorStyle::Block
461            && let Some(cursor_rect) = paragraph
462                .get_rects_for_range(
463                    cursor_index..cursor_index + 1,
464                    RectHeightStyle::Tight,
465                    RectWidthStyle::Tight,
466                )
467                .first()
468                .map(|text| text.rect)
469                .or_else(|| {
470                    // Show the cursor at the end of the text if possible
471                    let text_len = paragraph
472                        .get_glyph_position_at_coordinate((f32::MAX, f32::MAX))
473                        .position as usize;
474                    let last_rects = paragraph.get_rects_for_range(
475                        text_len.saturating_sub(1)..text_len,
476                        RectHeightStyle::Tight,
477                        RectWidthStyle::Tight,
478                    );
479
480                    if let Some(last_rect) = last_rects.first() {
481                        let mut caret = last_rect.rect;
482                        caret.left = caret.right;
483                        Some(caret)
484                    } else {
485                        let avg_line_height =
486                            paragraph.height() / paragraph.get_line_metrics().len().max(1) as f32;
487                        Some(SkRect::new(0., 0., 6., avg_line_height))
488                    }
489                })
490        {
491            let width = (cursor_rect.right - cursor_rect.left).max(6.0);
492            let cursor_rect = SkRect::new(
493                cursor_area.min_x() + cursor_rect.left,
494                cursor_area.min_y() + cursor_rect.top + cursor_vertical_offset,
495                cursor_area.min_x() + cursor_rect.left + width,
496                cursor_area.min_y() + cursor_rect.bottom + cursor_vertical_size_offset,
497            );
498
499            let mut paint = Paint::default();
500            paint.set_anti_alias(true);
501            paint.set_style(PaintStyle::Fill);
502            paint.set_color(self.cursor_style_data.color);
503
504            context.canvas.draw_rect(cursor_rect, &paint);
505        }
506
507        // Draw text (always uses visible_area with vertical_offset)
508        paragraph.paint(
509            context.canvas,
510            (visible_area.min_x(), visible_area.min_y() + vertical_offset),
511        );
512
513        // Draw cursor
514        if let Some(cursor_index) = self.cursor_index
515            && !visible_highlights
516        {
517            let cursor_rects = paragraph.get_rects_for_range(
518                cursor_index..cursor_index + 1,
519                RectHeightStyle::Tight,
520                RectWidthStyle::Tight,
521            );
522            if let Some(cursor_rect) = cursor_rects.first().map(|text| text.rect).or_else(|| {
523                // Show the cursor at the end of the text if possible
524                let text_len = paragraph
525                    .get_glyph_position_at_coordinate((f32::MAX, f32::MAX))
526                    .position as usize;
527                let last_rects = paragraph.get_rects_for_range(
528                    text_len.saturating_sub(1)..text_len,
529                    RectHeightStyle::Tight,
530                    RectWidthStyle::Tight,
531                );
532
533                if let Some(last_rect) = last_rects.first() {
534                    let mut caret = last_rect.rect;
535                    caret.left = caret.right;
536                    Some(caret)
537                } else {
538                    None
539                }
540            }) {
541                let paint_color = self.cursor_style_data.color;
542                match self.cursor_style {
543                    CursorStyle::Underline => {
544                        let thickness = 2.0;
545                        let underline_rect = SkRect::new(
546                            cursor_area.min_x() + cursor_rect.left,
547                            cursor_area.min_y() + cursor_rect.bottom - thickness
548                                + cursor_vertical_offset,
549                            cursor_area.min_x() + cursor_rect.right,
550                            cursor_area.min_y() + cursor_rect.bottom + cursor_vertical_size_offset,
551                        );
552
553                        let mut paint = Paint::default();
554                        paint.set_anti_alias(true);
555                        paint.set_style(PaintStyle::Fill);
556                        paint.set_color(paint_color);
557
558                        context.canvas.draw_rect(underline_rect, &paint);
559                    }
560                    CursorStyle::Line => {
561                        let cursor_rect = SkRect::new(
562                            cursor_area.min_x() + cursor_rect.left,
563                            cursor_area.min_y() + cursor_rect.top + cursor_vertical_offset,
564                            cursor_area.min_x() + cursor_rect.left + 2.,
565                            cursor_area.min_y() + cursor_rect.bottom + cursor_vertical_size_offset,
566                        );
567
568                        let mut paint = Paint::default();
569                        paint.set_anti_alias(true);
570                        paint.set_style(PaintStyle::Fill);
571                        paint.set_color(paint_color);
572
573                        context.canvas.draw_rect(cursor_rect, &paint);
574                    }
575                    _ => {}
576                }
577            }
578        }
579    }
580}
581
582impl From<Paragraph> for Element {
583    fn from(value: Paragraph) -> Self {
584        Element::Element {
585            key: value.key,
586            element: Rc::new(value.element),
587            elements: vec![],
588        }
589    }
590}
591
592impl KeyExt for Paragraph {
593    fn write_key(&mut self) -> &mut DiffKey {
594        &mut self.key
595    }
596}
597
598impl EventHandlersExt for Paragraph {
599    fn get_event_handlers(&mut self) -> &mut FxHashMap<EventName, EventHandlerType> {
600        &mut self.element.event_handlers
601    }
602}
603
604impl MaybeExt for Paragraph {}
605
606impl LayerExt for Paragraph {
607    fn get_layer(&mut self) -> &mut Layer {
608        &mut self.element.relative_layer
609    }
610}
611
612pub struct Paragraph {
613    key: DiffKey,
614    element: ParagraphElement,
615}
616
617impl LayoutExt for Paragraph {
618    fn get_layout(&mut self) -> &mut LayoutData {
619        &mut self.element.layout
620    }
621}
622
623impl ContainerExt for Paragraph {}
624
625impl AccessibilityExt for Paragraph {
626    fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
627        &mut self.element.accessibility
628    }
629}
630
631impl TextStyleExt for Paragraph {
632    fn get_text_style_data(&mut self) -> &mut TextStyleData {
633        &mut self.element.text_style_data
634    }
635}
636
637impl Paragraph {
638    pub fn try_downcast(element: &dyn ElementExt) -> Option<ParagraphElement> {
639        (element as &dyn Any)
640            .downcast_ref::<ParagraphElement>()
641            .cloned()
642    }
643
644    pub fn spans_iter(mut self, spans: impl Iterator<Item = Span<'static>>) -> Self {
645        let spans = spans.collect::<Vec<Span>>();
646        // TODO: Accessible paragraphs
647        // self.element.accessibility.builder.set_value(text.clone());
648        self.element.spans.extend(spans);
649        self
650    }
651
652    pub fn span(mut self, span: impl Into<Span<'static>>) -> Self {
653        let span = span.into();
654        // TODO: Accessible paragraphs
655        // self.element.accessibility.builder.set_value(text.clone());
656        self.element.spans.push(span);
657        self
658    }
659
660    pub fn cursor_color(mut self, cursor_color: impl Into<Color>) -> Self {
661        self.element.cursor_style_data.color = cursor_color.into();
662        self
663    }
664
665    pub fn highlight_color(mut self, highlight_color: impl Into<Color>) -> Self {
666        self.element.cursor_style_data.highlight_color = highlight_color.into();
667        self
668    }
669
670    pub fn cursor_style(mut self, cursor_style: impl Into<CursorStyle>) -> Self {
671        self.element.cursor_style = cursor_style.into();
672        self
673    }
674
675    pub fn holder(mut self, holder: ParagraphHolder) -> Self {
676        self.element.sk_paragraph = holder;
677        self
678    }
679
680    pub fn cursor_index(mut self, cursor_index: impl Into<Option<usize>>) -> Self {
681        self.element.cursor_index = cursor_index.into();
682        self
683    }
684
685    pub fn highlights(mut self, highlights: impl Into<Option<Vec<(usize, usize)>>>) -> Self {
686        if let Some(highlights) = highlights.into() {
687            self.element.highlights = highlights;
688        }
689        self
690    }
691
692    pub fn max_lines(mut self, max_lines: impl Into<Option<usize>>) -> Self {
693        self.element.max_lines = max_lines.into();
694        self
695    }
696
697    pub fn line_height(mut self, line_height: impl Into<Option<f32>>) -> Self {
698        self.element.line_height = line_height.into();
699        self
700    }
701
702    /// Set the cursor mode for the paragraph.
703    /// - `CursorMode::Fit`: cursor/highlights use the paragraph's visible_area. VerticalAlign affects cursor positions.
704    /// - `CursorMode::Expanded`: cursor/highlights use the paragraph's inner_area. VerticalAlign does NOT affect cursor positions.
705    pub fn cursor_mode(mut self, cursor_mode: impl Into<CursorMode>) -> Self {
706        self.element.cursor_mode = cursor_mode.into();
707        self
708    }
709
710    /// Set the vertical alignment for the paragraph text.
711    /// This affects how the text is rendered within the paragraph area, but cursor/highlight behavior
712    /// depends on the `cursor_mode` setting.
713    pub fn vertical_align(mut self, vertical_align: impl Into<VerticalAlign>) -> Self {
714        self.element.vertical_align = vertical_align.into();
715        self
716    }
717}
718
719#[derive(Clone, PartialEq, Hash)]
720pub struct Span<'a> {
721    pub text_style_data: TextStyleData,
722    pub text: Cow<'a, str>,
723}
724
725impl From<&'static str> for Span<'static> {
726    fn from(text: &'static str) -> Self {
727        Span {
728            text_style_data: TextStyleData::default(),
729            text: text.into(),
730        }
731    }
732}
733
734impl From<String> for Span<'static> {
735    fn from(text: String) -> Self {
736        Span {
737            text_style_data: TextStyleData::default(),
738            text: text.into(),
739        }
740    }
741}
742
743impl<'a> Span<'a> {
744    pub fn new(text: impl Into<Cow<'a, str>>) -> Self {
745        Self {
746            text: text.into(),
747            text_style_data: TextStyleData::default(),
748        }
749    }
750}
751
752impl<'a> TextStyleExt for Span<'a> {
753    fn get_text_style_data(&mut self) -> &mut TextStyleData {
754        &mut self.text_style_data
755    }
756}