Repository: flutter
Issue: [web] Flutter web can't handle `0xFE0F` (Unicode Variation Selectors)
Labels: engine,a: typography,platform-web,e: web_canvaskit,has reproducible steps,P2,e: web_skwasm,team-web,triaged-web

While testing some Emoji rendering (using `package:emoji`) I noticed that in some frames, we fire a "Could not find a set of Noto fonts to display all missing characters":

<img width="1374" alt="Screenshot 2024-10-28 at 5 48 10 PM" src="https://github.com/user-attachments/assets/9f10c182-3030-46bd-962c-405764be05e3">

The problem is that some emoji contain a `0xFE0F Variation Selector`[^1] that Flutter web is not handling correctly:

<img width="1488" alt="Screenshot 2024-10-28 at 5 57 12 PM" src="https://github.com/user-attachments/assets/b6d86222-c9c1-49b3-a19d-e43468940e35">

The "keycap" emoji series, for example: #️⃣ (`0x0023, 0xFE0F, 0x20E3`) look completely broken because they contain `0xFE0F` in the middle of the sequence:

| Actual | Expected |
|--------|----------|
| <img width="183" alt="Screenshot 2024-10-29 at 4 32 03 PM" src="https://github.com/user-attachments/assets/88ad0c04-ccf0-4191-907d-3ba9c0365968"> | <img width="197" alt="Screenshot 2024-10-29 at 4 33 39 PM" src="https://github.com/user-attachments/assets/715434e6-9782-4245-9141-a6e63b34558b"> |

### Impact

As of October 2024 (Emoji v16.0), there's ~220 emoji in the basic set[^2], and ~1000 in the ZWJ Sequences[^3] that contain `0xFE0F` and are affected by this bug. Those that contain `0xFE0F` in the middle of their sequence will render as separate emojis, instead of a single, combined one.

This is probably affecting other Unicode symbols, because the Variation Selectors are used across other parts of Unicode. From the wikipedia link:

> As of Unicode 13.0:
>
> * [CJK compatibility ideograph](https://en.wikipedia.org/wiki/CJK_Compatibility_Ideographs) variation sequences contain VS1–VS3 (U+FE00–U+FE02)
> * [CJK Unified Ideographs Extension A](https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_Extension_A) and [B](https://en.wikipedia.org/wiki/CJK_Unified_Ideographs_Extension_B) variation sequences contain VS1 (U+FE00) and VS2 (U+FE01)
> * **Emoji variation sequences contain VS16 (U+FE0F) for emoji-style (with color)  <sup><sub>(⬅ this bug!)</sub></sup>** or VS15 (U+FE0E) for text style (monochrome)
> * [Basic Latin](https://en.wikipedia.org/wiki/Basic_Latin_(Unicode_block)), [Halfwidth and Fullwidth Forms](https://en.wikipedia.org/wiki/Halfwidth_and_fullwidth_forms), [Manichaean](https://en.wikipedia.org/wiki/Manichaean_(Unicode_block)), [Myanmar](https://en.wikipedia.org/wiki/Myanmar_(Unicode_block)), [Myanmar Extended-A](https://en.wikipedia.org/wiki/Myanmar_Extended-A), [Phags-pa](https://en.wikipedia.org/wiki/Phags-pa_(Unicode_block)), and mathematical variation sequences contain only VS1 (U+FE00)
> * [Egyptian Hieroglyphs](https://en.wikipedia.org/wiki/Egyptian_Hieroglyphs_(Unicode_block)#Standardized_variants) variation sequences VS1–VS4 and VS7 (U+FE00–FE03, and FE06) are used to rotate specific signs
VS5, VS6, and VS8–VS14 (U+FE04, FE05, and FE07–FE0D) are not used for any variation sequences


[^1]: https://en.wikipedia.org/wiki/Variation_Selectors_(Unicode_block)
[^2]: https://unicode.org/Public/emoji/latest/emoji-sequences.txt
[^3]: https://unicode.org/Public/emoji/latest/emoji-zwj-sequences.txt

### Reproduction

Add any emoji that contains `0xfe0f`, for example `keycap: \x{23}` :

```dart
Text(
  String.fromCharCodes(<int>[0x0023, 0xFE0F, 0x20E3]), // keycap: \x{23}
),
```

Observe that the emoji looks broken, and there's the following warning on the JS console:

<details>

<summary>⚠️ Could not find a set of Noto fonts to display all missing characters. Please add a font asset for the missing characters. See: https://flutter.dev/docs/cookbook/design/fonts</summary>

```
...
findFontsForMissingCodePoints @ dart_sdk.js:162047
[_ensureFallbackFonts] @ dart_sdk.js:161989
...
addMissingCodePoints @ dart_sdk.js:161960
ensureFontsSupportText @ dart_sdk.js:161953
addText @ dart_sdk.js:159365
build @ placeholder_span.dart.js:1782
[_createParagraph] @ placeholder_span.dart.js:3362
...
```
</details>

---

<details>
<summary>Full repro <tt>main.dart</tt></summary>

```dart
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Flutter Demo',
      home: EmojiRepro(),
    );
  }
}

class EmojiRepro extends StatelessWidget {
  const EmojiRepro({super.key});
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            // Causes a warning in the console, and emoji looks broken!
            Text(
              String.fromCharCodes(<int>[0x0023, 0xFE0F, 0x20E3]), // keycap: \x{23}
              style: Theme.of(context).textTheme.displayLarge,
            ),
          ],
        ),
      ),
    );
  }
}
```

</details>

(Initially seen in #157763)