Blob Blame History Raw
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use app_units::Au;
use fnv::FnvHasher;
use font::{Font, FontDescriptor, FontGroup, FontHandleMethods, FontRef};
use font_cache_thread::FontTemplateInfo;
use font_template::FontTemplateDescriptor;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use platform::font::FontHandle;
pub use platform::font_context::FontContextHandle;
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::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
use style::computed_values::font_variant_caps::T as FontVariantCaps;
use style::properties::style_structs::Font as FontStyleStruct;
use style::values::computed::font::SingleFontFamily;
use webrender_api;

static SMALL_CAPS_SCALE_FACTOR: f32 = 0.8;      // Matches FireFox (see gfxFont.h)

#[derive(Debug)]
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: 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
/// this one.
static FONT_CACHE_EPOCH: AtomicUsize = ATOMIC_USIZE_INIT;

pub trait FontSource {
    fn get_font_instance(&mut self, key: webrender_api::FontKey, size: Au) -> webrender_api::FontInstanceKey;

    fn find_font_template(
        &mut self,
        family: SingleFontFamily,
        desc: FontTemplateDescriptor
    ) -> Option<FontTemplateInfo>;

    fn last_resort_font_template(&mut self, desc: FontTemplateDescriptor) -> FontTemplateInfo;
}

/// The FontContext represents the per-thread/thread state necessary for
/// working with fonts. It is the public API used by the layout and
/// paint code. It talks directly to the font cache thread where
/// required.
#[derive(Debug)]
pub struct FontContext<S: FontSource> {
    platform_handle: FontContextHandle,
    font_source: S,

    // 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>,

    font_group_cache:
        HashMap<FontGroupCacheKey, Rc<RefCell<FontGroup>>, BuildHasherDefault<FnvHasher>>,

    epoch: usize,
}

impl<S: FontSource> FontContext<S> {
    pub fn new(font_source: S) -> FontContext<S> {
        let handle = FontContextHandle::new();
        FontContext {
            platform_handle: handle,
            font_source,
            font_cache: vec!(),
            fallback_font_cache: vec!(),
            font_group_cache: HashMap::with_hasher(Default::default()),
            epoch: 0,
        }
    }

    /// 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(&mut 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 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,
                                                        info.font_template,
                                                        Some(actual_pt_size))?;

        let font_instance_key = self.font_source.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) {
        let current_epoch = FONT_CACHE_EPOCH.load(Ordering::SeqCst);
        if current_epoch == self.epoch {
            return
        }

        self.font_cache.clear();
        self.fallback_font_cache.clear();
        self.font_group_cache.clear();
        self.epoch = current_epoch
    }

    /// 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 cache_key = FontGroupCacheKey {
            size: style.font_size.size(),
            style,
        };

        if let Some(ref font_group) = self.font_group_cache.get(&cache_key) {
            return (*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(&mut self, descriptor: &FontDescriptor, family: &SingleFontFamily) -> FontCacheEntry {
        let font =
            self.font_source.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(&mut self, descriptor: &FontDescriptor) -> Option<FallbackFontCacheEntry> {
        let template_info = self.font_source.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<S: FontSource> MallocSizeOf for FontContext<S> {
    fn size_of(&self, ops: &mut MallocSizeOfOps) -> usize {
        // FIXME(njn): Measure other fields eventually.
        self.platform_handle.size_of(ops)
    }
}

#[derive(Debug)]
struct FontGroupCacheKey {
    style: Arc<FontStyleStruct>,
    size: Au,
}

impl PartialEq for FontGroupCacheKey {
    fn eq(&self, other: &FontGroupCacheKey) -> bool {
        self.style == other.style && self.size == other.size
    }
}

impl Eq for FontGroupCacheKey {}

impl Hash for FontGroupCacheKey {
    fn hash<H>(&self, hasher: &mut H) where H: Hasher {
        self.style.hash.hash(hasher)
    }
}

#[inline]
pub fn invalidate_font_caches() {
    FONT_CACHE_EPOCH.fetch_add(1, Ordering::SeqCst);
}