From f7421bf968e6feb7eb8af17f08d254c4e9096646 Mon Sep 17 00:00:00 2001 From: Greg Price Date: Wed, 4 Dec 2024 17:14:07 -0800 Subject: [PATCH] content: Use emoji font to show emoji nodes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For emoji like ❤ U+2764 HEAVY BLACK HEART, this causes us to show a colorful, larger glyph like ❤️ from the emoji font, instead of a glyph like ❤︎ that comes from a plain-text font and has the color and size of plain text. Fixes: #1104 --- lib/widgets/content.dart | 15 ++++++++++++++- lib/widgets/text.dart | 6 +++++- test/widgets/content_test.dart | 13 +++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/lib/widgets/content.dart b/lib/widgets/content.dart index e401e5004e..f3b369e876 100644 --- a/lib/widgets/content.dart +++ b/lib/widgets/content.dart @@ -54,6 +54,8 @@ class ContentTheme extends ThemeExtension { textStylePlainParagraph: _plainParagraphCommon(context).copyWith( color: const HSLColor.fromAHSL(1, 0, 0, 0.15).toColor(), debugLabel: 'ContentTheme.textStylePlainParagraph'), + textStyleEmoji: TextStyle( + fontFamily: emojiFontFamily, fontFamilyFallback: const []), codeBlockTextStyles: CodeBlockTextStyles.light(context), textStyleError: const TextStyle(fontSize: kBaseFontSize, color: Colors.red) .merge(weightVariableTextStyle(context, wght: 700)), @@ -85,6 +87,8 @@ class ContentTheme extends ThemeExtension { textStylePlainParagraph: _plainParagraphCommon(context).copyWith( color: const HSLColor.fromAHSL(1, 0, 0, 0.85).toColor(), debugLabel: 'ContentTheme.textStylePlainParagraph'), + textStyleEmoji: TextStyle( + fontFamily: emojiFontFamily, fontFamilyFallback: const []), codeBlockTextStyles: CodeBlockTextStyles.dark(context), textStyleError: const TextStyle(fontSize: kBaseFontSize, color: Colors.red) .merge(weightVariableTextStyle(context, wght: 700)), @@ -113,6 +117,7 @@ class ContentTheme extends ThemeExtension { required this.colorTableHeaderBackground, required this.colorThematicBreak, required this.textStylePlainParagraph, + required this.textStyleEmoji, required this.codeBlockTextStyles, required this.textStyleError, required this.textStyleErrorCode, @@ -152,6 +157,9 @@ class ContentTheme extends ThemeExtension { /// should not need styles from other sources, such as Material defaults. final TextStyle textStylePlainParagraph; + /// The [TextStyle] to use for Unicode emoji. + final TextStyle textStyleEmoji; + final CodeBlockTextStyles codeBlockTextStyles; final TextStyle textStyleError; final TextStyle textStyleErrorCode; @@ -201,6 +209,7 @@ class ContentTheme extends ThemeExtension { Color? colorTableHeaderBackground, Color? colorThematicBreak, TextStyle? textStylePlainParagraph, + TextStyle? textStyleEmoji, CodeBlockTextStyles? codeBlockTextStyles, TextStyle? textStyleError, TextStyle? textStyleErrorCode, @@ -222,6 +231,7 @@ class ContentTheme extends ThemeExtension { colorTableHeaderBackground: colorTableHeaderBackground ?? this.colorTableHeaderBackground, colorThematicBreak: colorThematicBreak ?? this.colorThematicBreak, textStylePlainParagraph: textStylePlainParagraph ?? this.textStylePlainParagraph, + textStyleEmoji: textStyleEmoji ?? this.textStyleEmoji, codeBlockTextStyles: codeBlockTextStyles ?? this.codeBlockTextStyles, textStyleError: textStyleError ?? this.textStyleError, textStyleErrorCode: textStyleErrorCode ?? this.textStyleErrorCode, @@ -250,6 +260,7 @@ class ContentTheme extends ThemeExtension { colorTableHeaderBackground: Color.lerp(colorTableHeaderBackground, other.colorTableHeaderBackground, t)!, colorThematicBreak: Color.lerp(colorThematicBreak, other.colorThematicBreak, t)!, textStylePlainParagraph: TextStyle.lerp(textStylePlainParagraph, other.textStylePlainParagraph, t)!, + textStyleEmoji: TextStyle.lerp(textStyleEmoji, other.textStyleEmoji, t)!, codeBlockTextStyles: CodeBlockTextStyles.lerp(codeBlockTextStyles, other.codeBlockTextStyles, t), textStyleError: TextStyle.lerp(textStyleError, other.textStyleError, t)!, textStyleErrorCode: TextStyle.lerp(textStyleErrorCode, other.textStyleErrorCode, t)!, @@ -1031,7 +1042,9 @@ class _InlineContentBuilder { child: UserMention(ambientTextStyle: widget.style, node: node)); case UnicodeEmojiNode(): - return TextSpan(text: node.emojiUnicode, recognizer: _recognizer); + return TextSpan(text: node.emojiUnicode, recognizer: _recognizer, + style: widget.style + .merge(ContentTheme.of(_context!).textStyleEmoji)); case ImageEmojiNode(): return WidgetSpan(alignment: PlaceholderAlignment.middle, diff --git a/lib/widgets/text.dart b/lib/widgets/text.dart index e50ac40668..03fa0f32bd 100644 --- a/lib/widgets/text.dart +++ b/lib/widgets/text.dart @@ -153,9 +153,13 @@ const kDefaultFontFamily = 'Source Sans 3'; /// The [TextStyle.fontFamilyFallback] for use with [kDefaultFontFamily]. List get defaultFontFamilyFallback => [ - _useAppleEmoji ? 'Apple Color Emoji' : 'Noto Color Emoji', + emojiFontFamily, ]; +String get emojiFontFamily { + return _useAppleEmoji ? 'Apple Color Emoji' : 'Noto Color Emoji'; +} + /// Whether to use the Apple Color Emoji font for showing emoji. /// /// When false, we use Noto Color Emoji instead. diff --git a/test/widgets/content_test.dart b/test/widgets/content_test.dart index 4fb5e01b61..bee5325714 100644 --- a/test/widgets/content_test.dart +++ b/test/widgets/content_test.dart @@ -877,6 +877,19 @@ void main() { testContentSmoke(ContentExample.emojiUnicode); testContentSmoke(ContentExample.emojiUnicodeMultiCodepoint); testContentSmoke(ContentExample.emojiUnicodeLiteral); + + testWidgets('use emoji font', (tester) async { + // Compare [ContentExample.emojiUnicode]. + const emojiHeartHtml = + '

:heart:

'; + await prepareContent(tester, plainContent(emojiHeartHtml)); + check(mergedStyleOf(tester, '\u{2764}')).isNotNull() + .fontFamily.equals(switch (defaultTargetPlatform) { + TargetPlatform.android => 'Noto Color Emoji', + TargetPlatform.iOS => 'Apple Color Emoji', + _ => throw StateError('unexpected platform in test'), + }); + }, variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS})); }); group('inline math', () {