diff --git a/components/gfx/font_cache_thread.rs b/components/gfx/font_cache_thread.rs index 72027689e2f..5a98e717548 100644 --- a/components/gfx/font_cache_thread.rs +++ b/components/gfx/font_cache_thread.rs @@ -18,6 +18,7 @@ use std::borrow::ToOwned; use std::collections::HashMap; use std::mem; use std::sync::{Arc, Mutex}; +use std::u32; use string_cache::Atom; use style::font_face::Source; use style::properties::longhands::font_family::computed_value::FontFamily; @@ -51,9 +52,6 @@ impl FontTemplates { // TODO(Issue #189): optimize lookup for // regular/bold/italic/bolditalic with fixed offsets and a // static decision table for fallback between these values. - - // TODO(Issue #190): if not in the fast path above, do - // expensive matching of weights, etc. for template in &mut self.templates { let maybe_template = template.data_for_descriptor(fctx, desc); if maybe_template.is_some() { @@ -61,6 +59,22 @@ impl FontTemplates { } } + // We didn't find an exact match. Do more expensive fuzzy matching. + // TODO(#190): Do a better job. + let (mut best_template_data, mut best_distance) = (None, u32::MAX); + for template in &mut self.templates { + if let Some((template_data, distance)) = + template.data_for_approximate_descriptor(fctx, desc) { + if distance < best_distance { + best_template_data = Some(template_data); + best_distance = distance + } + } + } + if best_template_data.is_some() { + return best_template_data + } + // If a request is made for a font family that exists, // pick the first valid font in the family if we failed // to find an exact match for the descriptor. @@ -81,8 +95,7 @@ impl FontTemplates { } } - let template = FontTemplate::new(identifier, - maybe_data); + let template = FontTemplate::new(identifier, maybe_data); self.templates.push(template); } } diff --git a/components/gfx/font_template.rs b/components/gfx/font_template.rs index 0b4cd6900ef..c05230a9930 100644 --- a/components/gfx/font_template.rs +++ b/components/gfx/font_template.rs @@ -7,6 +7,7 @@ use platform::font::FontHandle; use platform::font_context::FontContextHandle; use platform::font_template::FontTemplateData; use std::sync::{Arc, Weak}; +use std::u32; use string_cache::Atom; use style::computed_values::{font_stretch, font_weight}; @@ -31,13 +32,25 @@ impl FontTemplateDescriptor { italic: italic, } } + + /// Returns a score indicating how far apart visually the two font descriptors are. This is + /// used for fuzzy font selection. + /// + /// The smaller the score, the better the fonts match. 0 indicates an exact match. This must + /// be commutative (distance(A, B) == distance(B, A)). + #[inline] + fn distance_from(&self, other: &FontTemplateDescriptor) -> u32 { + if self.stretch != other.stretch || self.italic != other.italic { + // A value higher than all weights. + return 1000 + } + ((self.weight as i16) - (other.weight as i16)).abs() as u32 + } } impl PartialEq for FontTemplateDescriptor { fn eq(&self, other: &FontTemplateDescriptor) -> bool { - self.weight.is_bold() == other.weight.is_bold() && - self.stretch == other.stretch && - self.italic == other.italic + self.weight == other.weight && self.stretch == other.stretch && self.italic == other.italic } } @@ -88,52 +101,74 @@ impl FontTemplate { /// Get the data for creating a font if it matches a given descriptor. pub fn data_for_descriptor(&mut self, - fctx: &FontContextHandle, - requested_desc: &FontTemplateDescriptor) - -> Option> { + fctx: &FontContextHandle, + requested_desc: &FontTemplateDescriptor) + -> Option> { // The font template data can be unloaded when nothing is referencing // it (via the Weak reference to the Arc above). However, if we have // already loaded a font, store the style information about it separately, // so that we can do font matching against it again in the future // without having to reload the font (unless it is an actual match). match self.descriptor { - Some(actual_desc) => { - if *requested_desc == actual_desc { + Some(actual_desc) if *requested_desc == actual_desc => Some(self.data()), + Some(_) => None, + None => { + if self.instantiate(fctx).is_err() { + return None + } + + if self.descriptor + .as_ref() + .expect("Instantiation succeeded but no descriptor?") == requested_desc { Some(self.data()) } else { None } - }, - None if self.is_valid => { - let data = self.data(); - let handle: Result = - FontHandleMethods::new_from_template(fctx, data.clone(), None); - match handle { - Ok(handle) => { - let actual_desc = FontTemplateDescriptor::new(handle.boldness(), - handle.stretchiness(), - handle.is_italic()); - let desc_match = actual_desc == *requested_desc; + } + } + } - self.descriptor = Some(actual_desc); - self.is_valid = true; - if desc_match { - Some(data) - } else { - None - } - } - Err(()) => { - self.is_valid = false; - debug!("Unable to create a font from template {}", self.identifier); - None - } + /// Returns the font data along with the distance between this font's descriptor and the given + /// descriptor, if the font can be loaded. + pub fn data_for_approximate_descriptor(&mut self, + font_context: &FontContextHandle, + requested_descriptor: &FontTemplateDescriptor) + -> Option<(Arc, u32)> { + match self.descriptor { + Some(actual_descriptor) => { + Some((self.data(), actual_descriptor.distance_from(requested_descriptor))) + } + None => { + if self.instantiate(font_context).is_ok() { + let distance = self.descriptor + .as_ref() + .expect("Instantiation successful but no descriptor?") + .distance_from(requested_descriptor); + Some((self.data(), distance)) + } else { + None } } - None => None, } } + fn instantiate(&mut self, font_context: &FontContextHandle) -> Result<(), ()> { + if !self.is_valid { + return Err(()) + } + + let data = self.data(); + let handle: Result = FontHandleMethods::new_from_template(font_context, + data, + None); + self.is_valid = handle.is_ok(); + let handle = try!(handle); + self.descriptor = Some(FontTemplateDescriptor::new(handle.boldness(), + handle.stretchiness(), + handle.is_italic())); + Ok(()) + } + /// Get the data for creating a font. pub fn get(&mut self) -> Option> { if self.is_valid {