1use 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 Canvas,
16 FontCollection,
17 FontStyle,
18 Paint,
19 PaintStyle,
20 ParagraphBuilder,
21 ParagraphStyle,
22 PlaceholderAlignment,
23 PlaceholderStyle,
24 RectHeightStyle,
25 RectWidthStyle,
26 SkParagraph,
27 SkRect,
28 TextBaseline,
29 TextStyle,
30};
31use torin::prelude::{
32 Area,
33 Length,
34 Point2D,
35 Position,
36 PostMeasure,
37 Size2D,
38};
39use unicode_segmentation::UnicodeSegmentation;
40
41use crate::{
42 data::{
43 AccessibilityData,
44 CursorStyleData,
45 EffectData,
46 LayoutData,
47 StyleState,
48 TextStyleData,
49 TextStyleState,
50 },
51 diff_key::DiffKey,
52 element::{
53 Element,
54 ElementExt,
55 EventHandlers,
56 IntoElement,
57 LayoutContext,
58 PostMeasureContext,
59 RenderContext,
60 },
61 elements::rect::rect,
62 layers::Layer,
63 node_id::NodeId,
64 prelude::{
65 AccessibilityExt,
66 ChildrenExt,
67 Color,
68 ContainerExt,
69 ContainerPositionExt,
70 EventHandlersExt,
71 KeyExt,
72 LayerExt,
73 LayoutExt,
74 MaybeExt,
75 TextAlign,
76 TextStyleExt,
77 VerticalAlign,
78 },
79 style::cursor::{
80 CursorMode,
81 CursorStyle,
82 },
83 text_cache::CachedParagraph,
84 tree::DiffModifies,
85};
86
87pub fn paragraph() -> Paragraph {
100 Paragraph::default()
101}
102
103pub struct ParagraphHolderInner {
104 pub paragraph: Rc<SkParagraph>,
105 pub scale_factor: f64,
106}
107
108#[derive(Clone)]
111pub struct ParagraphHolder(pub Rc<RefCell<Option<ParagraphHolderInner>>>);
112
113impl PartialEq for ParagraphHolder {
114 fn eq(&self, other: &Self) -> bool {
115 Rc::ptr_eq(&self.0, &other.0)
116 }
117}
118
119impl Debug for ParagraphHolder {
120 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
121 f.write_str("ParagraphHolder")
122 }
123}
124
125impl Default for ParagraphHolder {
126 fn default() -> Self {
127 Self(Rc::new(RefCell::new(None)))
128 }
129}
130
131#[derive(PartialEq, Clone)]
133pub enum ParagraphContent {
134 Span,
135 Element,
136}
137
138#[derive(PartialEq, Clone)]
139pub struct ParagraphElement {
140 pub layout: LayoutData,
141 pub spans: Vec<Span<'static>>,
142 pub contents: Vec<ParagraphContent>,
143 pub accessibility: AccessibilityData,
144 pub text_style_data: TextStyleData,
145 pub cursor_style_data: CursorStyleData,
146 pub event_handlers: EventHandlers,
147 pub sk_paragraph: ParagraphHolder,
148 pub cursor_index: Option<usize>,
149 pub highlights: Vec<(usize, usize)>,
150 pub max_lines: Option<usize>,
151 pub line_height: Option<f32>,
152 pub relative_layer: Layer,
153 pub cursor_style: CursorStyle,
154 pub cursor_mode: CursorMode,
155 pub vertical_align: VerticalAlign,
156}
157
158impl Default for ParagraphElement {
159 fn default() -> Self {
160 let mut accessibility = AccessibilityData::default();
161 accessibility.builder.set_role(accesskit::Role::Paragraph);
162 Self {
163 layout: Default::default(),
164 spans: Default::default(),
165 contents: Default::default(),
166 accessibility,
167 text_style_data: Default::default(),
168 cursor_style_data: Default::default(),
169 event_handlers: Default::default(),
170 sk_paragraph: Default::default(),
171 cursor_index: Default::default(),
172 highlights: Default::default(),
173 max_lines: Default::default(),
174 line_height: Default::default(),
175 relative_layer: Default::default(),
176 cursor_style: CursorStyle::default(),
177 cursor_mode: CursorMode::default(),
178 vertical_align: VerticalAlign::default(),
179 }
180 }
181}
182
183impl Display for ParagraphElement {
184 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
185 f.write_str(
186 &self
187 .spans
188 .iter()
189 .map(|s| s.text.clone())
190 .collect::<Vec<_>>()
191 .join("\n"),
192 )
193 }
194}
195
196impl ElementExt for ParagraphElement {
197 fn changed(&self, other: &Rc<dyn ElementExt>) -> bool {
198 let Some(paragraph) = (other.as_ref() as &dyn Any).downcast_ref::<ParagraphElement>()
199 else {
200 return false;
201 };
202 self != paragraph
203 }
204
205 fn diff(&self, other: &Rc<dyn ElementExt>) -> DiffModifies {
206 let Some(paragraph) = (other.as_ref() as &dyn Any).downcast_ref::<ParagraphElement>()
207 else {
208 return DiffModifies::all();
209 };
210
211 let mut diff = DiffModifies::empty();
212
213 if self.spans != paragraph.spans || self.contents != paragraph.contents {
214 diff.insert(DiffModifies::STYLE);
215 diff.insert(DiffModifies::LAYOUT);
216 diff.insert(DiffModifies::ACCESSIBILITY);
217 }
218
219 if self.accessibility != paragraph.accessibility {
220 diff.insert(DiffModifies::ACCESSIBILITY);
221 }
222
223 if self.relative_layer != paragraph.relative_layer {
224 diff.insert(DiffModifies::LAYER);
225 }
226
227 if self.text_style_data != paragraph.text_style_data {
228 diff.insert(DiffModifies::STYLE);
229 }
230
231 if self.event_handlers != paragraph.event_handlers {
232 diff.insert(DiffModifies::EVENT_HANDLERS);
233 }
234
235 if self.cursor_index != paragraph.cursor_index
236 || self.highlights != paragraph.highlights
237 || self.cursor_mode != paragraph.cursor_mode
238 || self.cursor_style != paragraph.cursor_style
239 || self.cursor_style_data != paragraph.cursor_style_data
240 || self.vertical_align != paragraph.vertical_align
241 {
242 diff.insert(DiffModifies::STYLE);
243 }
244
245 if self.text_style_data != paragraph.text_style_data
246 || self.line_height != paragraph.line_height
247 || self.max_lines != paragraph.max_lines
248 {
249 diff.insert(DiffModifies::TEXT_STYLE);
250 diff.insert(DiffModifies::LAYOUT);
251 }
252
253 if self.layout != paragraph.layout {
254 diff.insert(DiffModifies::STYLE);
255 diff.insert(DiffModifies::LAYOUT);
256 }
257
258 diff
259 }
260
261 fn layout(&'_ self) -> Cow<'_, LayoutData> {
262 Cow::Borrowed(&self.layout)
263 }
264 fn effect(&'_ self) -> Option<Cow<'_, EffectData>> {
265 None
266 }
267
268 fn style(&'_ self) -> Cow<'_, StyleState> {
269 Cow::Owned(StyleState::default())
270 }
271
272 fn is_transparent(&self) -> bool {
273 false
274 }
275
276 fn text_style(&'_ self) -> Cow<'_, TextStyleData> {
277 Cow::Borrowed(&self.text_style_data)
278 }
279
280 fn accessibility(&'_ self) -> Cow<'_, AccessibilityData> {
281 Cow::Borrowed(&self.accessibility)
282 }
283
284 fn finish_accessibility(&self, builder: &mut accesskit::Node) {
285 builder.set_value(
286 self.spans
287 .iter()
288 .map(|span| span.text.as_ref())
289 .collect::<String>(),
290 );
291 }
292
293 fn layer(&self) -> Layer {
294 self.relative_layer
295 }
296
297 fn measure(&self, context: LayoutContext) -> Option<(Size2D, Rc<dyn Any>)> {
298 let cached_paragraph = CachedParagraph {
299 text_style_state: context.text_style_state,
300 spans: &self.spans,
301 max_lines: self.max_lines,
302 line_height: self.line_height,
303 width: context.area_size.width,
304 };
305 let paragraph = context
306 .text_cache
307 .utilize(context.node_id, &cached_paragraph)
308 .unwrap_or_else(|| {
309 let width = if self.max_lines == Some(1)
310 && context.text_style_state.text_align == TextAlign::default()
311 && context
312 .text_style_state
313 .text_overflow
314 .get_ellipsis()
315 .is_none()
316 {
317 f32::MAX
318 } else {
319 context.area_size.width + 1.0
320 };
321
322 let paragraph = self.build_paragraph(
323 context.text_style_state,
324 context.fallback_fonts,
325 context.scale_factor,
326 context.font_collection,
327 width,
328 &[],
329 );
330 context
331 .text_cache
332 .insert(context.node_id, &cached_paragraph, paragraph)
333 });
334
335 let size = Size2D::new(paragraph.longest_line(), paragraph.height()).max(Size2D::zero());
336
337 self.sk_paragraph
338 .0
339 .borrow_mut()
340 .replace(ParagraphHolderInner {
341 paragraph,
342 scale_factor: context.scale_factor,
343 });
344
345 Some((size, Rc::new(())))
346 }
347
348 fn should_hook_measurement(&self) -> bool {
349 true
350 }
351
352 fn should_measure_inner_children(&self) -> bool {
353 self.has_inline_content()
354 }
355
356 fn needs_post_measure(&self) -> bool {
357 self.has_inline_content()
358 }
359
360 fn post_measure(&self, context: PostMeasureContext) -> PostMeasure<NodeId> {
361 if context.children.is_empty() {
362 return PostMeasure::default();
363 }
364
365 let placeholders: Vec<Size2D> = context
366 .children
367 .iter()
368 .map(|child| {
369 context
370 .layout
371 .get(child)
372 .map(|node| node.area.size)
373 .unwrap()
374 })
375 .collect();
376
377 let width = self
378 .sk_paragraph
379 .0
380 .borrow()
381 .as_ref()
382 .map(|holder| holder.paragraph.max_width())
383 .unwrap();
384
385 let paragraph = self.build_paragraph(
386 context.text_style_state,
387 context.fallback_fonts,
388 context.scale_factor,
389 context.font_collection,
390 width,
391 &placeholders,
392 );
393 let rects = paragraph.get_rects_for_placeholders();
394 let paragraph_height = paragraph.height();
395 let content_size = Size2D::new(paragraph.longest_line(), paragraph_height);
397
398 self.sk_paragraph
399 .0
400 .borrow_mut()
401 .replace(ParagraphHolderInner {
402 paragraph: Rc::new(paragraph),
403 scale_factor: context.scale_factor,
404 });
405
406 let visible_area = context.node_layout.visible_area();
407 let vertical_offset = match self.vertical_align {
408 VerticalAlign::Start => 0.0,
409 VerticalAlign::Center => (visible_area.height() - paragraph_height).max(0.0) / 2.0,
410 };
411 let origin = visible_area.origin;
412
413 let mut offsets = Vec::new();
414 let mut hidden_children = Vec::new();
415 for (index, child_id) in context.children.iter().enumerate() {
416 let Some(current) = context.layout.get(child_id).map(|node| node.area.origin) else {
417 continue;
418 };
419 match rects.get(index) {
420 Some(rect) => {
421 let offset_x = origin.x + rect.rect.left - current.x;
422 let offset_y = origin.y + vertical_offset + rect.rect.top - current.y;
423 offsets.push((*child_id, Length::new(offset_x), Length::new(offset_y)));
424 }
425 None => hidden_children.push(*child_id),
427 }
428 }
429
430 PostMeasure {
431 content_size: Some(content_size),
432 offsets,
433 hidden_children,
434 }
435 }
436
437 fn events_handlers(&'_ self) -> Option<Cow<'_, EventHandlers>> {
438 Some(Cow::Borrowed(&self.event_handlers))
439 }
440
441 fn render(&self, context: RenderContext) {
442 let paragraph = self.sk_paragraph.0.borrow();
443 let ParagraphHolderInner { paragraph, .. } = paragraph.as_ref().unwrap();
444 let visible_area = context.layout_node.visible_area();
445
446 let cursor_area = match self.cursor_mode {
447 CursorMode::Fit => visible_area,
448 CursorMode::Expanded => context.layout_node.area,
449 };
450
451 let paragraph_height = paragraph.height();
452 let area_height = visible_area.height();
453 let vertical_offset = match self.vertical_align {
454 VerticalAlign::Start => 0.0,
455 VerticalAlign::Center => (area_height - paragraph_height).max(0.0) / 2.0,
456 };
457
458 let cursor_vertical_offset = match self.cursor_mode {
459 CursorMode::Fit => vertical_offset,
460 CursorMode::Expanded => 0.0,
461 };
462 let cursor_vertical_size_offset = match self.cursor_mode {
463 CursorMode::Fit => 0.,
464 CursorMode::Expanded => vertical_offset * 2.,
465 };
466
467 let to_cursor_area = |rect: SkRect| {
468 SkRect::new(
469 cursor_area.min_x() + rect.left,
470 cursor_area.min_y() + rect.top + cursor_vertical_offset,
471 cursor_area.min_x() + rect.right,
472 cursor_area.min_y() + rect.bottom + cursor_vertical_size_offset,
473 )
474 };
475
476 for (from, to) in self.highlights.iter() {
478 if from == to {
479 continue;
480 }
481 let (from, to) = { if from < to { (from, to) } else { (to, from) } };
482 let rects = paragraph.get_rects_for_range(
483 *from..*to,
484 RectHeightStyle::Tight,
485 RectWidthStyle::Tight,
486 );
487
488 let mut highlights_paint = Paint::default();
489 highlights_paint.set_anti_alias(true);
490 highlights_paint.set_style(PaintStyle::Fill);
491 highlights_paint.set_color(self.cursor_style_data.highlight_color);
492
493 if rects.is_empty() && *from == 0 {
494 let caret_rect =
495 paragraph.cursor_rect(&self.text(), *from, context.text_style_state.text_align);
496 context
497 .canvas
498 .draw_rect(to_cursor_area(caret_rect), &highlights_paint);
499 }
500
501 for rect in rects {
502 let mut rect = rect.rect;
503 rect.right = rect.right.max(6.);
504 context
505 .canvas
506 .draw_rect(to_cursor_area(rect), &highlights_paint);
507 }
508 }
509
510 let visible_highlights = self
511 .highlights
512 .iter()
513 .any(|highlight| highlight.0 != highlight.1);
514
515 let mut cursor_paint = Paint::default();
516 cursor_paint.set_anti_alias(true);
517 cursor_paint.set_style(PaintStyle::Fill);
518 cursor_paint.set_color(self.cursor_style_data.color);
519
520 if let Some(cursor_index) = self.cursor_index
522 && self.cursor_style == CursorStyle::Block
523 {
524 let mut cursor_rect = paragraph.cursor_rect(
525 &self.text(),
526 cursor_index,
527 context.text_style_state.text_align,
528 );
529 let width = (cursor_rect.right - cursor_rect.left).max(6.0);
530 cursor_rect.right = cursor_rect.left + width;
531 context
532 .canvas
533 .draw_rect(to_cursor_area(cursor_rect), &cursor_paint);
534 }
535
536 paragraph.paint_at(
538 context.canvas,
539 Point2D::new(visible_area.min_x(), visible_area.min_y() + vertical_offset),
540 );
541
542 if let Some(cursor_index) = self.cursor_index
544 && !visible_highlights
545 && self.cursor_style != CursorStyle::Block
546 {
547 let mut cursor_rect = paragraph.cursor_rect(
548 &self.text(),
549 cursor_index,
550 context.text_style_state.text_align,
551 );
552 match self.cursor_style {
553 CursorStyle::Underline => cursor_rect.top = cursor_rect.bottom - 2.,
554 _ => cursor_rect.right = cursor_rect.left + 2.,
555 }
556 context
557 .canvas
558 .draw_rect(to_cursor_area(cursor_rect), &cursor_paint);
559 }
560 }
561}
562
563impl ParagraphElement {
564 fn has_inline_content(&self) -> bool {
565 self.contents
566 .iter()
567 .any(|content| matches!(content, ParagraphContent::Element))
568 }
569
570 fn text(&self) -> String {
572 let mut text = String::new();
573 let mut spans = self.spans.iter();
574 for content in &self.contents {
575 match content {
576 ParagraphContent::Span => {
577 if let Some(span) = spans.next() {
578 text.push_str(&span.text);
579 }
580 }
581 ParagraphContent::Element => text.push('\u{FFFC}'),
582 }
583 }
584 text
585 }
586
587 fn has_shader_fill(&self, text_style_state: &TextStyleState) -> bool {
588 text_style_state.color.as_color().is_none()
589 || self.spans.iter().any(|span| {
590 span.text_style_data
591 .color
592 .as_ref()
593 .is_some_and(|color| color.as_color().is_none())
594 })
595 }
596
597 fn build_paragraph(
599 &self,
600 text_style_state: &TextStyleState,
601 fallback_fonts: &[Cow<'static, str>],
602 scale_factor: f64,
603 font_collection: &FontCollection,
604 width: f32,
605 placeholders: &[Size2D],
606 ) -> SkParagraph {
607 let build = |fill_area: Area| {
608 let mut paragraph_style = ParagraphStyle::default();
609
610 if let Some(ellipsis) = text_style_state.text_overflow.get_ellipsis() {
611 paragraph_style.set_ellipsis(ellipsis);
612 }
613
614 paragraph_style.set_text_style(&text_style_state.to_text_style(
615 fallback_fonts,
616 scale_factor,
617 self.line_height,
618 fill_area,
619 ));
620 paragraph_style.set_max_lines(self.max_lines);
621 paragraph_style.set_text_align(text_style_state.text_align.into());
622
623 let mut paragraph_builder = ParagraphBuilder::new(¶graph_style, font_collection);
624
625 let mut spans = self.spans.iter();
626 let mut placeholders = placeholders.iter();
627 for content in &self.contents {
628 match content {
629 ParagraphContent::Span => {
630 let Some(span) = spans.next() else { continue };
631 paragraph_builder.push_style(&span.to_text_style(
632 text_style_state,
633 fallback_fonts,
634 scale_factor,
635 self.line_height,
636 fill_area,
637 ));
638 paragraph_builder.add_text(&span.text);
639 }
640 ParagraphContent::Element => {
641 let Some(size) = placeholders.next() else {
642 continue;
643 };
644 paragraph_builder.add_placeholder(&PlaceholderStyle::new(
645 size.width,
646 size.height,
647 PlaceholderAlignment::Middle,
648 TextBaseline::Alphabetic,
649 0.0,
650 ));
651 }
652 }
653 }
654
655 let mut paragraph = paragraph_builder.build();
656 paragraph.layout(width);
657 paragraph
658 };
659
660 let paragraph = build(Area::default());
661
662 if self.has_shader_fill(text_style_state) {
665 return build(paragraph.fill_area());
666 }
667
668 paragraph
669 }
670}
671
672pub trait ParagraphCursorExt {
673 fn measured_cursor_rect(&self, text: &str, cursor_index: usize) -> Option<SkRect>;
675
676 fn cursor_rect(&self, text: &str, cursor_index: usize, text_align: TextAlign) -> SkRect;
678
679 fn cursor_index_at_point(&self, point: (f64, f64)) -> usize;
681}
682
683impl ParagraphCursorExt for SkParagraph {
684 fn measured_cursor_rect(&self, text: &str, cursor_index: usize) -> Option<SkRect> {
685 let mut cluster = 0..0;
686 let mut cluster_byte_index = 0;
687 for (byte_index, grapheme) in text.grapheme_indices(true) {
688 cluster = cluster.end..cluster.end + grapheme.encode_utf16().count();
689 cluster_byte_index = byte_index;
690 if cluster.end > cursor_index {
691 break;
692 }
693 }
694
695 if !cluster.is_empty() {
696 if cluster.end <= cursor_index
697 && let Some(cluster_line) = self.get_line_number_at(cluster_byte_index)
698 && let Some(line) = self.get_line_metrics_at(cluster_line + 1)
699 {
700 let left = line.left as f32;
701 let top = (line.baseline - line.ascent) as f32;
702 let bottom = (line.baseline + line.descent) as f32;
703 return Some(SkRect::new(left, top, left, bottom));
704 }
705
706 let rects = self.get_rects_for_range(
707 cluster.clone(),
708 RectHeightStyle::Tight,
709 RectWidthStyle::Tight,
710 );
711 if let Some(rect) = rects.first() {
712 let mut rect = rect.rect;
713 if cluster.end <= cursor_index {
714 rect.left = rect.right;
715 }
716 return Some(rect);
717 }
718 }
719
720 let line = self.get_line_metrics_at(0)?;
721 let left = line.left as f32;
722
723 Some(SkRect::new(left, 0., left + 6., line.height as f32))
724 }
725
726 fn cursor_rect(&self, text: &str, cursor_index: usize, text_align: TextAlign) -> SkRect {
727 if let Some(rect) = self.measured_cursor_rect(text, cursor_index) {
728 return rect;
729 }
730
731 let left = match text_align {
733 TextAlign::Center => self.max_width() / 2.,
734 TextAlign::Right | TextAlign::End => self.max_width() - 6.,
735 _ => 0.,
736 };
737
738 SkRect::new(left, 0., left + 6., self.height())
739 }
740
741 fn cursor_index_at_point(&self, point: (f64, f64)) -> usize {
742 let (horizontal_position, vertical_position) = point;
743 let mut horizontal_position = horizontal_position as i32;
744 if let Some(line) = self
745 .get_line_metrics()
746 .into_iter()
747 .find(|line| vertical_position < line.baseline + line.descent)
748 && !line.hard_break
749 {
750 horizontal_position = horizontal_position.min((line.left + line.width) as i32 - 1);
752 }
753
754 self.get_glyph_position_at_coordinate((horizontal_position, vertical_position as i32))
755 .position
756 .max(0) as usize
757 }
758}
759
760impl From<Paragraph> for Element {
761 fn from(value: Paragraph) -> Self {
762 let elements = value
763 .children
764 .into_iter()
765 .map(|child| {
766 rect()
767 .position(Position::new_absolute())
768 .child(child)
769 .into_element()
770 })
771 .collect();
772
773 Element::Element {
774 key: value.key,
775 element: Rc::new(value.element),
776 elements,
777 }
778 }
779}
780
781impl TextStyleState {
782 pub(crate) fn to_text_style(
784 &self,
785 fallback_fonts: &[Cow<'static, str>],
786 scale_factor: f64,
787 line_height: Option<f32>,
788 fill_area: Area,
789 ) -> TextStyle {
790 let mut text_style = TextStyle::default();
791
792 let mut font_families = self.font_families.clone();
793 font_families.extend_from_slice(fallback_fonts);
794
795 self.color.apply_to_text_style(&mut text_style, fill_area);
796 text_style.set_font_size(f32::from(self.font_size) * scale_factor as f32);
797 text_style.set_font_families(&font_families);
798 text_style.set_font_style(FontStyle::new(
799 self.font_weight.into(),
800 self.font_width.into(),
801 self.font_slant.into(),
802 ));
803
804 if self.text_height.needs_custom_height() {
805 text_style.set_height_override(true);
806 text_style.set_half_leading(true);
807 }
808
809 if let Some(line_height) = line_height {
810 text_style.set_height_override(true);
811 text_style.set_height(line_height);
812 }
813
814 for text_shadow in self.text_shadows.iter() {
815 text_style.add_shadow((*text_shadow).into());
816 }
817
818 text_style
819 }
820}
821
822impl Span<'_> {
823 fn to_text_style(
825 &self,
826 text_style_state: &TextStyleState,
827 fallback_fonts: &[Cow<'static, str>],
828 scale_factor: f64,
829 line_height: Option<f32>,
830 fill_area: Area,
831 ) -> TextStyle {
832 let span_style = TextStyleState::from_data(text_style_state, &self.text_style_data);
833 let mut text_style = TextStyle::new();
834
835 let mut font_families = text_style_state.font_families.clone();
836 font_families.extend_from_slice(fallback_fonts);
837
838 span_style
839 .color
840 .apply_to_text_style(&mut text_style, fill_area);
841 text_style.set_font_size(f32::from(span_style.font_size) * scale_factor as f32);
842 text_style.set_font_families(&font_families);
843 text_style.set_font_style(FontStyle::new(
844 span_style.font_weight.into(),
845 span_style.font_width.into(),
846 span_style.font_slant.into(),
847 ));
848 text_style.set_decoration_type(span_style.text_decoration.into());
849
850 if let Some(line_height) = line_height {
851 text_style.set_height_override(true);
852 text_style.set_height(line_height);
853 }
854
855 for text_shadow in span_style.text_shadows.iter() {
856 text_style.add_shadow((*text_shadow).into());
857 }
858
859 text_style
860 }
861}
862
863pub(crate) trait ParagraphPaintExt {
864 fn paint_at(&self, canvas: &Canvas, origin: Point2D);
866
867 fn fill_area(&self) -> Area;
869}
870
871impl ParagraphPaintExt for SkParagraph {
872 fn paint_at(&self, canvas: &Canvas, origin: Point2D) {
873 let layer = canvas.save();
874 canvas.translate(origin.to_tuple());
875 self.paint(canvas, (0., 0.));
876 canvas.restore_to_count(layer);
877 }
878
879 fn fill_area(&self) -> Area {
880 let max_width = self.max_width();
881 let width = if max_width < f32::MAX {
882 max_width
883 } else {
884 self.longest_line()
885 };
886
887 Area::new(Point2D::zero(), Size2D::new(width, self.height()))
888 }
889}
890
891impl KeyExt for Paragraph {
892 fn write_key(&mut self) -> &mut DiffKey {
893 &mut self.key
894 }
895}
896
897impl EventHandlersExt for Paragraph {
898 fn get_event_handlers(&mut self) -> &mut EventHandlers {
899 &mut self.element.event_handlers
900 }
901}
902
903impl MaybeExt for Paragraph {}
904
905impl LayerExt for Paragraph {
906 fn get_layer(&mut self) -> &mut Layer {
907 &mut self.element.relative_layer
908 }
909}
910
911#[derive(Default, Clone)]
912pub struct Paragraph {
913 key: DiffKey,
914 element: ParagraphElement,
915 children: Vec<Element>,
916}
917
918impl LayoutExt for Paragraph {
919 fn get_layout(&mut self) -> &mut LayoutData {
920 &mut self.element.layout
921 }
922}
923
924impl ContainerExt for Paragraph {}
925
926impl ChildrenExt for Paragraph {
929 fn get_children(&mut self) -> &mut Vec<Element> {
930 &mut self.children
931 }
932
933 fn child<C: IntoElement>(mut self, child: C) -> Self {
934 self.element.contents.push(ParagraphContent::Element);
935 self.children.push(child.into_element());
936 self
937 }
938
939 fn children(self, children: impl IntoIterator<Item = impl IntoElement>) -> Self {
940 children
941 .into_iter()
942 .fold(self, |paragraph, child| paragraph.child(child))
943 }
944
945 fn maybe_child<C: IntoElement>(self, child: Option<C>) -> Self {
946 match child {
947 Some(child) => self.child(child),
948 None => self,
949 }
950 }
951}
952
953impl AccessibilityExt for Paragraph {
954 fn get_accessibility_data(&mut self) -> &mut AccessibilityData {
955 &mut self.element.accessibility
956 }
957}
958
959impl TextStyleExt for Paragraph {
960 fn get_text_style_data(&mut self) -> &mut TextStyleData {
961 &mut self.element.text_style_data
962 }
963}
964
965impl Paragraph {
966 pub fn try_downcast(element: &dyn ElementExt) -> Option<ParagraphElement> {
967 (element as &dyn Any)
968 .downcast_ref::<ParagraphElement>()
969 .cloned()
970 }
971
972 pub fn spans_iter(mut self, spans: impl Iterator<Item = Span<'static>>) -> Self {
974 for span in spans {
975 self.push_span(span);
976 }
977 self
978 }
979
980 pub fn span(mut self, span: impl Into<Span<'static>>) -> Self {
982 self.push_span(span.into());
983 self
984 }
985
986 fn push_span(&mut self, span: Span<'static>) {
987 self.element.contents.push(ParagraphContent::Span);
988 self.element.spans.push(span);
989 }
990
991 pub fn cursor_style_data(mut self, cursor_style_data: CursorStyleData) -> Self {
993 self.element.cursor_style_data = cursor_style_data;
994 self
995 }
996
997 pub fn cursor_color(mut self, cursor_color: impl Into<Color>) -> Self {
999 self.element.cursor_style_data.color = cursor_color.into();
1000 self
1001 }
1002
1003 pub fn highlight_color(mut self, highlight_color: impl Into<Color>) -> Self {
1005 self.element.cursor_style_data.highlight_color = highlight_color.into();
1006 self
1007 }
1008
1009 pub fn cursor_style(mut self, cursor_style: impl Into<CursorStyle>) -> Self {
1011 self.element.cursor_style = cursor_style.into();
1012 self
1013 }
1014
1015 pub fn holder(mut self, holder: ParagraphHolder) -> Self {
1017 self.element.sk_paragraph = holder;
1018 self
1019 }
1020
1021 pub fn cursor_index(mut self, cursor_index: impl Into<Option<usize>>) -> Self {
1023 self.element.cursor_index = cursor_index.into();
1024 self
1025 }
1026
1027 pub fn highlights(mut self, highlights: impl Into<Option<Vec<(usize, usize)>>>) -> Self {
1029 if let Some(highlights) = highlights.into() {
1030 self.element.highlights = highlights;
1031 }
1032 self
1033 }
1034
1035 pub fn max_lines(mut self, max_lines: impl Into<Option<usize>>) -> Self {
1037 self.element.max_lines = max_lines.into();
1038 self
1039 }
1040
1041 pub fn line_height(mut self, line_height: impl Into<Option<f32>>) -> Self {
1043 self.element.line_height = line_height.into();
1044 self
1045 }
1046
1047 pub fn cursor_mode(mut self, cursor_mode: impl Into<CursorMode>) -> Self {
1051 self.element.cursor_mode = cursor_mode.into();
1052 self
1053 }
1054
1055 pub fn vertical_align(mut self, vertical_align: impl Into<VerticalAlign>) -> Self {
1059 self.element.vertical_align = vertical_align.into();
1060 self
1061 }
1062}
1063
1064#[derive(Clone, PartialEq, Hash)]
1075pub struct Span<'a> {
1076 pub text_style_data: TextStyleData,
1077 pub text: Cow<'a, str>,
1078}
1079
1080impl From<&'static str> for Span<'static> {
1081 fn from(text: &'static str) -> Self {
1082 Span {
1083 text_style_data: TextStyleData::default(),
1084 text: text.into(),
1085 }
1086 }
1087}
1088
1089impl From<String> for Span<'static> {
1090 fn from(text: String) -> Self {
1091 Span {
1092 text_style_data: TextStyleData::default(),
1093 text: text.into(),
1094 }
1095 }
1096}
1097
1098impl<'a> Span<'a> {
1099 pub fn new(text: impl Into<Cow<'a, str>>) -> Self {
1101 Self {
1102 text: text.into(),
1103 text_style_data: TextStyleData::default(),
1104 }
1105 }
1106}
1107
1108impl<'a> TextStyleExt for Span<'a> {
1109 fn get_text_style_data(&mut self) -> &mut TextStyleData {
1110 &mut self.text_style_data
1111 }
1112}