Added parameters boxHeightStyle, boxWidthStyle to RenderParagraph.getBoxesForSelection (#87183)

diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart
index ef5ee3d..6be6743 100644
--- a/packages/flutter/lib/src/rendering/paragraph.dart
+++ b/packages/flutter/lib/src/rendering/paragraph.dart
@@ -4,7 +4,7 @@
 
 import 'dart:collection';
 import 'dart:math' as math;
-import 'dart:ui' as ui show Gradient, Shader, TextBox, PlaceholderAlignment, TextHeightBehavior;
+import 'dart:ui' as ui show Gradient, Shader, TextBox, PlaceholderAlignment, TextHeightBehavior, BoxHeightStyle, BoxWidthStyle;
 
 import 'package:flutter/foundation.dart';
 import 'package:flutter/gestures.dart';
@@ -787,15 +787,35 @@
 
   /// Returns a list of rects that bound the given selection.
   ///
-  /// A given selection might have more than one rect if this text painter
-  /// contains bidirectional text because logically contiguous text might not be
-  /// visually contiguous.
+  /// The [boxHeightStyle] and [boxWidthStyle] arguments may be used to select
+  /// the shape of the [TextBox]es. These properties default to
+  /// [ui.BoxHeightStyle.tight] and [ui.BoxWidthStyle.tight] respectively and
+  /// must not be null.
+  ///
+  /// A given selection might have more than one rect if the [RenderParagraph]
+  /// contains multiple [InlineSpan]s or bidirectional text, because logically
+  /// contiguous text might not be visually contiguous.
   ///
   /// Valid only after [layout].
-  List<ui.TextBox> getBoxesForSelection(TextSelection selection) {
+  ///
+  /// See also:
+  ///
+  ///  * [TextPainter.getBoxesForSelection], the method in TextPainter to get
+  ///    the equivalent boxes.
+  List<ui.TextBox> getBoxesForSelection(
+    TextSelection selection, {
+    ui.BoxHeightStyle boxHeightStyle = ui.BoxHeightStyle.tight,
+    ui.BoxWidthStyle boxWidthStyle = ui.BoxWidthStyle.tight,
+  }) {
     assert(!debugNeedsLayout);
+    assert(boxHeightStyle != null);
+    assert(boxWidthStyle != null);
     _layoutTextWithConstraints(constraints);
-    return _textPainter.getBoxesForSelection(selection);
+    return _textPainter.getBoxesForSelection(
+      selection,
+      boxHeightStyle: boxHeightStyle,
+      boxWidthStyle: boxWidthStyle,
+    );
   }
 
   /// Returns the position within the text for the given pixel offset.
diff --git a/packages/flutter/test/rendering/paragraph_test.dart b/packages/flutter/test/rendering/paragraph_test.dart
index edef26e..8e6358d 100644
--- a/packages/flutter/test/rendering/paragraph_test.dart
+++ b/packages/flutter/test/rendering/paragraph_test.dart
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:ui' as ui show TextBox;
+import 'dart:ui' as ui show TextBox, BoxHeightStyle, BoxWidthStyle;
 
 import 'package:flutter/gestures.dart';
 import 'package:flutter/rendering.dart';
@@ -28,11 +28,19 @@
   TextSelection emptyListSelection;
 
   @override
-  List<ui.TextBox> getBoxesForSelection(TextSelection selection) {
+  List<ui.TextBox> getBoxesForSelection(
+    TextSelection selection, {
+    ui.BoxHeightStyle boxHeightStyle = ui.BoxHeightStyle.tight,
+    ui.BoxWidthStyle boxWidthStyle = ui.BoxWidthStyle.tight,
+  }) {
     if (selection == emptyListSelection) {
       return <ui.TextBox>[];
     }
-    return super.getBoxesForSelection(selection);
+    return super.getBoxesForSelection(
+      selection,
+      boxHeightStyle: boxHeightStyle,
+      boxWidthStyle: boxWidthStyle,
+    );
   }
 }
 
@@ -48,11 +56,19 @@
   }) : super(text, children: children, textDirection: textDirection);
 
   @override
