1use crate::utils::debug_display::{append_optional_value, build_base_debug_display};
2use crate::documents::{EmbeddedControlRun, IInlineHost, Inline, InlineCollection, TextElement};
3use crate::{Border, Control, ControlImpl, Decorator, TextLayoutRoundingPlacement};
4use ferroui_base::collections::FerroList;
5use ferroui_base::input::InputElementImpl;
6use ferroui_base::interactivity::InteractiveImpl;
7use ferroui_base::layout::{LayoutHelper, LayoutableImpl, LayoutableImplExt, VerticalAlignment};
8use ferroui_base::media::text_formatting::{
9 FormattedTextSource, GenericTextParagraphProperties, GenericTextRunProperties, ITextSource, TextCharacters,
10 TextEndOfParagraph, TextLayout, TextLineImpl, TextRun, TextRunCache, TextRunProperties,
11};
12use ferroui_base::media::{
13 DrawingContext, FlowDirection, FontFamily, FontFeatureCollection, FontStretch, FontStyle, FontWeight, IBrush, TextAlignment,
14 TextDecorationCollection, TextTrimming, TextWrapping, Typeface,
15};
16use ferroui_base::reactive::IDisposable;
17use ferroui_base::utilities::{ReadOnlyMemory, ValueSpan};
18use ferroui_base::{
19 ferro_class, ferro_property, instantiate, AttachedProperty, DirectProperty, FerroObjectImpl, FerroObjectImplExt,
20 FerroProperty, FerroPropertyChangedEventArgs, Point, Rect, Ref, Size, StyledElementImpl, StyledProperty,
21 StyledPropertyOptions, Thickness, Visual, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs, WeakRef,
22};
23use std::cell::{Cell, OnceCell, RefCell};
24use std::rc::Rc;
25
26#[repr(C)]
28pub struct TextBlock {
29 base: Control,
30 text_layout: RefCell<Option<Rc<TextLayout>>>,
31 text_run_cache: RefCell<Option<Rc<TextRunCache>>>,
32 constraint: Cell<Size>,
33 text_runs: RefCell<Option<Rc<Vec<Rc<dyn TextRun>>>>>,
34 inlines: RefCell<Option<InlineCollection>>,
35 inlines_invalidated: RefCell<Option<Rc<dyn IDisposable>>>,
36 inline_host: OnceCell<Rc<dyn IInlineHost>>,
37 clear_text_internal: Cell<bool>,
38 layout_text_alignment: Cell<TextAlignment>,
40 utf16_text: RefCell<Option<(String, ReadOnlyMemory<u16>)>>,
44}
45
46ferro_class! {
47 TextBlock: Control, virtuals TextBlockImpl: ControlImpl {
48 #[doc(hidden)]
50 fn render_core(this, context: &mut DrawingContext);
51 fn render_text_layout(this, context: &mut DrawingContext, origin: Point);
53 fn create_text_layout(this, text: Option<&str>) -> Rc<TextLayout>;
55 }
56}
57ferroui_base::ferro_class_info!(TextBlock { new: TextBlock::new });
58
59ferroui_base::ferro_impl_classes!(TextBlock: InteractiveImpl);
60ferroui_base::ferro_impl_classes!(TextBlock: InputElementImpl);
61impl ControlImpl for TextBlock {
62 fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
63 crate::automation::peers::TextBlockAutomationPeer::new(this).upcast()
64 }
65}
66ferroui_base::ferro_impl_classes!(TextBlock: StyledElementImpl);
67
68ferroui_base::ferro_overrides! { impl FerroObjectImpl for TextBlock {
69 fn constructed(this: &Self) {
70 Self::parent_constructed(this);
71
72 let inlines = InlineCollection::new();
73 inlines.set_logical_children(Some(this));
74 inlines.set_inline_host(Some(this.as_inline_host()));
75 this.set_inlines(Some(inlines));
76 }
77
78 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
79 Self::parent_on_property_changed(this, change);
80
81 if change.property() == Self::text_property().as_property()
82 && this.has_complex_content()
83 && !this.clear_text_internal.get()
84 {
85 if let Some(inlines) = this.inlines() {
86 inlines.clear();
87 }
88 }
89
90 match change.property().name() {
91 "FontSize" | "FontWeight" | "FontStyle" | "FontFamily" | "FontStretch" | "FlowDirection"
93 | "LetterSpacing" | "Text" | "TextDecorations" | "FontFeatures" | "Foreground" => {
94 this.invalidate_text_layout();
95 }
96 "TextWrapping" | "TextTrimming" | "TextAlignment" | "Padding" | "LineHeight" | "LineSpacing"
98 | "MaxLines" => {
99 this.invalidate_text_layout_keep_cache();
100 }
101 "LayoutRoundingPlacement" => {
104 this.invalidate_visual();
105 this.invalidate_arrange();
106 }
107 "Inlines" => {
108 let (old_value, new_value) = change.get_old_and_new_value::<Option<InlineCollection>>();
109 this.on_inlines_changed(old_value, new_value);
110 this.invalidate_text_layout();
111 }
112 _ => {}
113 }
114 }
115} }
116
117impl VisualImpl for TextBlock {
118 fn render(this: &Self, context: &mut DrawingContext) {
122 this.render_core(context);
123 }
124
125 fn bypass_flow_direction_policies(_this: &Self) -> bool {
126 true
127 }
128
129 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
130 Self::parent_on_detached_from_visual_tree(this, e);
131
132 this.dispose_text_layout();
133 }
134}
135
136impl LayoutableImpl for TextBlock {
137 fn on_measure_invalidated(this: &Self) {
138 this.dispose_text_layout();
139
140 Self::parent_on_measure_invalidated(this);
141 }
142
143 fn measure_override(this: &Self, available_size: Size) -> Size {
144 let mut padding = this.padding();
145
146 if this.use_layout_rounding() {
147 let scale = LayoutHelper::get_layout_scale(this);
148 padding = LayoutHelper::round_layout_thickness(padding, scale);
149 }
150
151 let deflated_size = available_size.deflate(padding);
152
153 if this.constraint.get() != deflated_size {
154 this.dispose_text_layout();
156 this.constraint.set(deflated_size);
157
158 this.invalidate_arrange();
160 }
161
162 this.ensure_text_runs();
163
164 if this.measure_embedded_controls(deflated_size) {
165 this.dispose_text_layout();
169 }
170
171 let text_layout = this.text_layout();
174
175 Size::new(text_layout.width_including_trailing_whitespace(), text_layout.height()).inflate(padding)
178 }
179
180 fn arrange_override(this: &Self, final_size: Size) -> Size {
181 let mut padding = this.padding();
182 let mut layout_scale = None;
183
184 if this.use_layout_rounding() {
185 let scale = LayoutHelper::get_layout_scale(this);
186 padding = LayoutHelper::round_layout_thickness(padding, scale);
187 layout_scale = Some(scale);
188 }
189
190 let available_size = final_size.deflate(padding);
191
192 if this.constraint.get() != available_size
201 || this.layout_text_alignment.get() != this.resolve_text_alignment(this.text_alignment())
202 {
203 this.dispose_text_layout();
204 this.constraint.set(available_size);
205 }
206
207 let text_layout = this.text_layout();
209
210 if this.has_complex_content() {
211 this.visual_children().clear();
213
214 let mut current_y = padding.top + this.layout_rounding_offset_of(&text_layout, padding, layout_scale);
217
218 for text_line in text_layout.text_lines() {
219 let mut current_x = padding.left + text_line.start();
220
221 for run in text_line.text_runs().iter() {
222 if let Some(drawable) = run.as_drawable() {
223 if let Some(control_run) = run.downcast_ref::<EmbeddedControlRun>() {
224 let control = control_run.control();
225
226 this.visual_children().add(control.clone().upcast());
228
229 let offset_y = TextLineImpl::get_baseline_offset(&**text_line, drawable);
230
231 control.arrange(Rect::from_position_size(
232 Point::new(current_x, current_y + offset_y),
233 control.desired_size(),
234 ));
235 }
236
237 current_x += drawable.size().width;
238 }
239 }
240
241 current_y += text_line.height();
242 }
243 }
244
245 final_size
246 }
247}
248
249impl TextBlockImpl for TextBlock {
250 fn render_core(this: &Self, context: &mut DrawingContext) {
251 if let Some(background) = this.background() {
252 context.fill_rectangle(&background, Rect::from_size(this.bounds().size()), 0.0);
253 }
254
255 let mut padding = this.padding();
256 let mut layout_scale = None;
257 if this.use_layout_rounding() {
258 let scale = LayoutHelper::get_layout_scale(this);
259 padding = LayoutHelper::round_layout_thickness(padding, scale);
260 layout_scale = Some(scale);
261 }
262
263 let mut top = padding.top;
264 let text_height = this.text_layout().height();
265 let bounds_height = this.bounds().height;
266
267 if bounds_height < text_height {
268 match this.vertical_alignment() {
269 VerticalAlignment::Center => top += (bounds_height - text_height) / 2.0,
270 VerticalAlignment::Bottom => top += bounds_height - text_height,
271 _ => {}
272 }
273 }
274
275 top += this.layout_rounding_offset_of(&this.text_layout(), padding, layout_scale);
280
281 this.render_text_layout(context, Point::new(padding.left, top));
282 }
283
284 fn render_text_layout(this: &Self, context: &mut DrawingContext, origin: Point) {
285 this.text_layout().draw(context, origin);
286 }
287
288 fn create_text_layout(this: &Self, text: Option<&str>) -> Rc<TextLayout> {
289 let typeface =
290 Typeface::with_style(this.font_family(), this.font_style(), this.font_weight(), this.font_stretch());
291
292 let default_properties: Rc<dyn TextRunProperties> = Rc::new(GenericTextRunProperties::with_all(
293 typeface,
294 this.font_size(),
295 this.text_decorations(),
296 this.foreground(),
297 None,
298 Default::default(),
299 None,
300 this.font_features(),
301 ));
302
303 let paragraph_properties = GenericTextParagraphProperties::with_all(
304 this.flow_direction(),
305 if this.is_measure_valid() { this.text_alignment() } else { TextAlignment::Left },
306 true,
307 false,
308 default_properties.clone(),
309 this.text_wrapping(),
310 this.line_height(),
311 0.0,
312 this.letter_spacing(),
313 );
314 paragraph_properties.set_line_spacing(this.line_spacing());
315
316 let text_source: Rc<dyn ITextSource> = if this.has_complex_content() {
317 this.ensure_text_runs();
318
319 let text_runs = this.text_runs.borrow().clone().unwrap_or_default();
320
321 Rc::new(InlinesTextSource::new(text_runs, None))
322 } else {
323 Rc::new(SimpleTextSource::new(this.utf16_text(text.unwrap_or("")), default_properties))
324 };
325
326 let max_size = this.get_max_size_from_constraint();
327
328 let text_run_cache = this.text_run_cache.borrow_mut().get_or_insert_with(|| Rc::new(TextRunCache::new())).clone();
329
330 Rc::new(TextLayout::from_text_source(
331 text_source,
332 Rc::new(paragraph_properties),
333 Some(this.text_trimming()),
334 max_size.width,
335 max_size.height,
336 this.max_lines(),
337 Some(text_run_cache),
338 ))
339 }
340}
341
342struct TextBlockInlineHost(WeakRef<TextBlock>);
344
345impl IInlineHost for TextBlockInlineHost {
346 fn invalidate(&self) {
347 if let Some(text_block) = self.0.upgrade() {
348 text_block.invalidate_text_layout();
349 }
350 }
351
352 fn with_visual_children(&self, f: &mut dyn FnMut(&FerroList<Ref<Visual>>)) {
353 if let Some(text_block) = self.0.upgrade() {
354 f(text_block.visual_children());
355 }
356 }
357
358 fn as_text_block(&self) -> Option<Ref<TextBlock>> {
359 self.0.upgrade()
360 }
361}
362
363ferroui_base::ferro_properties! { impl TextBlock {
364 ferro_property!(
365 pub fn background_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
367 Border::background_property().add_owner::<TextBlock>()
368 }
369 );
370
371 ferro_property!(
372 pub fn padding_property() -> StyledProperty<Thickness> {
374 Decorator::padding_property().add_owner::<TextBlock>()
375 }
376 );
377
378 ferro_property!(
379 pub fn font_family_property() -> StyledProperty<FontFamily> {
381 TextElement::font_family_property().add_owner::<TextBlock>()
382 }
383 );
384
385 ferro_property!(
386 pub fn font_size_property() -> StyledProperty<f64> {
388 TextElement::font_size_property().add_owner::<TextBlock>()
389 }
390 );
391
392 ferro_property!(
393 pub fn font_style_property() -> StyledProperty<FontStyle> {
395 TextElement::font_style_property().add_owner::<TextBlock>()
396 }
397 );
398
399 ferro_property!(
400 pub fn font_weight_property() -> StyledProperty<FontWeight> {
402 TextElement::font_weight_property().add_owner::<TextBlock>()
403 }
404 );
405
406 ferro_property!(
407 pub fn font_stretch_property() -> StyledProperty<FontStretch> {
409 TextElement::font_stretch_property().add_owner::<TextBlock>()
410 }
411 );
412
413 ferro_property!(
414 pub fn foreground_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
416 TextElement::foreground_property().add_owner::<TextBlock>()
417 }
418 );
419
420 ferro_property!(
421 pub fn baseline_offset_property() -> AttachedProperty<f64> {
423 FerroProperty::register_attached_with::<TextBlock, Control, _>(
424 "BaselineOffset",
425 StyledPropertyOptions::new(0.0).inherits(true),
426 )
427 }
428 );
429
430 ferro_property!(
431 pub fn layout_rounding_placement_property() -> AttachedProperty<TextLayoutRoundingPlacement> {
435 FerroProperty::register_attached_with::<TextBlock, Control, _>(
436 "LayoutRoundingPlacement",
437 StyledPropertyOptions::new(TextLayoutRoundingPlacement::Below).inherits(true),
438 )
439 }
440 );
441
442 ferro_property!(
443 pub fn line_height_property() -> AttachedProperty<f64> {
445 FerroProperty::register_attached_with::<TextBlock, Control, _>(
446 "LineHeight",
447 StyledPropertyOptions::new(f64::NAN).validate(Self::is_valid_line_height).inherits(true),
448 )
449 }
450 );
451
452 ferro_property!(
453 pub fn line_spacing_property() -> AttachedProperty<f64> {
455 FerroProperty::register_attached_with::<TextBlock, Control, _>(
456 "LineSpacing",
457 StyledPropertyOptions::new(0.0).validate(Self::is_valid_line_spacing).inherits(true),
458 )
459 }
460 );
461
462 ferro_property!(
463 pub fn letter_spacing_property() -> StyledProperty<f64> {
465 TextElement::letter_spacing_property().add_owner::<TextBlock>()
466 }
467 );
468
469 ferro_property!(
470 pub fn max_lines_property() -> AttachedProperty<i32> {
472 FerroProperty::register_attached_with::<TextBlock, Control, _>(
473 "MaxLines",
474 StyledPropertyOptions::new(0).validate(Self::is_valid_max_lines).inherits(true),
475 )
476 }
477 );
478
479 ferro_property!(
480 pub fn text_property() -> StyledProperty<Option<String>> {
482 FerroProperty::register::<TextBlock, _>("Text", None)
483 }
484 );
485
486 ferro_property!(
487 pub fn text_alignment_property() -> AttachedProperty<TextAlignment> {
489 FerroProperty::register_attached_with::<TextBlock, Control, _>(
490 "TextAlignment",
491 StyledPropertyOptions::new(TextAlignment::Start).inherits(true),
492 )
493 }
494 );
495
496 ferro_property!(
497 pub fn text_wrapping_property() -> AttachedProperty<TextWrapping> {
499 FerroProperty::register_attached_with::<TextBlock, Control, _>(
500 "TextWrapping",
501 StyledPropertyOptions::new(TextWrapping::default()).inherits(true),
502 )
503 }
504 );
505
506 ferro_property!(
507 pub fn text_trimming_property() -> AttachedProperty<Rc<dyn TextTrimming>> {
509 FerroProperty::register_attached_with::<TextBlock, Control, _>(
510 "TextTrimming",
511 StyledPropertyOptions::new(<dyn TextTrimming>::none()).inherits(true),
512 )
513 }
514 );
515
516 ferro_property!(
517 pub fn text_decorations_property() -> StyledProperty<Option<TextDecorationCollection>> {
519 Inline::text_decorations_property().add_owner::<TextBlock>()
520 }
521 );
522
523 ferro_property!(
524 pub fn font_features_property() -> StyledProperty<Option<FontFeatureCollection>> {
526 TextElement::font_features_property().add_owner::<TextBlock>()
527 }
528 );
529
530 ferro_property!(
531 pub fn inlines_property() -> DirectProperty<TextBlock, Option<InlineCollection>> {
533 FerroProperty::register_direct::<TextBlock, _>(
534 "Inlines",
535 |o| o.inlines(),
536 Some(|o: &TextBlock, v| o.set_inlines(v)),
537 None,
538 )
539 }
540 );
541} }
542
543impl TextBlock {
544 fn static_constructor() {
545 Visual::clip_to_bounds_property().override_default_value::<TextBlock>(true);
546
547 Visual::affects_render::<TextBlock>(&[
548 Self::background_property().as_property(),
549 Self::foreground_property().as_property(),
550 ]);
551 }
552
553 pub fn construct() -> Self {
555 Self {
556 base: Control::construct(),
557 text_layout: RefCell::new(None),
558 text_run_cache: RefCell::new(None),
559 constraint: Cell::new(Size::new(f64::NAN, f64::NAN)),
560 text_runs: RefCell::new(None),
561 inlines: RefCell::new(None),
562 inlines_invalidated: RefCell::new(None),
563 inline_host: OnceCell::new(),
564 clear_text_internal: Cell::new(false),
565 layout_text_alignment: Cell::new(TextAlignment::Left),
566 utf16_text: RefCell::new(None),
567 }
568 }
569
570 pub fn new() -> Ref<Self> {
572 instantiate(Self::construct())
573 }
574
575 pub fn text_layout(&self) -> Rc<TextLayout> {
580 if let Some(text_layout) = self.text_layout.borrow().as_ref() {
581 return text_layout.clone();
582 }
583
584 let text_alignment = if self.is_measure_valid() { self.text_alignment() } else { TextAlignment::Left };
587 self.layout_text_alignment.set(self.resolve_text_alignment(text_alignment));
588
589 let text_layout = self.create_text_layout_core();
590 *self.text_layout.borrow_mut() = Some(text_layout.clone());
591 text_layout
592 }
593
594 fn resolve_text_alignment(&self, text_alignment: TextAlignment) -> TextAlignment {
597 let left_to_right = self.flow_direction() == FlowDirection::LeftToRight;
598
599 match text_alignment {
600 TextAlignment::Start if left_to_right => TextAlignment::Left,
601 TextAlignment::Start => TextAlignment::Right,
602 TextAlignment::End if left_to_right => TextAlignment::Right,
603 TextAlignment::End => TextAlignment::Left,
604 other => other,
605 }
606 }
607
608 fn create_text_layout_core(&self) -> Rc<TextLayout> {
609 self.ensure_text_runs();
614
615 if self.is_measure_valid() {
620 self.measure_embedded_controls(self.get_max_size_from_constraint());
621 }
622
623 let text = self.text();
624 self.create_text_layout(text.as_deref())
625 }
626
627 pub fn padding(&self) -> Thickness {
629 self.get_value(Self::padding_property())
630 }
631
632 pub fn set_padding(&self, value: Thickness) {
633 self.set_value(Self::padding_property(), value)
634 }
635
636 pub fn background(&self) -> Option<Rc<dyn IBrush>> {
638 self.get_value(Self::background_property())
639 }
640
641 pub fn set_background(&self, value: Option<Rc<dyn IBrush>>) {
642 self.set_value(Self::background_property(), value)
643 }
644
645 pub fn text(&self) -> Option<String> {
647 self.get_value(Self::text_property())
648 }
649
650 pub fn set_text(&self, value: Option<&str>) {
651 self.set_value(Self::text_property(), value.map(str::to_owned))
652 }
653
654 pub fn font_family(&self) -> FontFamily {
656 self.get_value(Self::font_family_property())
657 }
658
659 pub fn set_font_family(&self, value: FontFamily) {
660 self.set_value(Self::font_family_property(), value)
661 }
662
663 pub fn font_size(&self) -> f64 {
665 self.get_value(Self::font_size_property())
666 }
667
668 pub fn set_font_size(&self, value: f64) {
669 self.set_value(Self::font_size_property(), value)
670 }
671
672 pub fn font_style(&self) -> FontStyle {
674 self.get_value(Self::font_style_property())
675 }
676
677 pub fn set_font_style(&self, value: FontStyle) {
678 self.set_value(Self::font_style_property(), value)
679 }
680
681 pub fn font_weight(&self) -> FontWeight {
683 self.get_value(Self::font_weight_property())
684 }
685
686 pub fn set_font_weight(&self, value: FontWeight) {
687 self.set_value(Self::font_weight_property(), value)
688 }
689
690 pub fn font_stretch(&self) -> FontStretch {
692 self.get_value(Self::font_stretch_property())
693 }
694
695 pub fn set_font_stretch(&self, value: FontStretch) {
696 self.set_value(Self::font_stretch_property(), value)
697 }
698
699 pub fn foreground(&self) -> Option<Rc<dyn IBrush>> {
702 self.get_value(Self::foreground_property())
703 }
704
705 pub fn set_foreground(&self, value: Option<Rc<dyn IBrush>>) {
706 self.set_value(Self::foreground_property(), value)
707 }
708
709 pub fn line_height(&self) -> f64 {
711 self.get_value(Self::line_height_property())
712 }
713
714 pub fn set_line_height(&self, value: f64) {
715 self.set_value(Self::line_height_property(), value)
716 }
717
718 pub fn line_spacing(&self) -> f64 {
720 self.get_value(Self::line_spacing_property())
721 }
722
723 pub fn set_line_spacing(&self, value: f64) {
724 self.set_value(Self::line_spacing_property(), value)
725 }
726
727 pub fn letter_spacing(&self) -> f64 {
729 self.get_value(Self::letter_spacing_property())
730 }
731
732 pub fn set_letter_spacing(&self, value: f64) {
733 self.set_value(Self::letter_spacing_property(), value)
734 }
735
736 pub fn max_lines(&self) -> i32 {
738 self.get_value(Self::max_lines_property())
739 }
740
741 pub fn set_max_lines(&self, value: i32) {
742 self.set_value(Self::max_lines_property(), value)
743 }
744
745 pub fn text_wrapping(&self) -> TextWrapping {
747 self.get_value(Self::text_wrapping_property())
748 }
749
750 pub fn set_text_wrapping(&self, value: TextWrapping) {
751 self.set_value(Self::text_wrapping_property(), value)
752 }
753
754 pub fn text_trimming(&self) -> Rc<dyn TextTrimming> {
756 self.get_value(Self::text_trimming_property())
757 }
758
759 pub fn set_text_trimming(&self, value: Rc<dyn TextTrimming>) {
760 self.set_value(Self::text_trimming_property(), value)
761 }
762
763 pub fn text_alignment(&self) -> TextAlignment {
765 self.get_value(Self::text_alignment_property())
766 }
767
768 pub fn set_text_alignment(&self, value: TextAlignment) {
769 self.set_value(Self::text_alignment_property(), value)
770 }
771
772 pub fn text_decorations(&self) -> Option<TextDecorationCollection> {
774 self.get_value(Self::text_decorations_property())
775 }
776
777 pub fn set_text_decorations(&self, value: Option<TextDecorationCollection>) {
778 self.set_value(Self::text_decorations_property(), value)
779 }
780
781 pub fn font_features(&self) -> Option<FontFeatureCollection> {
783 self.get_value(Self::font_features_property())
784 }
785
786 pub fn set_font_features(&self, value: Option<FontFeatureCollection>) {
787 self.set_value(Self::font_features_property(), value)
788 }
789
790 pub fn inlines(&self) -> Option<InlineCollection> {
792 self.inlines.borrow().clone()
793 }
794
795 pub fn set_inlines(&self, value: Option<InlineCollection>) {
796 self.set_and_raise(Self::inlines_property(), &self.inlines, value);
797 }
798
799 pub fn has_complex_content(&self) -> bool {
801 self.inlines.borrow().as_ref().is_some_and(|inlines| inlines.count() > 0)
802 }
803
804 pub fn get_max_size_from_constraint(&self) -> Size {
807 let constraint = self.constraint.get();
808 let max_width = if constraint.width.is_nan() { 0.0 } else { constraint.width };
809 let max_height = if constraint.height.is_nan() { 0.0 } else { constraint.height };
810 Size::new(max_width, max_height)
811 }
812
813 pub fn constraint(&self) -> Size {
815 self.constraint.get()
816 }
817
818 pub fn text_runs(&self) -> Option<Rc<Vec<Rc<dyn TextRun>>>> {
820 self.text_runs.borrow().clone()
821 }
822
823 pub fn baseline_offset(&self) -> f64 {
825 self.get_value(Self::baseline_offset_property())
826 }
827
828 pub fn set_baseline_offset(&self, value: f64) {
829 self.set_value(Self::baseline_offset_property(), value)
830 }
831
832 pub fn get_baseline_offset(control: &Control) -> f64 {
834 control.get_value(Self::baseline_offset_property())
835 }
836
837 pub fn set_baseline_offset_on(control: &Control, value: f64) {
839 control.set_value(Self::baseline_offset_property(), value)
840 }
841
842 pub fn layout_rounding_placement(&self) -> TextLayoutRoundingPlacement {
845 self.get_value(Self::layout_rounding_placement_property())
846 }
847
848 pub fn set_layout_rounding_placement(&self, value: TextLayoutRoundingPlacement) {
849 self.set_value(Self::layout_rounding_placement_property(), value)
850 }
851
852 pub fn get_layout_rounding_placement(control: &Control) -> TextLayoutRoundingPlacement {
854 control.get_value(Self::layout_rounding_placement_property())
855 }
856
857 pub fn set_layout_rounding_placement_on(control: &Control, value: TextLayoutRoundingPlacement) {
859 control.set_value(Self::layout_rounding_placement_property(), value)
860 }
861
862 pub fn layout_rounding_offset(&self) -> f64 {
870 if !self.use_layout_rounding() {
871 return 0.0;
872 }
873 let scale = LayoutHelper::get_layout_scale(self);
874 let padding = LayoutHelper::round_layout_thickness(self.padding(), scale);
875 self.layout_rounding_offset_of(&self.text_layout(), padding, Some(scale))
876 }
877
878 fn layout_rounding_offset_of(&self, text_layout: &TextLayout, padding: Thickness, layout_scale: Option<f64>) -> f64 {
881 let Some(scale) = layout_scale else {
882 return 0.0;
883 };
884 let placement = self.layout_rounding_placement();
885 if placement == TextLayoutRoundingPlacement::Below {
886 return 0.0;
887 }
888 placement.offset(text_layout.height() + padding.top + padding.bottom, scale)
889 }
890
891 pub fn get_text_alignment(control: &Control) -> TextAlignment {
893 control.get_value(Self::text_alignment_property())
894 }
895
896 pub fn set_text_alignment_on(control: &Control, alignment: TextAlignment) {
898 control.set_value(Self::text_alignment_property(), alignment)
899 }
900
901 pub fn get_text_wrapping(control: &Control) -> TextWrapping {
903 control.get_value(Self::text_wrapping_property())
904 }
905
906 pub fn set_text_wrapping_on(control: &Control, wrapping: TextWrapping) {
908 control.set_value(Self::text_wrapping_property(), wrapping)
909 }
910
911 pub fn get_text_trimming(control: &Control) -> Rc<dyn TextTrimming> {
913 control.get_value(Self::text_trimming_property())
914 }
915
916 pub fn set_text_trimming_on(control: &Control, trimming: Rc<dyn TextTrimming>) {
918 control.set_value(Self::text_trimming_property(), trimming)
919 }
920
921 pub fn get_line_height(control: &Control) -> f64 {
923 control.get_value(Self::line_height_property())
924 }
925
926 pub fn set_line_height_on(control: &Control, height: f64) {
928 control.set_value(Self::line_height_property(), height)
929 }
930
931 pub fn get_letter_spacing(control: &Control) -> f64 {
933 control.get_value(Self::letter_spacing_property())
934 }
935
936 pub fn set_letter_spacing_on(control: &Control, letter_spacing: f64) {
938 control.set_value(Self::letter_spacing_property(), letter_spacing)
939 }
940
941 pub fn get_max_lines(control: &Control) -> i32 {
943 control.get_value(Self::max_lines_property())
944 }
945
946 pub fn set_max_lines_on(control: &Control, max_lines: i32) {
948 control.set_value(Self::max_lines_property(), max_lines)
949 }
950
951 pub(crate) fn clear_text_internal(&self) {
953 struct Reset<'a>(&'a Cell<bool>);
954
955 impl Drop for Reset<'_> {
956 fn drop(&mut self) {
957 self.0.set(false);
958 }
959 }
960
961 self.clear_text_internal.set(true);
962 let _reset = Reset(&self.clear_text_internal);
963 self.set_current_value(Self::text_property(), None);
964 }
965
966 pub fn invalidate_text_layout(&self) {
968 if let Some(text_run_cache) = self.text_run_cache.borrow().as_ref() {
969 text_run_cache.invalidate();
970 }
971 *self.text_runs.borrow_mut() = None;
972 self.dispose_text_layout();
973 self.invalidate_visual();
974 self.invalidate_measure();
975 }
976
977 fn invalidate_text_layout_keep_cache(&self) {
978 self.dispose_text_layout();
979 self.invalidate_visual();
980 self.invalidate_measure();
981 }
982
983 #[allow(dead_code)] pub(crate) fn has_text_layout(&self) -> bool {
985 self.text_layout.borrow().is_some()
986 }
987
988 fn dispose_text_layout(&self) {
989 let text_layout = self.text_layout.borrow_mut().take();
990 if let Some(text_layout) = text_layout {
991 text_layout.dispose();
992 }
993 }
994
995 pub fn ensure_text_runs(&self) {
997 if self.text_runs.borrow().is_some() || !self.has_complex_content() {
998 return;
999 }
1000
1001 let mut text_runs: Vec<Rc<dyn TextRun>> = Vec::new();
1002
1003 if let Some(inlines) = self.inlines() {
1004 for inline in inlines.snapshot().iter() {
1005 inline.build_text_run(&mut text_runs);
1006 }
1007 }
1008
1009 *self.text_runs.borrow_mut() = Some(Rc::new(text_runs));
1010 }
1011
1012 fn measure_embedded_controls(&self, constraint: Size) -> bool {
1013 if !self.has_complex_content() {
1014 return false;
1015 }
1016
1017 let mut resized = false;
1018
1019 if let Some(inlines) = self.inlines() {
1020 for inline in inlines.snapshot().iter() {
1021 resized |= inline.measure_embedded_controls(constraint);
1022 }
1023 }
1024
1025 resized
1026 }
1027
1028 fn is_valid_max_lines(max_lines: &i32) -> bool {
1029 *max_lines >= 0
1030 }
1031
1032 fn is_valid_line_height(line_height: &f64) -> bool {
1033 line_height.is_nan() || *line_height > 0.0
1034 }
1035
1036 fn is_valid_line_spacing(line_spacing: &f64) -> bool {
1037 !line_spacing.is_nan() && !line_spacing.is_infinite()
1038 }
1039
1040 fn on_inlines_changed(&self, old_value: Option<InlineCollection>, new_value: Option<InlineCollection>) {
1041 self.visual_children().clear();
1042
1043 if let Some(old_value) = old_value {
1044 old_value.set_logical_children(None);
1045 old_value.set_inline_host(None);
1046 if let Some(subscription) = self.inlines_invalidated.borrow_mut().take() {
1047 subscription.dispose();
1048 }
1049 }
1050
1051 if let Some(new_value) = new_value {
1052 new_value.set_logical_children(Some(self));
1053 new_value.set_inline_host(Some(self.as_inline_host()));
1054 let weak = self.to_ref().downgrade();
1055 *self.inlines_invalidated.borrow_mut() = Some(new_value.invalidated(move || {
1056 if let Some(this) = weak.upgrade() {
1057 this.invalidate_text_layout();
1058 }
1059 }));
1060 }
1061 }
1062
1063 pub(crate) fn as_inline_host(&self) -> Rc<dyn IInlineHost> {
1065 self.inline_host.get_or_init(|| Rc::new(TextBlockInlineHost(self.to_ref().downgrade()))).clone()
1066 }
1067
1068 fn utf16_text(&self, text: &str) -> ReadOnlyMemory<u16> {
1071 if let Some((cached_text, utf16)) = self.utf16_text.borrow().as_ref() {
1072 if cached_text == text {
1073 return utf16.clone();
1074 }
1075 }
1076
1077 let utf16 = ReadOnlyMemory::<u16>::from_str(text);
1078 *self.utf16_text.borrow_mut() = Some((text.to_owned(), utf16.clone()));
1079 utf16
1080 }
1081}
1082
1083#[derive(Clone)]
1085pub struct SimpleTextSource {
1086 text: ReadOnlyMemory<u16>,
1087 default_properties: Rc<dyn TextRunProperties>,
1088}
1089
1090impl SimpleTextSource {
1091 pub fn new(text: ReadOnlyMemory<u16>, default_properties: Rc<dyn TextRunProperties>) -> Self {
1092 Self { text, default_properties }
1093 }
1094}
1095
1096impl ITextSource for SimpleTextSource {
1097 fn get_text_run(&self, text_source_index: i32) -> Option<Rc<dyn TextRun>> {
1098 if text_source_index > self.text.len() as i32 {
1099 return Some(Rc::new(TextEndOfParagraph::new()));
1100 }
1101
1102 let run_text = self.text.slice_from(text_source_index.max(0) as usize);
1103
1104 if run_text.is_empty() {
1105 return Some(Rc::new(TextEndOfParagraph::new()));
1106 }
1107
1108 Some(Rc::new(TextCharacters::new(run_text, self.default_properties.clone())))
1109 }
1110}
1111
1112#[derive(Clone)]
1115pub struct InlinesTextSource {
1116 text_runs: Rc<Vec<Rc<dyn TextRun>>>,
1117 text_modifier: Option<Rc<[ValueSpan<Rc<dyn TextRunProperties>>]>>,
1118}
1119
1120impl InlinesTextSource {
1121 pub fn new(
1122 text_runs: Rc<Vec<Rc<dyn TextRun>>>,
1123 text_modifier: Option<Rc<[ValueSpan<Rc<dyn TextRunProperties>>]>>,
1124 ) -> Self {
1125 Self { text_runs, text_modifier }
1126 }
1127
1128 pub fn text_runs(&self) -> &Rc<Vec<Rc<dyn TextRun>>> {
1130 &self.text_runs
1131 }
1132}
1133
1134impl ITextSource for InlinesTextSource {
1135 fn get_text_run(&self, text_source_index: i32) -> Option<Rc<dyn TextRun>> {
1136 let mut current_position = 0;
1137
1138 for text_run in self.text_runs.iter() {
1139 if text_run.length() == 0 {
1140 continue;
1141 }
1142
1143 if text_source_index >= current_position + text_run.length() {
1144 current_position += text_run.length();
1145
1146 continue;
1147 }
1148
1149 if let Some(text_characters) = text_run.downcast_ref::<TextCharacters>() {
1150 let skip = (text_source_index - current_position).max(0) as usize;
1151
1152 let text = text_run.text().slice_from(skip);
1153
1154 let text_style_run = FormattedTextSource::create_text_style_run(
1155 text.span(),
1156 text_source_index,
1157 text_characters.run_properties(),
1158 self.text_modifier.as_deref(),
1159 );
1160
1161 return Some(Rc::new(TextCharacters::new(
1162 text.slice(0, text_style_run.length() as usize),
1163 text_style_run.value().clone(),
1164 )));
1165 }
1166
1167 return Some(text_run.clone());
1168 }
1169
1170 Some(Rc::new(TextEndOfParagraph::new()))
1171 }
1172}
1173
1174impl TextBlock {
1175 pub(crate) fn build_debug_display(&self, builder: &mut String, include_content: bool) {
1178 build_base_debug_display(self, builder);
1179
1180 if include_content {
1181 let text = self.text().or_else(|| self.inlines().and_then(|inlines| inlines.text()));
1182 append_optional_value(builder, "Text", text.as_deref());
1183 }
1184 }
1185}