Lazy load fonts in a FontGroup

This is a step towards fixing #17267. To fix that, we need to be able to
try various different fallback fonts in turn, which would become
unweildy with the prior eager-loading strategy.

Prior to this change, FontGroup loaded up all Font instances, including
the fallback font, before any of them were checked for the presence of
the glyphs we're trying to render.

So for the following CSS:

    font-family: Helvetica, Arial;

The FontGroup would contain a Font instance for Helvetica, and a Font
instance for Arial, and a Font instance for the fallback font.

It may be that Helvetica contains glyphs for every character in the
document, and therefore Arial and the fallback font are not needed at
all.

This change makes the strategy lazy, so that we'll only create a Font
for Arial if we cannot find a glyph within Helvetica. I've also
substantially refactored the existing code in the process and added
some documentation along the way.
This commit is contained in:
Jon Leighton 2018-02-10 10:27:54 +01:00
parent 691f3be24a
commit f22e5ef3bd
7 changed files with 345 additions and 211 deletions

View file

@ -4,38 +4,54 @@
use app_units::Au;
use fnv::FnvHasher;
use font::{Font, FontGroup, FontHandleMethods};
use font_cache_thread::FontCacheThread;
use font_template::FontTemplateDescriptor;
use font::{Font, FontDescriptor, FontGroup, FontHandleMethods, FontRef};
use font_cache_thread::{FontCacheThread, FontTemplateInfo};
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use platform::font::FontHandle;
pub use platform::font_context::FontContextHandle;
use platform::font_template::FontTemplateData;
use servo_arc::Arc as ServoArc;
use smallvec::SmallVec;
use servo_arc::Arc;
use servo_atoms::Atom;
use std::cell::RefCell;
use std::collections::HashMap;
use std::default::Default;
use std::hash::{BuildHasherDefault, Hash, Hasher};
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
use style::computed_values::font_style::T as FontStyle;
use style::computed_values::font_variant_caps::T as FontVariantCaps;
use style::properties::style_structs;
use webrender_api;
use style::properties::style_structs::Font as FontStyleStruct;
use style::values::computed::font::SingleFontFamily;
static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8; // Matches FireFox (see gfxFont.h)
#[derive(Debug)]
struct LayoutFontCacheEntry {
family: String,
font: Option<Rc<RefCell<Font>>>,
struct FontCacheEntry {
family: Atom,
font: Option<FontRef>,
}
impl FontCacheEntry {
fn matches(&self, descriptor: &FontDescriptor, family: &SingleFontFamily) -> bool {
if self.family != *family.atom() {
return false
}
if let Some(ref font) = self.font {
(*font).borrow().descriptor == *descriptor
} else {
true
}
}
}
#[derive(Debug)]
struct FallbackFontCacheEntry {
font: Rc<RefCell<Font>>,
font: FontRef,
}
impl FallbackFontCacheEntry {
fn matches(&self, descriptor: &FontDescriptor) -> bool {
self.font.borrow().descriptor == *descriptor
}
}
/// An epoch for the font context cache. The cache is flushed if the current epoch does not match
@ -51,12 +67,16 @@ pub struct FontContext {
platform_handle: FontContextHandle,
font_cache_thread: FontCacheThread,
/// TODO: See bug https://github.com/servo/servo/issues/3300.
layout_font_cache: Vec<LayoutFontCacheEntry>,
// TODO: The font context holds a strong ref to the cached fonts
// so they will never be released. Find out a good time to drop them.
// See bug https://github.com/servo/servo/issues/3300
//
// GWTODO: Check on real pages if this is faster as Vec() or HashMap().
font_cache: Vec<FontCacheEntry>,
fallback_font_cache: Vec<FallbackFontCacheEntry>,
layout_font_group_cache:
HashMap<LayoutFontGroupCacheKey, Rc<FontGroup>, BuildHasherDefault<FnvHasher>>,
font_group_cache:
HashMap<FontGroupCacheKey, Rc<RefCell<FontGroup>>, BuildHasherDefault<FnvHasher>>,
epoch: usize,
}
@ -67,35 +87,32 @@ impl FontContext {
FontContext {
platform_handle: handle,
font_cache_thread: font_cache_thread,
layout_font_cache: vec!(),
font_cache: vec!(),
fallback_font_cache: vec!(),
layout_font_group_cache: HashMap::with_hasher(Default::default()),
font_group_cache: HashMap::with_hasher(Default::default()),
epoch: 0,
}
}
/// Create a font for use in layout calculations.
fn create_layout_font(&self,
template: Arc<FontTemplateData>,
descriptor: FontTemplateDescriptor,
pt_size: Au,
variant: FontVariantCaps,
font_key: webrender_api::FontKey) -> Result<Font, ()> {
/// Create a `Font` for use in layout calculations, from a `FontTemplateInfo` returned by the
/// cache thread (which contains the underlying font data) and a `FontDescriptor` which
/// contains the styling parameters.
fn create_font(&self, info: FontTemplateInfo, descriptor: FontDescriptor) -> Result<Font, ()> {
// TODO: (Bug #3463): Currently we only support fake small-caps
// painting. We should also support true small-caps (where the
// font supports it) in the future.
let actual_pt_size = match variant {
FontVariantCaps::SmallCaps => pt_size.scale_by(SMALL_CAPS_SCALE_FACTOR),
FontVariantCaps::Normal => pt_size,
let actual_pt_size = match descriptor.variant {
FontVariantCaps::SmallCaps => descriptor.pt_size.scale_by(SMALL_CAPS_SCALE_FACTOR),
FontVariantCaps::Normal => descriptor.pt_size,
};
let handle = FontHandle::new_from_template(&self.platform_handle,
template,
info.font_template,
Some(actual_pt_size))?;
let font_instance_key = self.font_cache_thread
.get_font_instance(font_key, actual_pt_size);
Ok(Font::new(handle, variant, descriptor, pt_size, actual_pt_size, font_instance_key))
.get_font_instance(info.font_key, actual_pt_size);
Ok(Font::new(handle, descriptor.to_owned(), actual_pt_size, font_instance_key))
}
fn expire_font_caches_if_necessary(&mut self) {
@ -104,133 +121,103 @@ impl FontContext {
return
}
self.layout_font_cache.clear();
self.font_cache.clear();
self.fallback_font_cache.clear();
self.layout_font_group_cache.clear();
self.font_group_cache.clear();
self.epoch = current_epoch
}
/// Create a group of fonts for use in layout calculations. May return
/// a cached font if this font instance has already been used by
/// this context.
pub fn layout_font_group_for_style(&mut self, style: ServoArc<style_structs::Font>)
-> Rc<FontGroup> {
/// Returns a `FontGroup` representing fonts which can be used for layout, given the `style`.
/// Font groups are cached, so subsequent calls with the same `style` will return a reference
/// to an existing `FontGroup`.
pub fn font_group(&mut self, style: Arc<FontStyleStruct>) -> Rc<RefCell<FontGroup>> {
self.expire_font_caches_if_necessary();
let layout_font_group_cache_key = LayoutFontGroupCacheKey {
pointer: style.clone(),
let cache_key = FontGroupCacheKey {
size: style.font_size.size(),
style,
};
if let Some(ref cached_font_group) = self.layout_font_group_cache.get(
&layout_font_group_cache_key) {
return (*cached_font_group).clone()
if let Some(ref font_group) = self.font_group_cache.get(&cache_key) {
return (*font_group).clone()
}
// TODO: The font context holds a strong ref to the cached fonts
// so they will never be released. Find out a good time to drop them.
let desc = FontTemplateDescriptor::new(style.font_weight,
style.font_stretch,
style.font_style == FontStyle::Italic ||
style.font_style == FontStyle::Oblique);
let mut fonts: SmallVec<[Rc<RefCell<Font>>; 8]> = SmallVec::new();
for family in style.font_family.0.iter() {
// GWTODO: Check on real pages if this is faster as Vec() or HashMap().
let mut cache_hit = false;
for cached_font_entry in &self.layout_font_cache {
if cached_font_entry.family == family.name() {
match cached_font_entry.font {
None => {
cache_hit = true;
break;
}
Some(ref cached_font_ref) => {
let cached_font = (*cached_font_ref).borrow();
if cached_font.descriptor == desc &&
cached_font.requested_pt_size == style.font_size.size() &&
cached_font.variant == style.font_variant_caps {
fonts.push((*cached_font_ref).clone());
cache_hit = true;
break;
}
}
}
}
}
if !cache_hit {
let template_info = self.font_cache_thread.find_font_template(family.clone(),
desc.clone());
match template_info {
Some(template_info) => {
let layout_font = self.create_layout_font(template_info.font_template,
desc.clone(),
style.font_size.size(),
style.font_variant_caps,
template_info.font_key);
let font = match layout_font {
Ok(layout_font) => {
let layout_font = Rc::new(RefCell::new(layout_font));
fonts.push(layout_font.clone());
Some(layout_font)
}
Err(_) => None
};
self.layout_font_cache.push(LayoutFontCacheEntry {
family: family.name().to_owned(),
font: font
});
}
None => {
self.layout_font_cache.push(LayoutFontCacheEntry {
family: family.name().to_owned(),
font: None,
});
}
}
}
}
// Add a last resort font as a fallback option.
let mut cache_hit = false;
for cached_font_entry in &self.fallback_font_cache {
let cached_font = cached_font_entry.font.borrow();
if cached_font.descriptor == desc &&
cached_font.requested_pt_size == style.font_size.size() &&
cached_font.variant == style.font_variant_caps {
fonts.push(cached_font_entry.font.clone());
cache_hit = true;
break;
}
}
if !cache_hit {
let template_info = self.font_cache_thread.last_resort_font_template(desc.clone());
let layout_font = self.create_layout_font(template_info.font_template,
desc.clone(),
style.font_size.size(),
style.font_variant_caps,
template_info.font_key);
match layout_font {
Ok(layout_font) => {
let layout_font = Rc::new(RefCell::new(layout_font));
self.fallback_font_cache.push(FallbackFontCacheEntry {
font: layout_font.clone(),
});
fonts.push(layout_font);
}
Err(_) => debug!("Failed to create fallback layout font!")
}
}
let font_group = Rc::new(FontGroup::new(fonts));
self.layout_font_group_cache.insert(layout_font_group_cache_key, font_group.clone());
let font_group = Rc::new(RefCell::new(FontGroup::new(&cache_key.style)));
self.font_group_cache.insert(cache_key, font_group.clone());
font_group
}
/// Returns a reference to an existing font cache entry matching `descriptor` and `family`, if
/// there is one.
fn font_cache_entry(&self, descriptor: &FontDescriptor, family: &SingleFontFamily) -> Option<&FontCacheEntry> {
self.font_cache.iter()
.find(|cache_entry| cache_entry.matches(&descriptor, &family))
}
/// Creates a new font cache entry matching `descriptor` and `family`.
fn create_font_cache_entry(&self, descriptor: &FontDescriptor, family: &SingleFontFamily) -> FontCacheEntry {
let font =
self.font_cache_thread.find_font_template(family.clone(), descriptor.template_descriptor.clone())
.and_then(|template_info|
self.create_font(template_info, descriptor.to_owned()).ok()
)
.map(|font| Rc::new(RefCell::new(font)));
FontCacheEntry { family: family.atom().to_owned(), font }
}
/// Returns a font from `family` matching the `descriptor`. Fonts are cached, so repeated calls
/// will return a reference to the same underlying `Font`.
pub fn font(&mut self, descriptor: &FontDescriptor, family: &SingleFontFamily) -> Option<FontRef> {
if let Some(entry) = self.font_cache_entry(descriptor, family) {
return entry.font.clone()
}
let entry = self.create_font_cache_entry(descriptor, family);
let font = entry.font.clone();
self.font_cache.push(entry);
font
}
/// Returns a reference to an existing fallback font cache entry matching `descriptor`, if
/// there is one.
fn fallback_font_cache_entry(&self, descriptor: &FontDescriptor) -> Option<&FallbackFontCacheEntry> {
self.fallback_font_cache.iter()
.find(|cache_entry| cache_entry.matches(descriptor))
}
/// Creates a new fallback font cache entry matching `descriptor`.
fn create_fallback_font_cache_entry(&self, descriptor: &FontDescriptor) -> Option<FallbackFontCacheEntry> {
let template_info = self.font_cache_thread.last_resort_font_template(descriptor.template_descriptor.clone());
match self.create_font(template_info, descriptor.to_owned()) {
Ok(font) =>
Some(FallbackFontCacheEntry {
font: Rc::new(RefCell::new(font))
}),
Err(_) => {
debug!("Failed to create fallback font!");
None
}
}
}
/// Returns a fallback font matching the `descriptor`. Fonts are cached, so repeated calls will
/// return a reference to the same underlying `Font`.
pub fn fallback_font(&mut self, descriptor: &FontDescriptor) -> Option<FontRef> {
if let Some(cached_entry) = self.fallback_font_cache_entry(descriptor) {
return Some(cached_entry.font.clone())
};
if let Some(entry) = self.create_fallback_font_cache_entry(descriptor) {
let font = entry.font.clone();
self.fallback_font_cache.push(entry);
Some(font)
} else {
None
}
}
}
impl MallocSizeOf for FontContext {
@ -241,22 +228,22 @@ impl MallocSizeOf for FontContext {
}
#[derive(Debug)]
struct LayoutFontGroupCacheKey {
pointer: ServoArc<style_structs::Font>,
struct FontGroupCacheKey {
style: Arc<FontStyleStruct>,
size: Au,
}
impl PartialEq for LayoutFontGroupCacheKey {
fn eq(&self, other: &LayoutFontGroupCacheKey) -> bool {
self.pointer == other.pointer && self.size == other.size
impl PartialEq for FontGroupCacheKey {
fn eq(&self, other: &FontGroupCacheKey) -> bool {
self.style == other.style && self.size == other.size
}
}
impl Eq for LayoutFontGroupCacheKey {}
impl Eq for FontGroupCacheKey {}
impl Hash for LayoutFontGroupCacheKey {
impl Hash for FontGroupCacheKey {
fn hash<H>(&self, hasher: &mut H) where H: Hasher {
self.pointer.hash.hash(hasher)
self.style.hash.hash(hasher)
}
}