-  List<ui.TextBox> getBoxesForSelection(TextSelection selection) {
+  List<ui.TextBox> getBoxesForSelection(
+    TextSelection selection, {
+    ui.BoxHeightStyle boxHeightStyle = ui.BoxHeightStyle.tight,
+    ui.BoxWidthStyle boxWidthStyle = ui.BoxWidthStyle.tight,
+  }) {
     if (text.getSpanForPosition(selection.base) is WidgetSpan) {
       return <ui.TextBox>[];
     }
-    return super.getBoxesForSelection(selection);
+    return super.getBoxesForSelection(
+      selection,
+      boxHeightStyle: boxHeightStyle,
+      boxWidthStyle: boxWidthStyle,
+    );
   }
 }
 
@@ -125,6 +141,93 @@
     expect(boxes.any((ui.TextBox box) => box.right == 100 && box.top == 10), isTrue);
   }, skip: isBrowser); // https://github.com/flutter/flutter/issues/61016
 
+  test('getBoxesForSelection test with multiple TextSpans and lines', () {
+    final RenderParagraph paragraph = RenderParagraph(
+      const TextSpan(
+        text: 'First ',
+        style: TextStyle(fontFamily: 'Ahem', fontSize: 10.0),
+        children: <InlineSpan>[
+          TextSpan(text: 'smallsecond ', style: TextStyle(fontSize: 5.0)),
+          TextSpan(text: 'third fourth fifth'),
+        ],
+      ),
+      textDirection: TextDirection.ltr,
+    );
+    // Do layout with width chosen so that this splits as
+    // First smallsecond |
+    // third fourth |
+    // fifth|
+    // The corresponding line widths come out to be:
+    // 1st line: 120px wide: 6 chars * 10px plus 12 chars * 5px.
+    // 2nd line: 130px wide: 13 chars * 10px.
+    // 3rd line: 50px wide.
+    layout(paragraph, constraints: const BoxConstraints(maxWidth: 140.0));
+
+    final List<ui.TextBox> boxes = paragraph.getBoxesForSelection(
+      const TextSelection(baseOffset: 0, extentOffset: 36),
+    );
+
+    expect(boxes.length, equals(4));
+
+    // The widths of the boxes should match the calculations above.
+    // The heights should all be 10, except for the box for 'smallsecond ',
+    // which should have height 5, and be alphabetic baseline-aligned with
+    // 'First '. The Ahem font specifies alphabetic baselines at 0.2em above the
+    // bottom extent, and 0.8em below the top, so the difference in top
+    // alignment becomes (10px * 0.8 - 5px * 0.8) = 4px.
+
+    // 'First ':
+    expect(boxes[0], const TextBox.fromLTRBD(0.0, 0.0, 60.0, 10.0, TextDirection.ltr));
+    // 'smallsecond ' in size 5:
+    expect(boxes[1], const TextBox.fromLTRBD(60.0, 4.0, 120.0, 9.0, TextDirection.ltr));
+    // 'third fourth ':
+    expect(boxes[2], const TextBox.fromLTRBD(0.0, 10.0, 130.0, 20.0, TextDirection.ltr));
+    // 'fifth':
+    expect(boxes[3], const TextBox.fromLTRBD(0.0, 20.0, 50.0, 30.0, TextDirection.ltr));
+  }, skip: !isLinux); // mac typography values can differ
+
+  test('getBoxesForSelection test with boxHeightStyle and boxWidthStyle set to max', () {
+    final RenderParagraph paragraph = RenderParagraph(
+      const TextSpan(
+        text: 'First ',
+        style: TextStyle(fontFamily: 'Ahem', fontSize: 10.0),
+        children: <InlineSpan>[
+          TextSpan(text: 'smallsecond ', style: TextStyle(fontSize: 8.0)),
+          TextSpan(text: 'third fourth fifth'),
+        ],
+      ),
+      textDirection: TextDirection.ltr,
+    );
+    // Do layout with width chosen so that this splits as
+    // First smallsecond |
+    // third fourth |
+    // fifth|
+    // The corresponding line widths come out to be:
+    // 1st line: 156px wide: 6 chars * 10px plus 12 chars * 8px.
+    // 2nd line: 130px wide: 13 chars * 10px.
+    // 3rd line: 50px wide.
+    layout(paragraph, constraints: const BoxConstraints(maxWidth: 160.0));
+
+    final List<ui.TextBox> boxes = paragraph.getBoxesForSelection(
+      const TextSelection(baseOffset: 0, extentOffset: 36),
+      boxHeightStyle: ui.BoxHeightStyle.max,
+      boxWidthStyle: ui.BoxWidthStyle.max,
+    );
+
+    expect(boxes.length, equals(5));
+
+    // 'First ':
+    expect(boxes[0], const TextBox.fromLTRBD(0.0, 0.0, 60.0, 10.0, TextDirection.ltr));
+    // 'smallsecond ' in size 8, but on same line as previous box, so height remains 10:
+    expect(boxes[1], const TextBox.fromLTRBD(60.0, 0.0, 156.0, 10.0, TextDirection.ltr));
+    // 'third fourth ':
+    expect(boxes[2], const TextBox.fromLTRBD(0.0, 10.0, 130.0, 20.0, TextDirection.ltr));
+    // extra box added to extend width, as per definition of ui.BoxWidthStyle.max:
+    expect(boxes[3], const TextBox.fromLTRBD(130.0, 10.0, 156.0, 20.0, TextDirection.ltr));
+    // 'fifth':
+    expect(boxes[4], const TextBox.fromLTRBD(0.0, 20.0, 50.0, 30.0, TextDirection.ltr));
+  }, skip: isBrowser); // https://github.com/flutter/flutter/issues/61016
+
   test('getWordBoundary control test', () {
     final RenderParagraph paragraph = RenderParagraph(
       const TextSpan(text: _kText),
@@ -419,6 +522,47 @@
     expect(boxes[4], const TextBox.fromLTRBD(48.0, 0.0, 62.0, 14.0, TextDirection.ltr));
   }, skip: isBrowser); // https://github.com/flutter/flutter/issues/61020
 
+  test('getBoxesForSelection with boxHeightStyle for inline widgets', () {
+    const TextSpan text = TextSpan(
+      text: 'a',
+      style: TextStyle(fontSize: 10.0),
+      children: <InlineSpan>[
+        WidgetSpan(child: SizedBox(width: 21, height: 21)),
+        WidgetSpan(child: SizedBox(width: 21, height: 21)),
+        TextSpan(text: 'a'),
+        WidgetSpan(child: SizedBox(width: 21, height: 21)),
+      ],
+    );
+    // Fake the render boxes that correspond to the WidgetSpans. We use
+    // RenderParagraph to reduce the dependencies this test has. The dimensions
+    // of these get used in place of the widths and heights specified in the
+    // SizedBoxes above: each comes out as (w,h) = (14,14).
+    final List<RenderBox> renderBoxes = <RenderBox>[
+      RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr),
+      RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr),
+      RenderParagraph(const TextSpan(text: 'b'), textDirection: TextDirection.ltr),
+    ];
+
+    final RenderParagraph paragraph = RenderParagraph(
+      text,
+      textDirection: TextDirection.ltr,
+      children: renderBoxes,
+    );
+    layout(paragraph, constraints: const BoxConstraints(maxWidth: 100.0));
+
+    final List<ui.TextBox> boxes = paragraph.getBoxesForSelection(
+      const TextSelection(baseOffset: 0, extentOffset: 8),
+      boxHeightStyle: ui.BoxHeightStyle.max,
+    );
+
+    expect(boxes.length, equals(5));
+    expect(boxes[0], const TextBox.fromLTRBD(0.0, 0.0, 10.0, 14.0, TextDirection.ltr));
+    expect(boxes[1], const TextBox.fromLTRBD(10.0, 0.0, 24.0, 14.0, TextDirection.ltr));
+    expect(boxes[2], const TextBox.fromLTRBD(24.0, 0.0, 38.0, 14.0, TextDirection.ltr));
+    expect(boxes[3], const TextBox.fromLTRBD(38.0, 0.0, 48.0, 14.0, TextDirection.ltr));
+    expect(boxes[4], const TextBox.fromLTRBD(48.0, 0.0, 62.0, 14.0, TextDirection.ltr));
+  }, skip: isBrowser); // https://github.com/flutter/flutter/issues/61020
+
   test('can compute IntrinsicHeight for widget span', () {
     // Regression test for https://github.com/flutter/flutter/issues/59316
     const double screenWidth = 100.0;