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