Skip to content

Commit

Permalink
Lazy load fonts in a FontGroup
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
jonleighton committed Feb 22, 2018
1 parent 691f3be commit f22e5ef
Show file tree
Hide file tree
Showing 7 changed files with 339 additions and 205 deletions.
141 changes: 124 additions & 17 deletions components/gfx/font.rs
Expand Up @@ -4,11 +4,13 @@

use app_units::Au;
use euclid::{Point2D, Rect, Size2D};
use font_context::FontContext;
use font_template::FontTemplateDescriptor;
use ordered_float::NotNaN;
use platform::font::{FontHandle, FontTable};
use platform::font_context::FontContextHandle;
use platform::font_template::FontTemplateData;
use servo_atoms::Atom;
use smallvec::SmallVec;
use std::borrow::ToOwned;
use std::cell::RefCell;
Expand All @@ -18,6 +20,8 @@ use std::str;
use std::sync::Arc;
use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
use style::computed_values::{font_stretch, font_variant_caps, font_weight};
use style::properties::style_structs::Font as FontStyleStruct;
use style::values::computed::font::SingleFontFamily;
use text::Shaper;
use text::glyph::{ByteIndex, GlyphData, GlyphId, GlyphStore};
use text::shaping::ShaperMethods;
Expand Down Expand Up @@ -59,6 +63,9 @@ pub trait FontHandleMethods: Sized {
fn can_do_fast_shaping(&self) -> bool;
fn metrics(&self) -> FontMetrics;
fn table_for_tag(&self, FontTableTag) -> Option<FontTable>;

/// A unique identifier for the font, allowing comparison.
fn identifier(&self) -> Atom;
}

// Used to abstract over the shaper's choice of fixed int representation.
Expand Down Expand Up @@ -100,13 +107,32 @@ pub struct FontMetrics {
pub line_gap: Au,
}

/// `FontDescriptor` describes the parameters of a `Font`. It represents rendering a given font
/// template at a particular size, with a particular font-variant-caps applied, etc. This contrasts
/// with `FontTemplateDescriptor` in that the latter represents only the parameters inherent in the
/// font data (weight, stretch, etc.).
#[derive(Clone, Debug, PartialEq)]
pub struct FontDescriptor {
pub template_descriptor: FontTemplateDescriptor,
pub variant: font_variant_caps::T,
pub pt_size: Au,
}

impl<'a> From<&'a FontStyleStruct> for FontDescriptor {
fn from(style: &'a FontStyleStruct) -> Self {
FontDescriptor {
template_descriptor: FontTemplateDescriptor::from(style),
variant: style.font_variant_caps,
pt_size: style.font_size.size(),
}
}
}

#[derive(Debug)]
pub struct Font {
pub handle: FontHandle,
pub metrics: FontMetrics,
pub variant: font_variant_caps::T,
pub descriptor: FontTemplateDescriptor,
pub requested_pt_size: Au,
pub descriptor: FontDescriptor,
pub actual_pt_size: Au,
shaper: Option<Shaper>,
shape_cache: RefCell<HashMap<ShapeCacheEntry, Arc<GlyphStore>>>,
Expand All @@ -116,25 +142,27 @@ pub struct Font {

impl Font {
pub fn new(handle: FontHandle,
variant: font_variant_caps::T,
descriptor: FontTemplateDescriptor,
requested_pt_size: Au,
descriptor: FontDescriptor,
actual_pt_size: Au,
font_key: webrender_api::FontInstanceKey) -> Font {
let metrics = handle.metrics();

Font {
handle: handle,
shaper: None,
variant: variant,
descriptor: descriptor,
requested_pt_size: requested_pt_size,
actual_pt_size: actual_pt_size,
metrics: metrics,
descriptor,
actual_pt_size,
metrics,
shape_cache: RefCell::new(HashMap::new()),
glyph_advance_cache: RefCell::new(HashMap::new()),
font_key: font_key,
font_key,
}
}

/// A unique identifier for the font, allowing comparison.
pub fn identifier(&self) -> Atom {
self.handle.identifier()
}
}

bitflags! {
Expand Down Expand Up @@ -260,13 +288,17 @@ impl Font {

#[inline]
pub fn glyph_index(&self, codepoint: char) -> Option<GlyphId> {
let codepoint = match self.variant {
let codepoint = match self.descriptor.variant {
font_variant_caps::T::SmallCaps => codepoint.to_uppercase().next().unwrap(), //FIXME: #5938
font_variant_caps::T::Normal => codepoint,
};
self.handle.glyph_index(codepoint)
}

pub fn has_glyph_for(&self, codepoint: char) -> bool {
self.glyph_index(codepoint).is_some()
}

pub fn glyph_h_kerning(&self, first_glyph: GlyphId, second_glyph: GlyphId)
-> FractionalPixel {
self.handle.glyph_h_kerning(first_glyph, second_glyph)
Expand All @@ -282,17 +314,92 @@ impl Font {
}
}

pub type FontRef = Rc<RefCell<Font>>;

/// A `FontGroup` is a prioritised list of fonts for a given set of font styles. It is used by
/// `TextRun` to decide which font to render a character with. If none of the fonts listed in the
/// styles are suitable, a fallback font may be used.
#[derive(Debug)]
pub struct FontGroup {
pub fonts: SmallVec<[Rc<RefCell<Font>>; 8]>,
descriptor: FontDescriptor,
families: SmallVec<[FontGroupFamily; 8]>,
}

impl FontGroup {
pub fn new(fonts: SmallVec<[Rc<RefCell<Font>>; 8]>) -> FontGroup {
FontGroup {
fonts: fonts,
pub fn new(style: &FontStyleStruct) -> FontGroup {
let descriptor = FontDescriptor::from(style);

let families =
style.font_family.0.iter()
.map(|family| FontGroupFamily::new(descriptor.clone(), family.clone()))
.collect();

FontGroup { descriptor, families }
}

/// Finds the first font, or else the first fallback font, which contains a glyph for
/// `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> {
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> {
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>(
&mut self,
mut font_context: &mut FontContext,
mut predicate: P
) -> Option<FontRef>
where P: FnMut(&FontRef) -> bool {
self.families.iter_mut()
.filter_map(|family| family.font(&mut font_context))
.find(|f| predicate(f))
.or_else(|| {
font_context.fallback_font(&self.descriptor)
.into_iter().find(predicate)
})
}
}

/// A `FontGroupFamily` is a single font family in a `FontGroup`. It corresponds to one of the
/// families listed in the `font-family` CSS property. The corresponding font data is lazy-loaded,
/// only if actually needed.
#[derive(Debug)]
struct FontGroupFamily {
descriptor: FontDescriptor,
family: SingleFontFamily,
loaded: bool,
font: Option<FontRef>,
}

impl FontGroupFamily {
fn new(descriptor: FontDescriptor, family: SingleFontFamily) -> FontGroupFamily {
FontGroupFamily {
descriptor,
family,
loaded: false,
font: None,
}
}

/// 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> {
if !self.loaded {
self.font = font_context.font(&self.descriptor, &self.family);
self.loaded = true;
}

self.font.clone()
}
}

pub struct RunMetrics {
Expand Down

0 comments on commit f22e5ef

Please sign in to comment.