diff --git a/packages/flutter/lib/src/painting/placeholder_span.dart b/packages/flutter/lib/src/painting/placeholder_span.dart index 4b570abd3f..1f0c0cbbbf 100644 --- a/packages/flutter/lib/src/painting/placeholder_span.dart +++ b/packages/flutter/lib/src/painting/placeholder_span.dart @@ -41,6 +41,9 @@ abstract class PlaceholderSpan extends InlineSpan { TextStyle? style, }) : super(style: style); + /// The unicode character to represent a placeholder. + static const String placeholderCodeUnit = '\uFFFC'; + /// How the placeholder aligns vertically with the text. /// /// See [ui.PlaceholderAlignment] for details on each mode. @@ -57,7 +60,7 @@ abstract class PlaceholderSpan extends InlineSpan { @override void computeToPlainText(StringBuffer buffer, {bool includeSemanticsLabels = true, bool includePlaceholders = true}) { if (includePlaceholders) { - buffer.write('\uFFFC'); + buffer.write(placeholderCodeUnit); } } diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index 78b849e4fa..690a96c19c 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart @@ -739,7 +739,7 @@ class TextPainter { // Get the Rect of the cursor (in logical pixels) based off the near edge // of the character upstream from the given string offset. Rect? _getRectFromUpstream(int offset, Rect caretPrototype) { - final String flattenedText = _text!.toPlainText(includePlaceholders: false); + final String flattenedText = _text!.toPlainText(includeSemanticsLabels: false); final int? prevCodeUnit = _text!.codeUnitAt(max(0, offset - 1)); if (prevCodeUnit == null) return null; @@ -789,7 +789,7 @@ class TextPainter { // Get the Rect of the cursor (in logical pixels) based off the near edge // of the character downstream from the given string offset. Rect? _getRectFromDownstream(int offset, Rect caretPrototype) { - final String flattenedText = _text!.toPlainText(includePlaceholders: false); + final String flattenedText = _text!.toPlainText(includeSemanticsLabels: false); // We cap the offset at the final index of the _text. final int? nextCodeUnit = _text!.codeUnitAt(min(offset, flattenedText.length - 1)); if (nextCodeUnit == null) diff --git a/packages/flutter/lib/src/widgets/widget_span.dart b/packages/flutter/lib/src/widgets/widget_span.dart index 0261377568..686bf7779c 100644 --- a/packages/flutter/lib/src/widgets/widget_span.dart +++ b/packages/flutter/lib/src/widgets/widget_span.dart @@ -139,7 +139,8 @@ class WidgetSpan extends PlaceholderSpan { @override int? codeUnitAtVisitor(int index, Accumulator offset) { - return null; + offset.increment(1); + return PlaceholderSpan.placeholderCodeUnit.codeUnitAt(0); } @override diff --git a/packages/flutter/test/painting/text_painter_test.dart b/packages/flutter/test/painting/text_painter_test.dart index 7afa967a4d..c27e443946 100644 --- a/packages/flutter/test/painting/text_painter_test.dart +++ b/packages/flutter/test/painting/text_painter_test.dart @@ -37,6 +37,24 @@ void main() { expect(caretOffset.dx, painter.width); }); + test('TextPainter caret test with WidgetSpan', () { + // Regression test for https://github.com/flutter/flutter/issues/98458. + final TextPainter painter = TextPainter() + ..textDirection = TextDirection.ltr; + + painter.text = const TextSpan(children: [ + TextSpan(text: 'before'), + WidgetSpan(child: Text('widget')), + TextSpan(text: 'after'), + ]); + painter.setPlaceholderDimensions(const [ + PlaceholderDimensions(size: Size(50, 30), baselineOffset: 25, alignment: ui.PlaceholderAlignment.bottom), + ]); + painter.layout(); + final Offset caretOffset = painter.getOffsetForCaret(ui.TextPosition(offset: painter.text!.toPlainText().length), ui.Rect.zero); + expect(caretOffset.dx, painter.width); + }, skip: isBrowser && !isCanvasKit); // https://github.com/flutter/flutter/issues/56308 + test('TextPainter null text test', () { final TextPainter painter = TextPainter() ..textDirection = TextDirection.ltr;