diff --git a/components/fonts/platform/macos/core_text_font_cache.rs b/components/fonts/platform/macos/core_text_font_cache.rs index 0d2fb950e5a..734a8ce7ff0 100644 --- a/components/fonts/platform/macos/core_text_font_cache.rs +++ b/components/fonts/platform/macos/core_text_font_cache.rs @@ -7,14 +7,14 @@ use std::sync::{Arc, OnceLock}; use app_units::Au; use core_foundation::base::TCFType; -use core_foundation::string::CFString; +use core_foundation::data::CFData; +use core_foundation::number::CFNumber; +use core_foundation::string::{CFString, CFStringRef}; use core_foundation::url::{CFURL, kCFURLPOSIXPathStyle}; -use core_graphics::data_provider::CGDataProvider; use core_graphics::display::CFDictionary; -use core_graphics::font::CGFont; -use core_text::font::CTFont; -use core_text::font_descriptor::kCTFontURLAttribute; +use core_text::font_descriptor::{kCTFontURLAttribute, kCTFontVariationAttribute}; use parking_lot::RwLock; +use webrender_api::FontVariation; use crate::FontData; use crate::platform::font::PlatformFont; @@ -30,41 +30,86 @@ static CACHE: CoreTextFontCache = CoreTextFontCache(OnceLock::new()); /// A [`HashMap`] of cached [`CTFont`] for a single [`FontIdentifier`]. There is one [`CTFont`] /// for each cached font size. -type CachedCTFont = HashMap; +type CachedCTFont = HashMap; + +#[derive(Eq, Hash, PartialEq)] +struct CoreTextFontCacheKey { + size: Au, + variations: Vec, +} impl CoreTextFontCache { pub(crate) fn core_text_font( font_identifier: FontIdentifier, data: Option<&FontData>, pt_size: f64, + variations: &[FontVariation], ) -> Option { //// If you pass a zero font size to one of the Core Text APIs, it'll replace it with //// 12.0. We don't want that! (Issue #10492.) let clamped_pt_size = pt_size.max(0.01); let au_size = Au::from_f64_px(clamped_pt_size); + let key = CoreTextFontCacheKey { + size: au_size, + variations: variations.to_owned(), + }; + let cache = CACHE.0.get_or_init(Default::default); { let cache = cache.read(); - if let Some(core_text_font) = cache + if let Some(platform_font) = cache .get(&font_identifier) - .and_then(|identifier_cache| identifier_cache.get(&au_size)) + .and_then(|identifier_cache| identifier_cache.get(&key)) { - return Some(PlatformFont::new_with_ctfont(core_text_font.clone())); + return Some(platform_font.clone()); } } + if !key.variations.is_empty() { + let core_text_font_no_variations = + Self::core_text_font(font_identifier.clone(), data, clamped_pt_size, &[])?; + let mut cache = cache.write(); + let entry = cache.entry(font_identifier.clone()).or_default(); + + // It could be that between the time of the cache miss above and now, after the write lock + // on the cache has been acquired, the cache was populated with the data that we need. Thus + // check again and return the CTFont if it is is already cached. + if let Some(core_text_font) = entry.get(&key) { + return Some(core_text_font.clone()); + } + + let platform_font = Self::add_variations_to_font( + core_text_font_no_variations, + &key.variations, + clamped_pt_size, + ); + entry.insert(key, platform_font.clone()); + return Some(platform_font); + } + let mut cache = cache.write(); let identifier_cache = cache.entry(font_identifier.clone()).or_default(); // It could be that between the time of the cache miss above and now, after the write lock // on the cache has been acquired, the cache was populated with the data that we need. Thus // check again and return the CTFont if it is is already cached. - if let Some(core_text_font) = identifier_cache.get(&au_size) { - return Some(PlatformFont::new_with_ctfont(core_text_font.clone())); + if let Some(platform_font) = identifier_cache.get(&key) { + return Some(platform_font.clone()); } - let core_text_font = match font_identifier { + let platform_font = + Self::create_font_without_variations(font_identifier, data, clamped_pt_size)?; + identifier_cache.insert(key, platform_font.clone()); + Some(platform_font) + } + + pub(crate) fn create_font_without_variations( + font_identifier: FontIdentifier, + data: Option<&FontData>, + clamped_pt_size: f64, + ) -> Option { + let descriptor = match font_identifier { FontIdentifier::Local(local_font_identifier) => { // Other platforms can instantiate a platform font by loading the data // from a file and passing an index in the case the file is a TTC bundle. @@ -72,7 +117,7 @@ impl CoreTextFontCache { // macOS is to create the font using a descriptor with both the PostScript // name and path. let cf_name = CFString::new(&local_font_identifier.postscript_name); - let mut descriptor = core_text::font_descriptor::new_from_postscript_name(&cf_name); + let descriptor = core_text::font_descriptor::new_from_postscript_name(&cf_name); let cf_path = CFString::new(&local_font_identifier.path); let url_attribute = unsafe { CFString::wrap_under_get_rule(kCTFontURLAttribute) }; @@ -80,25 +125,128 @@ impl CoreTextFontCache { url_attribute, CFURL::from_file_system_path(cf_path, kCFURLPOSIXPathStyle, false), )]); - if let Ok(descriptor_with_path) = - descriptor.create_copy_with_attributes(attributes.to_untyped()) - { - descriptor = descriptor_with_path; - } - core_text::font::new_from_descriptor(&descriptor, clamped_pt_size) + descriptor.create_copy_with_attributes(attributes.to_untyped()) }, FontIdentifier::Web(_) => { let data = data .expect("Should always have FontData for web fonts") .clone(); - let provider = CGDataProvider::from_buffer(Arc::new(data)); - let cgfont = CGFont::from_data_provider(provider).ok()?; - core_text::font::new_from_CGFont(&cgfont, clamped_pt_size) + let cf_data = CFData::from_arc(Arc::new(data)); + core_text::font_manager::create_font_descriptor_with_data(cf_data) }, }; - identifier_cache.insert(au_size, core_text_font.clone()); - Some(PlatformFont::new_with_ctfont(core_text_font)) + Some(PlatformFont::new_with_ctfont( + core_text::font::new_from_descriptor(&descriptor.ok()?, clamped_pt_size), + )) + } + + fn add_variations_to_font( + platform_font: PlatformFont, + specified_variations: &[FontVariation], + pt_size: f64, + ) -> PlatformFont { + if specified_variations.is_empty() { + return platform_font; + } + let Some(variations) = Self::get_variation_axis_information(&platform_font) else { + return platform_font; + }; + + let mut modified_variations = false; + let variations: Vec<_> = variations + .iter() + .map(|variation| { + let value = specified_variations + .iter() + .find_map(|specified_variation| { + if variation.tag == specified_variation.tag as i64 { + Some(specified_variation.value as f64) + } else { + None + } + }) + .unwrap_or(variation.default_value) + .clamp(variation.min_value, variation.max_value); + if value != variation.default_value { + modified_variations = true; + } + FontVariation { + tag: variation.tag as u32, + value: value as f32, + } + }) + .collect(); + + if !modified_variations { + return platform_font; + } + + let cftype_variations: Vec<_> = variations + .iter() + .map(|variation| { + ( + CFNumber::from(variation.tag as i64), + CFNumber::from(variation.value as f64), + ) + }) + .collect(); + let values_dict = CFDictionary::from_CFType_pairs(&cftype_variations); + + let variation_attribute = + unsafe { CFString::wrap_under_get_rule(kCTFontVariationAttribute) }; + let attrs_dict = CFDictionary::from_CFType_pairs(&[(variation_attribute, values_dict)]); + let ct_var_font_desc = platform_font + .ctfont + .copy_descriptor() + .create_copy_with_attributes(attrs_dict.to_untyped()) + .unwrap(); + + let ctfont = core_text::font::new_from_descriptor(&ct_var_font_desc, pt_size); + PlatformFont::new_with_ctfont_and_variations(ctfont, variations) + } + + fn get_variation_axis_information( + platform_font: &PlatformFont, + ) -> Option> { + Some( + platform_font + .ctfont + .get_variation_axes()? + .iter() + .filter_map(|axes| { + let tag = unsafe { axes.find(kCTFontVariationAxisIdentifierKey) } + .and_then(|tag| tag.downcast::())?; + let max_value = unsafe { axes.find(kCTFontVariationAxisMaximumValueKey) } + .and_then(|tag| tag.downcast::())?; + let min_value = unsafe { axes.find(kCTFontVariationAxisMinimumValueKey) } + .and_then(|tag| tag.downcast::())?; + let default_value = unsafe { axes.find(kCTFontVariationAxisDefaultValueKey) } + .and_then(|tag| tag.downcast::())?; + Some(VariationAxisInformation { + tag: tag.to_i64()?, + max_value: max_value.to_f64()?, + min_value: min_value.to_f64()?, + default_value: default_value.to_f64()?, + }) + }) + .collect(), + ) } } + +#[derive(Clone, Default, Debug)] +struct VariationAxisInformation { + tag: i64, + max_value: f64, + min_value: f64, + default_value: f64, +} + +unsafe extern "C" { + static kCTFontVariationAxisDefaultValueKey: CFStringRef; + static kCTFontVariationAxisIdentifierKey: CFStringRef; + static kCTFontVariationAxisMaximumValueKey: CFStringRef; + static kCTFontVariationAxisMinimumValueKey: CFStringRef; +} diff --git a/components/fonts/platform/macos/font.rs b/components/fonts/platform/macos/font.rs index e776337b550..1e11ed25225 100644 --- a/components/fonts/platform/macos/font.rs +++ b/components/fonts/platform/macos/font.rs @@ -31,6 +31,7 @@ use crate::{ const KERN_PAIR_LEN: usize = 6; +#[derive(Clone)] pub struct FontTable { data: CFData, } @@ -52,9 +53,10 @@ impl FontTableMethods for FontTable { } } -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct PlatformFont { - ctfont: CTFont, + pub(crate) ctfont: CTFont, + variations: Vec, h_kern_subtable: Option, } @@ -71,8 +73,16 @@ unsafe impl Send for PlatformFont {} impl PlatformFont { pub(crate) fn new_with_ctfont(ctfont: CTFont) -> Self { + Self::new_with_ctfont_and_variations(ctfont, vec![]) + } + + pub(crate) fn new_with_ctfont_and_variations( + ctfont: CTFont, + variations: Vec, + ) -> PlatformFont { Self { ctfont, + variations, h_kern_subtable: None, } } @@ -81,13 +91,14 @@ impl PlatformFont { font_identifier: FontIdentifier, data: Option<&FontData>, requested_size: Option, + variations: &[FontVariation], ) -> Result { let size = match requested_size { Some(s) => s.to_f64_px(), None => 0.0, }; let Some(mut platform_font) = - CoreTextFontCache::core_text_font(font_identifier, data, size) + CoreTextFontCache::core_text_font(font_identifier, data, size, variations) else { return Err("Could not generate CTFont for FontTemplateData"); }; @@ -162,6 +173,7 @@ impl PlatformFont { } } +#[derive(Clone)] struct CachedKernTable { font_table: FontTable, pair_data_range: Range, @@ -201,17 +213,22 @@ impl PlatformFontMethods for PlatformFont { font_identifier: FontIdentifier, data: &FontData, requested_size: Option, - _variations: &[FontVariation], + variations: &[FontVariation], ) -> Result { - Self::new(font_identifier, Some(data), requested_size) + Self::new(font_identifier, Some(data), requested_size, variations) } fn new_from_local_font_identifier( font_identifier: LocalFontIdentifier, requested_size: Option, - _variations: &[FontVariation], + variations: &[FontVariation], ) -> Result { - Self::new(FontIdentifier::Local(font_identifier), None, requested_size) + Self::new( + FontIdentifier::Local(font_identifier), + None, + requested_size, + variations, + ) } fn descriptor(&self) -> FontTemplateDescriptor { @@ -357,8 +374,7 @@ impl PlatformFontMethods for PlatformFont { } fn variations(&self) -> &[FontVariation] { - // FIXME: Implement this for macos - &[] + &self.variations } }