Skip to content

Commit

Permalink
Add test for FontContext/FontGroup functionality
Browse files Browse the repository at this point in the history
Unfortunately, this required quite a bit of changes to the non-test
code. That's because FontContext depends on a FontCacheThread, which in
turn depends on a CoreResourceThread and therefore lots of other data
structures.

It seemed like it would be very difficult to instantiate a FontContext
as it was, and even if we could it seems like overkill to have all these
data structures present for a relatively focused test.

Therefore, I created a FontSource trait which represents the interface
which FontContext uses to talk to FontCacheThread. FontCacheThread then
implements FontSource. Then, in the test, we can create a dummy
implementation of FontSource rather than using FontCacheThread.

This actually has the advantage that we can make our dummy
implementation behave in certain specific way which are useful for
testing, for example it can count the number of times
find_font_template() is called, which helps us verify that
caching/lazy-loading is working as intended.
  • Loading branch information
jonleighton committed Feb 22, 2018
1 parent f22e5ef commit e4acb3f
Show file tree
Hide file tree
Showing 61 changed files with 381 additions and 62 deletions.
24 changes: 17 additions & 7 deletions components/gfx/font.rs
Expand Up @@ -4,7 +4,7 @@

use app_units::Au;
use euclid::{Point2D, Rect, Size2D};
use font_context::FontContext;
use font_context::{FontContext, FontSource};
use font_template::FontTemplateDescriptor;
use ordered_float::NotNaN;
use platform::font::{FontHandle, FontTable};
Expand Down Expand Up @@ -341,23 +341,33 @@ impl FontGroup {
/// `codepoint`. If no such font is found, returns the first available font or fallback font
/// (which will cause a "glyph not found" character to be rendered). If no font at all can be
/// found, returns None.
pub fn find_by_codepoint(&mut self, mut font_context: &mut FontContext, codepoint: char) -> Option<FontRef> {
pub fn find_by_codepoint<S: FontSource>(
&mut self,
mut font_context: &mut FontContext<S>,
codepoint: char
) -> Option<FontRef> {
self.find(&mut font_context, |font| font.borrow().has_glyph_for(codepoint))
.or_else(|| self.first(&mut font_context))
}

pub fn first(&mut self, mut font_context: &mut FontContext) -> Option<FontRef> {
pub fn first<S: FontSource>(
&mut self,
mut font_context: &mut FontContext<S>
) -> Option<FontRef> {
self.find(&mut font_context, |_| true)
}

/// Find a font which returns true for `predicate`. This method mutates because we may need to
/// load new font data in the process of finding a suitable font.
fn find<P>(
fn find<S, P>(
&mut self,
mut font_context: &mut FontContext,
mut font_context: &mut FontContext<S>,
mut predicate: P
) -> Option<FontRef>
where P: FnMut(&FontRef) -> bool {
where
S: FontSource,
P: FnMut(&FontRef) -> bool
{
self.families.iter_mut()
.filter_map(|family| family.font(&mut font_context))
.find(|f| predicate(f))
Expand Down Expand Up @@ -392,7 +402,7 @@ impl FontGroupFamily {
/// Returns the font within this family which matches the style. We'll fetch the data from the
/// `FontContext` the first time this method is called, and return a cached reference on
/// subsequent calls.
fn font(&mut self, font_context: &mut FontContext) -> Option<FontRef> {
fn font<S: FontSource>(&mut self, font_context: &mut FontContext<S>) -> Option<FontRef> {
if !self.loaded {
self.font = font_context.font(&self.descriptor, &self.family);
self.loaded = true;
Expand Down
64 changes: 33 additions & 31 deletions components/gfx/font_cache_thread.rs
Expand Up @@ -3,6 +3,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

use app_units::Au;
use font_context::FontSource;
use font_template::{FontTemplate, FontTemplateDescriptor};
use fontsan;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
Expand Down Expand Up @@ -30,7 +31,7 @@ use style::values::computed::font::{SingleFontFamily, FamilyName};
use webrender_api;

/// A list of font templates that make up a given font family.
struct FontTemplates {
pub struct FontTemplates {
templates: Vec<FontTemplate>,
}

Expand All @@ -41,14 +42,14 @@ pub struct FontTemplateInfo {
}

impl FontTemplates {
fn new() -> FontTemplates {
pub fn new() -> FontTemplates {
FontTemplates {
templates: vec!(),
}
}

/// Find a font in this family that matches a given descriptor.
fn find_font_for_style(&mut self, desc: &FontTemplateDescriptor, fctx: &FontContextHandle)
pub fn find_font_for_style(&mut self, desc: &FontTemplateDescriptor, fctx: &FontContextHandle)
-> Option<Arc<FontTemplateData>> {
// TODO(Issue #189): optimize lookup for
// regular/bold/italic/bolditalic with fixed offsets and a
Expand Down Expand Up @@ -89,7 +90,7 @@ impl FontTemplates {
None
}

fn add_template(&mut self, identifier: Atom, maybe_data: Option<Vec<u8>>) {
pub fn add_template(&mut self, identifier: Atom, maybe_data: Option<Vec<u8>>) {
for template in &self.templates {
if *template.identifier() == identifier {
return;
Expand Down Expand Up @@ -414,8 +415,8 @@ impl FontCache {
}
}

/// The public interface to the font cache thread, used exclusively by
/// the per-thread/thread FontContext structures.
/// The public interface to the font cache thread, used by per-thread `FontContext` instances (via
/// the `FontSource` trait), and also by layout.
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct FontCacheThread {
chan: IpcSender<Command>,
Expand Down Expand Up @@ -453,7 +454,31 @@ impl FontCacheThread {
}
}

pub fn find_font_template(&self, family: SingleFontFamily, desc: FontTemplateDescriptor)
pub fn add_web_font(&self, family: FamilyName, sources: EffectiveSources, sender: IpcSender<()>) {
self.chan.send(Command::AddWebFont(LowercaseString::new(&family.name), sources, sender)).unwrap();
}

pub fn exit(&self) {
let (response_chan, response_port) = ipc::channel().unwrap();
self.chan.send(Command::Exit(response_chan)).expect("Couldn't send FontCacheThread exit message");
response_port.recv().expect("Couldn't receive FontCacheThread reply");
}
}

impl FontSource for FontCacheThread {
fn get_font_instance(&mut self, key: webrender_api::FontKey, size: Au) -> webrender_api::FontInstanceKey {
let (response_chan, response_port) =
ipc::channel().expect("failed to create IPC channel");
self.chan.send(Command::GetFontInstance(key, size, response_chan))
.expect("failed to send message to font cache thread");

let instance_key = response_port.recv()
.expect("failed to receive response to font request");

instance_key
}

fn find_font_template(&mut self, family: SingleFontFamily, desc: FontTemplateDescriptor)
-> Option<FontTemplateInfo> {
let (response_chan, response_port) =
ipc::channel().expect("failed to create IPC channel");
Expand All @@ -470,7 +495,7 @@ impl FontCacheThread {
}
}

pub fn last_resort_font_template(&self, desc: FontTemplateDescriptor)
fn last_resort_font_template(&mut self, desc: FontTemplateDescriptor)
-> FontTemplateInfo {
let (response_chan, response_port) =
ipc::channel().expect("failed to create IPC channel");
Expand All @@ -486,31 +511,8 @@ impl FontCacheThread {
}
}
}

pub fn add_web_font(&self, family: FamilyName, sources: EffectiveSources, sender: IpcSender<()>) {
self.chan.send(Command::AddWebFont(LowercaseString::new(&family.name), sources, sender)).unwrap();
}

pub fn get_font_instance(&self, key: webrender_api::FontKey, size: Au) -> webrender_api::FontInstanceKey {
let (response_chan, response_port) =
ipc::channel().expect("failed to create IPC channel");
self.chan.send(Command::GetFontInstance(key, size, response_chan))
.expect("failed to send message to font cache thread");

let instance_key = response_port.recv()
.expect("failed to receive response to font request");

instance_key
}

pub fn exit(&self) {
let (response_chan, response_port) = ipc::channel().unwrap();
self.chan.send(Command::Exit(response_chan)).expect("Couldn't send FontCacheThread exit message");
response_port.recv().expect("Couldn't receive FontCacheThread reply");
}
}


#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct LowercaseString {
inner: String,
Expand Down
41 changes: 27 additions & 14 deletions components/gfx/font_context.rs
Expand Up @@ -5,7 +5,8 @@
use app_units::Au;
use fnv::FnvHasher;
use font::{Font, FontDescriptor, FontGroup, FontHandleMethods, FontRef};
use font_cache_thread::{FontCacheThread, FontTemplateInfo};
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;
Expand All @@ -20,6 +21,7 @@ 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)

Expand Down Expand Up @@ -58,14 +60,26 @@ impl FallbackFontCacheEntry {
/// 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 {
pub struct FontContext<S: FontSource> {
platform_handle: FontContextHandle,
font_cache_thread: FontCacheThread,
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.
Expand All @@ -81,12 +95,12 @@ pub struct FontContext {
epoch: usize,
}

impl FontContext {
pub fn new(font_cache_thread: FontCacheThread) -> FontContext {
impl<S: FontSource> FontContext<S> {
pub fn new(font_source: S) -> FontContext<S> {
let handle = FontContextHandle::new();
FontContext {
platform_handle: handle,
font_cache_thread: font_cache_thread,
font_source,
font_cache: vec!(),
fallback_font_cache: vec!(),
font_group_cache: HashMap::with_hasher(Default::default()),
Expand All @@ -97,7 +111,7 @@ impl FontContext {
/// 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, ()> {
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.
Expand All @@ -110,8 +124,7 @@ impl FontContext {
info.font_template,
Some(actual_pt_size))?;

let font_instance_key = self.font_cache_thread
.get_font_instance(info.font_key, 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))
}

Expand Down Expand Up @@ -155,9 +168,9 @@ impl FontContext {
}

/// Creates a new font cache entry matching `descriptor` and `family`.
fn create_font_cache_entry(&self, descriptor: &FontDescriptor, family: &SingleFontFamily) -> FontCacheEntry {
fn create_font_cache_entry(&mut self, descriptor: &FontDescriptor, family: &SingleFontFamily) -> FontCacheEntry {
let font =
self.font_cache_thread.find_font_template(family.clone(), descriptor.template_descriptor.clone())
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()
)
Expand Down Expand Up @@ -187,8 +200,8 @@ impl FontContext {
}

/// 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());
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) =>
Expand Down Expand Up @@ -220,7 +233,7 @@ impl FontContext {
}
}

impl MallocSizeOf for FontContext {
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)
Expand Down

0 comments on commit e4acb3f

Please sign in to comment.