diff --git a/components/gfx/font.rs b/components/gfx/font.rs index 89553b809903..ef93af1170e5 100644 --- a/components/gfx/font.rs +++ b/components/gfx/font.rs @@ -32,7 +32,7 @@ use crate::platform::font::{FontTable, PlatformFont}; pub use crate::platform::font_list::fallback_font_families; use crate::text::glyph::{ByteIndex, GlyphData, GlyphId, GlyphStore}; use crate::text::shaping::ShaperMethods; -use crate::text::{FallbackFontSelectionOptions, Shaper}; +use crate::text::{EmojiPresentationPreference, FallbackFontSelectionOptions, Shaper}; #[macro_export] macro_rules! ot_tag { @@ -219,6 +219,11 @@ pub struct Font { /// the version of the font used to replace lowercase ASCII letters. It's up /// to the consumer of this font to properly use this reference. pub synthesized_small_caps: Option, + + /// Whether or not this font supports color bitmaps or a COLR table. This is + /// essentially equivalent to whether or not we use it for emoji presentation. + /// This is cached, because getting table data is expensive. + has_color_bitmap_or_colr_table: OnceLock, } impl malloc_size_of::MallocSizeOf for Font { @@ -250,6 +255,7 @@ impl Font { cached_shape_data: Default::default(), font_key: FontInstanceKey::default(), synthesized_small_caps, + has_color_bitmap_or_colr_table: OnceLock::new(), }) } @@ -261,6 +267,14 @@ impl Font { pub fn webrender_font_instance_flags(&self) -> FontInstanceFlags { self.handle.webrender_font_instance_flags() } + + pub fn has_color_bitmap_or_colr_table(&self) -> bool { + *self.has_color_bitmap_or_colr_table.get_or_init(|| { + self.table_for_tag(SBIX).is_some() || + self.table_for_tag(CBDT).is_some() || + self.table_for_tag(COLR).is_some() + }) + } } bitflags! { @@ -503,25 +517,45 @@ impl FontGroup { Some(font) }; - let glyph_in_font = |font: &FontRef| font.has_glyph_for(options.character); + let font_has_glyph_and_presentation = |font: &FontRef| { + // Do not select this font if it goes against our emoji preference. + match options.presentation_preference { + EmojiPresentationPreference::Text if font.has_color_bitmap_or_colr_table() => { + return false + }, + EmojiPresentationPreference::Emoji if !font.has_color_bitmap_or_colr_table() => { + return false + }, + _ => {}, + } + font.has_glyph_for(options.character) + }; + let char_in_template = |template: FontTemplateRef| template.char_in_unicode_range(options.character); - if let Some(font) = self.find(font_context, char_in_template, glyph_in_font) { + if let Some(font) = self.find( + font_context, + char_in_template, + font_has_glyph_and_presentation, + ) { return font_or_synthesized_small_caps(font); } if let Some(ref last_matching_fallback) = self.last_matching_fallback { if char_in_template(last_matching_fallback.template.clone()) && - glyph_in_font(last_matching_fallback) + font_has_glyph_and_presentation(last_matching_fallback) { return font_or_synthesized_small_caps(last_matching_fallback.clone()); } } - if let Some(font) = - self.find_fallback(font_context, options, char_in_template, glyph_in_font) - { + if let Some(font) = self.find_fallback( + font_context, + options, + char_in_template, + font_has_glyph_and_presentation, + ) { self.last_matching_fallback = Some(font.clone()); return font_or_synthesized_small_caps(font); } diff --git a/components/gfx/font_template.rs b/components/gfx/font_template.rs index 6c2999aef667..19c71ed83fa8 100644 --- a/components/gfx/font_template.rs +++ b/components/gfx/font_template.rs @@ -89,8 +89,8 @@ impl FontTemplateDescriptor { // a mismatch between the desired and actual glyph presentation (emoji vs text) // will take precedence over any of the style attributes. // - // TODO: Take into account Unicode presentation preferences here, in order to properly - // choose a font for emoji clusters that start with non-emoji characters. + // Also relevant for font selection is the emoji presentation preference, but this + // is handled later when filtering fonts based on the glyphs they contain. const STRETCH_FACTOR: f32 = 1.0e8; const STYLE_FACTOR: f32 = 1.0e4; const WEIGHT_FACTOR: f32 = 1.0e0; diff --git a/components/gfx/platform/freetype/font_list.rs b/components/gfx/platform/freetype/font_list.rs index 7fa0187a0cce..73d17ccd49ea 100644 --- a/components/gfx/platform/freetype/font_list.rs +++ b/components/gfx/platform/freetype/font_list.rs @@ -35,7 +35,7 @@ use super::c_str_to_string; use crate::font::map_platform_values_to_style_values; use crate::font_template::{FontTemplate, FontTemplateDescriptor}; use crate::platform::add_noto_fallback_families; -use crate::text::FallbackFontSelectionOptions; +use crate::text::{EmojiPresentationPreference, FallbackFontSelectionOptions}; /// An identifier for a local font on systems using Freetype. #[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] @@ -204,7 +204,7 @@ pub static SANS_SERIF_FONT_FAMILY: &str = "DejaVu Sans"; // Based on gfxPlatformGtk::GetCommonFallbackFonts() in Gecko pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> { let mut families = Vec::new(); - if options.prefer_emoji_presentation { + if options.presentation_preference == EmojiPresentationPreference::Emoji { families.push("Noto Color Emoji"); } diff --git a/components/gfx/platform/macos/font_list.rs b/components/gfx/platform/macos/font_list.rs index b590058e14be..c1d9621f61eb 100644 --- a/components/gfx/platform/macos/font_list.rs +++ b/components/gfx/platform/macos/font_list.rs @@ -17,7 +17,7 @@ use webrender_api::NativeFontHandle; use crate::font_template::{FontTemplate, FontTemplateDescriptor}; use crate::platform::add_noto_fallback_families; use crate::platform::font::CoreTextFontTraitsMapping; -use crate::text::FallbackFontSelectionOptions; +use crate::text::{EmojiPresentationPreference, FallbackFontSelectionOptions}; /// An identifier for a local font on a MacOS system. These values comes from the CoreText /// CTFontCollection. Note that `path` here is required. We do not load fonts that do not @@ -97,7 +97,7 @@ pub fn system_default_family(_generic_name: &str) -> Option { /// . pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> { let mut families = Vec::new(); - if options.prefer_emoji_presentation { + if options.presentation_preference == EmojiPresentationPreference::Emoji { families.push("Apple Color Emoji"); } diff --git a/components/gfx/platform/windows/font_list.rs b/components/gfx/platform/windows/font_list.rs index 9f3b29cff03c..9ac7011e2708 100644 --- a/components/gfx/platform/windows/font_list.rs +++ b/components/gfx/platform/windows/font_list.rs @@ -13,7 +13,7 @@ use style::values::computed::{FontStyle as StyleFontStyle, FontWeight as StyleFo use style::values::specified::font::FontStretchKeyword; use crate::font_template::{FontTemplate, FontTemplateDescriptor}; -use crate::text::FallbackFontSelectionOptions; +use crate::text::{EmojiPresentationPreference, FallbackFontSelectionOptions}; pub static SANS_SERIF_FONT_FAMILY: &str = "Arial"; @@ -92,8 +92,7 @@ where // Based on gfxWindowsPlatform::GetCommonFallbackFonts() in Gecko pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> { let mut families = Vec::new(); - - if options.prefer_emoji_presentation { + if options.presentation_preference == EmojiPresentationPreference::Emoji { families.push("Segoe UI Emoji"); } diff --git a/components/gfx/text/mod.rs b/components/gfx/text/mod.rs index ae61aab10318..9a44f743c7a9 100644 --- a/components/gfx/text/mod.rs +++ b/components/gfx/text/mod.rs @@ -2,7 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ -use unicode_properties::{emoji, UnicodeEmoji}; +use unicode_properties::{emoji, EmojiStatus, UnicodeEmoji}; pub use crate::text::shaping::Shaper; @@ -10,31 +10,59 @@ pub mod glyph; pub mod shaping; pub mod util; +/// Whether or not font fallback selection prefers the emoji or text representation +/// of a character. If `None` then either presentation is acceptable. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum EmojiPresentationPreference { + None, + Text, + Emoji, +} + #[derive(Clone, Copy, Debug)] pub struct FallbackFontSelectionOptions { pub character: char, - pub prefer_emoji_presentation: bool, + pub presentation_preference: EmojiPresentationPreference, } impl Default for FallbackFontSelectionOptions { fn default() -> Self { Self { character: ' ', - prefer_emoji_presentation: false, + presentation_preference: EmojiPresentationPreference::None, } } } impl FallbackFontSelectionOptions { pub fn new(character: char, next_character: Option) -> Self { - let prefer_emoji_presentation = match next_character { - Some(next_character) if emoji::is_emoji_presentation_selector(next_character) => true, - Some(next_character) if emoji::is_text_presentation_selector(next_character) => false, - _ => character.is_emoji_char(), + let presentation_preference = match next_character { + Some(next_character) if emoji::is_emoji_presentation_selector(next_character) => { + EmojiPresentationPreference::Emoji + }, + Some(next_character) if emoji::is_text_presentation_selector(next_character) => { + EmojiPresentationPreference::Text + }, + // We don't want to select emoji prsentation for any possible character that might be an emoji, because + // that includes characters such as '0' that are also used outside of emoji clusters. Instead, only + // select the emoji font for characters that explicitly have an emoji presentation (in the absence + // of the emoji presentation selectors above). + _ if matches!( + character.emoji_status(), + EmojiStatus::EmojiPresentation | + EmojiStatus::EmojiPresentationAndModifierBase | + EmojiStatus::EmojiPresentationAndEmojiComponent | + EmojiStatus::EmojiPresentationAndModifierAndEmojiComponent + ) => + { + EmojiPresentationPreference::Emoji + }, + _ if character.is_emoji_char() => EmojiPresentationPreference::Text, + _ => EmojiPresentationPreference::None, }; Self { character, - prefer_emoji_presentation, + presentation_preference, } } } diff --git a/tests/wpt/meta/css/css-fonts/font-variant-emoji-1.html.ini b/tests/wpt/meta/css/css-fonts/font-variant-emoji-1.html.ini deleted file mode 100644 index 48f039e7548b..000000000000 --- a/tests/wpt/meta/css/css-fonts/font-variant-emoji-1.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[font-variant-emoji-1.html] - expected: FAIL diff --git a/tests/wpt/meta/css/css-fonts/font-variant-emoji-2.html.ini b/tests/wpt/meta/css/css-fonts/font-variant-emoji-2.html.ini new file mode 100644 index 000000000000..68aae843f87b --- /dev/null +++ b/tests/wpt/meta/css/css-fonts/font-variant-emoji-2.html.ini @@ -0,0 +1,2 @@ +[font-variant-emoji-2.html] + expected: FAIL diff --git a/tests/wpt/meta/css/css-text/text-transform/text-transform-capitalize-026.html.ini b/tests/wpt/meta/css/css-text/text-transform/text-transform-capitalize-026.html.ini deleted file mode 100644 index 6e52a5556385..000000000000 --- a/tests/wpt/meta/css/css-text/text-transform/text-transform-capitalize-026.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[text-transform-capitalize-026.html] - expected: FAIL