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;