Skip to main content

inset_rendering/
paragraph.rs

1//! Flutter counterpart: `rendering/paragraph.dart` (`RenderParagraph`).
2//!
3//! Selection (`SelectionRegistrar` / `_SelectableFragment`), the fade overflow shader, span
4//! hit-test targets and semantics wait; see `PORTING.md`.
5
6use std::any::{Any, TypeId};
7use std::fmt;
8use std::rc::Rc;
9
10use inset_embedder::{
11    BoxHeightStyle, BoxWidthStyle, ClipOp, FontCollection, Matrix4, Offset, PlaceholderAlignment,
12    Rect, Size, TextAlign, TextBaseline, TextBox, TextDirection, TextHeightBehavior, TextPosition,
13    TextRange, TextSelection,
14};
15use inset_foundation::{App, Handle};
16use inset_painting::{
17    ClipContext, InlineSpanRef, PaintingBinding, PlaceholderDimensions, PlaceholderSpan,
18    RenderComparison, TextOverflow, TextPainter, TextScaler, TextWidthBasis,
19};
20
21use crate::box_::{AnyRenderBox, BoxConstraints, BoxHitTestResult, RenderBox, RenderBoxData};
22use crate::layout_helper::{ChildBaselineGetter, ChildLayoutHelper, ChildLayouter};
23use crate::object::{
24    AnyRenderObject, Constraints, ContainerParentData, ContainerParentDataMixin,
25    ContainerRenderObjectData, ContainerRenderObjectMixin, ParentData, RenderHandle, RenderObject,
26    RenderObjectData,
27};
28use crate::painting_context::PaintingContext;
29use crate::pipeline_owner::PipelineOwner;
30use crate::{RelayoutWhenSystemFontsChangeData, RelayoutWhenSystemFontsChangeMixin};
31
32const K_ELLIPSIS: &str = "\u{2026}";
33
34/// Parent data used by [`RenderParagraph`] and `RenderEditable` to annotate inline contents
35/// (such as `WidgetSpan`s) with.
36#[derive(Debug, Default)]
37pub struct TextParentData {
38    container_parent_data: ContainerParentData<AnyRenderBox>,
39    offset: Option<Offset>,
40
41    /// The [`PlaceholderSpan`] associated with this render child.
42    ///
43    /// This field is usually set by a `ParentDataWidget`, and is typically not `None` when
44    /// `perform_layout` is called.
45    pub span: Option<Rc<dyn PlaceholderSpan>>,
46}
47
48impl TextParentData {
49    /// The offset at which to paint the child in the parent's coordinate system.
50    ///
51    /// A `None` value indicates this inline widget is not laid out. For instance, when the
52    /// inline widget has never been laid out, or the inline widget is ellipsized away.
53    pub fn offset(&self) -> Option<Offset> {
54        self.offset
55    }
56}
57
58impl ParentData for TextParentData {
59    fn detach(&mut self) {
60        self.span = None;
61        self.offset = None;
62        ContainerParentDataMixin::detach(self);
63    }
64
65    fn provide(&self, id: TypeId) -> Option<&dyn Any> {
66        (id == TypeId::of::<TextParentData>()).then_some(self)
67    }
68
69    fn provide_mut(&mut self, id: TypeId) -> Option<&mut dyn Any> {
70        (id == TypeId::of::<TextParentData>()).then_some(self)
71    }
72}
73
74impl ContainerParentDataMixin for TextParentData {
75    type ChildType = AnyRenderBox;
76
77    fn container_parent_data(&self) -> &ContainerParentData<AnyRenderBox> {
78        &self.container_parent_data
79    }
80
81    fn container_parent_data_mut(&mut self) -> &mut ContainerParentData<AnyRenderBox> {
82        &mut self.container_parent_data
83    }
84}
85
86impl fmt::Display for TextParentData {
87    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
88        write!(f, "widget: {:?}, ", self.span)?;
89        match self.offset {
90            None => f.write_str("not laid out"),
91            Some(offset) => write!(f, "offset: {offset:?}"),
92        }
93    }
94}
95
96/// A mixin that provides useful default behaviors for text [`RenderBox`]es
97/// ([`RenderParagraph`] and `RenderEditable` for example) with inline content children
98/// managed by the [`ContainerRenderObjectMixin`] mixin.
99///
100/// This mixin assumes every child managed by the [`ContainerRenderObjectMixin`] mixin
101/// corresponds to a [`PlaceholderSpan`], and they are organized in logical order of the text
102/// (the order each [`PlaceholderSpan`] is encountered when the user reads the text).
103///
104/// To use this mixin in a [`RenderBox`] class:
105///
106///  * Call [`layout_inline_children`](Self::layout_inline_children) in the `perform_layout`
107///    and `compute_dry_layout` implementation, and during intrinsic size calculations, to get
108///    the size information of the inline widgets as a `Vec` of [`PlaceholderDimensions`].
109///    Determine the positioning of the inline widgets (which is usually done by a
110///    [`TextPainter`] using its line break algorithm).
111///
112///  * Call [`position_inline_children`](Self::position_inline_children) with the positioning
113///    information of the inline widgets.
114///
115///  * Implement [`RenderBox::apply_paint_transform`], optionally with
116///    [`default_apply_paint_transform`](Self::default_apply_paint_transform).
117///
118///  * Call [`paint_inline_children`](Self::paint_inline_children) in [`RenderObject::paint`]
119///    to paint the inline widgets.
120///
121///  * Call [`hit_test_inline_children`](Self::hit_test_inline_children) in
122///    [`RenderBox::hit_test_children`] to hit test the inline widgets.
123///
124/// See also:
125///
126///  * `WidgetSpan::extract_from_inline_span`, a helper function for extracting `WidgetSpan`s
127///    from an `InlineSpan` tree.
128pub trait RenderInlineChildrenContainerDefaults:
129    RenderBox + ContainerRenderObjectMixin<ChildType = AnyRenderBox, ParentDataType = TextParentData>
130{
131    /// Installs [`TextParentData`] on `child`, the mixin's `setupParentData`.
132    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
133        if !child.parent_data_is::<TextParentData>(app) {
134            child.set_parent_data(app, TextParentData::default());
135        }
136    }
137
138    /// Computes the layout for every inline child using the `max_width` constraint.
139    ///
140    /// Returns a list of [`PlaceholderDimensions`], representing the layout results for each
141    /// child managed by the [`ContainerRenderObjectMixin`] mixin.
142    ///
143    /// The `get_child_baseline` parameter and the `layout_child` parameter must be
144    /// consistent: if `layout_child` computes the size of the child without modifying the
145    /// actual layout of that child, then `get_child_baseline` must also be "dry", and vice
146    /// versa.
147    ///
148    /// Since this method does not impose a maximum height constraint on the inline children,
149    /// some children may become taller than this [`RenderBox`].
150    ///
151    /// See also:
152    ///
153    ///  * [`TextPainter::set_placeholder_dimensions`], the method that usually takes the
154    ///    layout results from this method as the input.
155    fn layout_inline_children(
156        self: RenderHandle<Self>,
157        app: &mut App,
158        max_width: f64,
159        layout_child: ChildLayouter,
160        get_child_baseline: ChildBaselineGetter,
161    ) -> Vec<PlaceholderDimensions> {
162        let constraints = BoxConstraints::new().max_width(max_width);
163        let mut dimensions = Vec::new();
164        let mut child = self.first_child(app);
165        while let Some(current) = child {
166            dimensions.push(layout_child_inline(
167                app,
168                current,
169                constraints,
170                layout_child,
171                get_child_baseline,
172            ));
173            child = self.child_after(app, current);
174        }
175        dimensions
176    }
177
178    /// Positions each inline child according to the coordinates provided in the `boxes`
179    /// list.
180    ///
181    /// The `boxes` list must be in logical order, which is the order each child is
182    /// encountered when the user reads the text. Usually the length of the list equals
183    /// [`child_count`](ContainerRenderObjectMixin::child_count), but it can be less than
184    /// that, when some children are omitted due to ellipsing. It never exceeds
185    /// [`child_count`](ContainerRenderObjectMixin::child_count).
186    ///
187    /// See also:
188    ///
189    ///  * [`TextPainter::inline_placeholder_boxes`], the method that can be used to get the
190    ///    input `boxes`.
191    fn position_inline_children(self: RenderHandle<Self>, app: &mut App, boxes: &[TextBox]) {
192        let mut child = self.first_child(app);
193        for text_box in boxes {
194            let Some(current) = child else {
195                debug_assert!(
196                    false,
197                    "Invalid number of boxes provided to positionInlineChildren. The number of \
198                     boxes ({}) exceeds the number of child render objects ({}).",
199                    boxes.len(),
200                    self.child_count(app)
201                );
202                return;
203            };
204            text_parent_data_mut(app, current).offset =
205                Some(Offset::new(text_box.left, text_box.top));
206            child = self.child_after(app, current);
207        }
208        while let Some(current) = child {
209            text_parent_data_mut(app, current).offset = None;
210            child = self.child_after(app, current);
211        }
212    }
213
214    /// Applies the transform that would be applied when painting the given child to the given
215    /// matrix.
216    ///
217    /// Render children whose [`TextParentData::offset`] is `None` zeros out the `transform` to
218    /// indicate they're invisible thus should not be painted.
219    fn default_apply_paint_transform(
220        self: RenderHandle<Self>,
221        app: &App,
222        child: AnyRenderObject,
223        transform: &mut Matrix4,
224    ) {
225        match child.parent_data_of::<TextParentData>(app).offset {
226            None => *transform = crate::object::zero_matrix(),
227            Some(offset) => crate::object::translate(transform, offset.dx(), offset.dy()),
228        }
229    }
230
231    /// Paints each inline child.
232    ///
233    /// Render children whose [`TextParentData::offset`] is `None` will be skipped by this
234    /// method.
235    fn paint_inline_children(
236        self: RenderHandle<Self>,
237        app: &mut App,
238        context: &mut PaintingContext,
239        offset: Offset,
240    ) {
241        let mut child = self.first_child(app);
242        while let Some(current) = child {
243            let Some(child_offset) = text_parent_data(app, current).offset else {
244                return;
245            };
246            context.paint_child(app, current.as_object(), child_offset + offset);
247            child = self.child_after(app, current);
248        }
249    }
250
251    /// Performs a hit test on each inline child.
252    ///
253    /// Render children whose [`TextParentData::offset`] is `None` will be skipped by this
254    /// method.
255    fn hit_test_inline_children(
256        self: RenderHandle<Self>,
257        app: &mut App,
258        result: &mut BoxHitTestResult<'_>,
259        position: Offset,
260    ) -> bool {
261        let mut child = self.first_child(app);
262        while let Some(current) = child {
263            let Some(child_offset) = text_parent_data(app, current).offset else {
264                return false;
265            };
266            let is_hit = result.add_with_paint_offset(
267                Some(child_offset),
268                position,
269                |result, transformed| current.hit_test(app, result, transformed),
270            );
271            if is_hit {
272                return true;
273            }
274            child = self.child_after(app, current);
275        }
276        false
277    }
278}
279
280/// The mixin's `_layoutChild`: lays `child` out with `layout_child` and describes it as a
281/// placeholder for its span.
282fn layout_child_inline(
283    app: &mut App,
284    child: AnyRenderBox,
285    child_constraints: BoxConstraints,
286    layout_child: ChildLayouter,
287    get_baseline: ChildBaselineGetter,
288) -> PlaceholderDimensions {
289    let span = text_parent_data(app, child).span.clone();
290    debug_assert!(span.is_some());
291    let Some(span) = span else {
292        return PlaceholderDimensions::EMPTY;
293    };
294    let size = layout_child(app, child, child_constraints);
295    let baseline_offset = match span.alignment() {
296        PlaceholderAlignment::AboveBaseline
297        | PlaceholderAlignment::BelowBaseline
298        | PlaceholderAlignment::Bottom
299        | PlaceholderAlignment::Middle
300        | PlaceholderAlignment::Top => None,
301        PlaceholderAlignment::Baseline => get_baseline(
302            app,
303            child,
304            child_constraints,
305            span.baseline()
306                .expect("a baseline-aligned placeholder names its baseline"),
307        ),
308    };
309    PlaceholderDimensions {
310        size,
311        alignment: span.alignment(),
312        baseline: span.baseline(),
313        baseline_offset,
314    }
315}
316
317fn text_parent_data(app: &App, child: AnyRenderBox) -> &TextParentData {
318    child.as_object().parent_data_of::<TextParentData>(app)
319}
320
321fn text_parent_data_mut(app: &mut App, child: AnyRenderBox) -> &mut TextParentData {
322    child.as_object().parent_data_of_mut::<TextParentData>(app)
323}
324
325/// A render object that displays a paragraph of text.
326///
327/// Dart's constructor arguments beyond `text` and `textDirection` are the setters; add the
328/// inline children with [`add`](ContainerRenderObjectMixin::add) or
329/// [`add_all`](ContainerRenderObjectMixin::add_all). The paragraph shapes against `fonts` when
330/// given, else the app-wide collection of `PaintingBinding`.
331pub struct RenderParagraph {
332    render_object: RenderObjectData,
333    render_box: RenderBoxData,
334    container: ContainerRenderObjectData<AnyRenderBox>,
335    text_painter: TextPainter,
336    text_intrinsics: Option<TextPainter>,
337    fonts_override: Option<Handle<FontCollection>>,
338    soft_wrap: bool,
339    overflow: TextOverflow,
340    device_pixel_ratio: f64,
341    needs_clipping: bool,
342    // Placeholder dimensions representing the sizes of child inline widgets.
343    //
344    // These need to be cached because the text painter's placeholder dimensions
345    // will be overwritten during intrinsic width/height calculations and must be
346    // restored to the original values before final layout and painting.
347    placeholder_dimensions: Option<Vec<PlaceholderDimensions>>,
348    system_fonts: RelayoutWhenSystemFontsChangeData,
349}
350
351impl RenderParagraph {
352    /// Creates a paragraph render object.
353    pub fn new(
354        app: &mut App,
355        text: InlineSpanRef,
356        text_direction: TextDirection,
357        fonts: Option<Handle<FontCollection>>,
358    ) -> RenderHandle<Self> {
359        debug_assert!(text.debug_assert_is_valid());
360        let mut text_painter = TextPainter::new();
361        text_painter.set_text(Some(text));
362        text_painter.set_text_direction(Some(text_direction));
363        RenderHandle::new_box(
364            app,
365            RenderParagraph {
366                render_object: RenderObjectData::new(),
367                render_box: RenderBoxData::new(),
368                container: ContainerRenderObjectData::new(),
369                text_painter,
370                text_intrinsics: None,
371                fonts_override: fonts,
372                soft_wrap: true,
373                overflow: TextOverflow::Clip,
374                device_pixel_ratio: 1.0,
375                needs_clipping: false,
376                placeholder_dimensions: None,
377                system_fonts: RelayoutWhenSystemFontsChangeData::new(),
378            },
379        )
380    }
381
382    fn painter(self: RenderHandle<Self>, app: &App) -> &TextPainter {
383        &self.get(app).text_painter
384    }
385
386    fn painter_mut(self: RenderHandle<Self>, app: &mut App) -> &mut TextPainter {
387        &mut self.get_mut(app).text_painter
388    }
389
390    /// The collection this paragraph shapes against: the override, else the app-wide one.
391    pub fn fonts(self: RenderHandle<Self>, app: &mut App) -> Handle<FontCollection> {
392        match self.get(app).fonts_override {
393            Some(fonts) => fonts,
394            None => PaintingBinding::instance(app).fonts(app),
395        }
396    }
397
398    /// The painter together with the collection it shapes against.
399    fn painter_with_fonts(
400        self: RenderHandle<Self>,
401        app: &mut App,
402    ) -> (&mut TextPainter, &mut FontCollection) {
403        let fonts = self.fonts(app);
404        let (this, fonts) = app.get_disjoint_mut(self.handle(), fonts);
405        (&mut this.text_painter, fonts)
406    }
407
408    /// The text to display.
409    pub fn text(self: RenderHandle<Self>, app: &App) -> InlineSpanRef {
410        self.painter(app)
411            .text()
412            .cloned()
413            .expect("a paragraph always has text")
414    }
415
416    /// Sets [`text`](Self::text).
417    pub fn set_text(self: RenderHandle<Self>, app: &mut App, value: InlineSpanRef) {
418        match self.text(app).compare_to(&*value) {
419            RenderComparison::Identical => {}
420            RenderComparison::Metadata => {
421                self.painter_mut(app).set_text(Some(value));
422            }
423            RenderComparison::Paint => {
424                self.painter_mut(app).set_text(Some(value));
425                self.mark_needs_paint(app);
426            }
427            RenderComparison::Layout => {
428                self.painter_mut(app).set_text(Some(value));
429                self.mark_needs_layout(app);
430            }
431        }
432    }
433
434    /// How the text should be aligned horizontally.
435    pub fn text_align(self: RenderHandle<Self>, app: &App) -> TextAlign {
436        self.painter(app).text_align()
437    }
438
439    /// Sets [`text_align`](Self::text_align).
440    pub fn set_text_align(self: RenderHandle<Self>, app: &mut App, value: TextAlign) {
441        if self.painter(app).text_align() == value {
442            return;
443        }
444        self.painter_mut(app).set_text_align(value);
445        self.mark_needs_paint(app);
446    }
447
448    /// The directionality of the text.
449    ///
450    /// This decides how the [`TextAlign::Start`], [`TextAlign::End`], and
451    /// [`TextAlign::Justify`] values of [`text_align`](Self::text_align) are interpreted.
452    ///
453    /// This is also used to disambiguate how to render bidirectional text. For example, if
454    /// the [`text`](Self::text) is an English phrase followed by a Hebrew phrase, in a
455    /// [`TextDirection::Ltr`] context the English phrase will be on the left and the Hebrew
456    /// phrase to its right, while in a [`TextDirection::Rtl`] context, the English phrase
457    /// will be on the right and the Hebrew phrase on its left.
458    pub fn text_direction(self: RenderHandle<Self>, app: &App) -> TextDirection {
459        self.painter(app)
460            .text_direction()
461            .expect("a paragraph always has a direction")
462    }
463
464    /// Sets [`text_direction`](Self::text_direction).
465    pub fn set_text_direction(self: RenderHandle<Self>, app: &mut App, value: TextDirection) {
466        if self.painter(app).text_direction() == Some(value) {
467            return;
468        }
469        self.painter_mut(app).set_text_direction(Some(value));
470        self.mark_needs_layout(app);
471    }
472
473    /// Whether the text should break at soft line breaks.
474    ///
475    /// If false, the glyphs in the text will be positioned as if there was unlimited
476    /// horizontal space.
477    ///
478    /// If the text exceeds the given number of lines, it will be truncated according to
479    /// [`overflow`](Self::overflow) and [`soft_wrap`](Self::soft_wrap).
480    pub fn soft_wrap(self: RenderHandle<Self>, app: &App) -> bool {
481        self.get(app).soft_wrap
482    }
483
484    /// Sets [`soft_wrap`](Self::soft_wrap).
485    pub fn set_soft_wrap(self: RenderHandle<Self>, app: &mut App, value: bool) {
486        if self.get(app).soft_wrap == value {
487            return;
488        }
489        self.get_mut(app).soft_wrap = value;
490        self.mark_needs_layout(app);
491    }
492
493    /// How visual overflow should be handled.
494    pub fn overflow(self: RenderHandle<Self>, app: &App) -> TextOverflow {
495        self.get(app).overflow
496    }
497
498    /// Sets [`overflow`](Self::overflow).
499    pub fn set_overflow(self: RenderHandle<Self>, app: &mut App, value: TextOverflow) {
500        if self.get(app).overflow == value {
501            return;
502        }
503        self.get_mut(app).overflow = value;
504        let ellipsis = (value == TextOverflow::Ellipsis).then(|| K_ELLIPSIS.to_owned());
505        self.painter_mut(app).set_ellipsis(ellipsis);
506        self.mark_needs_layout(app);
507    }
508
509    /// The font scaling strategy to use when laying out and rendering the text.
510    pub fn text_scaler(self: RenderHandle<Self>, app: &App) -> TextScaler {
511        self.painter(app).text_scaler().clone()
512    }
513
514    /// Sets [`text_scaler`](Self::text_scaler).
515    pub fn set_text_scaler(self: RenderHandle<Self>, app: &mut App, value: TextScaler) {
516        if *self.painter(app).text_scaler() == value {
517            return;
518        }
519        self.painter_mut(app).set_text_scaler(value);
520        self.mark_needs_layout(app);
521    }
522
523    /// The pixel ratio of the device this paragraph is being rendered on.
524    ///
525    /// Flutter repaints on change only on the web; here it is bookkeeping.
526    pub fn device_pixel_ratio(self: RenderHandle<Self>, app: &App) -> f64 {
527        self.get(app).device_pixel_ratio
528    }
529
530    /// Sets [`device_pixel_ratio`](Self::device_pixel_ratio).
531    pub fn set_device_pixel_ratio(self: RenderHandle<Self>, app: &mut App, value: f64) {
532        if self.get(app).device_pixel_ratio == value {
533            return;
534        }
535        self.get_mut(app).device_pixel_ratio = value;
536    }
537
538    /// An optional maximum number of lines for the text to span, wrapping if necessary.
539    ///
540    /// If the text exceeds the given number of lines, it will be truncated according to
541    /// [`overflow`](Self::overflow) and [`soft_wrap`](Self::soft_wrap).
542    pub fn max_lines(self: RenderHandle<Self>, app: &App) -> Option<i32> {
543        self.painter(app).max_lines()
544    }
545
546    /// Sets [`max_lines`](Self::max_lines). The value may be `None`. If it is not `None`, then
547    /// it must be greater than zero.
548    pub fn set_max_lines(self: RenderHandle<Self>, app: &mut App, value: Option<i32>) {
549        debug_assert!(value.is_none_or(|value| value > 0));
550        if self.painter(app).max_lines() == value {
551            return;
552        }
553        self.painter_mut(app).set_max_lines(value);
554        self.mark_needs_layout(app);
555    }
556
557    /// Defines how to measure the width of the rendered text.
558    pub fn text_width_basis(self: RenderHandle<Self>, app: &App) -> TextWidthBasis {
559        self.painter(app).text_width_basis()
560    }
561
562    /// Sets [`text_width_basis`](Self::text_width_basis).
563    pub fn set_text_width_basis(self: RenderHandle<Self>, app: &mut App, value: TextWidthBasis) {
564        if self.painter(app).text_width_basis() == value {
565            return;
566        }
567        self.painter_mut(app).set_text_width_basis(value);
568        self.mark_needs_layout(app);
569    }
570
571    /// Defines how to apply `TextStyle.height` over and under text.
572    pub fn text_height_behavior(self: RenderHandle<Self>, app: &App) -> Option<TextHeightBehavior> {
573        self.painter(app).text_height_behavior()
574    }
575
576    /// Sets [`text_height_behavior`](Self::text_height_behavior).
577    pub fn set_text_height_behavior(
578        self: RenderHandle<Self>,
579        app: &mut App,
580        value: Option<TextHeightBehavior>,
581    ) {
582        if self.painter(app).text_height_behavior() == value {
583            return;
584        }
585        self.painter_mut(app).set_text_height_behavior(value);
586        self.mark_needs_layout(app);
587    }
588
589    /// The height of a space in [`text`](Self::text) in logical pixels.
590    pub fn preferred_line_height(self: RenderHandle<Self>, app: &mut App) -> f64 {
591        let (painter, fonts) = self.painter_with_fonts(app);
592        painter.preferred_line_height(fonts)
593    }
594
595    /// Flutter's `_textIntrinsics`: a second painter kept in sync with this paragraph's own,
596    /// together with the collection it shapes against.
597    ///
598    /// Laying the paragraph's painter out for intrinsics would destroy the state its own layout
599    /// left behind.
600    fn text_intrinsics_with_fonts(
601        self: RenderHandle<Self>,
602        app: &mut App,
603    ) -> (&mut TextPainter, &mut FontCollection) {
604        let fonts = self.fonts(app);
605        let (this, fonts) = app.get_disjoint_mut(self.handle(), fonts);
606        let painter = &this.text_painter;
607        let intrinsics = this.text_intrinsics.get_or_insert_with(TextPainter::new);
608        intrinsics.set_text(painter.text().cloned());
609        intrinsics.set_text_align(painter.text_align());
610        intrinsics.set_text_direction(painter.text_direction());
611        intrinsics.set_text_scaler(painter.text_scaler().clone());
612        intrinsics.set_max_lines(painter.max_lines());
613        intrinsics.set_ellipsis(painter.ellipsis().map(str::to_owned));
614        intrinsics.set_text_width_basis(painter.text_width_basis());
615        intrinsics.set_text_height_behavior(painter.text_height_behavior());
616        (intrinsics, fonts)
617    }
618
619    /// Flutter's `_computeIntrinsicHeight`: the height the text takes at `width`.
620    fn compute_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
621        let placeholder_dimensions = self.layout_inline_children(
622            app,
623            width,
624            ChildLayoutHelper::dry_layout_child,
625            ChildLayoutHelper::get_dry_baseline,
626        );
627        let max_width = self.adjust_max_width(app, width);
628        let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
629        intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
630        intrinsics.layout(fonts, width, max_width);
631        intrinsics.height()
632    }
633
634    fn adjust_max_width(self: RenderHandle<Self>, app: &App, max_width: f64) -> f64 {
635        if self.get(app).soft_wrap || self.get(app).overflow == TextOverflow::Ellipsis {
636            max_width
637        } else {
638            f64::INFINITY
639        }
640    }
641
642    fn layout_text_with_constraints(
643        self: RenderHandle<Self>,
644        app: &mut App,
645        constraints: BoxConstraints,
646    ) {
647        let max_width = self.adjust_max_width(app, constraints.max_width);
648        let placeholder_dimensions = self.get(app).placeholder_dimensions.clone();
649        let (painter, fonts) = self.painter_with_fonts(app);
650        painter.set_placeholder_dimensions(placeholder_dimensions);
651        painter.layout(fonts, constraints.min_width, max_width);
652    }
653
654    /// Returns the offset at which to paint the caret.
655    ///
656    /// Valid only after layout.
657    pub fn get_offset_for_caret(
658        self: RenderHandle<Self>,
659        app: &mut App,
660        position: TextPosition,
661        caret_prototype: Rect,
662    ) -> Offset {
663        debug_assert!(!self.debug_needs_layout(app));
664        let constraints = self.constraints(app);
665        self.layout_text_with_constraints(app, constraints);
666        let (painter, fonts) = self.painter_with_fonts(app);
667        painter.get_offset_for_caret(fonts, position, caret_prototype)
668    }
669
670    /// Returns the full height of the caret at the given position.
671    ///
672    /// Valid only after layout.
673    pub fn get_full_height_for_caret(
674        self: RenderHandle<Self>,
675        app: &mut App,
676        position: TextPosition,
677    ) -> f64 {
678        debug_assert!(!self.debug_needs_layout(app));
679        let constraints = self.constraints(app);
680        self.layout_text_with_constraints(app, constraints);
681        let (painter, fonts) = self.painter_with_fonts(app);
682        painter.get_full_height_for_caret(fonts, position, Rect::ZERO)
683    }
684
685    /// Returns a list of rects that bound the given selection.
686    ///
687    /// The `box_height_style` and `box_width_style` arguments may be used to select the
688    /// shape of the [`TextBox`]es. These properties default to [`BoxHeightStyle::Tight`] and
689    /// [`BoxWidthStyle::Tight`] respectively.
690    ///
691    /// A given selection might have more than one rect if this text painter contains
692    /// bidirectional text because logically contiguous text might not be visually contiguous.
693    ///
694    /// Valid only after layout.
695    pub fn get_boxes_for_selection(
696        self: RenderHandle<Self>,
697        app: &mut App,
698        selection: TextSelection,
699        box_height_style: BoxHeightStyle,
700        box_width_style: BoxWidthStyle,
701    ) -> Vec<TextBox> {
702        debug_assert!(!self.debug_needs_layout(app));
703        let constraints = self.constraints(app);
704        self.layout_text_with_constraints(app, constraints);
705        self.painter(app)
706            .get_boxes_for_selection(selection, box_height_style, box_width_style)
707    }
708
709    /// Returns the position within the text for the given pixel offset.
710    ///
711    /// Valid only after layout.
712    pub fn get_position_for_offset(
713        self: RenderHandle<Self>,
714        app: &mut App,
715        offset: Offset,
716    ) -> TextPosition {
717        debug_assert!(!self.debug_needs_layout(app));
718        let constraints = self.constraints(app);
719        self.layout_text_with_constraints(app, constraints);
720        self.painter(app).get_position_for_offset(offset)
721    }
722
723    /// Returns the text range of the word at the given offset. Characters not part of a word,
724    /// such as spaces, symbols, and punctuation, have word breaks on both sides. In such
725    /// cases, this method will return a text range that contains the given text position.
726    ///
727    /// Word boundaries are defined more precisely in Unicode Standard Annex #29
728    /// <http://www.unicode.org/reports/tr29/#Word_Boundaries>.
729    ///
730    /// Valid only after layout.
731    pub fn get_word_boundary(
732        self: RenderHandle<Self>,
733        app: &mut App,
734        position: TextPosition,
735    ) -> TextRange {
736        debug_assert!(!self.debug_needs_layout(app));
737        let constraints = self.constraints(app);
738        self.layout_text_with_constraints(app, constraints);
739        self.painter(app).get_word_boundary(position)
740    }
741
742    /// Returns the size of the text as laid out.
743    ///
744    /// This can differ from [`size`](RenderBox::size) if the text overflowed or if the
745    /// [`constraints`](RenderBox::constraints) provided by the parent
746    /// [`RenderObject`](crate::RenderObject) forced the layout to be bigger than necessary
747    /// for the given [`text`](Self::text).
748    ///
749    /// This returns the [`TextPainter::size`] of the underlying [`TextPainter`].
750    ///
751    /// Valid only after layout.
752    pub fn text_size(self: RenderHandle<Self>, app: &App) -> Size {
753        debug_assert!(!self.debug_needs_layout(app));
754        self.painter(app).size()
755    }
756
757    /// Whether the text was truncated or ellipsized as laid out.
758    ///
759    /// This returns the [`TextPainter::did_exceed_max_lines`] of the underlying
760    /// [`TextPainter`].
761    ///
762    /// Valid only after layout.
763    pub fn did_exceed_max_lines(self: RenderHandle<Self>, app: &App) -> bool {
764        debug_assert!(!self.debug_needs_layout(app));
765        self.painter(app).did_exceed_max_lines()
766    }
767}
768
769impl RelayoutWhenSystemFontsChangeMixin for RenderParagraph {
770    fn relayout_when_system_fonts_change_data(
771        self: RenderHandle<Self>,
772        app: &App,
773    ) -> &RelayoutWhenSystemFontsChangeData {
774        &self.get(app).system_fonts
775    }
776
777    fn relayout_when_system_fonts_change_data_mut(
778        self: RenderHandle<Self>,
779        app: &mut App,
780    ) -> &mut RelayoutWhenSystemFontsChangeData {
781        &mut self.get_mut(app).system_fonts
782    }
783
784    fn system_fonts_did_change(self: RenderHandle<Self>, app: &mut App) {
785        self.as_render_object(app).mark_needs_layout(app);
786        self.painter_mut(app).mark_needs_layout();
787    }
788}
789
790impl ContainerRenderObjectMixin for RenderParagraph {
791    type ChildType = AnyRenderBox;
792    type ParentDataType = TextParentData;
793
794    fn container_data(
795        self: RenderHandle<Self>,
796        app: &App,
797    ) -> &ContainerRenderObjectData<AnyRenderBox> {
798        &self.get(app).container
799    }
800
801    fn container_data_mut(
802        self: RenderHandle<Self>,
803        app: &mut App,
804    ) -> &mut ContainerRenderObjectData<AnyRenderBox> {
805        &mut self.get_mut(app).container
806    }
807}
808
809impl RenderInlineChildrenContainerDefaults for RenderParagraph {}
810
811impl RenderObject for RenderParagraph {
812    crate::render_object_accessors!();
813
814    fn visit_children(
815        self: RenderHandle<Self>,
816        app: &App,
817        visitor: &mut dyn FnMut(AnyRenderObject),
818    ) {
819        ContainerRenderObjectMixin::visit_children(self, app, visitor)
820    }
821
822    fn did_attach(self: RenderHandle<Self>, app: &mut App, owner: Handle<PipelineOwner>) {
823        ContainerRenderObjectMixin::did_attach(self, app, owner);
824        RelayoutWhenSystemFontsChangeMixin::attach_system_fonts(self, app);
825    }
826
827    fn did_detach(self: RenderHandle<Self>, app: &mut App) {
828        RelayoutWhenSystemFontsChangeMixin::detach_system_fonts(self, app);
829        ContainerRenderObjectMixin::did_detach(self, app);
830    }
831
832    fn redepth_children(self: RenderHandle<Self>, app: &mut App) {
833        ContainerRenderObjectMixin::redepth_children(self, app)
834    }
835
836    fn perform_layout(self: RenderHandle<Self>, app: &mut App) {
837        let constraints = self.constraints(app);
838        let placeholder_dimensions = self.layout_inline_children(
839            app,
840            constraints.max_width,
841            ChildLayoutHelper::layout_child,
842            ChildLayoutHelper::get_baseline,
843        );
844        self.get_mut(app).placeholder_dimensions = Some(placeholder_dimensions);
845        self.layout_text_with_constraints(app, constraints);
846        let boxes = self
847            .painter_mut(app)
848            .inline_placeholder_boxes()
849            .expect("laid out just now");
850        self.position_inline_children(app, &boxes);
851        let text_size = self.painter(app).size();
852        let size = constraints.constrain(text_size);
853        self.set_size(app, size);
854
855        let did_overflow_height =
856            size.height() < text_size.height() || self.painter(app).did_exceed_max_lines();
857        let did_overflow_width = size.width() < text_size.width();
858        // TODO(abarth): We're only measuring the sizes of the line boxes here. If
859        // the glyphs draw outside the line boxes, we might think that there isn't
860        // visual overflow when there actually is visual overflow. This can become
861        // a problem if we start having horizontal overflow and introduce a clip
862        // that affects the actual (but undetected) vertical overflow.
863        let has_visual_overflow = did_overflow_width || did_overflow_height;
864        let needs_clipping = has_visual_overflow
865            && match self.get(app).overflow {
866                TextOverflow::Visible => false,
867                // The fade shader waits on gradients; a fade clips meanwhile.
868                TextOverflow::Clip | TextOverflow::Ellipsis | TextOverflow::Fade => true,
869            };
870        self.get_mut(app).needs_clipping = needs_clipping;
871    }
872
873    fn paint(
874        self: RenderHandle<Self>,
875        app: &mut App,
876        context: &mut PaintingContext,
877        offset: Offset,
878    ) {
879        // Ideally we could compute the min/max intrinsic width/height with a
880        // non-destructive operation. However, currently, computing these values
881        // will destroy state inside the painter. If that happens, we need to get back
882        // the correct state by calling _layout again.
883        //
884        // TODO(abarth): Make computing the min/max intrinsic width/height a
885        //  non-destructive operation.
886        //
887        // If you remove this call, make sure that changing the textAlign still
888        // works properly.
889        let constraints = self.constraints(app);
890        self.layout_text_with_constraints(app, constraints);
891
892        let needs_clipping = self.get(app).needs_clipping;
893        if needs_clipping {
894            let bounds = offset & self.size(app);
895            context.canvas().save();
896            context.canvas().clip_rect(bounds, ClipOp::Intersect);
897        }
898        let (painter, fonts) = self.painter_with_fonts(app);
899        painter.paint(fonts, context.canvas(), offset);
900
901        self.paint_inline_children(app, context, offset);
902
903        if needs_clipping {
904            context.canvas().restore();
905        }
906    }
907}
908
909impl RenderBox for RenderParagraph {
910    crate::render_box_accessors!();
911
912    fn setup_parent_data(self: RenderHandle<Self>, app: &mut App, child: AnyRenderObject) {
913        RenderInlineChildrenContainerDefaults::setup_parent_data(self, app, child);
914    }
915
916    fn compute_min_intrinsic_width(self: RenderHandle<Self>, app: &mut App, _height: f64) -> f64 {
917        let placeholder_dimensions = self.layout_inline_children(
918            app,
919            f64::INFINITY,
920            |app, child, _| Size::new(child.get_min_intrinsic_width(app, f64::INFINITY), 0.0),
921            ChildLayoutHelper::get_dry_baseline,
922        );
923        let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
924        intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
925        intrinsics.layout(fonts, 0.0, f64::INFINITY);
926        intrinsics.min_intrinsic_width()
927    }
928
929    fn compute_max_intrinsic_width(self: RenderHandle<Self>, app: &mut App, _height: f64) -> f64 {
930        let placeholder_dimensions = self.layout_inline_children(
931            app,
932            f64::INFINITY,
933            // Height and baseline is irrelevant as all text will be laid
934            // out in a single line. Therefore, using 0.0 as a dummy for the height.
935            |app, child, _| Size::new(child.get_max_intrinsic_width(app, f64::INFINITY), 0.0),
936            ChildLayoutHelper::get_dry_baseline,
937        );
938        let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
939        intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
940        intrinsics.layout(fonts, 0.0, f64::INFINITY);
941        intrinsics.max_intrinsic_width()
942    }
943
944    fn compute_min_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
945        self.compute_intrinsic_height(app, width)
946    }
947
948    fn compute_max_intrinsic_height(self: RenderHandle<Self>, app: &mut App, width: f64) -> f64 {
949        self.compute_intrinsic_height(app, width)
950    }
951
952    fn compute_dry_layout(
953        self: RenderHandle<Self>,
954        app: &mut App,
955        constraints: BoxConstraints,
956    ) -> Size {
957        let placeholder_dimensions = self.layout_inline_children(
958            app,
959            constraints.max_width,
960            ChildLayoutHelper::dry_layout_child,
961            ChildLayoutHelper::get_dry_baseline,
962        );
963        let max_width = self.adjust_max_width(app, constraints.max_width);
964        let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
965        intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
966        intrinsics.layout(fonts, constraints.min_width, max_width);
967        let size = intrinsics.size();
968        constraints.constrain(size)
969    }
970
971    fn compute_distance_to_actual_baseline(
972        self: RenderHandle<Self>,
973        app: &mut App,
974        _baseline: TextBaseline,
975    ) -> Option<f64> {
976        debug_assert!(!self.as_object().debug_needs_layout(app));
977        let constraints = self.constraints(app);
978        debug_assert!(constraints.debug_assert_is_valid(false));
979        self.layout_text_with_constraints(app, constraints);
980        // Since the metric for the ideographic baseline is inaccurate and the non-alphabetic
981        // baselines are based off of the alphabetic baseline, the alphabetic one is used for
982        // now to produce correct layouts.
983        Some(
984            self.painter(app)
985                .compute_distance_to_actual_baseline(TextBaseline::Alphabetic),
986        )
987    }
988
989    fn compute_dry_baseline(
990        self: RenderHandle<Self>,
991        app: &mut App,
992        constraints: BoxConstraints,
993        _baseline: TextBaseline,
994    ) -> Option<f64> {
995        debug_assert!(constraints.debug_assert_is_valid(false));
996        let placeholder_dimensions = self.layout_inline_children(
997            app,
998            constraints.max_width,
999            ChildLayoutHelper::dry_layout_child,
1000            ChildLayoutHelper::get_dry_baseline,
1001        );
1002        let max_width = self.adjust_max_width(app, constraints.max_width);
1003        let (intrinsics, fonts) = self.text_intrinsics_with_fonts(app);
1004        intrinsics.set_placeholder_dimensions(Some(placeholder_dimensions));
1005        intrinsics.layout(fonts, constraints.min_width, max_width);
1006        Some(intrinsics.compute_distance_to_actual_baseline(TextBaseline::Alphabetic))
1007    }
1008
1009    fn apply_paint_transform(
1010        self: RenderHandle<Self>,
1011        app: &App,
1012        child: AnyRenderObject,
1013        transform: &mut Matrix4,
1014    ) {
1015        self.default_apply_paint_transform(app, child, transform);
1016    }
1017
1018    fn hit_test_self(self: RenderHandle<Self>, _app: &App, _position: Offset) -> bool {
1019        true
1020    }
1021
1022    fn hit_test_children(
1023        self: RenderHandle<Self>,
1024        app: &mut App,
1025        result: &mut BoxHitTestResult<'_>,
1026        position: Offset,
1027    ) -> bool {
1028        // Spans are not hit-test targets yet, so the hit falls to the inline children.
1029        self.hit_test_inline_children(app, result, position)
1030    }
1031}
1032
1033#[cfg(test)]
1034mod tests {
1035    use inset_embedder::Color;
1036    use inset_foundation::AppCell;
1037    use inset_gestures::HitTestResult;
1038    use inset_painting::{TextSpan, TextStyle};
1039
1040    use super::*;
1041    use crate::layer::{ContainerLayer, ErasedLayer, OffsetLayer};
1042    use crate::pipeline_owner::PipelineOwner;
1043    use crate::proxy_box::RenderRepaintBoundary;
1044
1045    /// What the shell does at start-up: the app-wide fonts, here the OS fonts.
1046    fn install_fonts(app: &mut App) {
1047        let binding = PaintingBinding::instance(app);
1048        if !binding.has_fonts(app) {
1049            binding.install_fonts(app, |fonts| {
1050                fonts.add_source(valo_system_fonts::SystemFonts::load());
1051            });
1052        }
1053    }
1054
1055    fn span(text: &str) -> InlineSpanRef {
1056        TextSpan::new()
1057            .text(text)
1058            .style(TextStyle::new().font_size(20.0))
1059            .into_span()
1060    }
1061
1062    fn laid_out(
1063        app: &mut App,
1064        text: &str,
1065        constraints: BoxConstraints,
1066    ) -> (
1067        RenderHandle<RenderParagraph>,
1068        RenderHandle<RenderRepaintBoundary>,
1069    ) {
1070        install_fonts(app);
1071        let paragraph = RenderParagraph::new(app, span(text), TextDirection::Ltr, None);
1072        let root = RenderRepaintBoundary::new(app, Some(paragraph.as_box()));
1073        let owner = PipelineOwner::new(app, None, None);
1074        owner.set_root_node(app, Some(root.as_object()));
1075        root.schedule_initial_layout(app);
1076        root.layout(app, constraints, false);
1077        let paint_root = OffsetLayer::new(app, Offset::ZERO);
1078        paint_root.as_layer().attach(app, root.as_object().id());
1079        root.as_object()
1080            .schedule_initial_paint(app, paint_root.as_container_layer());
1081        (paragraph, root)
1082    }
1083
1084    /// `paragraph_test.dart`: the minimum intrinsic width is the widest word, the maximum is the
1085    /// whole text on one line, and the intrinsic height at a width is what the text takes there.
1086    #[test]
1087    fn paragraph_intrinsics_come_from_the_text() {
1088        let cell = AppCell::new();
1089        let mut app = cell.borrow_mut();
1090        install_fonts(&mut app);
1091        let paragraph = RenderParagraph::new(
1092            &mut app,
1093            span("one two three four"),
1094            TextDirection::Ltr,
1095            None,
1096        );
1097        let box_ = paragraph.as_box();
1098        let min_width = box_.get_min_intrinsic_width(&mut app, f64::INFINITY);
1099        let max_width = box_.get_max_intrinsic_width(&mut app, f64::INFINITY);
1100        assert!(min_width > 0.0);
1101        assert!(
1102            min_width < max_width,
1103            "the widest word is narrower than the whole text"
1104        );
1105
1106        let one_line = box_.get_min_intrinsic_height(&mut app, max_width);
1107        let wrapped = box_.get_min_intrinsic_height(&mut app, max_width / 2.0);
1108        assert!(wrapped > one_line, "half the width takes more lines");
1109        assert_eq!(box_.get_max_intrinsic_height(&mut app, max_width), one_line);
1110    }
1111
1112    /// The dry layout of a paragraph is the size it lays out to, and its dry baseline is the
1113    /// baseline that layout reports.
1114    #[test]
1115    fn paragraph_dry_layout_and_baseline_match_its_layout() {
1116        let cell = AppCell::new();
1117        let mut app = cell.borrow_mut();
1118        let constraints = BoxConstraints::new().max_width(200.0).max_height(1000.0);
1119        install_fonts(&mut app);
1120        let paragraph =
1121            RenderParagraph::new(&mut app, span("one two three"), TextDirection::Ltr, None);
1122        let dry = paragraph.as_box().get_dry_layout(&mut app, constraints);
1123        let dry_baseline =
1124            paragraph
1125                .as_box()
1126                .get_dry_baseline(&mut app, constraints, TextBaseline::Alphabetic);
1127
1128        let (paragraph, _root) = laid_out(&mut app, "one two three", constraints);
1129        assert_eq!(dry, paragraph.size(&app));
1130        let baseline =
1131            paragraph
1132                .as_box()
1133                .get_distance_to_baseline(&mut app, TextBaseline::Alphabetic, true);
1134        assert_eq!(dry_baseline, baseline);
1135        assert!(baseline.is_some_and(|baseline| baseline > 0.0));
1136    }
1137
1138    #[test]
1139    fn a_paragraph_sizes_itself_to_its_text_within_loose_constraints() {
1140        let cell = AppCell::new();
1141        let mut app = cell.borrow_mut();
1142        let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1143        let (paragraph, _root) = laid_out(&mut app, "Hello", loose);
1144        let size = paragraph.size(&app);
1145        assert!(size.width() > 0.0 && size.width() < 1000.0);
1146        assert_eq!(size, paragraph.text_size(&app));
1147        assert!(!paragraph.did_exceed_max_lines(&app));
1148    }
1149
1150    #[test]
1151    fn narrow_constraints_wrap_the_text() {
1152        let cell = AppCell::new();
1153        let mut app = cell.borrow_mut();
1154        let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1155        let (wide, _root) = laid_out(&mut app, "one two three four", loose);
1156        let natural = wide.size(&app);
1157        let narrow = BoxConstraints::new()
1158            .max_width(natural.width() / 2.0)
1159            .max_height(1000.0);
1160        let (wrapped, _root) = laid_out(&mut app, "one two three four", narrow);
1161        assert!(wrapped.size(&app).height() > natural.height());
1162        assert!(wrapped.size(&app).width() <= natural.width() / 2.0);
1163    }
1164
1165    #[test]
1166    fn soft_wrap_off_lets_the_text_overflow_and_clips_it() {
1167        let cell = AppCell::new();
1168        let mut app = cell.borrow_mut();
1169        let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1170        let (wide, _root) = laid_out(&mut app, "one two three four", loose);
1171        let natural = wide.size(&app);
1172        install_fonts(&mut app);
1173        let paragraph = RenderParagraph::new(
1174            &mut app,
1175            span("one two three four"),
1176            TextDirection::Ltr,
1177            None,
1178        );
1179        paragraph.set_soft_wrap(&mut app, false);
1180        let root = RenderRepaintBoundary::new(&mut app, Some(paragraph.as_box()));
1181        let owner = PipelineOwner::new(&mut app, None, None);
1182        owner.set_root_node(&mut app, Some(root.as_object()));
1183        root.schedule_initial_layout(&mut app);
1184        let narrow = BoxConstraints::new()
1185            .max_width(natural.width() / 2.0)
1186            .max_height(1000.0);
1187        root.layout(&mut app, narrow, false);
1188        assert_eq!(paragraph.size(&app).width(), natural.width() / 2.0);
1189        assert_eq!(
1190            paragraph.text_size(&app).width(),
1191            natural.width(),
1192            "laid out unwrapped"
1193        );
1194        assert!(paragraph.get(&app).needs_clipping);
1195
1196        paragraph.set_overflow(&mut app, TextOverflow::Visible);
1197        owner.flush_layout(&mut app);
1198        assert!(!paragraph.get(&app).needs_clipping);
1199    }
1200
1201    #[test]
1202    fn ellipsis_overflow_reports_exceeded_lines() {
1203        let cell = AppCell::new();
1204        let mut app = cell.borrow_mut();
1205        let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1206        let (wide, _root) = laid_out(&mut app, "one two three four", loose);
1207        let natural = wide.size(&app);
1208        install_fonts(&mut app);
1209        let paragraph = RenderParagraph::new(
1210            &mut app,
1211            span("one two three four"),
1212            TextDirection::Ltr,
1213            None,
1214        );
1215        paragraph.set_max_lines(&mut app, Some(1));
1216        paragraph.set_overflow(&mut app, TextOverflow::Ellipsis);
1217        let root = RenderRepaintBoundary::new(&mut app, Some(paragraph.as_box()));
1218        let owner = PipelineOwner::new(&mut app, None, None);
1219        owner.set_root_node(&mut app, Some(root.as_object()));
1220        root.schedule_initial_layout(&mut app);
1221        root.layout(
1222            &mut app,
1223            BoxConstraints::new()
1224                .max_width(natural.width() / 2.0)
1225                .max_height(1000.0),
1226            false,
1227        );
1228        assert!(paragraph.did_exceed_max_lines(&app));
1229        assert_eq!(paragraph.size(&app).height(), natural.height());
1230    }
1231
1232    #[test]
1233    fn setting_text_marks_layout_or_paint_by_the_comparison() {
1234        let cell = AppCell::new();
1235        let mut app = cell.borrow_mut();
1236        let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1237        let (paragraph, root) = laid_out(&mut app, "Hello", loose);
1238        let owner = root.owner(&app).unwrap();
1239        owner.flush_compositing_bits(&mut app);
1240        owner.flush_paint(&mut app);
1241        assert!(!paragraph.debug_needs_layout(&app));
1242
1243        let recolored = TextSpan::new()
1244            .text("Hello")
1245            .style(
1246                TextStyle::new()
1247                    .font_size(20.0)
1248                    .color(Color::from_argb(255, 255, 0, 0)),
1249            )
1250            .into_span();
1251        paragraph.set_text(&mut app, recolored);
1252        assert!(
1253            !paragraph.debug_needs_layout(&app),
1254            "a color change is paint only"
1255        );
1256        assert!(paragraph.as_object().debug_needs_paint(&app));
1257
1258        paragraph.set_text(&mut app, span("Hello there"));
1259        assert!(paragraph.debug_needs_layout(&app));
1260    }
1261
1262    #[test]
1263    fn painting_records_glyphs_and_hit_testing_lands_on_the_paragraph() {
1264        let cell = AppCell::new();
1265        let mut app = cell.borrow_mut();
1266        let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1267        let (paragraph, root) = laid_out(&mut app, "Hello", loose);
1268        let owner = root.owner(&app).unwrap();
1269        owner.flush_compositing_bits(&mut app);
1270        owner.flush_paint(&mut app);
1271        assert!(!paragraph.as_object().debug_needs_paint(&app));
1272
1273        let mut result = HitTestResult::new();
1274        let hit = paragraph.hit_test(
1275            &mut app,
1276            &mut BoxHitTestResult::wrap(&mut result),
1277            Offset::new(2.0, 2.0),
1278        );
1279        assert!(hit);
1280        assert_eq!(result.path().len(), 1);
1281        let position = paragraph.get_position_for_offset(&mut app, Offset::new(0.0, 1.0));
1282        assert_eq!(position.offset, 0);
1283        assert_eq!(
1284            paragraph.get_word_boundary(&mut app, TextPosition::new(1)),
1285            TextRange::new(0, 5)
1286        );
1287    }
1288}