Repository: flutter
Issue: WidgetSpan wrong caret position
Labels: a: text input,framework,f: material design,has reproducible steps,P2,team-text-input,triaged-text-input,found in release: 3.27,found in release: 3.28

### Steps to reproduce

I'm trying to make TextField which supports custom tag. And I would like to delete a whole tag via backspace, so I use UTF16 private use area( such as U+F0000~U+F0002) to be recognized one character.
But I have the bellow problem. It seems that the caret shows wrong position from its actual position. Do you have any idea to resolve it? Thanks. 

1. Open code sample scaffold
2. Tap "Tag1"
3. Tap "Tag2"
4. Tap "Tag3"
5. On the software keyboard, pressing space and move caret to between Tag2 and Tag3
6. On the software keyboard, tap backspace button

I debug showing caret position and found out sometimes my tag is recognized 2 characters, but sometimes my tag is recognized just 1 character. I guess this is something wrong. But the way how to fix doesn't come up with me.

### Expected results

"Tag2" is deleted because the caret is just after "Tag2".

### Actual results

"Tag1" is deleted.

### Code sample

<details open><summary>Code sample</summary>

```dart
import 'package:flutter/material.dart';

class ReproduceScreen extends StatefulWidget {
  @override
  State createState() => ReproduceScreenState();
}

class ReproduceScreenState extends State<ReproduceScreen> {
  final TagEditingController _controller = TagEditingController();
  int _caretPosition = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("Tag textfield"),
      ),
      body: Column(
        children: [
          TextField(
            controller: _controller,
          ),
          Row(
            children: [
              TextButton(
                  onPressed: () => onTagTapped(_tags[0].runes),
                  child: Text(_tags[0].name)),
              TextButton(
                  onPressed: () => onTagTapped(_tags[1].runes),
                  child: Text(_tags[1].name)),
              TextButton(
                  onPressed: () => onTagTapped(_tags[2].runes),
                  child: Text(_tags[2].name)),
            ],
          ),
          TextButton(
              onPressed: () {
                final offset = _controller.selection.base.offset;
                setState(() {
                  _caretPosition = offset;
                });
              }, child: Text("Update caret position")),
          Text("Caret position: $_caretPosition")
        ],
      ),
    );
  }

  void onTagTapped(int runes) {
    final offset = _controller.selection.base.offset;
    print(offset);
    // final part1 = _controller.text.substring(0,offset);
    // final part2 = _controller.text.substring(offset);
    final tagChar = String.fromCharCode(runes);
    // print("part1: $part1, part2: $part2");
    // _controller.text = "$part1$tagChar$part2";
    _controller.text = "${_controller.text}$tagChar";
  }

}

class TagEditingController extends TextEditingController {
  @override
  TextSpan buildTextSpan({required BuildContext context,
    TextStyle? style,
    required bool withComposing}) {
    final List<InlineSpan> spans = [];
    text.runes.forEach((int rune) {
      final char = String.fromCharCode(rune);
      final foundIndex = _tags.indexWhere((e) => e.runes == rune);
      if (foundIndex != -1) {
        final tag = _tags[foundIndex];
        spans.add(createTagSpan(tag));
      } else {
        spans.add(TextSpan(text: char, style: TextStyle(color: Colors.black)));
      }
    });

    return TextSpan(children: spans);
  }

  WidgetSpan createTagSpan(Tag tag) {
    return WidgetSpan(
        alignment: PlaceholderAlignment.middle,
        child: Container(
          padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8),
          margin: EdgeInsets.symmetric(horizontal: 2),
          decoration: BoxDecoration(
            color: Colors.deepOrangeAccent,
            borderRadius: BorderRadius.circular(4),
          ),
          child: IntrinsicWidth(
            child: Text(
              tag.name,
            ),
          ),
        ));
  }
}

class Tag {
  int runes;
  String name;

  Tag(this.runes, this.name);
}

final _tags = [
  Tag(0xF0000, "Tag1"),
  Tag(0xF0001, "Tag2"),
  Tag(0xF0002, "Tag3"),
];


```

</details>


### Screenshots or Video

<details open>
<summary>Screenshots / Video demonstration</summary>

[Upload media here]

https://github.com/user-attachments/assets/7b9cf275-a04d-41c2-aca6-33c523056434



</details>


### Logs

<details open><summary>Logs</summary>

```console
No any special log.
```

</details>


### Flutter Doctor output

<details open><summary>Doctor output</summary>

```console
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel stable, 3.27.1, on macOS 14.5 23F79 darwin-arm64 (Rosetta), locale en-JP)
[✓] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[✓] Xcode - develop for iOS and macOS (Xcode 15.4)
[✓] Chrome - develop for the web
[✓] Android Studio (version 2021.2)
[✓] Android Studio (version 2023.1)
[✓] IntelliJ IDEA Ultimate Edition (version 2019.1.3)
[✓] VS Code (version 1.96.2)
[✓] Connected device (6 available)
    ! Error: Browsing on the local area network for iPhone. Ensure the device is unlocked and attached with a cable or associated with the same local area network as this Mac.
      The device must be opted into Developer Mode to connect wirelessly. (code -27)
    ! Error: Browsing on the local area network for TakashiのiPad. Ensure the device is unlocked and attached with a cable or associated with the same local area netwok as this Mac.
      The device must be opted into Developer Mode to connect wirelessly. (code -27)
[✓] Network resources

• No issues found!

```

</details>