Use full textspan tree instead of top level textspan (#25574)

diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart
index 3af0629..906fc92 100644
--- a/packages/flutter/lib/src/painting/text_painter.dart
+++ b/packages/flutter/lib/src/painting/text_painter.dart
@@ -420,6 +420,7 @@
   // TODO(garyq): Use actual extended grapheme cluster length instead of
   // an increasing cluster length amount to achieve deterministic performance.
   Offset _getOffsetFromUpstream(int offset, Rect caretPrototype) {
+    final String flattenedText = _text.toPlainText();
     final int prevCodeUnit = _text.codeUnitAt(max(0, offset - 1));
     if (prevCodeUnit == null)
       return null;
@@ -427,7 +428,7 @@
     final bool needsSearch = _isUtf16Surrogate(prevCodeUnit) || _text.codeUnitAt(offset) == _zwjUtf16;
     int graphemeClusterLength = needsSearch ? 2 : 1;
     List<TextBox> boxes = <TextBox>[];
-    while (boxes.isEmpty && _text.text != null) {
+    while (boxes.isEmpty && flattenedText != null) {
       final int prevRuneOffset = offset - graphemeClusterLength;
       boxes = _paragraph.getBoxesForRange(prevRuneOffset, offset);
       // When the range does not include a full cluster, no boxes will be returned.
@@ -436,7 +437,7 @@
         // return empty boxes. We break and try from downstream instead.
         if (!needsSearch)
           break; // Only perform one iteration if no search is required.
-        if (prevRuneOffset < -_text.text.length)
+        if (prevRuneOffset < -flattenedText.length)
           break; // Stop iterating when beyond the max length of the text.
         // Multiply by two to log(n) time cover the entire text span. This allows
         // faster discovery of very long clusters and reduces the possibility
@@ -445,7 +446,7 @@
         graphemeClusterLength *= 2;
         continue;
       }
-      final TextBox box = boxes[0];
+      final TextBox box = boxes.first;
       final double caretEnd = box.end;
       final double dx = box.direction == TextDirection.rtl ? caretEnd - caretPrototype.width : caretEnd;
       return Offset(dx, box.top);
@@ -456,15 +457,16 @@
   // TODO(garyq): Use actual extended grapheme cluster length instead of
   // an increasing cluster length amount to achieve deterministic performance.
   Offset _getOffsetFromDownstream(int offset, Rect caretPrototype) {
+    final String flattenedText = _text.toPlainText();
     // We cap the offset at the final index of the _text.
-    final int nextCodeUnit = _text.codeUnitAt(min(offset, _text.text == null ? 0 : _text.text.length - 1));
+    final int nextCodeUnit = _text.codeUnitAt(min(offset, flattenedText == null ? 0 : flattenedText.length - 1));
     if (nextCodeUnit == null)
       return null;
     // Check for multi-code-unit glyphs such as emojis or zero width joiner
     final bool needsSearch = _isUtf16Surrogate(nextCodeUnit) || nextCodeUnit == _zwjUtf16;
     int graphemeClusterLength = needsSearch ? 2 : 1;
     List<TextBox> boxes = <TextBox>[];
-    while (boxes.isEmpty && _text.text != null) {
+    while (boxes.isEmpty && flattenedText != null) {
       final int nextRuneOffset = offset + graphemeClusterLength;
       boxes = _paragraph.getBoxesForRange(offset, nextRuneOffset);
       // When the range does not include a full cluster, no boxes will be returned.
@@ -473,7 +475,7 @@
         // return empty boxes. We break and try from upstream instead.
         if (!needsSearch)
           break; // Only perform one iteration if no search is required.
-        if (nextRuneOffset >= _text.text.length << 1)
+        if (nextRuneOffset >= flattenedText.length << 1)
           break; // Stop iterating when beyond the max length of the text.
         // Multiply by two to log(n) time cover the entire text span. This allows
         // faster discovery of very long clusters and reduces the possibility
@@ -482,7 +484,7 @@
         graphemeClusterLength *= 2;
         continue;
       }
-      final TextBox box = boxes[0];
+      final TextBox box = boxes.last;
       final double caretStart = box.start;
       final double dx = box.direction == TextDirection.rtl ? caretStart - caretPrototype.width : caretStart;
       return Offset(dx, box.top);
diff --git a/packages/flutter/test/painting/text_painter_test.dart b/packages/flutter/test/painting/text_painter_test.dart
index 06c5ded..d2105e8 100644
--- a/packages/flutter/test/painting/text_painter_test.dart
+++ b/packages/flutter/test/painting/text_painter_test.dart
@@ -29,6 +29,31 @@
     expect(caretOffset.dx, painter.width);
   });
 
+  test('TextPainter null text test', () {
+    final TextPainter painter = TextPainter()
+      ..textDirection = TextDirection.ltr;
+
+    List<TextSpan> children = <TextSpan>[const TextSpan(text: 'B'), const TextSpan(text: 'C')];
+    painter.text = TextSpan(text: null, children: children);
+    painter.layout();
+
+    Offset caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 0), ui.Rect.zero);
+    expect(caretOffset.dx, 0);
+    caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 1), ui.Rect.zero);
+    expect(caretOffset.dx, painter.width / 2);
+    caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 2), ui.Rect.zero);
+    expect(caretOffset.dx, painter.width);
+
+    children = <TextSpan>[];
+    painter.text = TextSpan(text: null, children: children);
+    painter.layout();
+
+    caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 0), ui.Rect.zero);
+    expect(caretOffset.dx, 0);
+    caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 1), ui.Rect.zero);
+    expect(caretOffset.dx, 0);
+  });
+
   test('TextPainter caret emoji test', () {
     final TextPainter painter = TextPainter()
       ..textDirection = TextDirection.ltr;