@@ -409,7 +409,13 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin<RenderBo
409409 if (end == - 1 ) {
410410 end = plainText.length;
411411 }
412- result.add (_SelectableFragment (paragraph: this , range: TextRange (start: start, end: end), fullText: plainText));
412+ result.add (
413+ _SelectableFragment (
414+ paragraph: this ,
415+ range: TextRange (start: start, end: end),
416+ fullText: plainText,
417+ ),
418+ );
413419 start = end;
414420 }
415421 start += 1 ;
@@ -1314,7 +1320,7 @@ class RenderParagraph extends RenderBox with ContainerRenderObjectMixin<RenderBo
13141320/// [PlaceholderSpan] . The [RenderParagraph] splits itself on [PlaceholderSpan]
13151321/// to create multiple `_SelectableFragment` s so that they can be selected
13161322/// separately.
1317- class _SelectableFragment with Selectable , ChangeNotifier implements TextLayoutMetrics {
1323+ class _SelectableFragment with Selectable , Diagnosticable , ChangeNotifier implements TextLayoutMetrics {
13181324 _SelectableFragment ({
13191325 required this .paragraph,
13201326 required this .fullText,
@@ -1366,7 +1372,6 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
13661372 ? startOffsetInParagraphCoordinates
13671373 : paragraph._getOffsetForPosition (TextPosition (offset: selectionEnd));
13681374 final bool flipHandles = isReversed != (TextDirection .rtl == paragraph.textDirection);
1369- final Matrix4 paragraphToFragmentTransform = getTransformToParagraph ()..invert ();
13701375 final TextSelection selection = TextSelection (
13711376 baseOffset: selectionStart,
13721377 extentOffset: selectionEnd,
@@ -1377,12 +1382,12 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
13771382 }
13781383 return SelectionGeometry (
13791384 startSelectionPoint: SelectionPoint (
1380- localPosition: MatrixUtils . transformPoint (paragraphToFragmentTransform, startOffsetInParagraphCoordinates) ,
1385+ localPosition: startOffsetInParagraphCoordinates,
13811386 lineHeight: paragraph._textPainter.preferredLineHeight,
13821387 handleType: flipHandles ? TextSelectionHandleType .right : TextSelectionHandleType .left
13831388 ),
13841389 endSelectionPoint: SelectionPoint (
1385- localPosition: MatrixUtils . transformPoint (paragraphToFragmentTransform, endOffsetInParagraphCoordinates) ,
1390+ localPosition: endOffsetInParagraphCoordinates,
13861391 lineHeight: paragraph._textPainter.preferredLineHeight,
13871392 handleType: flipHandles ? TextSelectionHandleType .left : TextSelectionHandleType .right,
13881393 ),
@@ -1665,7 +1670,16 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
16651670 // we do not need to look up the word boundary for that position. This is to
16661671 // maintain a selectables selection collapsed at 0 when the local position is
16671672 // not located inside its rect.
1668- final _WordBoundaryRecord ? wordBoundary = ! _rect.contains (localPosition) ? null : _getWordBoundaryAtPosition (position);
1673+ _WordBoundaryRecord ? wordBoundary = _rect.contains (localPosition) ? _getWordBoundaryAtPosition (position) : null ;
1674+ if (wordBoundary != null
1675+ && (wordBoundary.wordStart.offset < range.start && wordBoundary.wordEnd.offset <= range.start
1676+ || wordBoundary.wordStart.offset >= range.end && wordBoundary.wordEnd.offset > range.end)) {
1677+ // When the position is located at a placeholder inside of the text, then we may compute
1678+ // a word boundary that does not belong to the current selectable fragment. In this case
1679+ // we should invalidate the word boundary so that it is not taken into account when
1680+ // computing the target position.
1681+ wordBoundary = null ;
1682+ }
16691683 final TextPosition targetPosition = _clampTextPosition (isEnd ? _updateSelectionEndEdgeByWord (wordBoundary, position, existingSelectionStart, existingSelectionEnd) : _updateSelectionStartEdgeByWord (wordBoundary, position, existingSelectionStart, existingSelectionEnd));
16701684
16711685 _setSelectionPosition (targetPosition, isEnd: isEnd);
@@ -1717,23 +1731,26 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
17171731 }
17181732
17191733 SelectionResult _handleSelectWord (Offset globalPosition) {
1720- _selectableContainsOriginWord = true ;
1721-
17221734 final TextPosition position = paragraph.getPositionForOffset (paragraph.globalToLocal (globalPosition));
17231735 if (_positionIsWithinCurrentSelection (position) && _textSelectionStart != _textSelectionEnd) {
17241736 return SelectionResult .end;
17251737 }
17261738 final _WordBoundaryRecord wordBoundary = _getWordBoundaryAtPosition (position);
1727- if (wordBoundary.wordStart.offset < range.start && wordBoundary.wordEnd.offset < range.start) {
1739+ // This fragment may not contain the word, decide what direction the target
1740+ // fragment is located in. Because fragments are separated by placeholder
1741+ // spans, we also check if the beginning or end of the word is touching
1742+ // either edge of this fragment.
1743+ if (wordBoundary.wordStart.offset < range.start && wordBoundary.wordEnd.offset <= range.start) {
17281744 return SelectionResult .previous;
1729- } else if (wordBoundary.wordStart.offset > range.end && wordBoundary.wordEnd.offset > range.end) {
1745+ } else if (wordBoundary.wordStart.offset >= range.end && wordBoundary.wordEnd.offset > range.end) {
17301746 return SelectionResult .next;
17311747 }
17321748 // Fragments are separated by placeholder span, the word boundary shouldn't
17331749 // expand across fragments.
17341750 assert (wordBoundary.wordStart.offset >= range.start && wordBoundary.wordEnd.offset <= range.end);
17351751 _textSelectionStart = wordBoundary.wordStart;
17361752 _textSelectionEnd = wordBoundary.wordEnd;
1753+ _selectableContainsOriginWord = true ;
17371754 return SelectionResult .end;
17381755 }
17391756
@@ -1957,13 +1974,9 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
19571974 }
19581975 }
19591976
1960- Matrix4 getTransformToParagraph () {
1961- return Matrix4 .translationValues (_rect.left, _rect.top, 0.0 );
1962- }
1963-
19641977 @override
19651978 Matrix4 getTransformTo (RenderObject ? ancestor) {
1966- return getTransformToParagraph ().. multiply ( paragraph.getTransformTo (ancestor) );
1979+ return paragraph.getTransformTo (ancestor);
19671980 }
19681981
19691982 @override
@@ -1982,6 +1995,28 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
19821995 }
19831996 }
19841997
1998+ List <Rect >? _cachedBoundingBoxes;
1999+ @override
2000+ List <Rect > get boundingBoxes {
2001+ if (_cachedBoundingBoxes == null ) {
2002+ final List <TextBox > boxes = paragraph.getBoxesForSelection (
2003+ TextSelection (baseOffset: range.start, extentOffset: range.end),
2004+ );
2005+ if (boxes.isNotEmpty) {
2006+ _cachedBoundingBoxes = < Rect > [];
2007+ for (final TextBox textBox in boxes) {
2008+ _cachedBoundingBoxes! .add (textBox.toRect ());
2009+ }
2010+ } else {
2011+ final Offset offset = paragraph._getOffsetForPosition (TextPosition (offset: range.start));
2012+ final Rect rect = Rect .fromPoints (offset, offset.translate (0 , - paragraph._textPainter.preferredLineHeight));
2013+ _cachedBoundingBoxes = < Rect > [rect];
2014+ }
2015+ }
2016+ return _cachedBoundingBoxes! ;
2017+ }
2018+
2019+ Rect ? _cachedRect;
19852020 Rect get _rect {
19862021 if (_cachedRect == null ) {
19872022 final List <TextBox > boxes = paragraph.getBoxesForSelection (
@@ -2000,7 +2035,6 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
20002035 }
20012036 return _cachedRect! ;
20022037 }
2003- Rect ? _cachedRect;
20042038
20052039 void didChangeParagraphLayout () {
20062040 _cachedRect = null ;
@@ -2028,12 +2062,11 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
20282062 textBox.toRect ().shift (offset), selectionPaint);
20292063 }
20302064 }
2031- final Matrix4 transform = getTransformToParagraph ();
20322065 if (_startHandleLayerLink != null && value.startSelectionPoint != null ) {
20332066 context.pushLayer (
20342067 LeaderLayer (
20352068 link: _startHandleLayerLink! ,
2036- offset: offset + MatrixUtils . transformPoint (transform, value.startSelectionPoint! .localPosition) ,
2069+ offset: offset + value.startSelectionPoint! .localPosition,
20372070 ),
20382071 (PaintingContext context, Offset offset) { },
20392072 Offset .zero,
@@ -2043,7 +2076,7 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
20432076 context.pushLayer (
20442077 LeaderLayer (
20452078 link: _endHandleLayerLink! ,
2046- offset: offset + MatrixUtils . transformPoint (transform, value.endSelectionPoint! .localPosition) ,
2079+ offset: offset + value.endSelectionPoint! .localPosition,
20472080 ),
20482081 (PaintingContext context, Offset offset) { },
20492082 Offset .zero,
@@ -2071,4 +2104,12 @@ class _SelectableFragment with Selectable, ChangeNotifier implements TextLayoutM
20712104
20722105 @override
20732106 TextRange getWordBoundary (TextPosition position) => paragraph.getWordBoundary (position);
2107+
2108+ @override
2109+ void debugFillProperties (DiagnosticPropertiesBuilder properties) {
2110+ super .debugFillProperties (properties);
2111+ properties.add (DiagnosticsProperty <String >('textInsideRange' , range.textInside (fullText)));
2112+ properties.add (DiagnosticsProperty <TextRange >('range' , range));
2113+ properties.add (DiagnosticsProperty <String >('fullText' , fullText));
2114+ }
20742115}
0 commit comments