diff --git a/components/gfx/font.rs b/components/gfx/font.rs index 97709202a99..c418fce06d3 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::Shaper; +use crate::text::{FallbackFontSelectionOptions, Shaper}; #[macro_export] macro_rules! ot_tag { @@ -487,9 +487,12 @@ impl FontGroup { &mut self, font_context: &FontContext, codepoint: char, + next_codepoint: Option, ) -> Option { + let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint); + let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps && - codepoint.is_ascii_lowercase(); + options.character.is_ascii_lowercase(); let font_or_synthesized_small_caps = |font: FontRef| { if should_look_for_small_caps && font.synthesized_small_caps.is_some() { return font.synthesized_small_caps.clone(); @@ -497,9 +500,9 @@ impl FontGroup { Some(font) }; - let glyph_in_font = |font: &FontRef| font.has_glyph_for(codepoint); + let glyph_in_font = |font: &FontRef| font.has_glyph_for(options.character); let char_in_template = - |template: FontTemplateRef| template.char_in_unicode_range(codepoint); + |template: FontTemplateRef| template.char_in_unicode_range(options.character); if let Some(font) = self.find(font_context, char_in_template, glyph_in_font) { return font_or_synthesized_small_caps(font); @@ -513,12 +516,9 @@ impl FontGroup { } } - if let Some(font) = self.find_fallback( - font_context, - Some(codepoint), - char_in_template, - glyph_in_font, - ) { + if let Some(font) = + self.find_fallback(font_context, options, char_in_template, glyph_in_font) + { self.last_matching_fallback = Some(font.clone()); return font_or_synthesized_small_caps(font); } @@ -538,7 +538,14 @@ impl FontGroup { let space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' '); let font_predicate = |_: &FontRef| true; self.find(font_context, space_in_template, font_predicate) - .or_else(|| self.find_fallback(font_context, None, space_in_template, font_predicate)) + .or_else(|| { + self.find_fallback( + font_context, + FallbackFontSelectionOptions::default(), + space_in_template, + font_predicate, + ) + }) } /// Attempts to find a font which matches the given `template_predicate` and `font_predicate`. @@ -576,7 +583,7 @@ impl FontGroup { fn find_fallback( &mut self, font_context: &FontContext, - codepoint: Option, + options: FallbackFontSelectionOptions, template_predicate: TemplatePredicate, font_predicate: FontPredicate, ) -> Option @@ -586,7 +593,7 @@ impl FontGroup { FontPredicate: Fn(&FontRef) -> bool, { iter::once(FontFamilyDescriptor::serif()) - .chain(fallback_font_families(codepoint).into_iter().map(|family| { + .chain(fallback_font_families(options).into_iter().map(|family| { FontFamilyDescriptor::new(FontFamilyName::from(family), FontSearchScope::Local) })) .filter_map(|family_descriptor| { diff --git a/components/gfx/platform/freetype/android/font_list.rs b/components/gfx/platform/freetype/android/font_list.rs index 4f24d677889..f7fa2d7863d 100644 --- a/components/gfx/platform/freetype/android/font_list.rs +++ b/components/gfx/platform/freetype/android/font_list.rs @@ -18,6 +18,7 @@ use ucd::{Codepoint, UnicodeBlock}; use super::xml::{Attribute, Node}; use crate::font_template::{FontTemplate, FontTemplateDescriptor}; use crate::text::util::is_cjk; +use crate::text::FallbackFontSelectionOptions; lazy_static::lazy_static! { static ref FONT_LIST: FontList = FontList::new(); @@ -524,10 +525,10 @@ pub fn system_default_family(generic_name: &str) -> Option { } // Based on gfxAndroidPlatform::GetCommonFallbackFonts() in Gecko -pub fn fallback_font_families(codepoint: Option) -> Vec<&'static str> { +pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> { let mut families = vec![]; - if let Some(block) = codepoint.and_then(|c| c.block()) { + if let Some(block) = options.character.block() { match block { UnicodeBlock::Armenian => { families.push("Droid Sans Armenian"); @@ -565,7 +566,7 @@ pub fn fallback_font_families(codepoint: Option) -> Vec<&'static str> { }, _ => { - if is_cjk(codepoint.unwrap()) { + if is_cjk(options.character) { families.push("MotoyaLMaru"); families.push("Noto Sans CJK JP"); families.push("Droid Sans Japanese"); diff --git a/components/gfx/platform/freetype/font_list.rs b/components/gfx/platform/freetype/font_list.rs index 78ea0a2ad50..cf85d0cf4e8 100644 --- a/components/gfx/platform/freetype/font_list.rs +++ b/components/gfx/platform/freetype/font_list.rs @@ -28,12 +28,13 @@ use malloc_size_of_derive::MallocSizeOf; use serde::{Deserialize, Serialize}; use style::values::computed::{FontStretch, FontStyle, FontWeight}; use style::Atom; -use unicode_properties::UnicodeEmoji; +use ucd::{Codepoint, UnicodeBlock}; +use unicode_script::Script; use super::c_str_to_string; use crate::font::map_platform_values_to_style_values; use crate::font_template::{FontTemplate, FontTemplateDescriptor}; -use crate::text::util::is_cjk; +use crate::text::FallbackFontSelectionOptions; /// An identifier for a local font on systems using Freetype. #[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] @@ -200,33 +201,247 @@ pub fn system_default_family(generic_name: &str) -> Option { pub static SANS_SERIF_FONT_FAMILY: &str = "DejaVu Sans"; // Based on gfxPlatformGtk::GetCommonFallbackFonts() in Gecko -pub fn fallback_font_families(codepoint: Option) -> Vec<&'static str> { +pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> { let mut families = Vec::new(); - - if codepoint.map_or(false, |codepoint| codepoint.is_emoji_char()) { + if options.prefer_emoji_presentation { families.push("Noto Color Emoji"); } - families.extend(["DejaVu Serif", "FreeSerif", "DejaVu Sans", "FreeSans"]); - if let Some(codepoint) = codepoint { - if is_cjk(codepoint) { - families.push("TakaoPGothic"); - families.push("Droid Sans Fallback"); - families.push("WenQuanYi Micro Hei"); - families.push("NanumGothic"); - families.push("Noto Sans CJK HK"); - families.push("Noto Sans CJK JP"); - families.push("Noto Sans CJK KR"); - families.push("Noto Sans CJK SC"); - families.push("Noto Sans CJK TC"); - families.push("Noto Sans HK"); - families.push("Noto Sans JP"); - families.push("Noto Sans KR"); - families.push("Noto Sans SC"); - families.push("Noto Sans TC"); + let add_chinese_families = |families: &mut Vec<&str>| { + // TODO: Need to differentiate between traditional and simplified Han here! + families.push("Noto Sans CJK HK"); + families.push("Noto Sans CJK SC"); + families.push("Noto Sans CJK TC"); + families.push("Noto Sans HK"); + families.push("Noto Sans SC"); + families.push("Noto Sans TC"); + families.push("WenQuanYi Micro Hei"); + }; + + match Script::from(options.character) { + // In most cases, COMMON and INHERITED characters will be merged into + // their context, but if they occur without any specific script context + // we'll just try common default fonts here. + Script::Common | Script::Inherited | Script::Latin | Script::Cyrillic | Script::Greek => { + families.push("Noto Sans"); + }, + // CJK-related script codes are a bit troublesome because of unification; + // we'll probably just get HAN much of the time, so the choice of which + // language font to try for fallback is rather arbitrary. Usually, though, + // we hope that font prefs will have handled this earlier. + Script::Bopomofo | Script::Han => add_chinese_families(&mut families), + Script::Hanifi_Rohingya => families.push("Noto Sans Hanifi Rohingya"), + Script::Wancho => families.push("Noto Sans Wancho"), + _ => {}, + } + + if let Some(block) = options.character.block() { + match block { + UnicodeBlock::HalfwidthandFullwidthForms | + UnicodeBlock::EnclosedIdeographicSupplement => add_chinese_families(&mut families), + UnicodeBlock::Adlam => families.push("Noto Sans Adlam"), + UnicodeBlock::Ahom => families.push("Noto Serif Ahom"), + UnicodeBlock::AnatolianHieroglyphs => families.push("Noto Sans AnatoHiero"), + UnicodeBlock::Arabic | + UnicodeBlock::ArabicExtendedA | + UnicodeBlock::ArabicPresentationFormsA | + UnicodeBlock::ArabicPresentationFormsB => { + families.push("Noto Sans Arabic"); + families.push("Noto Naskh Arabic"); + }, + UnicodeBlock::ArabicMathematicalAlphabeticSymbols => { + families.push("Noto Sans Math"); + }, + UnicodeBlock::Armenian => families.push("Noto Sans Armenian"), + UnicodeBlock::Avestan => families.push("Noto Sans Avestan"), + UnicodeBlock::Balinese => families.push("Noto Sans Balinese"), + UnicodeBlock::Bamum | UnicodeBlock::BamumSupplement => families.push("Noto Sans Bamum"), + UnicodeBlock::BassaVah => families.push("Noto Sans Bassa Vah"), + UnicodeBlock::Batak => families.push("Noto Sans Batak"), + UnicodeBlock::Bengali => families.push("Noto Sans Bengali"), + UnicodeBlock::Bhaiksuki => families.push("Noto Sans Bhaiksuki"), + UnicodeBlock::Brahmi => families.push("Noto Sans Brahmi"), + UnicodeBlock::BraillePatterns => { + // These characters appear to be in DejaVu Serif. + }, + UnicodeBlock::Buginese => families.push("Noto Sans Buginese"), + UnicodeBlock::Buhid => families.push("Noto Sans Buhid"), + UnicodeBlock::Carian => families.push("Noto Sans Carian"), + UnicodeBlock::CaucasianAlbanian => families.push("Noto Sans Caucasian Albanian"), + UnicodeBlock::Chakma => families.push("Noto Sans Chakma"), + UnicodeBlock::Cham => families.push("Noto Sans Cham"), + UnicodeBlock::Cherokee | UnicodeBlock::CherokeeSupplement => { + families.push("Noto Sans Cherokee") + }, + UnicodeBlock::Coptic => families.push("Noto Sans Coptic"), + UnicodeBlock::Cuneiform | UnicodeBlock::CuneiformNumbersandPunctuation => { + families.push("Noto Sans Cuneiform") + }, + UnicodeBlock::CypriotSyllabary => families.push("Noto Sans Cypriot"), + UnicodeBlock::Deseret => families.push("Noto Sans Deseret"), + UnicodeBlock::Devanagari | + UnicodeBlock::DevanagariExtended | + UnicodeBlock::CommonIndicNumberForms => families.push("Noto Sans Devanagari"), + UnicodeBlock::Duployan => families.push("Noto Sans Duployan"), + UnicodeBlock::EgyptianHieroglyphs => families.push("Noto Sans Egyptian Hieroglyphs"), + UnicodeBlock::Elbasan => families.push("Noto Sans Elbasan"), + UnicodeBlock::Ethiopic | + UnicodeBlock::EthiopicExtended | + UnicodeBlock::EthiopicExtendedA | + UnicodeBlock::EthiopicSupplement => families.push("Noto Sans Ethiopic"), + UnicodeBlock::Georgian | UnicodeBlock::GeorgianSupplement => { + families.push("Noto Sans Georgian") + }, + UnicodeBlock::Glagolitic | UnicodeBlock::GlagoliticSupplement => { + families.push("Noto Sans Glagolitic") + }, + UnicodeBlock::Gothic => families.push("Noto Sans Gothic"), + UnicodeBlock::Grantha => families.push("Noto Sans Grantha"), + UnicodeBlock::Gujarati => families.push("Noto Sans Gujarati"), + UnicodeBlock::Gurmukhi => families.push("Noto Sans Gurmukhi"), + UnicodeBlock::HangulCompatibilityJamo | + UnicodeBlock::HangulJamo | + UnicodeBlock::HangulJamoExtendedA | + UnicodeBlock::HangulJamoExtendedB | + UnicodeBlock::HangulSyllables => { + families.push("Noto Sans KR"); + families.push("Noto Sans CJK KR"); + }, + UnicodeBlock::Hanunoo => families.push("Noto Sans Hanunoo"), + UnicodeBlock::Hatran => families.push("Noto Sans Hatran"), + UnicodeBlock::Hebrew => families.push("Noto Sans Hebrew"), + UnicodeBlock::Hiragana | + UnicodeBlock::Katakana | + UnicodeBlock::KatakanaPhoneticExtensions => { + families.push("TakaoPGothic"); + families.push("Noto Sans JP"); + families.push("Noto Sans CJK JP"); + }, + UnicodeBlock::ImperialAramaic => families.push("Noto Sans Imperial Aramaic"), + UnicodeBlock::InscriptionalPahlavi => families.push("Noto Sans Inscriptional Pahlavi"), + UnicodeBlock::InscriptionalParthian => { + families.push("Noto Sans Inscriptional Parthian") + }, + UnicodeBlock::Javanese => families.push("Noto Sans Javanese"), + UnicodeBlock::Kaithi => families.push("Noto Sans Kaithi"), + UnicodeBlock::Kannada => families.push("Noto Sans Kannada"), + UnicodeBlock::KayahLi => families.push("Noto Sans Kayah Li"), + UnicodeBlock::Kharoshthi => families.push("Noto Sans Kharoshthi"), + UnicodeBlock::Khmer | UnicodeBlock::KhmerSymbols => families.push("Noto Sans Khmer"), + UnicodeBlock::Khojki => families.push("Noto Sans Khojki"), + UnicodeBlock::Khudawadi => families.push("Noto Sans Khudawadi"), + UnicodeBlock::Lao => families.push("Noto Sans Lao"), + UnicodeBlock::Lepcha => families.push("Noto Sans Lepcha"), + UnicodeBlock::Limbu => families.push("Noto Sans Limbu"), + UnicodeBlock::LinearA => families.push("Noto Sans Linear A"), + UnicodeBlock::LinearBIdeograms | UnicodeBlock::LinearBSyllabary => { + families.push("Noto Sans Linear B") + }, + UnicodeBlock::Lisu => families.push("Noto Sans Lisu"), + UnicodeBlock::Lycian => families.push("Noto Sans Lycian"), + UnicodeBlock::Lydian => families.push("Noto Sans Lydian"), + UnicodeBlock::Mahajani => families.push("Noto Sans Mahajani"), + UnicodeBlock::Malayalam => families.push("Noto Sans Malayalam"), + UnicodeBlock::Mandaic => families.push("Noto Sans Mandaic"), + UnicodeBlock::Manichaean => families.push("Noto Sans Manichaean"), + UnicodeBlock::Marchen => families.push("Noto Sans Marchen"), + UnicodeBlock::MeeteiMayek | UnicodeBlock::MeeteiMayekExtensions => { + families.push("Noto Sans Meetei Mayek") + }, + UnicodeBlock::MendeKikakui => families.push("Noto Sans Mende Kikakui"), + UnicodeBlock::MeroiticCursive | UnicodeBlock::MeroiticHieroglyphs => { + families.push("Noto Sans Meroitic") + }, + UnicodeBlock::Miao => families.push("Noto Sans Miao"), + UnicodeBlock::Modi => families.push("Noto Sans Modi"), + UnicodeBlock::Mongolian | UnicodeBlock::MongolianSupplement => { + families.push("Noto Sans Mongolian") + }, + UnicodeBlock::Mro => families.push("Noto Sans Mro"), + UnicodeBlock::Multani => families.push("Noto Sans Multani"), + UnicodeBlock::MusicalSymbols => families.push("Noto Music"), + UnicodeBlock::Myanmar | + UnicodeBlock::MyanmarExtendedA | + UnicodeBlock::MyanmarExtendedB => families.push("Noto Sans Myanmar"), + UnicodeBlock::NKo => families.push("Noto Sans NKo"), + UnicodeBlock::Nabataean => families.push("Noto Sans Nabataean"), + UnicodeBlock::NewTaiLue => families.push("Noto Sans New Tai Lue"), + UnicodeBlock::Newa => families.push("Noto Sans Newa"), + UnicodeBlock::Ogham => families.push("Noto Sans Ogham"), + UnicodeBlock::OlChiki => families.push("Noto Sans Ol Chiki"), + UnicodeBlock::OldHungarian => families.push("Noto Sans Old Hungarian"), + UnicodeBlock::OldItalic => families.push("Noto Sans Old Italic"), + UnicodeBlock::OldNorthArabian => families.push("Noto Sans Old North Arabian"), + UnicodeBlock::OldPermic => families.push("Noto Sans Old Permic"), + UnicodeBlock::OldPersian => families.push("Noto Sans Old Persian"), + UnicodeBlock::OldSouthArabian => families.push("Noto Sans Old South Arabian"), + UnicodeBlock::OldTurkic => families.push("Noto Sans Old Turkic"), + UnicodeBlock::Oriya => families.push("Noto Sans Oriya"), + UnicodeBlock::Osage => families.push("Noto Sans Osage"), + UnicodeBlock::Osmanya => families.push("Noto Sans Osmanya"), + UnicodeBlock::PahawhHmong => families.push("Noto Sans Pahawh Hmong"), + UnicodeBlock::Palmyrene => families.push("Noto Sans Palmyrene"), + UnicodeBlock::PauCinHau => families.push("Noto Sans Pau Cin Hau"), + UnicodeBlock::Phagspa => families.push("Noto Sans PhagsPa"), + UnicodeBlock::Phoenician => families.push("Noto Sans Phoenician"), + UnicodeBlock::PsalterPahlavi => families.push("Noto Sans Psalter Pahlavi"), + UnicodeBlock::Rejang => families.push("Noto Sans Rejang"), + UnicodeBlock::Runic => families.push("Noto Sans Runic"), + UnicodeBlock::Samaritan => families.push("Noto Sans Samaritan"), + UnicodeBlock::Saurashtra => families.push("Noto Sans Saurashtra"), + UnicodeBlock::Sharada => families.push("Noto Sans Sharada"), + UnicodeBlock::Shavian => families.push("Noto Sans Shavian"), + UnicodeBlock::Siddham => families.push("Noto Sans Siddham"), + UnicodeBlock::Sinhala | UnicodeBlock::SinhalaArchaicNumbers => { + families.push("Noto Sans Sinhala") + }, + UnicodeBlock::SoraSompeng => families.push("Noto Sans Sora Sompeng"), + UnicodeBlock::Sundanese => families.push("Noto Sans Sundanese"), + UnicodeBlock::SuttonSignWriting => families.push("Noto Sans SignWrit"), + UnicodeBlock::SylotiNagri => families.push("Noto Sans Syloti Nagri"), + UnicodeBlock::Syriac => families.push("Noto Sans Syriac"), + UnicodeBlock::Tagalog => families.push("Noto Sans Tagalog"), + UnicodeBlock::Tagbanwa => families.push("Noto Sans Tagbanwa"), + UnicodeBlock::TaiLe => families.push("Noto Sans Tai Le"), + UnicodeBlock::TaiTham => families.push("Noto Sans Tai Tham"), + UnicodeBlock::TaiViet => families.push("Noto Sans Tai Viet"), + UnicodeBlock::Takri => families.push("Noto Sans Takri"), + UnicodeBlock::Tamil => families.push("Noto Sans Tamil"), + UnicodeBlock::Tangut | + UnicodeBlock::TangutComponents | + UnicodeBlock::IdeographicSymbolsandPunctuation => families.push("Noto Serif Tangut"), + UnicodeBlock::Telugu => families.push("Noto Sans Telugu"), + UnicodeBlock::Thaana => { + families.push("Noto Sans Thaana"); + }, + UnicodeBlock::Thai => families.push("Noto Sans Thai"), + UnicodeBlock::Tibetan => families.push("Noto Serif Tibetan"), + UnicodeBlock::Tifinagh => families.push("Noto Sans Tifinagh"), + UnicodeBlock::Tirhuta => families.push("Noto Sans Tirhuta"), + UnicodeBlock::Ugaritic => families.push("Noto Sans Ugaritic"), + UnicodeBlock::UnifiedCanadianAboriginalSyllabics | + UnicodeBlock::UnifiedCanadianAboriginalSyllabicsExtended => { + families.push("Noto Sans Canadian Aboriginal") + }, + UnicodeBlock::Vai => families.push("Noto Sans Vai"), + UnicodeBlock::WarangCiti => families.push("Noto Sans Warang Citi"), + UnicodeBlock::YiSyllables | UnicodeBlock::YiRadicals => { + families.push("Noto Sans Yi"); + }, + _ => {}, } } + families.push("DejaVu Serif"); + families.push("FreeSerif"); + families.push("DejaVu Sans"); + families.push("DejaVu Sans Mono"); + families.push("FreeSans"); + families.push("Noto Sans Symbols"); + families.push("Noto Sans Symbols2"); + families.push("Symbola"); + families.push("Droid Sans Fallback"); + families } diff --git a/components/gfx/platform/freetype/ohos/font_list.rs b/components/gfx/platform/freetype/ohos/font_list.rs index a9a21d63b7b..9f0d4b5bb42 100644 --- a/components/gfx/platform/freetype/ohos/font_list.rs +++ b/components/gfx/platform/freetype/ohos/font_list.rs @@ -17,6 +17,7 @@ use webrender_api::NativeFontHandle; use crate::font_template::{FontTemplate, FontTemplateDescriptor}; use crate::text::util::is_cjk; +use crate::text::FallbackFontSelectionOptions; lazy_static::lazy_static! { static ref FONT_LIST: FontList = FontList::new(); @@ -195,10 +196,10 @@ pub fn system_default_family(generic_name: &str) -> Option { } // Based on fonts present in OpenHarmony. -pub fn fallback_font_families(codepoint: Option) -> Vec<&'static str> { +pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> { let mut families = vec![]; - if let Some(block) = codepoint.and_then(|c| c.block()) { + if let Some(block) = options.character.block() { match block { UnicodeBlock::Hebrew => { families.push("Noto Sans Hebrew"); @@ -229,7 +230,7 @@ pub fn fallback_font_families(codepoint: Option) -> Vec<&'static str> { }, _ => { - if is_cjk(codepoint.unwrap()) { + if is_cjk(options.character) { families.push("Noto Sans JP"); families.push("Noto Sans KR"); } diff --git a/components/gfx/platform/macos/font.rs b/components/gfx/platform/macos/font.rs index 7ab00b51cd0..0e4617a8360 100644 --- a/components/gfx/platform/macos/font.rs +++ b/components/gfx/platform/macos/font.rs @@ -193,7 +193,6 @@ impl PlatformFontMethods for PlatformFont { can_do_fast_shaping: false, }; handle.h_kern_subtable = handle.find_h_kern_subtable(); - // TODO (#11310): Implement basic support for GPOS and GSUB. handle.can_do_fast_shaping = handle.h_kern_subtable.is_some() && handle.table_for_tag(GPOS).is_none() && handle.table_for_tag(GSUB).is_none(); diff --git a/components/gfx/platform/macos/font_list.rs b/components/gfx/platform/macos/font_list.rs index faeb54af0d4..9815bf8c3b2 100644 --- a/components/gfx/platform/macos/font_list.rs +++ b/components/gfx/platform/macos/font_list.rs @@ -17,6 +17,7 @@ use webrender_api::NativeFontHandle; use crate::font_template::{FontTemplate, FontTemplateDescriptor}; use crate::platform::font::CoreTextFontTraitsMapping; use crate::text::util::unicode_plane; +use crate::text::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 @@ -94,16 +95,14 @@ pub fn system_default_family(_generic_name: &str) -> Option { /// Get the list of fallback fonts given an optional codepoint. This is /// based on `gfxPlatformMac::GetCommonFallbackFonts()` in Gecko from /// . -pub fn fallback_font_families(codepoint: Option) -> Vec<&'static str> { - let mut families = vec!["Lucida Grande"]; - let Some(codepoint) = codepoint else { - families.push("Geneva"); - families.push("Arial Unicode MS"); - return families; - }; +pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> { + let mut families = Vec::new(); + if options.prefer_emoji_presentation { + families.push("Apple Color Emoji"); + } - let script = Script::from(codepoint); - if let Some(block) = codepoint.block() { + let script = Script::from(options.character); + if let Some(block) = options.character.block() { match block { // In most cases, COMMON and INHERITED characters will be merged into // their context, but if they occur without any specific script context @@ -126,7 +125,7 @@ pub fn fallback_font_families(codepoint: Option) -> Vec<&'static str> { _ if matches!(script, Script::Bopomofo | Script::Han) => { // TODO: Need to differentiate between traditional and simplified Han here! families.push("Songti SC"); - if codepoint as u32 > 0x10000 { + if options.character as u32 > 0x10000 { // macOS installations with MS Office may have these -ExtB fonts families.push("SimSun-ExtB"); } @@ -306,17 +305,19 @@ pub fn fallback_font_families(codepoint: Option) -> Vec<&'static str> { } // https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Multilingual_Plane - let unicode_plane = unicode_plane(codepoint); + let unicode_plane = unicode_plane(options.character); if let 1 = unicode_plane { - let b = (codepoint as u32) >> 8; - if b >= 0x1f0 && b < 0x1f7 { - families.push("Apple Color Emoji"); + let b = (options.character as u32) >> 8; + if b == 0x27 { + families.push("Zapf Dingbats"); } + families.push("Geneva"); families.push("Apple Symbols"); families.push("STIXGeneral"); + families.push("Hiragino Sans"); + families.push("Hiragino Kaku Gothic ProN"); } - families.push("Geneva"); families.push("Arial Unicode MS"); families } diff --git a/components/gfx/platform/windows/font_list.rs b/components/gfx/platform/windows/font_list.rs index 5d1f076d6f9..2d5d9dbb016 100644 --- a/components/gfx/platform/windows/font_list.rs +++ b/components/gfx/platform/windows/font_list.rs @@ -14,6 +14,7 @@ use ucd::{Codepoint, UnicodeBlock}; use crate::font_template::{FontTemplate, FontTemplateDescriptor}; use crate::text::util::unicode_plane; +use crate::text::FallbackFontSelectionOptions; pub static SANS_SERIF_FONT_FAMILY: &str = "Arial"; @@ -90,260 +91,263 @@ where } // Based on gfxWindowsPlatform::GetCommonFallbackFonts() in Gecko -pub fn fallback_font_families(codepoint: Option) -> Vec<&'static str> { - let mut families = vec!["Arial"]; +pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> { + let mut families = Vec::new(); - if let Some(codepoint) = codepoint { - match unicode_plane(codepoint) { - // https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane - 0 => { - if let Some(block) = codepoint.block() { - match block { - UnicodeBlock::CyrillicSupplement | - UnicodeBlock::Armenian | - UnicodeBlock::Hebrew => { - families.push("Estrangelo Edessa"); - families.push("Cambria"); - }, + if options.prefer_emoji_presentation { + families.push("Segoe UI Emoji"); + } - UnicodeBlock::Arabic | UnicodeBlock::ArabicSupplement => { - families.push("Microsoft Uighur"); - }, + families.push("Arial"); + match unicode_plane(options.character) { + // https://en.wikipedia.org/wiki/Plane_(Unicode)#Basic_Multilingual_Plane + 0 => { + if let Some(block) = options.character.block() { + match block { + UnicodeBlock::CyrillicSupplement | + UnicodeBlock::Armenian | + UnicodeBlock::Hebrew => { + families.push("Estrangelo Edessa"); + families.push("Cambria"); + }, - UnicodeBlock::Syriac => { - families.push("Estrangelo Edessa"); - }, + UnicodeBlock::Arabic | UnicodeBlock::ArabicSupplement => { + families.push("Microsoft Uighur"); + }, - UnicodeBlock::Thaana => { - families.push("MV Boli"); - }, + UnicodeBlock::Syriac => { + families.push("Estrangelo Edessa"); + }, - UnicodeBlock::NKo => { - families.push("Ebrima"); - }, + UnicodeBlock::Thaana => { + families.push("MV Boli"); + }, - UnicodeBlock::Devanagari | UnicodeBlock::Bengali => { - families.push("Nirmala UI"); - families.push("Utsaah"); - families.push("Aparajita"); - }, + UnicodeBlock::NKo => { + families.push("Ebrima"); + }, - UnicodeBlock::Gurmukhi | - UnicodeBlock::Gujarati | - UnicodeBlock::Oriya | - UnicodeBlock::Tamil | - UnicodeBlock::Telugu | - UnicodeBlock::Kannada | - UnicodeBlock::Malayalam | - UnicodeBlock::Sinhala | - UnicodeBlock::Lepcha | - UnicodeBlock::OlChiki | - UnicodeBlock::CyrillicExtendedC | - UnicodeBlock::SundaneseSupplement | - UnicodeBlock::VedicExtensions => { - families.push("Nirmala UI"); - }, + UnicodeBlock::Devanagari | UnicodeBlock::Bengali => { + families.push("Nirmala UI"); + families.push("Utsaah"); + families.push("Aparajita"); + }, - UnicodeBlock::Thai => { - families.push("Leelawadee UI"); - }, + UnicodeBlock::Gurmukhi | + UnicodeBlock::Gujarati | + UnicodeBlock::Oriya | + UnicodeBlock::Tamil | + UnicodeBlock::Telugu | + UnicodeBlock::Kannada | + UnicodeBlock::Malayalam | + UnicodeBlock::Sinhala | + UnicodeBlock::Lepcha | + UnicodeBlock::OlChiki | + UnicodeBlock::CyrillicExtendedC | + UnicodeBlock::SundaneseSupplement | + UnicodeBlock::VedicExtensions => { + families.push("Nirmala UI"); + }, - UnicodeBlock::Lao => { - families.push("Lao UI"); - }, + UnicodeBlock::Thai => { + families.push("Leelawadee UI"); + }, - UnicodeBlock::Myanmar | - UnicodeBlock::MyanmarExtendedA | - UnicodeBlock::MyanmarExtendedB => { - families.push("Myanmar Text"); - }, + UnicodeBlock::Lao => { + families.push("Lao UI"); + }, - UnicodeBlock::HangulJamo | - UnicodeBlock::HangulJamoExtendedA | - UnicodeBlock::HangulSyllables | - UnicodeBlock::HangulJamoExtendedB | - UnicodeBlock::HangulCompatibilityJamo => { - families.push("Malgun Gothic"); - }, + UnicodeBlock::Myanmar | + UnicodeBlock::MyanmarExtendedA | + UnicodeBlock::MyanmarExtendedB => { + families.push("Myanmar Text"); + }, - UnicodeBlock::Ethiopic | - UnicodeBlock::EthiopicSupplement | - UnicodeBlock::EthiopicExtended | - UnicodeBlock::EthiopicExtendedA => { - families.push("Nyala"); - }, + UnicodeBlock::HangulJamo | + UnicodeBlock::HangulJamoExtendedA | + UnicodeBlock::HangulSyllables | + UnicodeBlock::HangulJamoExtendedB | + UnicodeBlock::HangulCompatibilityJamo => { + families.push("Malgun Gothic"); + }, - UnicodeBlock::Cherokee => { - families.push("Plantagenet Cherokee"); - }, + UnicodeBlock::Ethiopic | + UnicodeBlock::EthiopicSupplement | + UnicodeBlock::EthiopicExtended | + UnicodeBlock::EthiopicExtendedA => { + families.push("Nyala"); + }, - UnicodeBlock::UnifiedCanadianAboriginalSyllabics | - UnicodeBlock::UnifiedCanadianAboriginalSyllabicsExtended => { - families.push("Euphemia"); - families.push("Segoe UI"); - }, + UnicodeBlock::Cherokee => { + families.push("Plantagenet Cherokee"); + }, - UnicodeBlock::Khmer | UnicodeBlock::KhmerSymbols => { - families.push("Khmer UI"); - families.push("Leelawadee UI"); - }, + UnicodeBlock::UnifiedCanadianAboriginalSyllabics | + UnicodeBlock::UnifiedCanadianAboriginalSyllabicsExtended => { + families.push("Euphemia"); + families.push("Segoe UI"); + }, - UnicodeBlock::Mongolian => { - families.push("Mongolian Baiti"); - }, + UnicodeBlock::Khmer | UnicodeBlock::KhmerSymbols => { + families.push("Khmer UI"); + families.push("Leelawadee UI"); + }, - UnicodeBlock::TaiLe => { - families.push("Microsoft Tai Le"); - }, + UnicodeBlock::Mongolian => { + families.push("Mongolian Baiti"); + }, - UnicodeBlock::NewTaiLue => { - families.push("Microsoft New Tai Lue"); - }, + UnicodeBlock::TaiLe => { + families.push("Microsoft Tai Le"); + }, - UnicodeBlock::Buginese | - UnicodeBlock::TaiTham | - UnicodeBlock::CombiningDiacriticalMarksExtended => { - families.push("Leelawadee UI"); - }, + UnicodeBlock::NewTaiLue => { + families.push("Microsoft New Tai Lue"); + }, - UnicodeBlock::GeneralPunctuation | - UnicodeBlock::SuperscriptsandSubscripts | - UnicodeBlock::CurrencySymbols | - UnicodeBlock::CombiningDiacriticalMarksforSymbols | - UnicodeBlock::LetterlikeSymbols | - UnicodeBlock::NumberForms | - UnicodeBlock::Arrows | - UnicodeBlock::MathematicalOperators | - UnicodeBlock::MiscellaneousTechnical | - UnicodeBlock::ControlPictures | - UnicodeBlock::OpticalCharacterRecognition | - UnicodeBlock::EnclosedAlphanumerics | - UnicodeBlock::BoxDrawing | - UnicodeBlock::BlockElements | - UnicodeBlock::GeometricShapes | - UnicodeBlock::MiscellaneousSymbols | - UnicodeBlock::Dingbats | - UnicodeBlock::MiscellaneousMathematicalSymbolsA | - UnicodeBlock::SupplementalArrowsA | - UnicodeBlock::SupplementalArrowsB | - UnicodeBlock::MiscellaneousMathematicalSymbolsB | - UnicodeBlock::SupplementalMathematicalOperators | - UnicodeBlock::MiscellaneousSymbolsandArrows | - UnicodeBlock::Glagolitic | - UnicodeBlock::LatinExtendedC | - UnicodeBlock::Coptic => { - families.push("Segoe UI"); - families.push("Segoe UI Symbol"); - families.push("Cambria"); - families.push("Meiryo"); - families.push("Lucida Sans Unicode"); - families.push("Ebrima"); - }, + UnicodeBlock::Buginese | + UnicodeBlock::TaiTham | + UnicodeBlock::CombiningDiacriticalMarksExtended => { + families.push("Leelawadee UI"); + }, - UnicodeBlock::GeorgianSupplement | - UnicodeBlock::Tifinagh | - UnicodeBlock::CyrillicExtendedA | - UnicodeBlock::SupplementalPunctuation | - UnicodeBlock::CJKRadicalsSupplement | - UnicodeBlock::KangxiRadicals | - UnicodeBlock::IdeographicDescriptionCharacters => { - families.push("Segoe UI"); - families.push("Segoe UI Symbol"); - families.push("Meiryo"); - }, + UnicodeBlock::GeneralPunctuation | + UnicodeBlock::SuperscriptsandSubscripts | + UnicodeBlock::CurrencySymbols | + UnicodeBlock::CombiningDiacriticalMarksforSymbols | + UnicodeBlock::LetterlikeSymbols | + UnicodeBlock::NumberForms | + UnicodeBlock::Arrows | + UnicodeBlock::MathematicalOperators | + UnicodeBlock::MiscellaneousTechnical | + UnicodeBlock::ControlPictures | + UnicodeBlock::OpticalCharacterRecognition | + UnicodeBlock::EnclosedAlphanumerics | + UnicodeBlock::BoxDrawing | + UnicodeBlock::BlockElements | + UnicodeBlock::GeometricShapes | + UnicodeBlock::MiscellaneousSymbols | + UnicodeBlock::Dingbats | + UnicodeBlock::MiscellaneousMathematicalSymbolsA | + UnicodeBlock::SupplementalArrowsA | + UnicodeBlock::SupplementalArrowsB | + UnicodeBlock::MiscellaneousMathematicalSymbolsB | + UnicodeBlock::SupplementalMathematicalOperators | + UnicodeBlock::MiscellaneousSymbolsandArrows | + UnicodeBlock::Glagolitic | + UnicodeBlock::LatinExtendedC | + UnicodeBlock::Coptic => { + families.push("Segoe UI"); + families.push("Segoe UI Symbol"); + families.push("Cambria"); + families.push("Meiryo"); + families.push("Lucida Sans Unicode"); + families.push("Ebrima"); + }, - UnicodeBlock::BraillePatterns => { - families.push("Segoe UI Symbol"); - }, + UnicodeBlock::GeorgianSupplement | + UnicodeBlock::Tifinagh | + UnicodeBlock::CyrillicExtendedA | + UnicodeBlock::SupplementalPunctuation | + UnicodeBlock::CJKRadicalsSupplement | + UnicodeBlock::KangxiRadicals | + UnicodeBlock::IdeographicDescriptionCharacters => { + families.push("Segoe UI"); + families.push("Segoe UI Symbol"); + families.push("Meiryo"); + }, - UnicodeBlock::CJKSymbolsandPunctuation | - UnicodeBlock::Hiragana | - UnicodeBlock::Katakana | - UnicodeBlock::Bopomofo | - UnicodeBlock::Kanbun | - UnicodeBlock::BopomofoExtended | - UnicodeBlock::CJKStrokes | - UnicodeBlock::KatakanaPhoneticExtensions | - UnicodeBlock::CJKUnifiedIdeographs => { - families.push("Microsoft YaHei"); - families.push("Yu Gothic"); - }, + UnicodeBlock::BraillePatterns => { + families.push("Segoe UI Symbol"); + }, - UnicodeBlock::EnclosedCJKLettersandMonths => { - families.push("Malgun Gothic"); - }, + UnicodeBlock::CJKSymbolsandPunctuation | + UnicodeBlock::Hiragana | + UnicodeBlock::Katakana | + UnicodeBlock::Bopomofo | + UnicodeBlock::Kanbun | + UnicodeBlock::BopomofoExtended | + UnicodeBlock::CJKStrokes | + UnicodeBlock::KatakanaPhoneticExtensions | + UnicodeBlock::CJKUnifiedIdeographs => { + families.push("Microsoft YaHei"); + families.push("Yu Gothic"); + }, - UnicodeBlock::YijingHexagramSymbols => { - families.push("Segoe UI Symbol"); - }, + UnicodeBlock::EnclosedCJKLettersandMonths => { + families.push("Malgun Gothic"); + }, - UnicodeBlock::YiSyllables | UnicodeBlock::YiRadicals => { - families.push("Microsoft Yi Baiti"); - families.push("Segoe UI"); - }, + UnicodeBlock::YijingHexagramSymbols => { + families.push("Segoe UI Symbol"); + }, - UnicodeBlock::Vai | - UnicodeBlock::CyrillicExtendedB | - UnicodeBlock::Bamum | - UnicodeBlock::ModifierToneLetters | - UnicodeBlock::LatinExtendedD => { - families.push("Ebrima"); - families.push("Segoe UI"); - families.push("Cambria Math"); - }, + UnicodeBlock::YiSyllables | UnicodeBlock::YiRadicals => { + families.push("Microsoft Yi Baiti"); + families.push("Segoe UI"); + }, - UnicodeBlock::SylotiNagri | - UnicodeBlock::CommonIndicNumberForms | - UnicodeBlock::Phagspa | - UnicodeBlock::Saurashtra | - UnicodeBlock::DevanagariExtended => { - families.push("Microsoft PhagsPa"); - families.push("Nirmala UI"); - }, + UnicodeBlock::Vai | + UnicodeBlock::CyrillicExtendedB | + UnicodeBlock::Bamum | + UnicodeBlock::ModifierToneLetters | + UnicodeBlock::LatinExtendedD => { + families.push("Ebrima"); + families.push("Segoe UI"); + families.push("Cambria Math"); + }, - UnicodeBlock::KayahLi | UnicodeBlock::Rejang | UnicodeBlock::Javanese => { - families.push("Malgun Gothic"); - families.push("Javanese Text"); - families.push("Leelawadee UI"); - }, + UnicodeBlock::SylotiNagri | + UnicodeBlock::CommonIndicNumberForms | + UnicodeBlock::Phagspa | + UnicodeBlock::Saurashtra | + UnicodeBlock::DevanagariExtended => { + families.push("Microsoft PhagsPa"); + families.push("Nirmala UI"); + }, - UnicodeBlock::AlphabeticPresentationForms => { - families.push("Microsoft Uighur"); - families.push("Gabriola"); - families.push("Sylfaen"); - }, + UnicodeBlock::KayahLi | UnicodeBlock::Rejang | UnicodeBlock::Javanese => { + families.push("Malgun Gothic"); + families.push("Javanese Text"); + families.push("Leelawadee UI"); + }, - UnicodeBlock::ArabicPresentationFormsA | - UnicodeBlock::ArabicPresentationFormsB => { - families.push("Traditional Arabic"); - families.push("Arabic Typesetting"); - }, + UnicodeBlock::AlphabeticPresentationForms => { + families.push("Microsoft Uighur"); + families.push("Gabriola"); + families.push("Sylfaen"); + }, - UnicodeBlock::VariationSelectors | - UnicodeBlock::VerticalForms | - UnicodeBlock::CombiningHalfMarks | - UnicodeBlock::CJKCompatibilityForms | - UnicodeBlock::SmallFormVariants | - UnicodeBlock::HalfwidthandFullwidthForms | - UnicodeBlock::Specials => { - families.push("Microsoft JhengHei"); - }, + UnicodeBlock::ArabicPresentationFormsA | + UnicodeBlock::ArabicPresentationFormsB => { + families.push("Traditional Arabic"); + families.push("Arabic Typesetting"); + }, - _ => {}, - } + UnicodeBlock::VariationSelectors | + UnicodeBlock::VerticalForms | + UnicodeBlock::CombiningHalfMarks | + UnicodeBlock::CJKCompatibilityForms | + UnicodeBlock::SmallFormVariants | + UnicodeBlock::HalfwidthandFullwidthForms | + UnicodeBlock::Specials => { + families.push("Microsoft JhengHei"); + }, + + _ => {}, } - }, + } + }, - // https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Multilingual_Plane - 1 => { - families.push("Segoe UI Symbol"); - families.push("Ebrima"); - families.push("Nirmala UI"); - families.push("Cambria Math"); - }, + // https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Multilingual_Plane + 1 => { + families.push("Segoe UI Symbol"); + families.push("Ebrima"); + families.push("Nirmala UI"); + families.push("Cambria Math"); + }, - _ => {}, - } + _ => {}, } families.push("Arial Unicode MS"); diff --git a/components/gfx/tests/font_context.rs b/components/gfx/tests/font_context.rs index dc61775db18..85f3159bbd5 100644 --- a/components/gfx/tests/font_context.rs +++ b/components/gfx/tests/font_context.rs @@ -17,6 +17,7 @@ use gfx::font_cache_thread::{CSSFontFaceDescriptors, FontIdentifier, FontSource} use gfx::font_context::FontContext; use gfx::font_store::FontTemplates; use gfx::font_template::{FontTemplate, FontTemplateRef}; +use gfx::text::FallbackFontSelectionOptions; use ipc_channel::ipc; use net_traits::ResourceThreads; use servo_arc::Arc; @@ -53,7 +54,10 @@ impl MockFontCacheThread { let mut families = HashMap::new(); families.insert("CSSTest ASCII".to_owned(), csstest_ascii); families.insert("CSSTest Basic".to_owned(), csstest_basic); - families.insert(fallback_font_families(None)[0].to_owned(), fallback); + families.insert( + fallback_font_families(FallbackFontSelectionOptions::default())[0].to_owned(), + fallback, + ); MockFontCacheThread { families: RefCell::new(families), @@ -211,7 +215,10 @@ fn test_font_group_find_by_codepoint() { let group = context.font_group(Arc::new(style)); - let font = group.write().find_by_codepoint(&mut context, 'a').unwrap(); + let font = group + .write() + .find_by_codepoint(&mut context, 'a', None) + .unwrap(); assert_eq!( font.identifier(), MockFontCacheThread::identifier_for_font_name("csstest-ascii") @@ -222,7 +229,10 @@ fn test_font_group_find_by_codepoint() { "only the first font in the list should have been loaded" ); - let font = group.write().find_by_codepoint(&mut context, 'a').unwrap(); + let font = group + .write() + .find_by_codepoint(&mut context, 'a', None) + .unwrap(); assert_eq!( font.identifier(), MockFontCacheThread::identifier_for_font_name("csstest-ascii") @@ -233,7 +243,10 @@ fn test_font_group_find_by_codepoint() { "we shouldn't load the same font a second time" ); - let font = group.write().find_by_codepoint(&mut context, 'á').unwrap(); + let font = group + .write() + .find_by_codepoint(&mut context, 'á', None) + .unwrap(); assert_eq!( font.identifier(), MockFontCacheThread::identifier_for_font_name("csstest-basic-regular") @@ -251,14 +264,20 @@ fn test_font_fallback() { let group = context.font_group(Arc::new(style)); - let font = group.write().find_by_codepoint(&mut context, 'a').unwrap(); + let font = group + .write() + .find_by_codepoint(&mut context, 'a', None) + .unwrap(); assert_eq!( font.identifier(), MockFontCacheThread::identifier_for_font_name("csstest-ascii"), "a family in the group should be used if there is a matching glyph" ); - let font = group.write().find_by_codepoint(&mut context, 'á').unwrap(); + let font = group + .write() + .find_by_codepoint(&mut context, 'á', None) + .unwrap(); assert_eq!( font.identifier(), MockFontCacheThread::identifier_for_font_name("csstest-basic-regular"), diff --git a/components/gfx/text/mod.rs b/components/gfx/text/mod.rs index b797f78dc03..bd8707615ba 100644 --- a/components/gfx/text/mod.rs +++ b/components/gfx/text/mod.rs @@ -2,6 +2,8 @@ * 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}; + pub use crate::text::shaping::Shaper; pub use crate::text::text_run::TextRun; @@ -9,3 +11,32 @@ pub mod glyph; pub mod shaping; pub mod text_run; pub mod util; + +#[derive(Clone, Copy, Debug)] +pub struct FallbackFontSelectionOptions { + pub character: char, + pub prefer_emoji_presentation: bool, +} + +impl Default for FallbackFontSelectionOptions { + fn default() -> Self { + Self { + character: ' ', + prefer_emoji_presentation: false, + } + } +} + +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(), + }; + Self { + character, + prefer_emoji_presentation, + } + } +} diff --git a/components/layout/text.rs b/components/layout/text.rs index 2875ccd0d77..f774d3d1ffa 100644 --- a/components/layout/text.rs +++ b/components/layout/text.rs @@ -204,7 +204,7 @@ impl TextRunScanner { .unwrap_or_else(|| { let space_width = font_group .write() - .find_by_codepoint(font_context, ' ') + .find_by_codepoint(font_context, ' ', None) .and_then(|font| { font.glyph_index(' ') .map(|glyph_id| font.glyph_h_advance(glyph_id)) @@ -246,9 +246,10 @@ impl TextRunScanner { let (mut start_position, mut end_position) = (0, 0); for (byte_index, character) in text.char_indices() { if !character.is_control() { - let font = font_group - .write() - .find_by_codepoint(font_context, character); + let font = + font_group + .write() + .find_by_codepoint(font_context, character, None); let bidi_level = match bidi_levels { Some(levels) => levels[*paragraph_bytes_processed], diff --git a/components/layout_2020/flow/text_run.rs b/components/layout_2020/flow/text_run.rs index e82d1a6159b..0ef5ee011fd 100644 --- a/components/layout_2020/flow/text_run.rs +++ b/components/layout_2020/flow/text_run.rs @@ -316,10 +316,11 @@ impl TextRun { } else { Box::new(collapsed) }; + let char_iterator = TwoCharsAtATimeIterator::new(char_iterator); let mut next_byte_index = 0; let text = char_iterator - .map(|character| { + .map(|(character, next_character)| { let current_byte_index = next_byte_index; next_byte_index += character.len_utf8(); @@ -338,10 +339,11 @@ impl TextRun { return character; } - let font = match font_group - .write() - .find_by_codepoint(font_context, character) - { + let font = match font_group.write().find_by_codepoint( + font_context, + character, + next_character, + ) { Some(font) => font, None => return character, }; @@ -791,3 +793,40 @@ fn capitalize_string(string: &str, allow_word_at_start: bool) -> String { output_string } + +pub struct TwoCharsAtATimeIterator { + /// The input character iterator. + iterator: InputIterator, + /// The first character to produce in the next run of the iterator. + next_character: Option, +} + +impl TwoCharsAtATimeIterator { + fn new(iterator: InputIterator) -> Self { + Self { + iterator, + next_character: None, + } + } +} + +impl Iterator for TwoCharsAtATimeIterator +where + InputIterator: Iterator, +{ + type Item = (char, Option); + + fn next(&mut self) -> Option { + // If the iterator isn't initialized do that now. + if self.next_character.is_none() { + self.next_character = self.iterator.next(); + } + + let Some(character) = self.next_character else { + return None; + }; + + self.next_character = self.iterator.next(); + return Some((character, self.next_character.clone())); + } +} diff --git a/components/layout_thread_2020/lib.rs b/components/layout_thread_2020/lib.rs index 4d518d78d77..c581ab5f997 100644 --- a/components/layout_thread_2020/lib.rs +++ b/components/layout_thread_2020/lib.rs @@ -1218,7 +1218,7 @@ impl FontMetricsProvider for LayoutFontMetricsProvider { .or_else(|| { font_group .write() - .find_by_codepoint(font_context, '0')? + .find_by_codepoint(font_context, '0', None)? .metrics .zero_horizontal_advance }) @@ -1229,7 +1229,7 @@ impl FontMetricsProvider for LayoutFontMetricsProvider { .or_else(|| { font_group .write() - .find_by_codepoint(font_context, '\u{6C34}')? + .find_by_codepoint(font_context, '\u{6C34}', None)? .metrics .ic_horizontal_advance })