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