Only stop search for upstream character if we hit a newline (#86960)
diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index e0baa54..1553bfc 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart
@@ -690,6 +690,9 @@ if (prevCodeUnit == null) return null; + // If the upstream character is a newline, cursor is at start of next line + const int NEWLINE_CODE_UNIT = 10; + // Check for multi-code-unit glyphs such as emojis or zero width joiner. final bool needsSearch = _isUtf16Surrogate(prevCodeUnit) || _text!.codeUnitAt(offset) == _zwjUtf16 || _isUnicodeDirectionality(prevCodeUnit); int graphemeClusterLength = needsSearch ? 2 : 1; @@ -703,7 +706,7 @@ if (boxes.isEmpty) { // When we are at the beginning of the line, a non-surrogate position will // return empty boxes. We break and try from downstream instead. - if (!needsSearch) { + if (!needsSearch && prevCodeUnit == NEWLINE_CODE_UNIT) { break; // Only perform one iteration if no search is required. } if (prevRuneOffset < -flattenedText.length) { @@ -718,8 +721,6 @@ } final TextBox box = boxes.first; - // If the upstream character is a newline, cursor is at start of next line - const int NEWLINE_CODE_UNIT = 10; if (prevCodeUnit == NEWLINE_CODE_UNIT) { return Rect.fromLTRB(_emptyOffset.dx, box.bottom, _emptyOffset.dx, box.bottom + box.bottom - box.top); }
diff --git a/packages/flutter/test/painting/text_painter_test.dart b/packages/flutter/test/painting/text_painter_test.dart index 542a56d..2d55829 100644 --- a/packages/flutter/test/painting/text_painter_test.dart +++ b/packages/flutter/test/painting/text_painter_test.dart
@@ -8,6 +8,9 @@ import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; +const bool isCanvasKit = + bool.fromEnvironment('FLUTTER_WEB_USE_SKIA', defaultValue: false); + void main() { test('TextPainter caret test', () { final TextPainter painter = TextPainter() @@ -123,7 +126,7 @@ expect(caretOffset.dx, 98); // <medium skin tone modifier> caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 23), ui.Rect.zero); expect(caretOffset.dx, 126); // end of string - }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56308 + }, skip: isBrowser && !isCanvasKit); // https://github.com/flutter/flutter/issues/56308 test('TextPainter caret center space test', () { final TextPainter painter = TextPainter() @@ -145,7 +148,7 @@ expect(caretOffset.dx, 35); caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 2), ui.Rect.zero); expect(caretOffset.dx, 49); - }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56308 + }, skip: isBrowser && !isCanvasKit); // https://github.com/flutter/flutter/issues/56308 test('TextPainter error test', () { final TextPainter painter = TextPainter(textDirection: TextDirection.ltr); @@ -743,7 +746,7 @@ expect(painter.inlinePlaceholderBoxes![11], const TextBox.fromLTRBD(250, 30, 300, 60, TextDirection.ltr)); expect(painter.inlinePlaceholderBoxes![12], const TextBox.fromLTRBD(300, 30, 351, 60, TextDirection.ltr)); expect(painter.inlinePlaceholderBoxes![13], const TextBox.fromLTRBD(351, 30, 401, 60, TextDirection.ltr)); - }, skip: isBrowser); // https://github.com/flutter/flutter/issues/42086 + }, skip: isBrowser && !isCanvasKit); // https://github.com/flutter/flutter/issues/42086 // Null values are valid. See https://github.com/flutter/flutter/pull/48346#issuecomment-584839221 test('TextPainter set TextHeightBehavior null test', () { @@ -832,7 +835,7 @@ ui.Rect.zero, )!; expect(caretHeight, 50.0); - }, skip: isBrowser); // https://github.com/flutter/flutter/issues/56308 + }, skip: isBrowser && !isCanvasKit); // https://github.com/flutter/flutter/issues/56308 group('TextPainter line-height', () { test('half-leading', () { @@ -952,7 +955,7 @@ ).first.toRect(); expect(glyphBox, newGlyphBox); }); - }, skip: isBrowser); + }, skip: isBrowser && !isCanvasKit); test('TextPainter handles invalid UTF-16', () { Object? exception; @@ -971,6 +974,22 @@ expect(painter.width, equals(fontSize)); expect(exception, isNotNull); }, skip: kIsWeb); + + test('Diacritic', () { + final TextPainter painter = TextPainter() + ..textDirection = TextDirection.ltr; + + // Two letters followed by a diacritic + const String text = 'ฟห้'; + painter.text = const TextSpan(text: text); + painter.layout(); + + final ui.Offset caretOffset = painter.getOffsetForCaret( + const ui.TextPosition( + offset: text.length, affinity: TextAffinity.upstream), + ui.Rect.zero); + expect(caretOffset.dx, painter.width); + }, skip: kIsWeb && !isCanvasKit); } class MockCanvas extends Fake implements Canvas {