mirror of
https://github.com/servo/servo.git
synced 2025-08-06 22:15:33 +01:00
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:
parent
b1debf2068
commit
e5fbb3d487
17 changed files with 365 additions and 320 deletions
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue