Bug 1971053 - patch 2 - Prefer to use the specified font from CSS for potentially-emoji characters unless a specific presentation is explicitly requested. a=dmeehan

Original Revision: https://phabricator.services.mozilla.com/D253740

Differential Revision: https://phabricator.services.mozilla.com/D253939
This commit is contained in:
Jonathan Kew
2025-07-04 17:08:16 +00:00
committed by dmeehan@mozilla.com
parent 38ca6718c3
commit 753cdf7af0
16 changed files with 218 additions and 46 deletions

View File

@@ -2348,6 +2348,12 @@ bool gfxFcPlatformFontList::FindAndAddFamiliesLocked(
cacheKey.Append(':'); cacheKey.Append(':');
} }
// Include the generic family in the cache key, to maintain the distinction
// between fonts explicitly requested by name and the results of resolving
// CSS generics.
cacheKey.AppendInt(int(aGeneric));
cacheKey.Append(':');
cacheKey.Append(familyName); cacheKey.Append(familyName);
auto vis = auto vis =
aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User; aPresContext ? aPresContext->GetFontVisibility() : FontVisibility::User;

View File

@@ -123,6 +123,11 @@ inline bool PrefersColor(FontPresentation aPresentation) {
return aPresentation >= FontPresentation::EmojiDefault; return aPresentation >= FontPresentation::EmojiDefault;
} }
inline bool IsExplicitPresentation(FontPresentation aPresentation) {
return aPresentation == FontPresentation::TextExplicit ||
aPresentation == FontPresentation::EmojiExplicit;
}
// when searching through pref langs, max number of pref langs // when searching through pref langs, max number of pref langs
const uint32_t kMaxLenPrefLangList = 32; const uint32_t kMaxLenPrefLangList = 32;

View File

