fonts: Improve font fallback (#32286)

- Better detect situations where emoji is necessary by looking ahead one
  character while laying out. This allow processing Unicode presentation
  selectors. When detecting emoji, put emoji fonts at the front of
  fallback lists for all platforms.

  This enables monochrome emoji on Windows. Full-color emoji on Windows
  probably needs full support for processing the COLR table and drawing
  separate glyph color layers.

- Improve the font fallback list on FreeType platforms. Ideally, Servo
  would be able to look through the entire font list to find the best
  font for a certain character, but until that time we can make sure the
  font list contains the "Noto Sans" fonts which cover most situations.

Fixes #31664.
Fixes #12944.
This commit is contained in:
Martin Robinson 2024-05-27 12:02:26 +02:00 committed by GitHub
parent 5f0866379a
commit 43a3c9c319
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 610 additions and 292 deletions

View file

@ -32,7 +32,7 @@ use crate::platform::font::{FontTable, PlatformFont};
pub use crate::platform::font_list::fallback_font_families; pub use crate::platform::font_list::fallback_font_families;
use crate::text::glyph::{ByteIndex, GlyphData, GlyphId, GlyphStore}; use crate::text::glyph::{ByteIndex, GlyphData, GlyphId, GlyphStore};
use crate::text::shaping::ShaperMethods; use crate::text::shaping::ShaperMethods;
use crate::text::Shaper; use crate::text::{FallbackFontSelectionOptions, Shaper};
#[macro_export] #[macro_export]
macro_rules! ot_tag { macro_rules! ot_tag {
@ -487,9 +487,12 @@ impl FontGroup {
&mut self, &mut self,
font_context: &FontContext<S>, font_context: &FontContext<S>,
codepoint: char, codepoint: char,
next_codepoint: Option<char>,
) -> Option<FontRef> { ) -> Option<FontRef> {
let options = FallbackFontSelectionOptions::new(codepoint, next_codepoint);
let should_look_for_small_caps = self.descriptor.variant == font_variant_caps::T::SmallCaps && 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| { let font_or_synthesized_small_caps = |font: FontRef| {
if should_look_for_small_caps && font.synthesized_small_caps.is_some() { if should_look_for_small_caps && font.synthesized_small_caps.is_some() {
return font.synthesized_small_caps.clone(); return font.synthesized_small_caps.clone();
@ -497,9 +500,9 @@ impl FontGroup {
Some(font) 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 = 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) { if let Some(font) = self.find(font_context, char_in_template, glyph_in_font) {
return font_or_synthesized_small_caps(font); return font_or_synthesized_small_caps(font);
@ -513,12 +516,9 @@ impl FontGroup {
} }
} }
if let Some(font) = self.find_fallback( if let Some(font) =
font_context, self.find_fallback(font_context, options, char_in_template, glyph_in_font)
Some(codepoint), {
char_in_template,
glyph_in_font,
) {
self.last_matching_fallback = Some(font.clone()); self.last_matching_fallback = Some(font.clone());
return font_or_synthesized_small_caps(font); 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 space_in_template = |template: FontTemplateRef| template.char_in_unicode_range(' ');
let font_predicate = |_: &FontRef| true; let font_predicate = |_: &FontRef| true;
self.find(font_context, space_in_template, font_predicate) 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`. /// Attempts to find a font which matches the given `template_predicate` and `font_predicate`.
@ -576,7 +583,7 @@ impl FontGroup {
fn find_fallback<S, TemplatePredicate, FontPredicate>( fn find_fallback<S, TemplatePredicate, FontPredicate>(
&mut self, &mut self,
font_context: &FontContext<S>, font_context: &FontContext<S>,
codepoint: Option<char>, options: FallbackFontSelectionOptions,
template_predicate: TemplatePredicate, template_predicate: TemplatePredicate,
font_predicate: FontPredicate, font_predicate: FontPredicate,
) -> Option<FontRef> ) -> Option<FontRef>
@ -586,7 +593,7 @@ impl FontGroup {
FontPredicate: Fn(&FontRef) -> bool, FontPredicate: Fn(&FontRef) -> bool,
{ {
iter::once(FontFamilyDescriptor::serif()) 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) FontFamilyDescriptor::new(FontFamilyName::from(family), FontSearchScope::Local)
})) }))
.filter_map(|family_descriptor| { .filter_map(|family_descriptor| {

View file

@ -18,6 +18,7 @@ use ucd::{Codepoint, UnicodeBlock};
use super::xml::{Attribute, Node}; use super::xml::{Attribute, Node};
use crate::font_template::{FontTemplate, FontTemplateDescriptor}; use crate::font_template::{FontTemplate, FontTemplateDescriptor};
use crate::text::util::is_cjk; use crate::text::util::is_cjk;
use crate::text::FallbackFontSelectionOptions;
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref FONT_LIST: FontList = FontList::new(); static ref FONT_LIST: FontList = FontList::new();
@ -524,10 +525,10 @@ pub fn system_default_family(generic_name: &str) -> Option<String> {
} }
// Based on gfxAndroidPlatform::GetCommonFallbackFonts() in Gecko // Based on gfxAndroidPlatform::GetCommonFallbackFonts() in Gecko
pub fn fallback_font_families(codepoint: Option<char>) -> Vec<&'static str> { pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> {
let mut families = vec![]; let mut families = vec![];
if let Some(block) = codepoint.and_then(|c| c.block()) { if let Some(block) = options.character.block() {
match block { match block {
UnicodeBlock::Armenian => { UnicodeBlock::Armenian => {
families.push("Droid Sans Armenian"); families.push("Droid Sans Armenian");
@ -565,7 +566,7 @@ pub fn fallback_font_families(codepoint: Option<char>) -> Vec<&'static str> {
}, },
_ => { _ => {
if is_cjk(codepoint.unwrap()) { if is_cjk(options.character) {
families.push("MotoyaLMaru"); families.push("MotoyaLMaru");
families.push("Noto Sans CJK JP"); families.push("Noto Sans CJK JP");
families.push("Droid Sans Japanese"); families.push("Droid Sans Japanese");

View file

@ -28,12 +28,13 @@ use malloc_size_of_derive::MallocSizeOf;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use style::values::computed::{FontStretch, FontStyle, FontWeight}; use style::values::computed::{FontStretch, FontStyle, FontWeight};
use style::Atom; use style::Atom;
use unicode_properties::UnicodeEmoji; use ucd::{Codepoint, UnicodeBlock};
use unicode_script::Script;
use super::c_str_to_string; use super::c_str_to_string;
use crate::font::map_platform_values_to_style_values; use crate::font::map_platform_values_to_style_values;
use crate::font_template::{FontTemplate, FontTemplateDescriptor}; 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. /// An identifier for a local font on systems using Freetype.
#[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] #[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
@ -200,33 +201,247 @@ pub fn system_default_family(generic_name: &str) -> Option<String> {
pub static SANS_SERIF_FONT_FAMILY: &str = "DejaVu Sans"; pub static SANS_SERIF_FONT_FAMILY: &str = "DejaVu Sans";
// Based on gfxPlatformGtk::GetCommonFallbackFonts() in Gecko // Based on gfxPlatformGtk::GetCommonFallbackFonts() in Gecko
pub fn fallback_font_families(codepoint: Option<char>) -> Vec<&'static str> { pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> {
let mut families = Vec::new(); let mut families = Vec::new();
if options.prefer_emoji_presentation {
if codepoint.map_or(false, |codepoint| codepoint.is_emoji_char()) {
families.push("Noto Color Emoji"); families.push("Noto Color Emoji");
} }
families.extend(["DejaVu Serif", "FreeSerif", "DejaVu Sans", "FreeSans"]);
if let Some(codepoint) = codepoint { let add_chinese_families = |families: &mut Vec<&str>| {
if is_cjk(codepoint) { // TODO: Need to differentiate between traditional and simplified Han here!
families.push("TakaoPGothic"); families.push("Noto Sans CJK HK");
families.push("Droid Sans Fallback"); families.push("Noto Sans CJK SC");
families.push("WenQuanYi Micro Hei"); families.push("Noto Sans CJK TC");
families.push("NanumGothic"); families.push("Noto Sans HK");
families.push("Noto Sans CJK HK"); families.push("Noto Sans SC");
families.push("Noto Sans CJK JP"); families.push("Noto Sans TC");
families.push("Noto Sans CJK KR"); families.push("WenQuanYi Micro Hei");
families.push("Noto Sans CJK SC"); };
families.push("Noto Sans CJK TC");
families.push("Noto Sans HK"); match Script::from(options.character) {
families.push("Noto Sans JP"); // In most cases, COMMON and INHERITED characters will be merged into
families.push("Noto Sans KR"); // their context, but if they occur without any specific script context
families.push("Noto Sans SC"); // we'll just try common default fonts here.
families.push("Noto Sans TC"); 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 families
} }

View file

@ -17,6 +17,7 @@ use webrender_api::NativeFontHandle;
use crate::font_template::{FontTemplate, FontTemplateDescriptor}; use crate::font_template::{FontTemplate, FontTemplateDescriptor};
use crate::text::util::is_cjk; use crate::text::util::is_cjk;
use crate::text::FallbackFontSelectionOptions;
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref FONT_LIST: FontList = FontList::new(); static ref FONT_LIST: FontList = FontList::new();
@ -195,10 +196,10 @@ pub fn system_default_family(generic_name: &str) -> Option<String> {
} }
// Based on fonts present in OpenHarmony. // Based on fonts present in OpenHarmony.
pub fn fallback_font_families(codepoint: Option<char>) -> Vec<&'static str> { pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> {
let mut families = vec![]; let mut families = vec![];
if let Some(block) = codepoint.and_then(|c| c.block()) { if let Some(block) = options.character.block() {
match block { match block {
UnicodeBlock::Hebrew => { UnicodeBlock::Hebrew => {
families.push("Noto Sans Hebrew"); families.push("Noto Sans Hebrew");
@ -229,7 +230,7 @@ pub fn fallback_font_families(codepoint: Option<char>) -> Vec<&'static str> {
}, },
_ => { _ => {
if is_cjk(codepoint.unwrap()) { if is_cjk(options.character) {
families.push("Noto Sans JP"); families.push("Noto Sans JP");
families.push("Noto Sans KR"); families.push("Noto Sans KR");
} }

View file

@ -193,7 +193,6 @@ impl PlatformFontMethods for PlatformFont {
can_do_fast_shaping: false, can_do_fast_shaping: false,
}; };
handle.h_kern_subtable = handle.find_h_kern_subtable(); 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.can_do_fast_shaping = handle.h_kern_subtable.is_some() &&
handle.table_for_tag(GPOS).is_none() && handle.table_for_tag(GPOS).is_none() &&
handle.table_for_tag(GSUB).is_none(); handle.table_for_tag(GSUB).is_none();

View file

@ -17,6 +17,7 @@ use webrender_api::NativeFontHandle;
use crate::font_template::{FontTemplate, FontTemplateDescriptor}; use crate::font_template::{FontTemplate, FontTemplateDescriptor};
use crate::platform::font::CoreTextFontTraitsMapping; use crate::platform::font::CoreTextFontTraitsMapping;
use crate::text::util::unicode_plane; 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 /// 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 /// 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<String> {
/// Get the list of fallback fonts given an optional codepoint. This is /// Get the list of fallback fonts given an optional codepoint. This is
/// based on `gfxPlatformMac::GetCommonFallbackFonts()` in Gecko from /// based on `gfxPlatformMac::GetCommonFallbackFonts()` in Gecko from
/// <https://searchfox.org/mozilla-central/source/gfx/thebes/gfxPlatformMac.cpp>. /// <https://searchfox.org/mozilla-central/source/gfx/thebes/gfxPlatformMac.cpp>.
pub fn fallback_font_families(codepoint: Option<char>) -> Vec<&'static str> { pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> {
let mut families = vec!["Lucida Grande"]; let mut families = Vec::new();
let Some(codepoint) = codepoint else { if options.prefer_emoji_presentation {
families.push("Geneva"); families.push("Apple Color Emoji");
families.push("Arial Unicode MS"); }
return families;
};
let script = Script::from(codepoint); let script = Script::from(options.character);
if let Some(block) = codepoint.block() { if let Some(block) = options.character.block() {
match block { match block {
// In most cases, COMMON and INHERITED characters will be merged into // In most cases, COMMON and INHERITED characters will be merged into
// their context, but if they occur without any specific script context // their context, but if they occur without any specific script context
@ -126,7 +125,7 @@ pub fn fallback_font_families(codepoint: Option<char>) -> Vec<&'static str> {
_ if matches!(script, Script::Bopomofo | Script::Han) => { _ if matches!(script, Script::Bopomofo | Script::Han) => {
// TODO: Need to differentiate between traditional and simplified Han here! // TODO: Need to differentiate between traditional and simplified Han here!
families.push("Songti SC"); 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 // macOS installations with MS Office may have these -ExtB fonts
families.push("SimSun-ExtB"); families.push("SimSun-ExtB");
} }
@ -306,17 +305,19 @@ pub fn fallback_font_families(codepoint: Option<char>) -> Vec<&'static str> {
} }
// https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Multilingual_Plane // 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 { if let 1 = unicode_plane {
let b = (codepoint as u32) >> 8; let b = (options.character as u32) >> 8;
if b >= 0x1f0 && b < 0x1f7 { if b == 0x27 {
families.push("Apple Color Emoji"); families.push("Zapf Dingbats");
} }
families.push("Geneva");
families.push("Apple Symbols"); families.push("Apple Symbols");
families.push("STIXGeneral"); families.push("STIXGeneral");
families.push("Hiragino Sans");
families.push("Hiragino Kaku Gothic ProN");
} }
families.push("Geneva");
families.push("Arial Unicode MS"); families.push("Arial Unicode MS");
families families
} }

View file

@ -14,6 +14,7 @@ use ucd::{Codepoint, UnicodeBlock};
use crate::font_template::{FontTemplate, FontTemplateDescriptor}; use crate::font_template::{FontTemplate, FontTemplateDescriptor};
use crate::text::util::unicode_plane; use crate::text::util::unicode_plane;
use crate::text::FallbackFontSelectionOptions;
pub static SANS_SERIF_FONT_FAMILY: &str = "Arial"; pub static SANS_SERIF_FONT_FAMILY: &str = "Arial";
@ -90,260 +91,263 @@ where
} }
// Based on gfxWindowsPlatform::GetCommonFallbackFonts() in Gecko // Based on gfxWindowsPlatform::GetCommonFallbackFonts() in Gecko
pub fn fallback_font_families(codepoint: Option<char>) -> Vec<&'static str> { pub fn fallback_font_families(options: FallbackFontSelectionOptions) -> Vec<&'static str> {
let mut families = vec!["Arial"]; let mut families = Vec::new();
if let Some(codepoint) = codepoint { if options.prefer_emoji_presentation {
match unicode_plane(codepoint) { families.push("Segoe UI Emoji");
// 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");
},
UnicodeBlock::Arabic | UnicodeBlock::ArabicSupplement => { families.push("Arial");
families.push("Microsoft Uighur"); 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 => { UnicodeBlock::Arabic | UnicodeBlock::ArabicSupplement => {
families.push("Estrangelo Edessa"); families.push("Microsoft Uighur");
}, },
UnicodeBlock::Thaana => { UnicodeBlock::Syriac => {
families.push("MV Boli"); families.push("Estrangelo Edessa");
}, },
UnicodeBlock::NKo => { UnicodeBlock::Thaana => {
families.push("Ebrima"); families.push("MV Boli");
}, },
UnicodeBlock::Devanagari | UnicodeBlock::Bengali => { UnicodeBlock::NKo => {
families.push("Nirmala UI"); families.push("Ebrima");
families.push("Utsaah"); },
families.push("Aparajita");
},
UnicodeBlock::Gurmukhi | UnicodeBlock::Devanagari | UnicodeBlock::Bengali => {
UnicodeBlock::Gujarati | families.push("Nirmala UI");
UnicodeBlock::Oriya | families.push("Utsaah");
UnicodeBlock::Tamil | families.push("Aparajita");
UnicodeBlock::Telugu | },
UnicodeBlock::Kannada |
UnicodeBlock::Malayalam |
UnicodeBlock::Sinhala |
UnicodeBlock::Lepcha |
UnicodeBlock::OlChiki |
UnicodeBlock::CyrillicExtendedC |
UnicodeBlock::SundaneseSupplement |
UnicodeBlock::VedicExtensions => {
families.push("Nirmala UI");
},
UnicodeBlock::Thai => { UnicodeBlock::Gurmukhi |
families.push("Leelawadee UI"); 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 => { UnicodeBlock::Thai => {
families.push("Lao UI"); families.push("Leelawadee UI");
}, },
UnicodeBlock::Myanmar | UnicodeBlock::Lao => {
UnicodeBlock::MyanmarExtendedA | families.push("Lao UI");
UnicodeBlock::MyanmarExtendedB => { },
families.push("Myanmar Text");
},
UnicodeBlock::HangulJamo | UnicodeBlock::Myanmar |
UnicodeBlock::HangulJamoExtendedA | UnicodeBlock::MyanmarExtendedA |
UnicodeBlock::HangulSyllables | UnicodeBlock::MyanmarExtendedB => {
UnicodeBlock::HangulJamoExtendedB | families.push("Myanmar Text");
UnicodeBlock::HangulCompatibilityJamo => { },
families.push("Malgun Gothic");
},
UnicodeBlock::Ethiopic | UnicodeBlock::HangulJamo |
UnicodeBlock::EthiopicSupplement | UnicodeBlock::HangulJamoExtendedA |
UnicodeBlock::EthiopicExtended | UnicodeBlock::HangulSyllables |
UnicodeBlock::EthiopicExtendedA => { UnicodeBlock::HangulJamoExtendedB |
families.push("Nyala"); UnicodeBlock::HangulCompatibilityJamo => {
}, families.push("Malgun Gothic");
},
UnicodeBlock::Cherokee => { UnicodeBlock::Ethiopic |
families.push("Plantagenet Cherokee"); UnicodeBlock::EthiopicSupplement |
}, UnicodeBlock::EthiopicExtended |
UnicodeBlock::EthiopicExtendedA => {
families.push("Nyala");
},
UnicodeBlock::UnifiedCanadianAboriginalSyllabics | UnicodeBlock::Cherokee => {
UnicodeBlock::UnifiedCanadianAboriginalSyllabicsExtended => { families.push("Plantagenet Cherokee");
families.push("Euphemia"); },
families.push("Segoe UI");
},
UnicodeBlock::Khmer | UnicodeBlock::KhmerSymbols => { UnicodeBlock::UnifiedCanadianAboriginalSyllabics |
families.push("Khmer UI"); UnicodeBlock::UnifiedCanadianAboriginalSyllabicsExtended => {
families.push("Leelawadee UI"); families.push("Euphemia");
}, families.push("Segoe UI");
},
UnicodeBlock::Mongolian => { UnicodeBlock::Khmer | UnicodeBlock::KhmerSymbols => {
families.push("Mongolian Baiti"); families.push("Khmer UI");
}, families.push("Leelawadee UI");
},
UnicodeBlock::TaiLe => { UnicodeBlock::Mongolian => {
families.push("Microsoft Tai Le"); families.push("Mongolian Baiti");
}, },
UnicodeBlock::NewTaiLue => { UnicodeBlock::TaiLe => {
families.push("Microsoft New Tai Lue"); families.push("Microsoft Tai Le");
}, },
UnicodeBlock::Buginese | UnicodeBlock::NewTaiLue => {
UnicodeBlock::TaiTham | families.push("Microsoft New Tai Lue");
UnicodeBlock::CombiningDiacriticalMarksExtended => { },
families.push("Leelawadee UI");
},
UnicodeBlock::GeneralPunctuation | UnicodeBlock::Buginese |
UnicodeBlock::SuperscriptsandSubscripts | UnicodeBlock::TaiTham |
UnicodeBlock::CurrencySymbols | UnicodeBlock::CombiningDiacriticalMarksExtended => {
UnicodeBlock::CombiningDiacriticalMarksforSymbols | families.push("Leelawadee UI");
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::GeorgianSupplement | UnicodeBlock::GeneralPunctuation |
UnicodeBlock::Tifinagh | UnicodeBlock::SuperscriptsandSubscripts |
UnicodeBlock::CyrillicExtendedA | UnicodeBlock::CurrencySymbols |
UnicodeBlock::SupplementalPunctuation | UnicodeBlock::CombiningDiacriticalMarksforSymbols |
UnicodeBlock::CJKRadicalsSupplement | UnicodeBlock::LetterlikeSymbols |
UnicodeBlock::KangxiRadicals | UnicodeBlock::NumberForms |
UnicodeBlock::IdeographicDescriptionCharacters => { UnicodeBlock::Arrows |
families.push("Segoe UI"); UnicodeBlock::MathematicalOperators |
families.push("Segoe UI Symbol"); UnicodeBlock::MiscellaneousTechnical |
families.push("Meiryo"); 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 => { UnicodeBlock::GeorgianSupplement |
families.push("Segoe UI Symbol"); 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::BraillePatterns => {
UnicodeBlock::Hiragana | families.push("Segoe UI Symbol");
UnicodeBlock::Katakana | },
UnicodeBlock::Bopomofo |
UnicodeBlock::Kanbun |
UnicodeBlock::BopomofoExtended |
UnicodeBlock::CJKStrokes |
UnicodeBlock::KatakanaPhoneticExtensions |
UnicodeBlock::CJKUnifiedIdeographs => {
families.push("Microsoft YaHei");
families.push("Yu Gothic");
},
UnicodeBlock::EnclosedCJKLettersandMonths => { UnicodeBlock::CJKSymbolsandPunctuation |
families.push("Malgun Gothic"); 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 => { UnicodeBlock::EnclosedCJKLettersandMonths => {
families.push("Segoe UI Symbol"); families.push("Malgun Gothic");
}, },
UnicodeBlock::YiSyllables | UnicodeBlock::YiRadicals => { UnicodeBlock::YijingHexagramSymbols => {
families.push("Microsoft Yi Baiti"); families.push("Segoe UI Symbol");
families.push("Segoe UI"); },
},
UnicodeBlock::Vai | UnicodeBlock::YiSyllables | UnicodeBlock::YiRadicals => {
UnicodeBlock::CyrillicExtendedB | families.push("Microsoft Yi Baiti");
UnicodeBlock::Bamum | families.push("Segoe UI");
UnicodeBlock::ModifierToneLetters | },
UnicodeBlock::LatinExtendedD => {
families.push("Ebrima");
families.push("Segoe UI");
families.push("Cambria Math");
},
UnicodeBlock::SylotiNagri | UnicodeBlock::Vai |
UnicodeBlock::CommonIndicNumberForms | UnicodeBlock::CyrillicExtendedB |
UnicodeBlock::Phagspa | UnicodeBlock::Bamum |
UnicodeBlock::Saurashtra | UnicodeBlock::ModifierToneLetters |
UnicodeBlock::DevanagariExtended => { UnicodeBlock::LatinExtendedD => {
families.push("Microsoft PhagsPa"); families.push("Ebrima");
families.push("Nirmala UI"); families.push("Segoe UI");
}, families.push("Cambria Math");
},
UnicodeBlock::KayahLi | UnicodeBlock::Rejang | UnicodeBlock::Javanese => { UnicodeBlock::SylotiNagri |
families.push("Malgun Gothic"); UnicodeBlock::CommonIndicNumberForms |
families.push("Javanese Text"); UnicodeBlock::Phagspa |
families.push("Leelawadee UI"); UnicodeBlock::Saurashtra |
}, UnicodeBlock::DevanagariExtended => {
families.push("Microsoft PhagsPa");
families.push("Nirmala UI");
},
UnicodeBlock::AlphabeticPresentationForms => { UnicodeBlock::KayahLi | UnicodeBlock::Rejang | UnicodeBlock::Javanese => {
families.push("Microsoft Uighur"); families.push("Malgun Gothic");
families.push("Gabriola"); families.push("Javanese Text");
families.push("Sylfaen"); families.push("Leelawadee UI");
}, },
UnicodeBlock::ArabicPresentationFormsA | UnicodeBlock::AlphabeticPresentationForms => {
UnicodeBlock::ArabicPresentationFormsB => { families.push("Microsoft Uighur");
families.push("Traditional Arabic"); families.push("Gabriola");
families.push("Arabic Typesetting"); families.push("Sylfaen");
}, },
UnicodeBlock::VariationSelectors | UnicodeBlock::ArabicPresentationFormsA |
UnicodeBlock::VerticalForms | UnicodeBlock::ArabicPresentationFormsB => {
UnicodeBlock::CombiningHalfMarks | families.push("Traditional Arabic");
UnicodeBlock::CJKCompatibilityForms | families.push("Arabic Typesetting");
UnicodeBlock::SmallFormVariants | },
UnicodeBlock::HalfwidthandFullwidthForms |
UnicodeBlock::Specials => {
families.push("Microsoft JhengHei");
},
_ => {}, 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 // https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Multilingual_Plane
1 => { 1 => {
families.push("Segoe UI Symbol"); families.push("Segoe UI Symbol");
families.push("Ebrima"); families.push("Ebrima");
families.push("Nirmala UI"); families.push("Nirmala UI");
families.push("Cambria Math"); families.push("Cambria Math");
}, },
_ => {}, _ => {},
}
} }
families.push("Arial Unicode MS"); families.push("Arial Unicode MS");

View file

@ -17,6 +17,7 @@ use gfx::font_cache_thread::{CSSFontFaceDescriptors, FontIdentifier, FontSource}
use gfx::font_context::FontContext; use gfx::font_context::FontContext;
use gfx::font_store::FontTemplates; use gfx::font_store::FontTemplates;
use gfx::font_template::{FontTemplate, FontTemplateRef}; use gfx::font_template::{FontTemplate, FontTemplateRef};
use gfx::text::FallbackFontSelectionOptions;
use ipc_channel::ipc; use ipc_channel::ipc;
use net_traits::ResourceThreads; use net_traits::ResourceThreads;
use servo_arc::Arc; use servo_arc::Arc;
@ -53,7 +54,10 @@ impl MockFontCacheThread {
let mut families = HashMap::new(); let mut families = HashMap::new();
families.insert("CSSTest ASCII".to_owned(), csstest_ascii); families.insert("CSSTest ASCII".to_owned(), csstest_ascii);
families.insert("CSSTest Basic".to_owned(), csstest_basic); 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 { MockFontCacheThread {
families: RefCell::new(families), families: RefCell::new(families),
@ -211,7 +215,10 @@ fn test_font_group_find_by_codepoint() {
let group = context.font_group(Arc::new(style)); 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!( assert_eq!(
font.identifier(), font.identifier(),
MockFontCacheThread::identifier_for_font_name("csstest-ascii") 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" "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!( assert_eq!(
font.identifier(), font.identifier(),
MockFontCacheThread::identifier_for_font_name("csstest-ascii") 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" "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!( assert_eq!(
font.identifier(), font.identifier(),
MockFontCacheThread::identifier_for_font_name("csstest-basic-regular") 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 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!( assert_eq!(
font.identifier(), font.identifier(),
MockFontCacheThread::identifier_for_font_name("csstest-ascii"), MockFontCacheThread::identifier_for_font_name("csstest-ascii"),
"a family in the group should be used if there is a matching glyph" "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!( assert_eq!(
font.identifier(), font.identifier(),
MockFontCacheThread::identifier_for_font_name("csstest-basic-regular"), MockFontCacheThread::identifier_for_font_name("csstest-basic-regular"),

View file

@ -2,6 +2,8 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * 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/. */ * 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::shaping::Shaper;
pub use crate::text::text_run::TextRun; pub use crate::text::text_run::TextRun;
@ -9,3 +11,32 @@ pub mod glyph;
pub mod shaping; pub mod shaping;
pub mod text_run; pub mod text_run;
pub mod util; 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<char>) -> 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,
}
}
}

View file

@ -204,7 +204,7 @@ impl TextRunScanner {
.unwrap_or_else(|| { .unwrap_or_else(|| {
let space_width = font_group let space_width = font_group
.write() .write()
.find_by_codepoint(font_context, ' ') .find_by_codepoint(font_context, ' ', None)
.and_then(|font| { .and_then(|font| {
font.glyph_index(' ') font.glyph_index(' ')
.map(|glyph_id| font.glyph_h_advance(glyph_id)) .map(|glyph_id| font.glyph_h_advance(glyph_id))
@ -246,9 +246,10 @@ impl TextRunScanner {
let (mut start_position, mut end_position) = (0, 0); let (mut start_position, mut end_position) = (0, 0);
for (byte_index, character) in text.char_indices() { for (byte_index, character) in text.char_indices() {
if !character.is_control() { if !character.is_control() {
let font = font_group let font =
.write() font_group
.find_by_codepoint(font_context, character); .write()
.find_by_codepoint(font_context, character, None);
let bidi_level = match bidi_levels { let bidi_level = match bidi_levels {
Some(levels) => levels[*paragraph_bytes_processed], Some(levels) => levels[*paragraph_bytes_processed],

View file

@ -316,10 +316,11 @@ impl TextRun {
} else { } else {
Box::new(collapsed) Box::new(collapsed)
}; };
let char_iterator = TwoCharsAtATimeIterator::new(char_iterator);
let mut next_byte_index = 0; let mut next_byte_index = 0;
let text = char_iterator let text = char_iterator
.map(|character| { .map(|(character, next_character)| {
let current_byte_index = next_byte_index; let current_byte_index = next_byte_index;
next_byte_index += character.len_utf8(); next_byte_index += character.len_utf8();
@ -338,10 +339,11 @@ impl TextRun {
return character; return character;
} }
let font = match font_group let font = match font_group.write().find_by_codepoint(
.write() font_context,
.find_by_codepoint(font_context, character) character,
{ next_character,
) {
Some(font) => font, Some(font) => font,
None => return character, None => return character,
}; };
@ -791,3 +793,40 @@ fn capitalize_string(string: &str, allow_word_at_start: bool) -> String {
output_string output_string
} }
pub struct TwoCharsAtATimeIterator<InputIterator> {
/// The input character iterator.
iterator: InputIterator,
/// The first character to produce in the next run of the iterator.
next_character: Option<char>,
}
impl<InputIterator> TwoCharsAtATimeIterator<InputIterator> {
fn new(iterator: InputIterator) -> Self {
Self {
iterator,
next_character: None,
}
}
}
impl<InputIterator> Iterator for TwoCharsAtATimeIterator<InputIterator>
where
InputIterator: Iterator<Item = char>,
{
type Item = (char, Option<char>);
fn next(&mut self) -> Option<Self::Item> {
// 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()));
}
}

View file

@ -1218,7 +1218,7 @@ impl FontMetricsProvider for LayoutFontMetricsProvider {
.or_else(|| { .or_else(|| {
font_group font_group
.write() .write()
.find_by_codepoint(font_context, '0')? .find_by_codepoint(font_context, '0', None)?
.metrics .metrics
.zero_horizontal_advance .zero_horizontal_advance
}) })
@ -1229,7 +1229,7 @@ impl FontMetricsProvider for LayoutFontMetricsProvider {
.or_else(|| { .or_else(|| {
font_group font_group
.write() .write()
.find_by_codepoint(font_context, '\u{6C34}')? .find_by_codepoint(font_context, '\u{6C34}', None)?
.metrics .metrics
.ic_horizontal_advance .ic_horizontal_advance
}) })