diff --git a/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart b/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart index c933f750b790e331a1c166c3c14093539217eab6..f566f08bb2e0a5e7afcd071abde9a27c9976f22e 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/font_fallbacks.dart @@ -126,7 +126,8 @@ class FontFallbackData { fonts.addAll(typefacesForFamily); } } - final List codeUnitsSupported = List.filled(codeUnits.length, false); + final List codeUnitsSupported = + List.filled(codeUnits.length, false); final String testString = String.fromCharCodes(codeUnits); for (SkFont font in fonts) { final Uint8List glyphs = font.getGlyphIDs(testString); @@ -167,11 +168,13 @@ class FontFallbackData { // fonts. Check them and update the cache. final List codeUnits = _codeUnitsToCheckAgainstFallbackFonts.toList(); _codeUnitsToCheckAgainstFallbackFonts.clear(); - final List codeUnitsSupported = List.filled(codeUnits.length, false); + final List codeUnitsSupported = + List.filled(codeUnits.length, false); final String testString = String.fromCharCodes(codeUnits); for (String font in globalFontFallbacks) { - final List? fontsForFamily = skiaFontCollection.familyToFontMap[font]; + final List? fontsForFamily = + skiaFontCollection.familyToFontMap[font]; if (fontsForFamily == null) { printWarning('A fallback font was registered but we ' 'cannot retrieve the typeface for it.'); @@ -227,8 +230,20 @@ class FontFallbackData { final int fontFallbackTag = fontFallbackCounts[family]!; fontFallbackCounts[family] = fontFallbackCounts[family]! + 1; final String countedFamily = '$family $fontFallbackTag'; + // Insert emoji font before all other fallback fonts so we use the emoji + // whenever it's available. registeredFallbackFonts.add(RegisteredFont(bytes, countedFamily, typeface)); - globalFontFallbacks.add(countedFamily); + // Insert emoji font before all other fallback fonts so we use the emoji + // whenever it's available. + if (family == 'Noto Color Emoji Compat') { + if (globalFontFallbacks.first == 'Roboto') { + globalFontFallbacks.insert(1, countedFamily); + } else { + globalFontFallbacks.insert(0, countedFamily); + } + } else { + globalFontFallbacks.add(countedFamily); + } } } @@ -351,14 +366,16 @@ _ResolvedNotoFont? _makeResolvedNotoFontFromCss(String css, String name) { if (startEnd.length == 1) { final String singleRange = startEnd.single; assert(singleRange.startsWith('U+')); - final int rangeValue = int.parse(singleRange.substring(2), radix: 16); + final int rangeValue = + int.parse(singleRange.substring(2), radix: 16); fontFaceUnicodeRanges.add(CodeunitRange(rangeValue, rangeValue)); } else { assert(startEnd.length == 2); final String startRange = startEnd[0]; final String endRange = startEnd[1]; assert(startRange.startsWith('U+')); - final int startValue = int.parse(startRange.substring(2), radix: 16); + final int startValue = + int.parse(startRange.substring(2), radix: 16); final int endValue = int.parse(endRange, radix: 16); fontFaceUnicodeRanges.add(CodeunitRange(startValue, endValue)); } @@ -410,15 +427,15 @@ Future _registerSymbolsAndEmoji() async { return; } data.registeredSymbolsAndEmoji = true; - const String symbolsUrl = - 'https://fonts.googleapis.com/css2?family=Noto+Sans+Symbols'; const String emojiUrl = 'https://fonts.googleapis.com/css2?family=Noto+Color+Emoji+Compat'; + const String symbolsUrl = + 'https://fonts.googleapis.com/css2?family=Noto+Sans+Symbols'; - final String symbolsCss = - await notoDownloadQueue.downloader.downloadAsString(symbolsUrl); final String emojiCss = await notoDownloadQueue.downloader.downloadAsString(emojiUrl); + final String symbolsCss = + await notoDownloadQueue.downloader.downloadAsString(symbolsUrl); String? extractUrlFromCss(String css) { for (final String line in LineSplitter.split(css)) { @@ -436,21 +453,21 @@ Future _registerSymbolsAndEmoji() async { return null; } - final String? symbolsFontUrl = extractUrlFromCss(symbolsCss); final String? emojiFontUrl = extractUrlFromCss(emojiCss); + final String? symbolsFontUrl = extractUrlFromCss(symbolsCss); - if (symbolsFontUrl != null) { + if (emojiFontUrl != null) { notoDownloadQueue.add(_ResolvedNotoSubset( - symbolsFontUrl, 'Noto Sans Symbols', const [])); + emojiFontUrl, 'Noto Color Emoji Compat', const [])); } else { - printWarning('Error parsing CSS for Noto Symbols font.'); + printWarning('Error parsing CSS for Noto Emoji font.'); } - if (emojiFontUrl != null) { + if (symbolsFontUrl != null) { notoDownloadQueue.add(_ResolvedNotoSubset( - emojiFontUrl, 'Noto Color Emoji Compat', const [])); + symbolsFontUrl, 'Noto Sans Symbols', const [])); } else { - printWarning('Error parsing CSS for Noto Emoji font.'); + printWarning('Error parsing CSS for Noto Symbols font.'); } } diff --git a/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart b/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart index c133eca8f3e924b5169bf294cc8d072f9d4aa28e..2214af0bc76c225891acf1bd9c5bea90e18b1b0f 100644 --- a/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart +++ b/lib/web_ui/test/canvaskit/fallback_fonts_golden_test.dart @@ -56,24 +56,6 @@ void testMain() { }); test('will download Noto Naskh Arabic if Arabic text is added', () async { - final Completer fontChangeCompleter = Completer(); - // Intercept the system font change message. - ui.window.onPlatformMessage = (String name, ByteData? data, - ui.PlatformMessageResponseCallback? callback) { - if (name == 'flutter/system') { - const JSONMessageCodec codec = JSONMessageCodec(); - final dynamic message = codec.decodeMessage(data); - if (message is Map) { - if (message['type'] == 'fontsChange') { - fontChangeCompleter.complete(); - } - } - } - if (savedCallback != null) { - savedCallback!(name, data, callback); - } - }; - TestDownloader.mockDownloads[ 'https://fonts.googleapis.com/css2?family=Noto+Naskh+Arabic+UI'] = ''' @@ -98,7 +80,7 @@ void testMain() { EnginePlatformDispatcher.instance.rasterizer! .debugRunPostFrameCallbacks(); - await fontChangeCompleter.future; + await notoDownloadQueue.debugWhenIdle(); expect(FontFallbackData.instance.globalFontFallbacks, contains('Noto Naskh Arabic UI 0')); @@ -123,30 +105,71 @@ void testMain() { // TODO: https://github.com/flutter/flutter/issues/71520 }, skip: isIosSafari || isFirefox); - test('will download Noto Emojis and Noto Symbols if no matching Noto Font', + test('will put the Noto Emoji font before other fallback fonts in the list', () async { - final Completer fontChangeCompleter = Completer(); - // Intercept the system font change message. - ui.window.onPlatformMessage = (String name, ByteData? data, - ui.PlatformMessageResponseCallback? callback) { - if (name == 'flutter/system') { - const JSONMessageCodec codec = JSONMessageCodec(); - final dynamic message = codec.decodeMessage(data); - if (message is Map) { - if (message['type'] == 'fontsChange') { - fontChangeCompleter.complete(); - } - } - } - if (savedCallback != null) { - savedCallback!(name, data, callback); - } - }; - TestDownloader.mockDownloads[ 'https://fonts.googleapis.com/css2?family=Noto+Color+Emoji+Compat'] = ''' +@font-face { + font-family: 'Noto Color Emoji'; + src: url(packages/ui/assets/NotoColorEmoji.ttf) format('ttf'); +} +'''; + + TestDownloader.mockDownloads[ + 'https://fonts.googleapis.com/css2?family=Noto+Naskh+Arabic+UI'] = + ''' /* arabic */ +@font-face { + font-family: 'Noto Naskh Arabic UI'; + font-style: normal; + font-weight: 400; + src: url(packages/ui/assets/NotoNaskhArabic-Regular.ttf) format('ttf'); + unicode-range: U+0600-06FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE80-FEFC; +} +'''; + + expect(FontFallbackData.instance.globalFontFallbacks, ['Roboto']); + + // Creating this paragraph should cause us to start to download the + // Arabic fallback font. + CkParagraphBuilder pb = CkParagraphBuilder( + CkParagraphStyle(), + ); + pb.addText('مرحبا'); + + EnginePlatformDispatcher.instance.rasterizer! + .debugRunPostFrameCallbacks(); + await notoDownloadQueue.debugWhenIdle(); + + expect(FontFallbackData.instance.globalFontFallbacks, + ['Roboto', 'Noto Naskh Arabic UI 0']); + + pb = CkParagraphBuilder( + CkParagraphStyle(), + ); + pb.pushStyle(ui.TextStyle(fontSize: 26)); + pb.addText('Hello 😊 مرحبا'); + pb.pop(); + final CkParagraph paragraph = pb.build(); + paragraph.layout(ui.ParagraphConstraints(width: 1000)); + + EnginePlatformDispatcher.instance.rasterizer! + .debugRunPostFrameCallbacks(); + await notoDownloadQueue.debugWhenIdle(); + + expect(FontFallbackData.instance.globalFontFallbacks, [ + 'Roboto', + 'Noto Color Emoji Compat 0', + 'Noto Naskh Arabic UI 0', + ]); + }); + + test('will download Noto Emojis and Noto Symbols if no matching Noto Font', + () async { + TestDownloader.mockDownloads[ + 'https://fonts.googleapis.com/css2?family=Noto+Color+Emoji+Compat'] = + ''' @font-face { font-family: 'Noto Color Emoji'; src: url(packages/ui/assets/NotoColorEmoji.ttf) format('ttf'); @@ -164,7 +187,7 @@ void testMain() { EnginePlatformDispatcher.instance.rasterizer! .debugRunPostFrameCallbacks(); - await fontChangeCompleter.future; + await notoDownloadQueue.debugWhenIdle(); expect(FontFallbackData.instance.globalFontFallbacks, contains('Noto Color Emoji Compat 0'));