diff --git a/components/gfx/platform/macos/font.rs b/components/gfx/platform/macos/font.rs index 9fd3934ed07..de8248c71ff 100644 --- a/components/gfx/platform/macos/font.rs +++ b/components/gfx/platform/macos/font.rs @@ -10,7 +10,6 @@ use std::{fmt, ptr}; /// Implementation of Quartz (CoreGraphics) fonts. use app_units::Au; use byteorder::{BigEndian, ByteOrder}; -use core_foundation::base::CFIndex; use core_foundation::data::CFData; use core_foundation::string::UniChar; use core_graphics::font::CGGlyph; @@ -209,17 +208,26 @@ impl PlatformFontMethods for PlatformFont { } fn glyph_index(&self, codepoint: char) -> Option { - let characters: [UniChar; 1] = [codepoint as UniChar]; - let mut glyphs: [CGGlyph; 1] = [0 as CGGlyph]; - let count: CFIndex = 1; + // CTFontGetGlyphsForCharacters takes UniChar, which are UTF-16 encoded characters. We are taking + // a char here which is a 32bit Unicode character. This will encode into a maximum of two + // UTF-16 code units and produce a maximum of 1 glyph. We could safely pass 2 as the length + // of the buffer to CTFontGetGlyphsForCharacters, but passing the actual number of encoded + // code units ensures that the resulting glyph is always placed in the first slot in the output + // buffer. + let mut characters: [UniChar; 2] = [0, 0]; + let encoded_characters = codepoint.encode_utf16(&mut characters); + let mut glyphs: [CGGlyph; 2] = [0, 0]; let result = unsafe { - self.ctfont - .get_glyphs_for_characters(characters.as_ptr(), glyphs.as_mut_ptr(), count) + self.ctfont.get_glyphs_for_characters( + encoded_characters.as_ptr(), + glyphs.as_mut_ptr(), + encoded_characters.len() as isize, + ) }; + // If the call failed or the glyph is the zero glyph no glyph was found for this character. if !result || glyphs[0] == 0 { - // No glyph for this character return None; } diff --git a/components/gfx/platform/macos/font_list.rs b/components/gfx/platform/macos/font_list.rs index dddd0b93275..1792fb7a593 100644 --- a/components/gfx/platform/macos/font_list.rs +++ b/components/gfx/platform/macos/font_list.rs @@ -10,6 +10,7 @@ use log::debug; use serde::{Deserialize, Serialize}; use style::Atom; use ucd::{Codepoint, UnicodeBlock}; +use unicode_script::Script; use webrender_api::NativeFontHandle; use crate::font_template::{FontTemplate, FontTemplateDescriptor}; @@ -92,158 +93,231 @@ pub fn system_default_family(_generic_name: &str) -> Option { None } -// Based on gfxPlatformMac::GetCommonFallbackFonts() in Gecko +/// 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; + }; - 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::Arabic | - UnicodeBlock::Syriac | - UnicodeBlock::ArabicSupplement | - UnicodeBlock::Thaana | - UnicodeBlock::NKo => { - families.push("Geeza Pro"); - }, - - UnicodeBlock::Devanagari => { - families.push("Devanagari Sangam MN"); - }, - - UnicodeBlock::Gurmukhi => { - families.push("Gurmukhi MN"); - }, - - UnicodeBlock::Gujarati => { - families.push("Gujarati Sangam MN"); - }, - - UnicodeBlock::Tamil => { - families.push("Tamil MN"); - }, - - UnicodeBlock::Lao => { - families.push("Lao MN"); - }, - - UnicodeBlock::Tibetan => { - families.push("Songti SC"); - }, - - UnicodeBlock::Myanmar => { - families.push("Myanmar MN"); - }, - - UnicodeBlock::Ethiopic | - UnicodeBlock::EthiopicSupplement | - UnicodeBlock::EthiopicExtended | - UnicodeBlock::EthiopicExtendedA => { - families.push("Kefa"); - }, - - UnicodeBlock::Cherokee => { - families.push("Plantagenet Cherokee"); - }, - - UnicodeBlock::UnifiedCanadianAboriginalSyllabics | - UnicodeBlock::UnifiedCanadianAboriginalSyllabicsExtended => { - families.push("Euphemia UCAS"); - }, - - UnicodeBlock::Mongolian | - UnicodeBlock::YiSyllables | - UnicodeBlock::YiRadicals => { - families.push("STHeiti"); - }, - - UnicodeBlock::Khmer | UnicodeBlock::KhmerSymbols => { - families.push("Khmer MN"); - }, - - UnicodeBlock::TaiLe => { - families.push("Microsoft Tai Le"); - }, - - 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::SupplementalPunctuation => { - families.push("Hiragino Kaku Gothic ProN"); - families.push("Apple Symbols"); - families.push("Menlo"); - families.push("STIXGeneral"); - }, - - UnicodeBlock::BraillePatterns => { - families.push("Apple Braille"); - }, - - UnicodeBlock::Bopomofo | - UnicodeBlock::HangulCompatibilityJamo | - UnicodeBlock::Kanbun | - UnicodeBlock::BopomofoExtended | - UnicodeBlock::CJKStrokes | - UnicodeBlock::KatakanaPhoneticExtensions => { - families.push("Hiragino Sans GB"); - }, - - UnicodeBlock::YijingHexagramSymbols | - UnicodeBlock::CyrillicExtendedB | - UnicodeBlock::Bamum | - UnicodeBlock::ModifierToneLetters | - UnicodeBlock::LatinExtendedD | - UnicodeBlock::ArabicPresentationFormsA | - UnicodeBlock::HalfwidthandFullwidthForms | - UnicodeBlock::Specials => { - families.push("Apple Symbols"); - }, - - _ => {}, - } + let script = Script::from(codepoint); + if let Some(block) = codepoint.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 + // we'll just try common default fonts here. + _ if matches!( + script, + Script::Common | + Script::Inherited | + Script::Latin | + Script::Cyrillic | + Script::Greek + ) => + { + families.push("Lucida Grande"); + }, + // 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. + _ 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 { + // macOS installations with MS Office may have these -ExtB fonts + families.push("SimSun-ExtB"); } }, - - // https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Multilingual_Plane - 1 => { - families.push("Apple Symbols"); - families.push("STIXGeneral"); + UnicodeBlock::Hiragana | + UnicodeBlock::Katakana | + UnicodeBlock::KatakanaPhoneticExtensions => { + families.push("Hiragino Sans"); + families.push("Hiragino Kaku Gothic ProN"); }, - - // https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Ideographic_Plane - 2 => { - // Systems with MS Office may have these fonts - families.push("MingLiU-ExtB"); - families.push("SimSun-ExtB"); + UnicodeBlock::HangulJamo | + UnicodeBlock::HangulJamoExtendedA | + UnicodeBlock::HangulJamoExtendedB | + UnicodeBlock::HangulCompatibilityJamo | + UnicodeBlock::HangulSyllables => { + families.push("Nanum Gothic"); + families.push("Apple SD Gothic Neo"); }, - + UnicodeBlock::Arabic => families.push("Geeza Pro"), + UnicodeBlock::Armenian => families.push("Mshtakan"), + UnicodeBlock::Bengali => families.push("Bangla Sangam MN"), + UnicodeBlock::Cherokee => families.push("Plantagenet Cherokee"), + UnicodeBlock::Coptic => families.push("Noto Sans Coptic"), + UnicodeBlock::Deseret => families.push("Baskerville"), + UnicodeBlock::Devanagari | UnicodeBlock::DevanagariExtended => { + families.push("Devanagari Sangam MN") + }, + UnicodeBlock::Ethiopic | + UnicodeBlock::EthiopicExtended | + UnicodeBlock::EthiopicExtendedA | + UnicodeBlock::EthiopicSupplement => families.push("Kefa"), + UnicodeBlock::Georgian | UnicodeBlock::GeorgianSupplement => families.push("Helvetica"), + UnicodeBlock::Gothic => families.push("Noto Sans Gothic"), + UnicodeBlock::Gujarati => families.push("Gujarati Sangam MN"), + UnicodeBlock::Gurmukhi => families.push("Gurmukhi MN"), + UnicodeBlock::Hebrew => families.push("Lucida Grande"), + UnicodeBlock::Kannada => families.push("Kannada MN"), + UnicodeBlock::Khmer => families.push("Khmer MN"), + UnicodeBlock::Lao => families.push("Lao MN"), + UnicodeBlock::Malayalam => families.push("Malayalam Sangam MN"), + UnicodeBlock::Mongolian | UnicodeBlock::MongolianSupplement => { + families.push("Noto Sans Mongolian") + }, + UnicodeBlock::Myanmar | + UnicodeBlock::MyanmarExtendedA | + UnicodeBlock::MyanmarExtendedB => families.push("Myanmar MN"), + UnicodeBlock::Ogham => families.push("Noto Sans Ogham"), + UnicodeBlock::OldItalic => families.push("Noto Sans Old Italic"), + UnicodeBlock::Oriya => families.push("Oriya Sangam MN"), + UnicodeBlock::Runic => families.push("Noto Sans Runic"), + UnicodeBlock::Sinhala | UnicodeBlock::SinhalaArchaicNumbers => { + families.push("Sinhala Sangam MN") + }, + UnicodeBlock::Syriac => families.push("Noto Sans Syriac"), + UnicodeBlock::Tamil => families.push("Tamil MN"), + UnicodeBlock::Telugu => families.push("Telugu MN"), + UnicodeBlock::Thaana => { + families.push("Noto Sans Thaana"); + families.push("Thonburi"); + }, + UnicodeBlock::Tibetan => families.push("Kailasa"), + UnicodeBlock::UnifiedCanadianAboriginalSyllabics | + UnicodeBlock::UnifiedCanadianAboriginalSyllabicsExtended => { + families.push("Euphemia UCAS") + }, + UnicodeBlock::YiSyllables | UnicodeBlock::YiRadicals => { + families.push("Noto Sans Yi"); + families.push("STHeiti"); + }, + UnicodeBlock::Tagalog => families.push("Noto Sans Tagalog"), + UnicodeBlock::Hanunoo => families.push("Noto Sans Hanunoo"), + UnicodeBlock::Buhid => families.push("Noto Sans Buhid"), + UnicodeBlock::Tagbanwa => families.push("Noto Sans Tagbanwa"), + UnicodeBlock::BraillePatterns => families.push("Apple Braille"), + UnicodeBlock::CypriotSyllabary => families.push("Noto Sans Cypriot"), + UnicodeBlock::Limbu => families.push("Noto Sans Limbu"), + UnicodeBlock::LinearBIdeograms | UnicodeBlock::LinearBSyllabary => { + families.push("Noto Sans Linear B") + }, + UnicodeBlock::Osmanya => families.push("Noto Sans Osmanya"), + UnicodeBlock::Shavian => families.push("Noto Sans Shavian"), + UnicodeBlock::TaiLe => families.push("Noto Sans Tai Le"), + UnicodeBlock::Ugaritic => families.push("Noto Sans Ugaritic"), + UnicodeBlock::Buginese => families.push("Noto Sans Buginese"), + UnicodeBlock::Glagolitic | UnicodeBlock::GlagoliticSupplement => { + families.push("Noto Sans Glagolitic") + }, + UnicodeBlock::Kharoshthi => families.push("Noto Sans Kharoshthi"), + UnicodeBlock::SylotiNagri => families.push("Noto Sans Syloti Nagri"), + UnicodeBlock::NewTaiLue => families.push("Noto Sans New Tai Lue"), + UnicodeBlock::Tifinagh => families.push("Noto Sans Tifinagh"), + UnicodeBlock::OldPersian => families.push("Noto Sans Old Persian"), + UnicodeBlock::Balinese => families.push("Noto Sans Balinese"), + UnicodeBlock::Batak => families.push("Noto Sans Batak"), + UnicodeBlock::Brahmi => families.push("Noto Sans Brahmi"), + UnicodeBlock::Cham => families.push("Noto Sans Cham"), + UnicodeBlock::EgyptianHieroglyphs => families.push("Noto Sans Egyptian Hieroglyphs"), + UnicodeBlock::PahawhHmong => families.push("Noto Sans Pahawh Hmong"), + UnicodeBlock::OldHungarian => families.push("Noto Sans Old Hungarian"), + UnicodeBlock::Javanese => families.push("Noto Sans Javanese"), + UnicodeBlock::KayahLi => families.push("Noto Sans Kayah Li"), + UnicodeBlock::Lepcha => families.push("Noto Sans Lepcha"), + UnicodeBlock::LinearA => families.push("Noto Sans Linear A"), + UnicodeBlock::Mandaic => families.push("Noto Sans Mandaic"), + UnicodeBlock::NKo => families.push("Noto Sans NKo"), + UnicodeBlock::OldTurkic => families.push("Noto Sans Old Turkic"), + UnicodeBlock::OldPermic => families.push("Noto Sans Old Permic"), + UnicodeBlock::Phagspa => families.push("Noto Sans PhagsPa"), + UnicodeBlock::Phoenician => families.push("Noto Sans Phoenician"), + UnicodeBlock::Miao => families.push("Noto Sans Miao"), + UnicodeBlock::Vai => families.push("Noto Sans Vai"), + UnicodeBlock::Cuneiform | UnicodeBlock::CuneiformNumbersandPunctuation => { + families.push("Noto Sans Cuneiform") + }, + UnicodeBlock::Carian => families.push("Noto Sans Carian"), + UnicodeBlock::TaiTham => families.push("Noto Sans Tai Tham"), + UnicodeBlock::Lycian => families.push("Noto Sans Lycian"), + UnicodeBlock::Lydian => families.push("Noto Sans Lydian"), + UnicodeBlock::OlChiki => families.push("Noto Sans Ol Chiki"), + UnicodeBlock::Rejang => families.push("Noto Sans Rejang"), + UnicodeBlock::Saurashtra => families.push("Noto Sans Saurashtra"), + UnicodeBlock::Sundanese => families.push("Noto Sans Sundanese"), + UnicodeBlock::MeeteiMayek | UnicodeBlock::MeeteiMayekExtensions => { + families.push("Noto Sans Meetei Mayek") + }, + UnicodeBlock::ImperialAramaic => families.push("Noto Sans Imperial Aramaic"), + UnicodeBlock::Avestan => families.push("Noto Sans Avestan"), + UnicodeBlock::Chakma => families.push("Noto Sans Chakma"), + UnicodeBlock::Kaithi => families.push("Noto Sans Kaithi"), + UnicodeBlock::Manichaean => families.push("Noto Sans Manichaean"), + UnicodeBlock::InscriptionalPahlavi => families.push("Noto Sans Inscriptional Pahlavi"), + UnicodeBlock::PsalterPahlavi => families.push("Noto Sans Psalter Pahlavi"), + UnicodeBlock::InscriptionalParthian => { + families.push("Noto Sans Inscriptional Parthian") + }, + UnicodeBlock::Samaritan => families.push("Noto Sans Samaritan"), + UnicodeBlock::TaiViet => families.push("Noto Sans Tai Viet"), + UnicodeBlock::Bamum | UnicodeBlock::BamumSupplement => families.push("Noto Sans Bamum"), + UnicodeBlock::Lisu => families.push("Noto Sans Lisu"), + UnicodeBlock::OldSouthArabian => families.push("Noto Sans Old South Arabian"), + UnicodeBlock::BassaVah => families.push("Noto Sans Bassa Vah"), + UnicodeBlock::Duployan => families.push("Noto Sans Duployan"), + UnicodeBlock::Elbasan => families.push("Noto Sans Elbasan"), + UnicodeBlock::Grantha => families.push("Noto Sans Grantha"), + UnicodeBlock::MendeKikakui => families.push("Noto Sans Mende Kikakui"), + UnicodeBlock::MeroiticCursive | UnicodeBlock::MeroiticHieroglyphs => { + families.push("Noto Sans Meroitic") + }, + UnicodeBlock::OldNorthArabian => families.push("Noto Sans Old North Arabian"), + UnicodeBlock::Nabataean => families.push("Noto Sans Nabataean"), + UnicodeBlock::Palmyrene => families.push("Noto Sans Palmyrene"), + UnicodeBlock::Khudawadi => families.push("Noto Sans Khudawadi"), + UnicodeBlock::WarangCiti => families.push("Noto Sans Warang Citi"), + UnicodeBlock::Mro => families.push("Noto Sans Mro"), + UnicodeBlock::Sharada => families.push("Noto Sans Sharada"), + UnicodeBlock::SoraSompeng => families.push("Noto Sans Sora Sompeng"), + UnicodeBlock::Takri => families.push("Noto Sans Takri"), + UnicodeBlock::Khojki => families.push("Noto Sans Khojki"), + UnicodeBlock::Tirhuta => families.push("Noto Sans Tirhuta"), + UnicodeBlock::CaucasianAlbanian => families.push("Noto Sans Caucasian Albanian"), + UnicodeBlock::Mahajani => families.push("Noto Sans Mahajani"), + UnicodeBlock::Ahom => families.push("Noto Serif Ahom"), + UnicodeBlock::Hatran => families.push("Noto Sans Hatran"), + UnicodeBlock::Modi => families.push("Noto Sans Modi"), + UnicodeBlock::Multani => families.push("Noto Sans Multani"), + UnicodeBlock::PauCinHau => families.push("Noto Sans Pau Cin Hau"), + UnicodeBlock::Siddham => families.push("Noto Sans Siddham"), + UnicodeBlock::Adlam => families.push("Noto Sans Adlam"), + UnicodeBlock::Bhaiksuki => families.push("Noto Sans Bhaiksuki"), + UnicodeBlock::Marchen => families.push("Noto Sans Marchen"), + UnicodeBlock::Newa => families.push("Noto Sans Newa"), + UnicodeBlock::Osage => families.push("Noto Sans Osage"), + _ if script == Script::Hanifi_Rohingya => families.push("Noto Sans Hanifi Rohingya"), + _ if script == Script::Wancho => families.push("Noto Sans Wancho"), _ => {}, } } + // https://en.wikipedia.org/wiki/Plane_(Unicode)#Supplementary_Multilingual_Plane + let unicode_plane = unicode_plane(codepoint); + if let 1 = unicode_plane { + let b = (codepoint as u32) >> 8; + if b >= 0x1f0 && b < 0x1f7 { + families.push("Apple Color Emoji"); + } + families.push("Apple Symbols"); + families.push("STIXGeneral"); + } + families.push("Geneva"); families.push("Arial Unicode MS"); families