@@ -1856,18 +1856,8 @@ gfxFontGroup::gfxFontGroup(nsPresContext* aPresContext,
mUserFontSet(aUserFontSet), mUserFontSet(aUserFontSet),
mTextPerf(aTextPerf), mTextPerf(aTextPerf),
mPageLang(gfxPlatformFontList::GetFontPrefLangFor(aLanguage)), mPageLang(gfxPlatformFontList::GetFontPrefLangFor(aLanguage)),
mExplicitLanguage(aExplicitLanguage) { mExplicitLanguage(aExplicitLanguage),
switch (aVariantEmoji) { mFontVariantEmoji(aVariantEmoji) {
case StyleFontVariantEmoji::Normal:
case StyleFontVariantEmoji::Unicode:
break;
case StyleFontVariantEmoji::Text:
mEmojiPresentation = FontPresentation::TextExplicit;
break;
case StyleFontVariantEmoji::Emoji:
mEmojiPresentation = FontPresentation::EmojiExplicit;
break;
}
// We don't use SetUserFontSet() here, as we want to unconditionally call // We don't use SetUserFontSet() here, as we want to unconditionally call
// EnsureFontList() rather than only do UpdateUserFonts() if it changed. // EnsureFontList() rather than only do UpdateUserFonts() if it changed.
} }
@@ -3209,7 +3199,11 @@ already_AddRefed<gfxFont> gfxFontGroup::FindFontForChar(
if (EmojiPresentation emojiPresentation = GetEmojiPresentation(aCh); if (EmojiPresentation emojiPresentation = GetEmojiPresentation(aCh);
emojiPresentation != TextOnly) { emojiPresentation != TextOnly) {
// Default presentation from the font-variant-emoji property. // Default presentation from the font-variant-emoji property.
presentation = mEmojiPresentation; if (mFontVariantEmoji == StyleFontVariantEmoji::Emoji) {
presentation = FontPresentation::EmojiExplicit;
} else if (mFontVariantEmoji == StyleFontVariantEmoji::Text) {
presentation = FontPresentation::TextExplicit;
}
// If there wasn't an explicit font-variant-emoji setting, default to // If there wasn't an explicit font-variant-emoji setting, default to
// what Unicode prefers for this character. // what Unicode prefers for this character.
if (presentation == FontPresentation::Any) { if (presentation == FontPresentation::Any) {
@@ -3225,8 +3219,7 @@ already_AddRefed<gfxFont> gfxFontGroup::FindFontForChar(
// glyph. // glyph.
// If the prefer-text selector is present, we specifically look for a // If the prefer-text selector is present, we specifically look for a
// font that will provide a monochrome glyph. // font that will provide a monochrome glyph.
if (aNextCh == kVariationSelector16 || if (aNextCh == kVariationSelector16 || IsEmojiSkinToneModifier(aNextCh) ||
(aNextCh >= kEmojiSkinToneFirst && aNextCh <= kEmojiSkinToneLast) ||
gfxFontUtils::IsEmojiFlagAndTag(aCh, aNextCh)) { gfxFontUtils::IsEmojiFlagAndTag(aCh, aNextCh)) {
// Emoji presentation is explicitly requested by a variation selector // Emoji presentation is explicitly requested by a variation selector
// or the presence of a skin-tone codepoint. // or the presence of a skin-tone codepoint.
@@ -3305,8 +3298,14 @@ already_AddRefed<gfxFont> gfxFontGroup::FindFontForChar(
// Handle a candidate font that could support the character, returning true // Handle a candidate font that could support the character, returning true
// if we should go ahead and return |f|, false to continue searching. // if we should go ahead and return |f|, false to continue searching.
auto CheckCandidate = [&](gfxFont* f, FontMatchType t) -> bool { auto CheckCandidate = [&](gfxFont* f, FontMatchType t) -> bool {
// If no preference, then just accept the font. // If no preference, or if it's an explicitly-named family in the fontgroup
if (presentation == FontPresentation::Any) { // and font-variant-emoji is 'normal', then we accept the font.
if (presentation == FontPresentation::Any ||
(!IsExplicitPresentation(presentation) &&
t.kind == FontMatchType::Kind::kFontGroup &&
t.generic == StyleGenericFontFamily::None &&
mFontVariantEmoji == StyleFontVariantEmoji::Normal &&
!gfxFontUtils::IsRegionalIndicator(aCh))) {
*aMatchType = t; *aMatchType = t;
return true; return true;
} }
@@ -3447,21 +3446,6 @@ already_AddRefed<gfxFont> gfxFontGroup::FindFontForChar(
} }
} }
// If it's an emoji codepoint and we found a named-family candidate (not a
// generic) in the font list, we accept it even if it doesn't match the
// presentation (so authors can deliberately request fonts that do not match
// the Unicode emoji default presentation style for a given character). But
// don't do this if a particular presentation was explicitly requested in the
// text, or for Regional Indicator chars (because of Segoe UI Emoji).
if (candidateFont &&
candidateMatchType.generic == StyleGenericFontFamily::None &&
presentation != FontPresentation::EmojiExplicit &&
presentation != FontPresentation::TextExplicit &&
!gfxFontUtils::IsRegionalIndicator(aCh)) {
*aMatchType = candidateMatchType;
return candidateFont.forget();
}
if (fontListLength == 0) { if (fontListLength == 0) {
RefPtr<gfxFont> defaultFont = GetDefaultFont(); RefPtr<gfxFont> defaultFont = GetDefaultFont();
if (defaultFont->HasCharacter(aCh) || if (defaultFont->HasCharacter(aCh) ||
@@ -3612,11 +3596,13 @@ void gfxFontGroup::ComputeRanges(nsTArray<TextRange>& aRanges, const T* aString,
// the font selected for an adjacent character, and does not need to // the font selected for an adjacent character, and does not need to
// consider emoji vs text presentation. // consider emoji vs text presentation.
if ((font = GetFontAt(0, ch)) != nullptr && font->HasCharacter(ch) && if ((font = GetFontAt(0, ch)) != nullptr && font->HasCharacter(ch) &&
// In 8-bit text, the only time emoji presentation might be needed (
// is if it is explicitly requested with font-variant, as no 8-bit // In 8-bit text, we can unconditionally accept the first font if
// chars are emoji by default. // font-variant-emoji is 'normal', or if the character does not
((sizeof(T) == sizeof(uint8_t) && // have the emoji property; there cannot be adjacent characters
(mEmojiPresentation != FontPresentation::EmojiExplicit || // that would affect it.
(sizeof(T) == sizeof(uint8_t) &&
(mFontVariantEmoji == StyleFontVariantEmoji::Normal ||
GetEmojiPresentation(ch) == TextOnly)) || GetEmojiPresentation(ch) == TextOnly)) ||
// For 16-bit text, we need to consider cluster extenders etc. // For 16-bit text, we need to consider cluster extenders etc.
(sizeof(T) == sizeof(char16_t) && (sizeof(T) == sizeof(char16_t) &&
@@ -3624,7 +3610,12 @@ void gfxFontGroup::ComputeRanges(nsTArray<TextRange>& aRanges, const T* aString,
!gfxFontUtils::IsJoinControl(ch) && !gfxFontUtils::IsJoinControl(ch) &&
!gfxFontUtils::IsJoinCauser(prevCh) && !gfxFontUtils::IsJoinCauser(prevCh) &&
!gfxFontUtils::IsVarSelector(ch) && !gfxFontUtils::IsVarSelector(ch) &&
GetEmojiPresentation(ch) == TextOnly)))) { (GetEmojiPresentation(ch) == TextOnly ||
(!(IsEmojiPresentationSelector(nextCh) ||
IsEmojiSkinToneModifier(nextCh) ||
gfxFontUtils::IsEmojiFlagAndTag(ch, nextCh)) &&
mFontVariantEmoji == StyleFontVariantEmoji::Normal &&
mFonts[0].Generic() == StyleGenericFontFamily::None)))))) {
matchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()}; matchType = {FontMatchType::Kind::kFontGroup, mFonts[0].Generic()};
} else { } else {
font = font =

View File

@@ -1399,7 +1399,7 @@ class gfxFontGroup final : public gfxTextRunFactory {
bool mResolvedFonts = false; // Whether the mFonts array has been set up. bool mResolvedFonts = false; // Whether the mFonts array has been set up.
FontPresentation mEmojiPresentation = FontPresentation::Any; StyleFontVariantEmoji mFontVariantEmoji = StyleFontVariantEmoji::Normal;
// Generic font family used to select among font prefs during fallback. // Generic font family used to select among font prefs during fallback.
mozilla::StyleGenericFontFamily mFallbackGeneric = mozilla::StyleGenericFontFamily mFallbackGeneric =

View File

@@ -56,10 +56,16 @@ enum EmojiPresentation { TextOnly = 0, TextDefault = 1, EmojiDefault = 2 };
const uint32_t kVariationSelector15 = 0xFE0E; // text presentation const uint32_t kVariationSelector15 = 0xFE0E; // text presentation
const uint32_t kVariationSelector16 = 0xFE0F; // emoji presentation const uint32_t kVariationSelector16 = 0xFE0F; // emoji presentation
static inline bool IsEmojiPresentationSelector(uint32_t aCh) {
return aCh >= kVariationSelector15 && aCh <= kVariationSelector16;
}
// Unicode values for EMOJI MODIFIER FITZPATRICK TYPE-* // Unicode values for EMOJI MODIFIER FITZPATRICK TYPE-*
const uint32_t kEmojiSkinToneFirst = 0x1f3fb; const uint32_t kEmojiSkinToneFirst = 0x1f3fb;
const uint32_t kEmojiSkinToneLast = 0x1f3ff; const uint32_t kEmojiSkinToneLast = 0x1f3ff;
static inline bool IsEmojiSkinToneModifier(uint32_t aCh) {
return aCh >= kEmojiSkinToneFirst && aCh <= kEmojiSkinToneLast;
}
extern const hb_unicode_general_category_t sICUtoHBcategory[]; extern const hb_unicode_general_category_t sICUtoHBcategory[];

View File

@@ -0,0 +1,17 @@
<!DOCTYPE html>
<style>
@font-face {
font-family: Ahem;
src: url(../fonts/Ahem.ttf);
}
div {
font: 25px/2 "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", Ahem;
}
span.emoji {
font-variant-emoji: emoji;
}
</style>
<p>Expect digits to be rendered from the emoji font:</p>
<div>abc<span class=emoji>123</span>xyz</div>

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<style>
@font-face {
font-family: Ahem;
src: url(../fonts/Ahem.ttf);
}
div {
font: 25px/2 "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", Ahem;
}
</style>
<p>Expect digits to be rendered from the emoji font:</p>
<div>abc123xyz</div>

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<style>
@font-face {
font-family: Ahem;
src: url(../fonts/Ahem.ttf);
}
div {
font: 25px/2 "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", Ahem;
}
</style>
<p>Expect digits to be rendered from the emoji font:</p>
<div style="font-variant-emoji: emoji">abc123xyz</div>

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<style>
@font-face {
font-family: Ahem;
src: url(../fonts/Ahem.ttf);
}
div {
font: 25px/2 "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", Ahem;
}
span.text {
font-family: Ahem;
font-variant-emoji: text;
}
</style>
<p>Expect digits to be rendered from Ahem:</p>
<div style="font-variant-emoji: text">abc<span class=text>123</span>xyz</div>

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<style>
@font-face {
font-family: Ahem;
src: url(../fonts/Ahem.ttf);
}
div {
font: 25px/2 "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", Ahem;
}
</style>
<p>Expect digits to be rendered from Ahem:</p>
<div style="font-variant-emoji: text">abc123xyz</div>

View File

@@ -0,0 +1,14 @@
<!DOCTYPE html>
<style>
@font-face {
font-family: Ahem;
src: url(../fonts/Ahem.ttf);
}
div {
font: 25px/2 "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", Ahem;
}
</style>
<p>Expect digits to be rendered from Ahem:</p>
<div style="font-variant-emoji: unicode">abc123xyz</div>

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<style>
@font-face {
font-family: Ahem;
src: url(../fonts/Ahem.ttf);
}
div {
font: 25px/2 "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", Ahem;
}
</style>
<!-- presence of ZWSP should not affect font selection -->
<p>Expect digits to be rendered from the emoji font:</p>
<div>abc123xyz&#x200b;</div>

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<style>
@font-face {
font-family: Ahem;
src: url(../fonts/Ahem.ttf);
}
div {
font: 25px/2 "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", Ahem;
}
</style>
<!-- presence of ZWSP should not affect font selection -->
<p>Expect digits to be rendered from the emoji font:</p>
<div style="font-variant-emoji: emoji">abc123xyz&#x200b;</div>

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<style>
@font-face {
font-family: Ahem;
src: url(../fonts/Ahem.ttf);
}
div {
font: 25px/2 "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", Ahem;
}
</style>
<!-- presence of ZWSP should not affect font selection -->
<p>Expect digits to be rendered from Ahem:</p>
<div style="font-variant-emoji: text">abc123xyz&#x200b;</div>

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<style>
@font-face {
font-family: Ahem;
src: url(../fonts/Ahem.ttf);
}
div {
font: 25px/2 "Apple Color Emoji", "Segoe UI Emoji", "Noto Color Emoji", Ahem;
}
</style>
<!-- presence of ZWSP should not affect font selection -->
<p>Expect digits to be rendered from Ahem:</p>
<div style="font-variant-emoji: unicode">abc123xyz&#x200b;</div>

View File

@@ -213,3 +213,12 @@ test-pref(privacy.resistFingerprinting,true) == system-font-rfp.html system-font
# Bug 1970980 - Test for regional-indicator emoji handling with Windows-specific fonts # Bug 1970980 - Test for regional-indicator emoji handling with Windows-specific fonts
skip-if(!winWidget) pref(layout.css.font-variant-emoji.enabled,true) == 1970980-regional-indicators.html 1970980-regional-indicators-ref.html skip-if(!winWidget) pref(layout.css.font-variant-emoji.enabled,true) == 1970980-regional-indicators.html 1970980-regional-indicators-ref.html
pref(layout.css.font-variant-emoji.enabled,true) == 1971148-digits-color-font-01.html 1971148-digits-color-font-01-ref.html
pref(layout.css.font-variant-emoji.enabled,true) == 1971148-digits-color-font-02.html 1971148-digits-color-font-01-ref.html
pref(layout.css.font-variant-emoji.enabled,true) == 1971148-digits-color-font-03.html 1971148-digits-color-font-03-ref.html
pref(layout.css.font-variant-emoji.enabled,true) == 1971148-digits-color-font-04.html 1971148-digits-color-font-03-ref.html
pref(layout.css.font-variant-emoji.enabled,true) == 1971148-digits-color-font-05.html 1971148-digits-color-font-01-ref.html
pref(layout.css.font-variant-emoji.enabled,true) == 1971148-digits-color-font-06.html 1971148-digits-color-font-01-ref.html
pref(layout.css.font-variant-emoji.enabled,true) == 1971148-digits-color-font-07.html 1971148-digits-color-font-03-ref.html
pref(layout.css.font-variant-emoji.enabled,true) == 1971148-digits-color-font-08.html 1971148-digits-color-font-03-ref.html