Skip to main content

inset_rendering/
paragraph.rs

1//! Flutter counterpart: `rendering/paragraph.dart` (`RenderParagraph`).
2//!
3//! Selection (`SelectionRegistrar` / `_SelectableFragment`), the fade overflow shader and
4//! 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        let painter = self.painter(app);
1029        let glyph = painter.get_closest_glyph_for_offset(position);
1030        // The hit-test can't fall through the horizontal gaps between visually
1031        // adjacent characters on the same line, even with a large letter-spacing or
1032        // text justification, as grapheme_cluster_layout_bounds.width is the advance
1033        // width to the next character, so there's no gap between their
1034        // grapheme_cluster_layout_bounds rects.
1035        let span_hit = glyph
1036            .filter(|glyph| glyph.grapheme_cluster_layout_bounds.contains(position))
1037            .and_then(|glyph| {
1038                painter
1039                    .text()
1040                    .expect("a paragraph always has text")
1041                    .clone()
1042                    .get_span_for_position(TextPosition::new(
1043                        glyph.grapheme_cluster_code_unit_range.start,
1044                    ))
1045                    .and_then(|span| span.to_hit_test_target())
1046            });
1047        match span_hit {
1048            Some(span) => {
1049                result.add(span.into());
1050                true
1051            }
1052            None => self.hit_test_inline_children(app, result, position),
1053        }
1054    }
1055}
1056
1057#[cfg(test)]
1058mod tests {
1059    use inset_embedder::Color;
1060    use inset_foundation::AppCell;
1061    use inset_gestures::HitTestResult;
1062    use inset_painting::{TextSpan, TextStyle};
1063
1064    use super::*;
1065    use crate::layer::{ContainerLayer, ErasedLayer, OffsetLayer};
1066    use crate::pipeline_owner::PipelineOwner;
1067    use crate::proxy_box::RenderRepaintBoundary;
1068
1069    /// What the shell does at start-up: the app-wide fonts, here the OS fonts.
1070    fn install_fonts(app: &mut App) {
1071        let binding = PaintingBinding::instance(app);
1072        if !binding.has_fonts(app) {
1073            binding.install_fonts(app, |fonts| {
1074                fonts.add_source(valo_system_fonts::SystemFonts::load());
1075            });
1076        }
1077    }
1078
1079    fn span(text: &str) -> InlineSpanRef {
1080        TextSpan::new()
1081            .text(text)
1082            .style(TextStyle::new().font_size(20.0))
1083            .into_span()
1084    }
1085
1086    fn laid_out(
1087        app: &mut App,
1088        text: &str,
1089        constraints: BoxConstraints,
1090    ) -> (
1091        RenderHandle<RenderParagraph>,
1092        RenderHandle<RenderRepaintBoundary>,
1093    ) {
1094        laid_out_span(app, span(text), constraints)
1095    }
1096
1097    fn laid_out_span(
1098        app: &mut App,
1099        text: InlineSpanRef,
1100        constraints: BoxConstraints,
1101    ) -> (
1102        RenderHandle<RenderParagraph>,
1103        RenderHandle<RenderRepaintBoundary>,
1104    ) {
1105        install_fonts(app);
1106        let paragraph = RenderParagraph::new(app, text, TextDirection::Ltr, None);
1107        let root = RenderRepaintBoundary::new(app, Some(paragraph.as_box()));
1108        let owner = PipelineOwner::new(app, None, None);
1109        owner.set_root_node(app, Some(root.as_object()));
1110        root.schedule_initial_layout(app);
1111        root.layout(app, constraints, false);
1112        let paint_root = OffsetLayer::new(app, Offset::ZERO);
1113        paint_root.as_layer().attach(app, root.as_object().id());
1114        root.as_object()
1115            .schedule_initial_paint(app, paint_root.as_container_layer());
1116        (paragraph, root)
1117    }
1118
1119    /// `paragraph_test.dart`: the minimum intrinsic width is the widest word, the maximum is the
1120    /// whole text on one line, and the intrinsic height at a width is what the text takes there.
1121    #[test]
1122    fn paragraph_intrinsics_come_from_the_text() {
1123        let cell = AppCell::new();
1124        let mut app = cell.borrow_mut();
1125        install_fonts(&mut app);
1126        let paragraph = RenderParagraph::new(
1127            &mut app,
1128            span("one two three four"),
1129            TextDirection::Ltr,
1130            None,
1131        );
1132        let box_ = paragraph.as_box();
1133        let min_width = box_.get_min_intrinsic_width(&mut app, f64::INFINITY);
1134        let max_width = box_.get_max_intrinsic_width(&mut app, f64::INFINITY);
1135        assert!(min_width > 0.0);
1136        assert!(
1137            min_width < max_width,
1138            "the widest word is narrower than the whole text"
1139        );
1140
1141        let one_line = box_.get_min_intrinsic_height(&mut app, max_width);
1142        let wrapped = box_.get_min_intrinsic_height(&mut app, max_width / 2.0);
1143        assert!(wrapped > one_line, "half the width takes more lines");
1144        assert_eq!(box_.get_max_intrinsic_height(&mut app, max_width), one_line);
1145    }
1146
1147    /// The dry layout of a paragraph is the size it lays out to, and its dry baseline is the
1148    /// baseline that layout reports.
1149    #[test]
1150    fn paragraph_dry_layout_and_baseline_match_its_layout() {
1151        let cell = AppCell::new();
1152        let mut app = cell.borrow_mut();
1153        let constraints = BoxConstraints::new().max_width(200.0).max_height(1000.0);
1154        install_fonts(&mut app);
1155        let paragraph =
1156            RenderParagraph::new(&mut app, span("one two three"), TextDirection::Ltr, None);
1157        let dry = paragraph.as_box().get_dry_layout(&mut app, constraints);
1158        let dry_baseline =
1159            paragraph
1160                .as_box()
1161                .get_dry_baseline(&mut app, constraints, TextBaseline::Alphabetic);
1162
1163        let (paragraph, _root) = laid_out(&mut app, "one two three", constraints);
1164        assert_eq!(dry, paragraph.size(&app));
1165        let baseline =
1166            paragraph
1167                .as_box()
1168                .get_distance_to_baseline(&mut app, TextBaseline::Alphabetic, true);
1169        assert_eq!(dry_baseline, baseline);
1170        assert!(baseline.is_some_and(|baseline| baseline > 0.0));
1171    }
1172
1173    #[test]
1174    fn a_paragraph_sizes_itself_to_its_text_within_loose_constraints() {
1175        let cell = AppCell::new();
1176        let mut app = cell.borrow_mut();
1177        let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1178        let (paragraph, _root) = laid_out(&mut app, "Hello", loose);
1179        let size = paragraph.size(&app);
1180        assert!(size.width() > 0.0 && size.width() < 1000.0);
1181        assert_eq!(size, paragraph.text_size(&app));
1182        assert!(!paragraph.did_exceed_max_lines(&app));
1183    }
1184
1185    #[test]
1186    fn narrow_constraints_wrap_the_text() {
1187        let cell = AppCell::new();
1188        let mut app = cell.borrow_mut();
1189        let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1190        let (wide, _root) = laid_out(&mut app, "one two three four", loose);
1191        let natural = wide.size(&app);
1192        let narrow = BoxConstraints::new()
1193            .max_width(natural.width() / 2.0)
1194            .max_height(1000.0);
1195        let (wrapped, _root) = laid_out(&mut app, "one two three four", narrow);
1196        assert!(wrapped.size(&app).height() > natural.height());
1197        assert!(wrapped.size(&app).width() <= natural.width() / 2.0);
1198    }
1199
1200    #[test]
1201    fn soft_wrap_off_lets_the_text_overflow_and_clips_it() {
1202        let cell = AppCell::new();
1203        let mut app = cell.borrow_mut();
1204        let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1205        let (wide, _root) = laid_out(&mut app, "one two three four", loose);
1206        let natural = wide.size(&app);
1207        install_fonts(&mut app);
1208        let paragraph = RenderParagraph::new(
1209            &mut app,
1210            span("one two three four"),
1211            TextDirection::Ltr,
1212            None,
1213        );
1214        paragraph.set_soft_wrap(&mut app, false);
1215        let root = RenderRepaintBoundary::new(&mut app, Some(paragraph.as_box()));
1216        let owner = PipelineOwner::new(&mut app, None, None);
1217        owner.set_root_node(&mut app, Some(root.as_object()));
1218        root.schedule_initial_layout(&mut app);
1219        let narrow = BoxConstraints::new()
1220            .max_width(natural.width() / 2.0)
1221            .max_height(1000.0);
1222        root.layout(&mut app, narrow, false);
1223        assert_eq!(paragraph.size(&app).width(), natural.width() / 2.0);
1224        assert_eq!(
1225            paragraph.text_size(&app).width(),
1226            natural.width(),
1227            "laid out unwrapped"
1228        );
1229        assert!(paragraph.get(&app).needs_clipping);
1230
1231        paragraph.set_overflow(&mut app, TextOverflow::Visible);
1232        owner.flush_layout(&mut app);
1233        assert!(!paragraph.get(&app).needs_clipping);
1234    }
1235
1236    #[test]
1237    fn ellipsis_overflow_reports_exceeded_lines() {
1238        let cell = AppCell::new();
1239        let mut app = cell.borrow_mut();
1240        let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1241        let (wide, _root) = laid_out(&mut app, "one two three four", loose);
1242        let natural = wide.size(&app);
1243        install_fonts(&mut app);
1244        let paragraph = RenderParagraph::new(
1245            &mut app,
1246            span("one two three four"),
1247            TextDirection::Ltr,
1248            None,
1249        );
1250        paragraph.set_max_lines(&mut app, Some(1));
1251        paragraph.set_overflow(&mut app, TextOverflow::Ellipsis);
1252        let root = RenderRepaintBoundary::new(&mut app, Some(paragraph.as_box()));
1253        let owner = PipelineOwner::new(&mut app, None, None);
1254        owner.set_root_node(&mut app, Some(root.as_object()));
1255        root.schedule_initial_layout(&mut app);
1256        root.layout(
1257            &mut app,
1258            BoxConstraints::new()
1259                .max_width(natural.width() / 2.0)
1260                .max_height(1000.0),
1261            false,
1262        );
1263        assert!(paragraph.did_exceed_max_lines(&app));
1264        assert_eq!(paragraph.size(&app).height(), natural.height());
1265    }
1266
1267    #[test]
1268    fn setting_text_marks_layout_or_paint_by_the_comparison() {
1269        let cell = AppCell::new();
1270        let mut app = cell.borrow_mut();
1271        let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1272        let (paragraph, root) = laid_out(&mut app, "Hello", loose);
1273        let owner = root.owner(&app).unwrap();
1274        owner.flush_compositing_bits(&mut app);
1275        owner.flush_paint(&mut app);
1276        assert!(!paragraph.debug_needs_layout(&app));
1277
1278        let recolored = TextSpan::new()
1279            .text("Hello")
1280            .style(
1281                TextStyle::new()
1282                    .font_size(20.0)
1283                    .color(Color::from_argb(255, 255, 0, 0)),
1284            )
1285            .into_span();
1286        paragraph.set_text(&mut app, recolored);
1287        assert!(
1288            !paragraph.debug_needs_layout(&app),
1289            "a color change is paint only"
1290        );
1291        assert!(paragraph.as_object().debug_needs_paint(&app));
1292
1293        paragraph.set_text(&mut app, span("Hello there"));
1294        assert!(paragraph.debug_needs_layout(&app));
1295    }
1296
1297    #[test]
1298    fn painting_records_glyphs_and_hit_testing_lands_on_the_span_and_the_paragraph() {
1299        let cell = AppCell::new();
1300        let mut app = cell.borrow_mut();
1301        let loose = BoxConstraints::new().max_width(1000.0).max_height(1000.0);
1302        let (paragraph, root) = laid_out(&mut app, "Hello", loose);
1303        let owner = root.owner(&app).unwrap();
1304        owner.flush_compositing_bits(&mut app);
1305        owner.flush_paint(&mut app);
1306        assert!(!paragraph.as_object().debug_needs_paint(&app));
1307
1308        let mut result = HitTestResult::new();
1309        let hit = paragraph.hit_test(
1310            &mut app,
1311            &mut BoxHitTestResult::wrap(&mut result),
1312            Offset::new(2.0, 2.0),
1313        );
1314        assert!(hit);
1315        assert_eq!(result.path().len(), 2, "the span, then the paragraph");
1316        let position = paragraph.get_position_for_offset(&mut app, Offset::new(0.0, 1.0));
1317        assert_eq!(position.offset, 0);
1318        assert_eq!(
1319            paragraph.get_word_boundary(&mut app, TextPosition::new(1)),
1320            TextRange::new(0, 5)
1321        );
1322    }
1323
1324    // ---- spans as hit-test targets and mouse tracker annotations ----
1325
1326    /// The boxes of the text between UTF-16 offsets `start` and `end`, one per line.
1327    fn boxes_of(
1328        app: &mut App,
1329        paragraph: RenderHandle<RenderParagraph>,
1330        start: i32,
1331        end: i32,
1332    ) -> Vec<TextBox> {
1333        paragraph.get_boxes_for_selection(
1334            app,
1335            TextSelection::new(start, end),
1336            BoxHeightStyle::Tight,
1337            BoxWidthStyle::Tight,
1338        )
1339    }
1340
1341    fn center_of(text_box: &TextBox) -> Offset {
1342        Offset::new(
1343            (text_box.left + text_box.right) / 2.0,
1344            (text_box.top + text_box.bottom) / 2.0,
1345        )
1346    }
1347
1348    /// The text of each span a hit test at `position` added, front to back.
1349    fn spans_hit(
1350        app: &mut App,
1351        paragraph: RenderHandle<RenderParagraph>,
1352        position: Offset,
1353    ) -> (bool, Vec<String>) {
1354        let mut result = HitTestResult::new();
1355        let hit = paragraph.hit_test(app, &mut BoxHitTestResult::wrap(&mut result), position);
1356        let spans = result
1357            .path()
1358            .iter()
1359            .filter_map(|entry| {
1360                let target: &dyn Any = entry.target();
1361                target.downcast_ref::<TextSpan>()?.text.clone()
1362            })
1363            .collect();
1364        (hit, spans)
1365    }
1366
1367    /// `paragraph_test.dart`, "Basic TextSpan Hit testing": a hit on a glyph adds the span the
1368    /// glyph is in, and a hit off the glyphs adds no span.
1369    #[test]
1370    fn a_hit_on_a_glyph_adds_its_span_and_a_hit_off_the_glyphs_adds_none() {
1371        let cell = AppCell::new();
1372        let mut app = cell.borrow_mut();
1373        let text = TextSpan::new()
1374            .style(TextStyle::new().font_size(10.0))
1375            .children(vec![
1376                TextSpan::new().text("A".repeat(10)).into_span(),
1377                TextSpan::new()
1378                    .text("BC")
1379                    .style(TextStyle::new().letter_spacing(26.0))
1380                    .into_span(),
1381            ])
1382            .into_span();
1383        let (paragraph, _root) =
1384            laid_out_span(&mut app, text, BoxConstraints::tight_for(Some(300.0), None));
1385
1386        let first_a = center_of(&boxes_of(&mut app, paragraph, 0, 1)[0]);
1387        let last_a = center_of(&boxes_of(&mut app, paragraph, 9, 10)[0]);
1388        let b = boxes_of(&mut app, paragraph, 10, 11)[0];
1389        let c = center_of(&boxes_of(&mut app, paragraph, 11, 12)[0]);
1390        for position in [first_a, last_a] {
1391            assert_eq!(
1392                spans_hit(&mut app, paragraph, position),
1393                (true, vec!["A".repeat(10)])
1394            );
1395        }
1396        // Past B's glyph, in its letter spacing: still B, as the advance reaches C.
1397        let between_b_and_c = Offset::new(b.right - 1.0, center_of(&b).dy());
1398        for position in [center_of(&b), between_b_and_c, c] {
1399            assert_eq!(
1400                spans_hit(&mut app, paragraph, position),
1401                (true, vec!["BC".to_owned()])
1402            );
1403        }
1404
1405        let after_c = Offset::new(299.0, c.dy());
1406        assert_eq!(
1407            spans_hit(&mut app, paragraph, after_c),
1408            (true, Vec::new()),
1409            "the paragraph hits itself, and no span is under the pointer"
1410        );
1411        assert_eq!(
1412            spans_hit(&mut app, paragraph, Offset::new(9999.0, 9999.0)),
1413            (false, Vec::new())
1414        );
1415    }
1416
1417    /// Plays `RendererBinding`'s part: hit-tests the paragraph's root before the gesture binding
1418    /// adds itself to the path.
1419    struct RootHitTester {
1420        root: RenderHandle<RenderRepaintBoundary>,
1421    }
1422
1423    impl inset_gestures::GestureBindingOverridesObject for RootHitTester {
1424        fn hit_test_in_view(
1425            self: Handle<Self>,
1426            app: &mut App,
1427            result: &mut HitTestResult,
1428            position: Offset,
1429            _view_id: inset_embedder::ViewId,
1430        ) {
1431            let root = app.get(self).root;
1432            root.hit_test(app, &mut BoxHitTestResult::wrap(result), position);
1433        }
1434
1435        fn will_dispatch_event(
1436            self: Handle<Self>,
1437            _app: &mut App,
1438            _event: &inset_gestures::PointerEvent,
1439            _hit_test_result: Option<&HitTestResult>,
1440        ) {
1441        }
1442    }
1443
1444    fn tap_at(app: &mut App, position: Offset) {
1445        use inset_gestures::{GestureBinding, PointerDownEvent, PointerEvent, PointerUpEvent};
1446        let binding = GestureBinding::instance(app);
1447        binding.handle_pointer_event(
1448            app,
1449            PointerEvent::Down(PointerDownEvent {
1450                position,
1451                ..PointerDownEvent::default()
1452            }),
1453        );
1454        app.drain_microtasks();
1455        binding.handle_pointer_event(
1456            app,
1457            PointerEvent::Up(PointerUpEvent {
1458                position,
1459                ..PointerUpEvent::default()
1460            }),
1461        );
1462        app.drain_microtasks();
1463    }
1464
1465    /// A press on a span reaches its `TapGestureRecognizer`; a tap on the text beside it does
1466    /// not.
1467    #[test]
1468    fn a_tap_on_a_span_runs_its_recognizer_and_a_tap_beside_it_does_not() {
1469        use inset_gestures::{GestureBinding, GestureRecognizerLeaf, TapGestureRecognizer};
1470
1471        let cell = AppCell::new();
1472        let mut app = cell.borrow_mut();
1473        let taps = Rc::new(std::cell::Cell::new(0));
1474        let recognizer = TapGestureRecognizer::new(&mut app);
1475        let counter = Rc::clone(&taps);
1476        recognizer.set_on_tap(
1477            &mut app,
1478            Some(inset_foundation::Listener::new(move |_app| {
1479                counter.set(counter.get() + 1)
1480            })),
1481        );
1482        let text = TextSpan::new()
1483            .style(TextStyle::new().font_size(20.0))
1484            .children(vec![
1485                TextSpan::new().text("Tap ").into_span(),
1486                TextSpan::new()
1487                    .text("here")
1488                    .recognizer(recognizer.as_recognizer())
1489                    .into_span(),
1490                TextSpan::new().text(" please").into_span(),
1491            ])
1492            .into_span();
1493        let (paragraph, root) = laid_out_span(
1494            &mut app,
1495            text,
1496            BoxConstraints::new().max_width(1000.0).max_height(1000.0),
1497        );
1498        let hit_tester = app.create(RootHitTester { root });
1499        GestureBinding::instance(&mut app).set_overrides(&mut app, Rc::new(hit_tester));
1500
1501        let here = center_of(&boxes_of(&mut app, paragraph, 4, 8)[0]);
1502        tap_at(&mut app, here);
1503        assert_eq!(taps.get(), 1);
1504
1505        let beside = center_of(&boxes_of(&mut app, paragraph, 0, 3)[0]);
1506        tap_at(&mut app, beside);
1507        assert_eq!(taps.get(), 1, "the plain span has no recognizer");
1508        recognizer.as_recognizer().dispose(&mut app);
1509    }
1510
1511    /// A span's cursor shows while the mouse is over it: the click cursor by default for a span
1512    /// with a recognizer, the one it names otherwise, and the one behind when it defers.
1513    #[test]
1514    fn the_mouse_shows_the_cursor_of_the_span_under_it() {
1515        use inset_gestures::{
1516            GestureRecognizerLeaf, PointerAddedEvent, PointerEvent, PointerHoverEvent,
1517            TapGestureRecognizer,
1518        };
1519        use inset_services::{MouseCursorRef, SystemMouseCursors};
1520
1521        use crate::mouse_tracker::MouseTracker;
1522
1523        let cell = AppCell::new();
1524        let mut app = cell.borrow_mut();
1525        let recognizer = TapGestureRecognizer::new(&mut app);
1526        let text = TextSpan::new()
1527            .style(TextStyle::new().font_size(20.0))
1528            .children(vec![
1529                TextSpan::new().text("plain ").into_span(),
1530                TextSpan::new()
1531                    .text("link")
1532                    .recognizer(recognizer.as_recognizer())
1533                    .into_span(),
1534                TextSpan::new()
1535                    .text(" forbidden")
1536                    .mouse_cursor(SystemMouseCursors::FORBIDDEN)
1537                    .into_span(),
1538            ])
1539            .into_span();
1540        let (paragraph, root) = laid_out_span(
1541            &mut app,
1542            text,
1543            BoxConstraints::new().max_width(1000.0).max_height(1000.0),
1544        );
1545        let tracker = MouseTracker::new(
1546            &mut app,
1547            Rc::new(move |app: &mut App, position: Offset, _view| {
1548                let mut result = HitTestResult::new();
1549                root.hit_test(app, &mut BoxHitTestResult::wrap(&mut result), position);
1550                result
1551            }),
1552        );
1553        let plain = center_of(&boxes_of(&mut app, paragraph, 0, 5)[0]);
1554        let link = center_of(&boxes_of(&mut app, paragraph, 6, 10)[0]);
1555        let forbidden = center_of(&boxes_of(&mut app, paragraph, 11, 20)[0]);
1556        tracker.update_with_event(
1557            &mut app,
1558            &PointerEvent::Added(PointerAddedEvent {
1559                kind: inset_embedder::PointerDeviceKind::Mouse,
1560                position: plain,
1561                ..PointerAddedEvent::default()
1562            }),
1563            None,
1564        );
1565        let cursor_at = |app: &mut App, position: Offset| -> MouseCursorRef {
1566            tracker.update_with_event(
1567                app,
1568                &PointerEvent::Hover(PointerHoverEvent {
1569                    kind: inset_embedder::PointerDeviceKind::Mouse,
1570                    position,
1571                    ..PointerHoverEvent::default()
1572                }),
1573                None,
1574            );
1575            tracker.debug_device_active_cursor(app, 0).unwrap()
1576        };
1577        let basic: MouseCursorRef = SystemMouseCursors::BASIC.into();
1578        let click: MouseCursorRef = SystemMouseCursors::CLICK.into();
1579        let forbidden_cursor: MouseCursorRef = SystemMouseCursors::FORBIDDEN.into();
1580        assert!(*cursor_at(&mut app, link) == *click);
1581        assert!(*cursor_at(&mut app, plain) == *basic, "a plain span defers");
1582        assert!(*cursor_at(&mut app, forbidden) == *forbidden_cursor);
1583        recognizer.as_recognizer().dispose(&mut app);
1584    }
1585
1586    /// A span that wraps over two lines is one annotation: the mouse enters it once on the
1587    /// first line, stays in it on the second, and exits it once.
1588    #[test]
1589    fn a_wrapped_span_is_entered_once_across_its_lines() {
1590        use std::cell::RefCell;
1591
1592        use inset_gestures::{
1593            PointerAddedEvent, PointerEnterEvent, PointerEvent, PointerExitEvent, PointerHoverEvent,
1594        };
1595
1596        use crate::mouse_tracker::MouseTracker;
1597
1598        let cell = AppCell::new();
1599        let mut app = cell.borrow_mut();
1600        let log: Rc<RefCell<Vec<&str>>> = Rc::default();
1601        let enter_log = Rc::clone(&log);
1602        let exit_log = Rc::clone(&log);
1603        let words = "one two three four five six seven eight";
1604        let text = TextSpan::new()
1605            .text(words)
1606            .style(TextStyle::new().font_size(20.0))
1607            .on_enter(Rc::new(move |_app: &mut App, _event: PointerEnterEvent| {
1608                enter_log.borrow_mut().push("enter")
1609            }))
1610            .on_exit(Rc::new(move |_app: &mut App, _event: PointerExitEvent| {
1611                exit_log.borrow_mut().push("exit")
1612            }))
1613            .into_span();
1614        let (paragraph, root) = laid_out_span(
1615            &mut app,
1616            text,
1617            BoxConstraints::new().max_width(120.0).max_height(1000.0),
1618        );
1619        let boxes = boxes_of(&mut app, paragraph, 0, 3);
1620        let first_line = center_of(&boxes[0]);
1621        let last = words.len() as i32;
1622        let last_box = *boxes_of(&mut app, paragraph, last - 1, last)
1623            .last()
1624            .unwrap();
1625        assert!(last_box.top > boxes[0].bottom - 1.0, "the span wraps");
1626        let last_line = center_of(&last_box);
1627        let tracker = MouseTracker::new(
1628            &mut app,
1629            Rc::new(move |app: &mut App, position: Offset, _view| {
1630                let mut result = HitTestResult::new();
1631                root.hit_test(app, &mut BoxHitTestResult::wrap(&mut result), position);
1632                result
1633            }),
1634        );
1635        let mouse = |position: Offset| {
1636            PointerEvent::Hover(PointerHoverEvent {
1637                kind: inset_embedder::PointerDeviceKind::Mouse,
1638                position,
1639                ..PointerHoverEvent::default()
1640            })
1641        };
1642        tracker.update_with_event(
1643            &mut app,
1644            &PointerEvent::Added(PointerAddedEvent {
1645                kind: inset_embedder::PointerDeviceKind::Mouse,
1646                position: Offset::new(5000.0, 5000.0),
1647                ..PointerAddedEvent::default()
1648            }),
1649            None,
1650        );
1651        tracker.update_with_event(&mut app, &mouse(first_line), None);
1652        tracker.update_with_event(&mut app, &mouse(last_line), None);
1653        assert_eq!(*log.borrow(), ["enter"]);
1654        tracker.update_with_event(&mut app, &mouse(Offset::new(5000.0, 5000.0)), None);
1655        assert_eq!(*log.borrow(), ["enter", "exit"]);
1656    }
1657}