1use inset_embedder::{
8 BoxHeightStyle, BoxWidthStyle, ClipOp, FontCollection, Offset, Rect, Size, TextAlign,
9 TextBaseline, TextBox, TextDirection, TextHeightBehavior, TextPosition, TextRange,
10 TextSelection,
11};
12use inset_foundation::{App, Handle};
13use inset_painting::{
14 ClipContext, InlineSpanRef, PaintingBinding, RenderComparison, TextOverflow, TextPainter,
15 TextScaler, TextWidthBasis,
16};
17
18use crate::box_::{BoxConstraints, BoxHitTestResult, RenderBox, RenderBoxData};
19use crate::object::{AnyRenderObject, Constraints, RenderHandle, RenderObject, RenderObjectData};
20use crate::painting_context::PaintingContext;
21use crate::{RelayoutWhenSystemFontsChangeData, RelayoutWhenSystemFontsChangeMixin};
22
23const K_ELLIPSIS: &str = "\u{2026}";
24
25pub struct RenderParagraph {
31 render_object: RenderObjectData,
32 render_box: RenderBoxData,
33 text_painter: TextPainter,
34 text_intrinsics: Option<TextPainter>,
35 fonts_override: Option<Handle<FontCollection>>,
36 soft_wrap: bool,
37 overflow: TextOverflow,
38 device_pixel_ratio: f64,
39 needs_clipping: bool,
40 system_fonts: RelayoutWhenSystemFontsChangeData,
41}
42
43impl RenderParagraph {
44 pub fn new(
46 app: &mut App,
47 text: InlineSpanRef,
48 text_direction: TextDirection,
49 fonts: Option<Handle<FontCollection>>,
50 ) -> RenderHandle<Self> {
51 debug_assert!(text.debug_assert_is_valid());
52 let mut text_painter = TextPainter::new();
53 text_painter.set_text(Some(text));
54 text_painter.set_text_direction(Some(text_direction));
55 RenderHandle::new_box(
56 app,
57 RenderParagraph {
58 render_object: RenderObjectData::new(),
59 render_box: RenderBoxData::new(),
60 text_painter,
61 text_intrinsics: None,
62 fonts_override: fonts,
63 soft_wrap: true,
64 overflow: TextOverflow::Clip,
65 device_pixel_ratio: 1.0,
66 needs_clipping: false,
67 system_fonts: RelayoutWhenSystemFontsChangeData::new(),
68 },
69 )
70 }
71
72 fn painter(self: RenderHandle<Self>, app: &App) -> &TextPainter {
73 &self.get(app).text_painter
74 }
75
76 fn painter_mut(self: RenderHandle<Self>, app: &mut App) -> &mut TextPainter {
77 &mut self.get_mut(app).text_painter
78 }
79
80 pub fn fonts(self: RenderHandle<Self>, app: &mut App) -> Handle<FontCollection> {
82 match self.get(app).fonts_override {
83 Some(fonts) => fonts,
84 None => PaintingBinding::instance(app).fonts(app),
85 }
86 }
87
88 fn painter_with_fonts(
90 self: RenderHandle<Self>,
91 app: &mut App,
92 ) -> (&mut TextPainter, &mut FontCollection) {
93 let fonts = self.fonts(app);
94 let (this, fonts) = app.get_disjoint_mut(self.handle(), fonts);
95 (&mut this.text_painter, fonts)
96 }
97
98 pub fn text(self: RenderHandle<Self>, app: &App) -> InlineSpanRef {
100 self.painter(app)
101 .text()
102 .cloned()
103 .expect("a paragraph always has text")
104 }
105
106 pub fn set_text(self: RenderHandle<Self>, app: &mut App, value: InlineSpanRef) {
108 match self.text(app).compare_to(&*value) {
109 RenderComparison::Identical => {}
110 RenderComparison::Metadata => {
111 self.painter_mut(app).set_text(Some(value));
112 }
113 RenderComparison::Paint => {
114 self.painter_mut(app).set_text(Some(value));
115 self.mark_needs_paint(app);
116 }
117 RenderComparison::Layout => {
118 self.painter_mut(app).set_text(Some(value));
119 self.mark_needs_layout(app);
120 }
121 }
122 }
123
124 pub fn text_align(self: RenderHandle<Self>, app: &App) -> TextAlign {
126 self.painter(app).text_align()
127 }
128
129 pub fn set_text_align(self: RenderHandle<Self>, app: &mut App, value: TextAlign) {
131 if self.painter(app).text_align() == value {
132 return;
133 }
134 self.painter_mut(app).set_text_align(value);
135 self.mark_needs_paint(app);
136 }
137
138 pub fn text_direction(self: RenderHandle<Self>, app: &App) -> TextDirection {
149 self.painter(app)
150 .text_direction()
151 .expect("a paragraph always has a direction")
152 }
153
154 pub fn set_text_direction(self: RenderHandle<Self>, app: &mut App, value: TextDirection) {
156 if self.painter(app).text_direction() == Some(value) {
157 return;
158 }
159 self.painter_mut(app).set_text_direction(Some(value));
160 self.mark_needs_layout(app);
161 }
162
163 pub fn soft_wrap(self: RenderHandle<Self>, app: &App) -> bool {
171 self.get(app).soft_wrap
172 }
173
174 pub fn set_soft_wrap(self: RenderHandle<Self>, app: &mut App, value: bool) {
176 if self.get(app).soft_wrap == value {
177 return;
178 }
179 self.get_mut(app).soft_wrap = value;
180 self.mark_needs_layout(app);
181 }
182
183 pub fn overflow(self: RenderHandle<Self>, app: &App) -> TextOverflow {
185 self.get(app).overflow
186 }
187
188 pub fn set_overflow(self: RenderHandle<Self>, app: &mut App, value: TextOverflow) {
190 if self.get(app).overflow == value {
191 return;
192 }
193 self.get_mut(app).overflow = value;
194 let ellipsis = (value == TextOverflow::Ellipsis).then(|| K_ELLIPSIS.to_owned());
195 self.painter_mut(app).set_ellipsis(ellipsis);
196 self.mark_needs_layout(app);
197 }
198
199 pub fn text_scaler(self: RenderHandle<Self>, app: &App) -> TextScaler {
201 self.painter(app).text_scaler().clone()
202 }
203
204 pub fn set_text_scaler(self: RenderHandle<Self>, app: &mut App, value: TextScaler) {
206 if *self.painter(app).text_scaler() == value {
207 return;
208 }
209 self.painter_mut(app).set_text_scaler(value);
210 self.mark_needs_layout(app);
211 }
212
213 pub fn device_pixel_ratio(self: RenderHandle<Self>, app: &App) -> f64 {
217 self.get(app).device_pixel_ratio
218 }
219
220 pub fn set_device_pixel_ratio(self: RenderHandle<Self>, app: &mut App, value: f64) {
222 if self.get(app).device_pixel_ratio == value {
223 return;
224 }
225 self.get_mut(app).device_pixel_ratio = value;
226 }
227
228 pub fn max_lines(self: RenderHandle<Self>, app: &App) -> Option<i32> {
233 self.painter(app).max_lines()
234 }
235
236 pub fn set_max_lines(self: RenderHandle<Self>, app: &mut App, value: Option<i32>) {
239 debug_assert!(value.is_none_or(|value| value > 0));
240 if self.painter(app).max_lines() == value {
241 return;
242 }
243 self.painter_mut(app).set_max_lines(value);
244 self.mark_needs_layout(app);
245 }
246
247 pub fn text_width_basis(self: RenderHandle<Self>, app: &App) -> TextWidthBasis {
249 self.painter(app).text_width_basis()
250 }
251
252 pub fn set_text_width_basis(self: RenderHandle<Self>, app: &mut App, value: TextWidthBasis) {
254 if self.painter(app).text_width_basis() == value {
255 return;
256 }
257 self.painter_mut(app).set_text_width_basis(value);
258 self.mark_needs_layout(app);
259 }
260
261 pub fn text_height_behavior(self: RenderHandle<Self>, app: &App) -> Option<TextHeightBehavior> {
263 self.painter(app).text_height_behavior()
264 }
265
266 pub fn set_text_height_behavior(
268 self: RenderHandle<Self>,
269 app: &mut App,
270 value: Option<TextHeightBehavior>,
271 ) {
272 if self.painter(app).text_height_behavior() == value {
273 return;
274 }
275 self.painter_mut(app).set_text_height_behavior(value);
276 self.mark_needs_layout(app);
277 }
278
279 pub fn preferred_line_height(self: RenderHandle<Self>, app: &mut App) -> f64 {
281 let (painter, fonts) = self.painter_with_fonts(app);
282 painter.preferred_line_height(fonts)
283 }
284
285 fn text_intrinsics_with_fonts(
291 self: RenderHandle<Self>,
292 app: &mut App,
293 ) -> (&mut TextPainter, &mut FontCollection) {
294 let fonts = self.fonts(app);
295 let (this, fonts) = app.get_disjoint_mut(self.handle(), fonts);
296 let painter = &this.text_painter;
297 let intrinsics = this.text_intrinsics.get_or_insert_with(TextPainter::new);
298 intrinsics.set_text(painter.text().cloned());
299 intrinsics.set_text_align(painter.text_align());
300 intrinsics.set_text_direction(painter.text_direction());
301 intrinsics.set_text_scaler(painter.text_scaler().clone());
302 intrinsics.set_max_lines(painter.max_lines());
303 intrinsics.set_ellipsis(painter.ellipsis().map(str::to_owned));
304 intrinsics.set_text_width_basis(painter.text_width_basis());
305 intrinsics.set_text_height_behavior(painter.text_height_behavior());
306 (intrinsics, fonts)
307 }
308
309 fn compute_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
311 let max_width = self.adjust_max_width(app, width);
312 let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
313 intrinsics.layout(fonts, width, max_width);
314 intrinsics.height()
315 }
316
317 fn adjust_max_width(self: RenderHandle<Self>, app: &App, max_width: f64) -> f64 {
318 if self.get(app).soft_wrap || self.get(app).overflow == TextOverflow::Ellipsis {
319 max_width
320 } else {
321 f64::INFINITY
322 }
323 }
324
325 fn layout_text_with_constraints(
326 self: RenderHandle<Self>,
327 app: &mut App,
328 constraints: BoxConstraints,
329 ) {
330 let max_width = self.adjust_max_width(app, constraints.max_width);
331 let (painter, fonts) = self.painter_with_fonts(app);
332 painter.set_placeholder_dimensions(None);
333 painter.layout(fonts, constraints.min_width, max_width);
334 }
335
336 pub fn get_offset_for_caret(
340 self: RenderHandle<Self>,
341 app: &mut App,
342 position: TextPosition,
343 caret_prototype: Rect,
344 ) -> Offset {
345 debug_assert!(!self.debug_needs_layout(app));
346 let constraints = self.constraints(app);
347 self.layout_text_with_constraints(app, constraints);
348 let (painter, fonts) = self.painter_with_fonts(app);
349 painter.get_offset_for_caret(fonts, position, caret_prototype)
350 }
351
352 pub fn get_full_height_for_caret(
356 self: RenderHandle<Self>,
357 app: &mut App,
358 position: TextPosition,
359 ) -> f64 {
360 debug_assert!(!self.debug_needs_layout(app));
361 let constraints = self.constraints(app);
362 self.layout_text_with_constraints(app, constraints);
363 let (painter, fonts) = self.painter_with_fonts(app);
364 painter.get_full_height_for_caret(fonts, position, Rect::ZERO)
365 }
366
367 pub fn get_boxes_for_selection(
378 self: RenderHandle<Self>,
379 app: &mut App,
380 selection: TextSelection,
381 box_height_style: BoxHeightStyle,
382 box_width_style: BoxWidthStyle,
383 ) -> Vec<TextBox> {
384 debug_assert!(!self.debug_needs_layout(app));
385 let constraints = self.constraints(app);
386 self.layout_text_with_constraints(app, constraints);
387 self.painter(app)
388 .get_boxes_for_selection(selection, box_height_style, box_width_style)
389 }
390
391 pub fn get_position_for_offset(
395 self: RenderHandle<Self>,
396 app: &mut App,
397 offset: Offset,
398 ) -> TextPosition {
399 debug_assert!(!self.debug_needs_layout(app));
400 let constraints = self.constraints(app);
401 self.layout_text_with_constraints(app, constraints);
402 self.painter(app).get_position_for_offset(offset)
403 }
404
405 pub fn get_word_boundary(
414 self: RenderHandle<Self>,
415 app: &mut App,
416 position: TextPosition,
417 ) -> TextRange {
418 debug_assert!(!self.debug_needs_layout(app));
419 let constraints = self.constraints(app);
420 self.layout_text_with_constraints(app, constraints);
421 self.painter(app).get_word_boundary(position)
422 }
423
424 pub fn text_size(self: RenderHandle<Self>, app: &App) -> Size {
435 debug_assert!(!self.debug_needs_layout(app));
436 self.painter(app).size()
437 }
438
439 pub fn did_exceed_max_lines(self: RenderHandle<Self>, app: &App) -> bool {
446 debug_assert!(!self.debug_needs_layout(app));
447 self.painter(app).did_exceed_max_lines()
448 }
449}
450
451impl RelayoutWhenSystemFontsChangeMixin for RenderParagraph {
452 fn relayout_when_system_fonts_change_data(
453 self: RenderHandle<Self>,
454 app: &App,
455 ) -> &RelayoutWhenSystemFontsChangeData {
456 &self.get(app).system_fonts
457 }
458
459 fn relayout_when_system_fonts_change_data_mut(
460 self: RenderHandle<Self>,
461 app: &mut App,
462 ) -> &mut RelayoutWhenSystemFontsChangeData {
463 &mut self.get_mut(app).system_fonts
464 }
465
466 fn system_fonts_did_change(self: RenderHandle<Self>, app: &mut App) {
467 self.as_render_object(app).mark_needs_layout(app);
468 self.painter_mut(app).mark_needs_layout();
469 }
470}
471
472impl RenderObject for RenderParagraph {
473 crate::render_object_accessors!();
474
475 fn visit_children(
476 self: RenderHandle<Self>,
477 _app: &App,
478 _visitor: &mut dyn FnMut(AnyRenderObject),
479 ) {
480 }
482
483 fn did_attach(self: RenderHandle<Self>, app: &mut App, _owner: Handle<crate::PipelineOwner>) {
484 RelayoutWhenSystemFontsChangeMixin::attach_system_fonts(self, app);
485 }
486
487 fn did_detach(self: RenderHandle<Self>, app: &mut App) {
488 RelayoutWhenSystemFontsChangeMixin::detach_system_fonts(self, app);
489 }
490
491 fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
492 let constraints = self.constraints(app);
493 self.layout_text_with_constraints(app, constraints);
494 let text_size = self.painter(app).size();
495 let size = constraints.constrain(text_size);
496 self.set_size(app, size);
497
498 let did_overflow_height =
499 size.height() < text_size.height() || self.painter(app).did_exceed_max_lines();
500 let did_overflow_width = size.width() < text_size.width();
501 let has_visual_overflow = did_overflow_width || did_overflow_height;
507 let needs_clipping = has_visual_overflow
508 && match self.get(app).overflow {
509 TextOverflow::Visible => false,
510 TextOverflow::Clip | TextOverflow::Ellipsis | TextOverflow::Fade => true,
512 };
513 self.get_mut(app).needs_clipping = needs_clipping;
514 }
515
516 fn paint(
517 self: RenderHandle<Self>,
518 app: &mut App,
519 context: &mut PaintingContext,
520 offset: Offset,
521 ) {
522 let constraints = self.constraints(app);
533 self.layout_text_with_constraints(app, constraints);
534
535 let needs_clipping = self.get(app).needs_clipping;
536 if needs_clipping {
537 let bounds = offset & self.size(app);
538 context.canvas().save();
539 context.canvas().clip_rect(bounds, ClipOp::Intersect);
540 }
541 let (painter, fonts) = self.painter_with_fonts(app);
542 painter.paint(fonts, context.canvas(), offset);
543 if needs_clipping {
544 context.canvas().restore();
545 }
546 }
547}
548
549impl RenderBox for RenderParagraph {
550 crate::render_box_accessors!();
551
552 fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, _height: f64) -> f64 {
553 let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
554 intrinsics.layout(fonts, 0.0, f64::INFINITY);
555 intrinsics.min_intrinsic_width()
556 }
557
558 fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, _height: f64) -> f64 {
559 let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
560 intrinsics.layout(fonts, 0.0, f64::INFINITY);
561 intrinsics.max_intrinsic_width()
562 }
563
564 fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
565 self.compute_intrinsic_height(app, width)
566 }
567
568 fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
569 self.compute_intrinsic_height(app, width)
570 }
571
572 fn compute_dry_layout(
573 self: RenderHandle<Self>,
574 app: &mut App,
575 constraints: BoxConstraints,
576 ) -> Size {
577 let max_width = self.adjust_max_width(app, constraints.max_width);
578 let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
579 intrinsics.layout(fonts, constraints.min_width, max_width);
580 let size = intrinsics.size();
581 constraints.constrain(size)
582 }
583
584 fn compute_distance_to_actual_baseline(
585 self: RenderHandle<Self>,
586 app: &mut App,
587 _baseline: TextBaseline,
588 ) -> Option<f64> {
589 debug_assert!(!self.as_object().debug_needs_layout(app));
590 let constraints = self.constraints(app);
591 debug_assert!(constraints.debug_assert_is_valid(false));
592 self.layout_text_with_constraints(app, constraints);
593 Some(
597 self.painter(app)
598 .compute_distance_to_actual_baseline(TextBaseline::Alphabetic),
599 )
600 }
601
602 fn compute_dry_baseline(
603 self: RenderHandle<Self>,
604 app: &mut App,
605 constraints: BoxConstraints,
606 _baseline: TextBaseline,
607 ) -> Option<f64> {
608 debug_assert!(constraints.debug_assert_is_valid(false));
609 let max_width = self.adjust_max_width(app, constraints.max_width);
610 let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
611 intrinsics.layout(fonts, constraints.min_width, max_width);
612 Some(intrinsics.compute_distance_to_actual_baseline(TextBaseline::Alphabetic))
613 }
614
615 fn hit_test_self(self: RenderHandle<Self>, _app: &App, _position: Offset) -> bool {
616 true
617 }
618
619 fn hit_test_children(
620 self: RenderHandle<Self>,
621 _app: &mut App,
622 _result: &mut BoxHitTestResult<'_>,
623 _position: Offset,
624 ) -> bool {
625 false
627 }
628}
629
630#[cfg(test)]
631mod tests {
632 use inset_embedder::Color;
633 use inset_foundation::AppCell;
634 use inset_gestures::HitTestResult;
635 use inset_painting::{TextSpan, TextStyle};
636
637 use super::*;
638 use crate::layer::{ContainerLayer, ErasedLayer, OffsetLayer};
639 use crate::pipeline_owner::PipelineOwner;
640 use crate::proxy_box::RenderRepaintBoundary;
641
642 fn install_fonts(app: &mut App) {
644 let binding = PaintingBinding::instance(app);
645 if !binding.has_fonts(app) {
646 binding.install_fonts(app, |fonts| {
647 fonts.add_source(valo_system_fonts::SystemFonts::load());
648 });
649 }
650 }
651
652 fn span(text: &str) -> InlineSpanRef {
653 TextSpan::new()
654 .text(text)
655 .style(TextStyle::new().font_size(20.0))
656 .into_span()
657 }
658
659 fn laid_out(
660 app: &mut App,
661 text: &str,
662 constraints: BoxConstraints,
663 ) -> (
664 RenderHandle<RenderParagraph>,
665 RenderHandle<RenderRepaintBoundary>,
666 ) {
667 install_fonts(app);
668 let paragraph = RenderParagraph::new(app, span(text), TextDirection::Ltr, None);
669 let root = RenderRepaintBoundary::new(app, Some(paragraph.as_box()));
670 let owner = PipelineOwner::new(app, None, None);
671 owner.set_root_node(app, Some(root.as_object()));
672 root.schedule_initial_layout(app);
673 root.layout(app, constraints, false);
674 let paint_root = OffsetLayer::new(app, Offset::ZERO);
675 paint_root.as_layer().attach(app, root.as_object().id());
676 root.as_object()
677 .schedule_initial_paint(app, paint_root.as_container_layer());
678 (paragraph, root)
679 }
680
681 #[test]
684 fn paragraph_intrinsics_come_from_the_text() {
685 let cell = AppCell::new();
686 let mut app = cell.borrow_mut();
687 install_fonts(&mut app);
688 let paragraph = RenderParagraph::new(
689 &mut app,
690 span("one two three four"),
691 TextDirection::Ltr,
692 None,
693 );
694 let box_ = paragraph.as_box();
695 let min_width = box_.get_min_intrinsic_width(&mut app, f64::INFINITY);
696 let max_width = box_.get_max_intrinsic_width(&mut app, f64::INFINITY);
697 assert!(min_width > 0.0);
698 assert!(
699 min_width < max_width,
700 "the widest word is narrower than the whole text"
701 );
702
703 let one_line = box_.get_min_intrinsic_height(&mut app, max_width);
704 let wrapped = box_.get_min_intrinsic_height(&mut app, max_width / 2.0);
705 assert!(wrapped > one_line, "half the width takes more lines");
706 assert_eq!(box_.get_max_intrinsic_height(&mut app, max_width), one_line);
707 }
708
709 #[test]
712 fn paragraph_dry_layout_and_baseline_match_its_layout() {
713 let cell = AppCell::new();
714 let mut app = cell.borrow_mut();
715 let constraints = BoxConstraints::new().max_width(200.0).max_height(1000.0);
716 install_fonts(&mut app);
717 let paragraph =
718 RenderParagraph::new(&mut app, span("one two three"), TextDirection::Ltr, None);
719 let dry = paragraph.as_box().get_dry_layout(&mut app, constraints);
720 let dry_baseline =
721 paragraph
722 .as_box()
723 .get_dry_baseline(&mut app, constraints, TextBaseline::Alphabetic);
724
725 let (paragraph, _root) = laid_out(&mut app, "one two three", constraints);
726 assert_eq!(dry, paragraph.size(&app));
727 let baseline =
728 paragraph
729 .as_box()
730 .get_distance_to_baseline(&mut app, TextBaseline::Alphabetic, true);
731 assert_eq!(dry_baseline, baseline);
732 assert!(baseline.is_some_and(|baseline| baseline > 0.0));
733 }
734
735 #[test]
736 fn a_paragraph_sizes_itself_to_its_text_within_loose_constraints() {
737 let cell = AppCell::new();
738 let mut app = cell.borrow_mut();
739 let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
740 let (paragraph, _root) = laid_out(&mut app, "Hello", loose);
741 let size = paragraph.size(&app);
742 assert!(size.width() > 0.0 && size.width() < 1000.0);
743 assert_eq!(size, paragraph.text_size(&app));
744 assert!(!paragraph.did_exceed_max_lines(&app));
745 }
746
747 #[test]
748 fn narrow_constraints_wrap_the_text() {
749 let cell = AppCell::new();
750 let mut app = cell.borrow_mut();
751 let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
752 let (wide, _root) = laid_out(&mut app, "one two three four", loose);
753 let natural = wide.size(&app);
754 let narrow = BoxConstraints::new()
755 .max_width(natural.width() / 2.0)
756 .max_height(1000.0);
757 let (wrapped, _root) = laid_out(&mut app, "one two three four", narrow);
758 assert!(wrapped.size(&app).height() > natural.height());
759 assert!(wrapped.size(&app).width() <= natural.width() / 2.0);
760 }
761
762 #[test]
763 fn soft_wrap_off_lets_the_text_overflow_and_clips_it() {
764 let cell = AppCell::new();
765 let mut app = cell.borrow_mut();
766 let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
767 let (wide, _root) = laid_out(&mut app, "one two three four", loose);
768 let natural = wide.size(&app);
769 install_fonts(&mut app);
770 let paragraph = RenderParagraph::new(
771 &mut app,
772 span("one two three four"),
773 TextDirection::Ltr,
774 None,
775 );
776 paragraph.set_soft_wrap(&mut app, false);
777 let root = RenderRepaintBoundary::new(&mut app, Some(paragraph.as_box()));
778 let owner = PipelineOwner::new(&mut app, None, None);
779 owner.set_root_node(&mut app, Some(root.as_object()));
780 root.schedule_initial_layout(&mut app);
781 let narrow = BoxConstraints::new()
782 .max_width(natural.width() / 2.0)
783 .max_height(1000.0);
784 root.layout(&mut app, narrow, false);
785 assert_eq!(paragraph.size(&app).width(), natural.width() / 2.0);
786 assert_eq!(
787 paragraph.text_size(&app).width(),
788 natural.width(),
789 "laid out unwrapped"
790 );
791 assert!(paragraph.get(&app).needs_clipping);
792
793 paragraph.set_overflow(&mut app, TextOverflow::Visible);
794 owner.flush_layout(&mut app);
795 assert!(!paragraph.get(&app).needs_clipping);
796 }
797
798 #[test]
799 fn ellipsis_overflow_reports_exceeded_lines() {
800 let cell = AppCell::new();
801 let mut app = cell.borrow_mut();
802 let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
803 let (wide, _root) = laid_out(&mut app, "one two three four", loose);
804 let natural = wide.size(&app);
805 install_fonts(&mut app);
806 let paragraph = RenderParagraph::new(
807 &mut app,
808 span("one two three four"),
809 TextDirection::Ltr,
810 None,
811 );
812 paragraph.set_max_lines(&mut app, Some(1));
813 paragraph.set_overflow(&mut app, TextOverflow::Ellipsis);
814 let root = RenderRepaintBoundary::new(&mut app, Some(paragraph.as_box()));
815 let owner = PipelineOwner::new(&mut app, None, None);
816 owner.set_root_node(&mut app, Some(root.as_object()));
817 root.schedule_initial_layout(&mut app);
818 root.layout(
819 &mut app,
820 BoxConstraints::new()
821 .max_width(natural.width() / 2.0)
822 .max_height(1000.0),
823 false,
824 );
825 assert!(paragraph.did_exceed_max_lines(&app));
826 assert_eq!(paragraph.size(&app).height(), natural.height());
827 }
828
829 #[test]
830 fn setting_text_marks_layout_or_paint_by_the_comparison() {
831 let cell = AppCell::new();
832 let mut app = cell.borrow_mut();
833 let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
834 let (paragraph, root) = laid_out(&mut app, "Hello", loose);
835 let owner = root.owner(&app).unwrap();
836 owner.flush_compositing_bits(&mut app);
837 owner.flush_paint(&mut app);
838 assert!(!paragraph.debug_needs_layout(&app));
839
840 let recolored = TextSpan::new()
841 .text("Hello")
842 .style(
843 TextStyle::new()
844 .font_size(20.0)
845 .color(Color::from_argb(255, 255, 0, 0)),
846 )
847 .into_span();
848 paragraph.set_text(&mut app, recolored);
849 assert!(
850 !paragraph.debug_needs_layout(&app),
851 "a color change is paint only"
852 );
853 assert!(paragraph.as_object().debug_needs_paint(&app));
854
855 paragraph.set_text(&mut app, span("Hello there"));
856 assert!(paragraph.debug_needs_layout(&app));
857 }
858
859 #[test]
860 fn painting_records_glyphs_and_hit_testing_lands_on_the_paragraph() {
861 let cell = AppCell::new();
862 let mut app = cell.borrow_mut();
863 let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
864 let (paragraph, root) = laid_out(&mut app, "Hello", loose);
865 let owner = root.owner(&app).unwrap();
866 owner.flush_compositing_bits(&mut app);
867 owner.flush_paint(&mut app);
868 assert!(!paragraph.as_object().debug_needs_paint(&app));
869
870 let mut result = HitTestResult::new();
871 let hit = paragraph.hit_test(
872 &mut app,
873 &mut BoxHitTestResult::wrap(&mut result),
874 Offset::new(2.0, 2.0),
875 );
876 assert!(hit);
877 assert_eq!(result.path().len(), 1);
878 let position = paragraph.get_position_for_offset(&mut app, Offset::new(0.0, 1.0));
879 assert_eq!(position.offset, 0);
880 assert_eq!(
881 paragraph.get_word_boundary(&mut app, TextPosition::new(1)),
882 TextRange::new(0, 5)
883 );
884 }
885}