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 {