mirror of
https://github.com/servo/servo.git
synced 2025-08-25 07:08:21 +01:00
fonts: Add font variation support for macOS (#38760)
This change adds font variation support for macOS. The main bulk of the change is reading the default, min, and max values for each variation axis from the font and instantiating a new CoreText font with the appropriate values. In addition, fonts with variations are now properly cached in the CoreText font cache. Testing: There are no tests for this change as we do not run WPT tests for macOS and font variations are currently turned off by default. Eventually, when the feature is turned on there will be test for it. These changes are just laying the groundwork for the full implementation. Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
parent
ad3018a921
commit
2022831e4f
2 changed files with 197 additions and 33 deletions
|
@ -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<Au, CTFont>;
|
||||
type CachedCTFont = HashMap<CoreTextFontCacheKey, PlatformFont>;
|
||||
|
||||
#[derive(Eq, Hash, PartialEq)]
|
||||
struct CoreTextFontCacheKey {
|
||||
size: Au,
|
||||
variations: Vec<FontVariation>,
|
||||
}
|
||||
|
||||
impl CoreTextFontCache {
|
||||
pub(crate) fn core_text_font(
|
||||
font_identifier: FontIdentifier,
|
||||
data: Option<&FontData>,
|
||||
pt_size: f64,
|
||||
variations: &[FontVariation],
|
||||
) -> Option<PlatformFont> {
|
||||
//// 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<PlatformFont> {
|
||||
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<Vec<VariationAxisInformation>> {
|
||||
Some(
|
||||
platform_font
|
||||
.ctfont
|
||||
.get_variation_axes()?
|
||||
.iter()
|
||||
.filter_map(|axes| {
|
||||
let tag = unsafe { axes.find(kCTFontVariationAxisIdentifierKey) }
|
||||
.and_then(|tag| tag.downcast::<CFNumber>())?;
|
||||
let max_value = unsafe { axes.find(kCTFontVariationAxisMaximumValueKey) }
|
||||
.and_then(|tag| tag.downcast::<CFNumber>())?;
|
||||
let min_value = unsafe { axes.find(kCTFontVariationAxisMinimumValueKey) }
|
||||
.and_then(|tag| tag.downcast::<CFNumber>())?;
|
||||
let default_value = unsafe { axes.find(kCTFontVariationAxisDefaultValueKey) }
|
||||
.and_then(|tag| tag.downcast::<CFNumber>())?;
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -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<FontVariation>,
|
||||
h_kern_subtable: Option<CachedKernTable>,
|
||||
}
|
||||
|
||||
|
@ -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<FontVariation>,
|
||||
) -> PlatformFont {
|
||||
Self {
|
||||
ctfont,
|
||||
variations,
|
||||
h_kern_subtable: None,
|
||||
}
|
||||
}
|
||||
|
@ -81,13 +91,14 @@ impl PlatformFont {
|
|||
font_identifier: FontIdentifier,
|
||||
data: Option<&FontData>,
|
||||
requested_size: Option<Au>,
|
||||
variations: &[FontVariation],
|
||||
) -> Result<PlatformFont, &'static str> {
|
||||
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<usize>,
|
||||
|
@ -201,17 +213,22 @@ impl PlatformFontMethods for PlatformFont {
|
|||
font_identifier: FontIdentifier,
|
||||
data: &FontData,
|
||||
requested_size: Option<Au>,
|
||||
_variations: &[FontVariation],
|
||||
variations: &[FontVariation],
|
||||
) -> Result<PlatformFont, &'static str> {
|
||||
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<Au>,
|
||||
_variations: &[FontVariation],
|
||||
variations: &[FontVariation],
|
||||
) -> Result<PlatformFont, &'static str> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue