gfx: Perform fuzzy matching on font weights if an exact match wasn't

found.

Partially addresses #190.
Partially addresses #9487.
This commit is contained in:
Patrick Walton 2016-05-09 15:59:10 -07:00
parent 0617727f5b
commit 479c6c9c42
2 changed files with 86 additions and 38 deletions

View file

@ -18,6 +18,7 @@ use std::borrow::ToOwned;
use std::collections::HashMap; use std::collections::HashMap;
use std::mem; use std::mem;
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
use std::u32;
use string_cache::Atom; use string_cache::Atom;
use style::font_face::Source; use style::font_face::Source;
use style::properties::longhands::font_family::computed_value::FontFamily; use style::properties::longhands::font_family::computed_value::FontFamily;
@ -51,9 +52,6 @@ impl FontTemplates {
// TODO(Issue #189): optimize lookup for // TODO(Issue #189): optimize lookup for
// regular/bold/italic/bolditalic with fixed offsets and a // regular/bold/italic/bolditalic with fixed offsets and a
// static decision table for fallback between these values. // 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 { for template in &mut self.templates {
let maybe_template = template.data_for_descriptor(fctx, desc); let maybe_template = template.data_for_descriptor(fctx, desc);
if maybe_template.is_some() { 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, // If a request is made for a font family that exists,
// pick the first valid font in the family if we failed // pick the first valid font in the family if we failed
// to find an exact match for the descriptor. // to find an exact match for the descriptor.
@ -81,8 +95,7 @@ impl FontTemplates {
} }
} }
let template = FontTemplate::new(identifier, let template = FontTemplate::new(identifier, maybe_data);
maybe_data);
self.templates.push(template); self.templates.push(template);
} }
} }

View file

@ -7,6 +7,7 @@ use platform::font::FontHandle;
use platform::font_context::FontContextHandle; use platform::font_context::FontContextHandle;
use platform::font_template::FontTemplateData; use platform::font_template::FontTemplateData;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use std::u32;
use string_cache::Atom; use string_cache::Atom;
use style::computed_values::{font_stretch, font_weight}; use style::computed_values::{font_stretch, font_weight};
@ -31,13 +32,25 @@ impl FontTemplateDescriptor {
italic: italic, 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 { impl PartialEq for FontTemplateDescriptor {
fn eq(&self, other: &FontTemplateDescriptor) -> bool { fn eq(&self, other: &FontTemplateDescriptor) -> bool {
self.weight.is_bold() == other.weight.is_bold() && self.weight == other.weight && self.stretch == other.stretch && self.italic == other.italic
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. /// Get the data for creating a font if it matches a given descriptor.
pub fn data_for_descriptor(&mut self, pub fn data_for_descriptor(&mut self,
fctx: &FontContextHandle, fctx: &FontContextHandle,
requested_desc: &FontTemplateDescriptor) requested_desc: &FontTemplateDescriptor)
-> Option<Arc<FontTemplateData>> { -> Option<Arc<FontTemplateData>> {
// The font template data can be unloaded when nothing is referencing // The font template data can be unloaded when nothing is referencing
// it (via the Weak reference to the Arc above). However, if we have // it (via the Weak reference to the Arc above). However, if we have
// already loaded a font, store the style information about it separately, // already loaded a font, store the style information about it separately,
// so that we can do font matching against it again in the future // 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). // without having to reload the font (unless it is an actual match).
match self.descriptor { match self.descriptor {
Some(actual_desc) => { Some(actual_desc) if *requested_desc == actual_desc => Some(self.data()),
if *requested_desc == actual_desc { 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()) Some(self.data())
} else { } else {
None None
} }
}, }
None if self.is_valid => { }
let data = self.data(); }
let handle: Result<FontHandle, ()> =
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); /// Returns the font data along with the distance between this font's descriptor and the given
self.is_valid = true; /// descriptor, if the font can be loaded.
if desc_match { pub fn data_for_approximate_descriptor(&mut self,
Some(data) font_context: &FontContextHandle,
} else { requested_descriptor: &FontTemplateDescriptor)
None -> Option<(Arc<FontTemplateData>, u32)> {
} match self.descriptor {
} Some(actual_descriptor) => {
Err(()) => { Some((self.data(), actual_descriptor.distance_from(requested_descriptor)))
self.is_valid = false; }
debug!("Unable to create a font from template {}", self.identifier); None => {
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<FontHandle, ()> = 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. /// Get the data for creating a font.
pub fn get(&mut self) -> Option<Arc<FontTemplateData>> { pub fn get(&mut self) -> Option<Arc<FontTemplateData>> {
if self.is_valid { if self.is_valid {