fonts: Add FontIdentifier and LocalFontIdentifier (#31658)

Instead of using a simple `Atom` to identify a local font, use a data
structure. This allows us to carry more information necessary to
identify a local font (such as a path on MacOS). We need this for the
new version of WebRender, as fonts on MacOS now require a path.

This has a lot of benefits:
 1. We can avoid loading fonts without paths on MacOS, which should
    avoid a lot of problems with flakiness and ensure we always load the
    same font for a given identifier.
 2. This clarifies the difference between web fonts and local fonts,
    though there is more work to do here.
 3. This avoid a *lot* of font shenanigans, such as trying to work
    backwards from the name of the font to the path of the font we
    actually matched. In general, we can remove a lot of code trying to
    accomplish these shenanigans.
 4. Getting the font bytes always returns an `Arc` now avoiding an extra
    full font copy in the case of Canvas.
This commit is contained in:
Martin Robinson 2024-03-14 12:31:00 +01:00 committed by GitHub
parent b1debf2068
commit e5fbb3d487
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 365 additions and 320 deletions

View file

@ -19,13 +19,13 @@ use core_text::font_descriptor::{
kCTFontDefaultOrientation, SymbolicTraitAccessors, TraitAccessors,
};
use log::debug;
use servo_atoms::Atom;
use style::values::computed::font::{FontStretch, FontStyle, FontWeight};
use crate::font::{
FontHandleMethods, FontMetrics, FontTableMethods, FontTableTag, FractionalPixel, GPOS, GSUB,
KERN,
};
use crate::font_cache_thread::FontIdentifier;
use crate::platform::font_template::FontTemplateData;
use crate::platform::macos::font_context::FontContextHandle;
use crate::text::glyph::GlyphId;
@ -325,7 +325,7 @@ impl FontHandleMethods for FontHandle {
result.and_then(|data| Some(FontTable::wrap(data)))
}
fn identifier(&self) -> Atom {
self.font_data.identifier.clone()
fn identifier(&self) -> &FontIdentifier {
&self.font_data.identifier
}
}

View file

@ -3,10 +3,21 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use log::debug;
use serde::{Deserialize, Serialize};
use style::Atom;
use ucd::{Codepoint, UnicodeBlock};
use crate::text::util::unicode_plane;
/// 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
/// have paths.
#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct LocalFontIdentifier {
pub postscript_name: Atom,
pub path: Atom,
}
pub fn for_each_available_family<F>(mut callback: F)
where
F: FnMut(String),
@ -19,7 +30,7 @@ where
pub fn for_each_variation<F>(family_name: &str, mut callback: F)
where
F: FnMut(String),
F: FnMut(LocalFontIdentifier),
{
debug!("Looking for faces of family: {}", family_name);
@ -27,7 +38,15 @@ where
if let Some(family_collection) = family_collection {
if let Some(family_descriptors) = family_collection.get_descriptors() {
for family_descriptor in family_descriptors.iter() {
callback(family_descriptor.font_name());
let path = family_descriptor.font_path();
let path = match path.as_ref().and_then(|path| path.to_str()) {
Some(path) => path,
None => continue,
};
callback(LocalFontIdentifier {
postscript_name: Atom::from(family_descriptor.font_name()),
path: Atom::from(path),
})
}
}
}

View file

@ -2,34 +2,27 @@
* 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 std::borrow::ToOwned;
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::fmt;
use std::fs::{self, File};
use std::fs::File;
use std::io::{Error as IoError, Read};
use std::ops::Deref;
use std::path::Path;
use std::sync::{Arc, Mutex, RwLock};
use app_units::Au;
use core_foundation::array::CFArray;
use core_foundation::base::{CFType, TCFType};
use core_foundation::dictionary::CFDictionary;
use core_foundation::string::CFString;
use core_graphics::data_provider::CGDataProvider;
use core_graphics::font::CGFont;
use core_text::font::CTFont;
use core_text::{font_collection, font_descriptor};
use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use servo_atoms::Atom;
use servo_url::ServoUrl;
use webrender_api::NativeFontHandle;
/// Platform specific font representation for mac.
/// The identifier is a PostScript font name. The
/// CTFont object is cached here for use by the
/// paint functions that create CGFont references.
use crate::font_cache_thread::FontIdentifier;
/// Platform specific font representation for MacOS. CTFont object is cached here for use
/// by the paint functions that create CGFont references.
#[derive(Deserialize, Serialize)]
pub struct FontTemplateData {
// If you add members here, review the Debug impl below
@ -41,8 +34,7 @@ pub struct FontTemplateData {
/// the other side, because `CTFont` instances cannot be sent across processes. This is
/// harmless, however, because it can always be recreated.
ctfont: CachedCTFont,
pub identifier: Atom,
pub identifier: FontIdentifier,
pub font_data: RwLock<Option<Arc<Vec<u8>>>>,
}
@ -68,10 +60,13 @@ unsafe impl Send for FontTemplateData {}
unsafe impl Sync for FontTemplateData {}
impl FontTemplateData {
pub fn new(identifier: Atom, font_data: Option<Vec<u8>>) -> Result<FontTemplateData, IoError> {
pub fn new(
identifier: FontIdentifier,
font_data: Option<Vec<u8>>,
) -> Result<FontTemplateData, IoError> {
Ok(FontTemplateData {
ctfont: CachedCTFont(Mutex::new(HashMap::new())),
identifier: identifier.to_owned(),
identifier,
font_data: RwLock::new(font_data.map(Arc::new)),
})
}
@ -79,105 +74,53 @@ impl FontTemplateData {
/// Retrieves the Core Text font instance, instantiating it if necessary.
pub fn ctfont(&self, pt_size: f64) -> Option<CTFont> {
let mut ctfonts = self.ctfont.lock().unwrap();
let pt_size_key = Au::from_f64_px(pt_size);
if !ctfonts.contains_key(&pt_size_key) {
// 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 mut font_data = self.font_data.write().unwrap();
let ctfont = match *font_data {
Some(ref bytes) => {
let fontprov = CGDataProvider::from_buffer(bytes.clone());
let cgfont_result = CGFont::from_data_provider(fontprov);
match cgfont_result {
Ok(cgfont) => {
Some(core_text::font::new_from_CGFont(&cgfont, clamped_pt_size))
},
Err(_) => None,
}
},
None => {
// We can't rely on Core Text to load a font for us by postscript
// name here, due to https://github.com/servo/servo/issues/23290.
// The APIs will randomly load the wrong font, forcing us to use
// the roundabout route of creating a Core Graphics font from a
// a set of descriptors and then creating a Core Text font from
// that one.
let attributes: CFDictionary<CFString, CFType> =
CFDictionary::from_CFType_pairs(&[(
CFString::new("NSFontNameAttribute"),
CFString::new(&*self.identifier).as_CFType(),
)]);
let descriptor = font_descriptor::new_from_attributes(&attributes);
let descriptors = CFArray::from_CFTypes(&[descriptor]);
let collection = font_collection::new_from_descriptors(&descriptors);
collection.get_descriptors().and_then(|descriptors| {
let descriptor = descriptors.get(0).unwrap();
let font_path = Path::new(&descriptor.font_path().unwrap()).to_owned();
fs::read(&font_path).ok().and_then(|bytes| {
let font_bytes = Arc::new(bytes);
let fontprov = CGDataProvider::from_buffer(font_bytes.clone());
CGFont::from_data_provider(fontprov).ok().map(|cgfont| {
*font_data = Some(font_bytes);
core_text::font::new_from_CGFont(&cgfont, clamped_pt_size)
})
})
})
},
};
if let Some(ctfont) = ctfont {
ctfonts.insert(pt_size_key, ctfont);
}
let entry = ctfonts.entry(Au::from_f64_px(pt_size));
match entry {
Entry::Occupied(entry) => return Some(entry.get().clone()),
Entry::Vacant(_) => {},
}
ctfonts.get(&pt_size_key).map(|ctfont| (*ctfont).clone())
// 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 provider = CGDataProvider::from_buffer(self.bytes());
let cgfont = CGFont::from_data_provider(provider).ok()?;
let ctfont = core_text::font::new_from_CGFont(&cgfont, clamped_pt_size);
// Cache the newly created CTFont font.
entry.or_insert(ctfont.clone());
Some(ctfont)
}
/// Returns a clone of the data in this font. This may be a hugely expensive
/// Returns a reference to the data in this font. This may be a hugely expensive
/// operation (depending on the platform) which performs synchronous disk I/O
/// and should never be done lightly.
pub fn bytes(&self) -> Vec<u8> {
if let Some(font_data) = self.bytes_if_in_memory() {
return font_data;
}
// This is spooky action at a distance, but getting a CTFont from this template
// will (in the common case) bring the bytes into memory if they were not there
// already. This also helps work around intermittent panics like
// https://github.com/servo/servo/issues/24622 that occur for unclear reasons.
let ctfont = self.ctfont(0.0);
if let Some(font_data) = self.bytes_if_in_memory() {
return font_data;
}
let path = ServoUrl::parse(
&*ctfont
.expect("No Core Text font available!")
.url()
.expect("No URL for Core Text font!")
.get_string()
.to_string(),
)
.expect("Couldn't parse Core Text font URL!")
.to_file_path()
.expect("Core Text font didn't name a path!");
let mut bytes = Vec::new();
File::open(path)
.expect("Couldn't open font file!")
.read_to_end(&mut bytes)
.unwrap();
bytes
pub fn bytes(&self) -> Arc<Vec<u8>> {
self.font_data
.write()
.unwrap()
.get_or_insert_with(|| {
let path = match &self.identifier {
FontIdentifier::Local(local) => local.path.clone(),
FontIdentifier::Web(_) => unreachable!("Web fonts should always have data."),
};
let mut bytes = Vec::new();
File::open(Path::new(&*path))
.expect("Couldn't open font file!")
.read_to_end(&mut bytes)
.unwrap();
Arc::new(bytes)
})
.clone()
}
/// Returns a clone of the bytes in this font if they are in memory. This function never
/// performs disk I/O.
pub fn bytes_if_in_memory(&self) -> Option<Vec<u8>> {
self.font_data
.read()
.unwrap()
.as_ref()
.map(|bytes| (**bytes).clone())
/// Returns a reference to the bytes in this font if they are in memory.
/// This function never performs disk I/O.
pub fn bytes_if_in_memory(&self) -> Option<Arc<Vec<u8>>> {
self.font_data.read().unwrap().as_ref().cloned()
}
/// Returns the native font that underlies this font template, if applicable.