Skip to content

Commit

Permalink
auto merge of #548 : sfowler/servo/glyph-store-cache, r=pcwalton
Browse files Browse the repository at this point in the history
This PR makes text runs store the results of shaping as a vector of ARC<GlyphStore>; each element of the vector holds the shaped glyphs for a nonbreakable unit of text (basically a word). This change allows us to cache the shaped glyphs for the words, an approach that Gecko (and probably WebKit) uses. We get pretty good cache hit ratios even on the first run of layout for a page (I saw 62% on Wikipedia's main page today), although a lot of that is due to whitespace. This really comes into its own on subsequent layout runs, though, which are completely cached in the typical case.
  • Loading branch information
bors-servo committed Jun 27, 2013
2 parents a066ce6 + 677fce2 commit 74ab914
Show file tree
Hide file tree
Showing 8 changed files with 290 additions and 176 deletions.
102 changes: 67 additions & 35 deletions src/components/gfx/font.rs
Expand Up @@ -14,15 +14,19 @@ use std::result;
use std::ptr;
use std::str;
use std::vec;
use servo_util::cache::{Cache, HashCache};
use text::glyph::{GlyphStore, GlyphIndex};
use text::shaping::ShaperMethods;
use text::{Shaper, TextRun};
use extra::arc::ARC;

use azure::{AzFloat, AzScaledFontRef};
use azure::scaled_font::ScaledFont;
use azure::azure_hl::{BackendType, ColorPattern};
use geom::{Point2D, Rect, Size2D};

use servo_util::time;
use servo_util::time::profile;
use servo_util::time::ProfilerChan;

// FontHandle encapsulates access to the platform's font API,
Expand Down Expand Up @@ -86,7 +90,7 @@ pub struct FontMetrics {
}

// TODO(Issue #200): use enum from CSS bindings for 'font-weight'
#[deriving(Eq)]
#[deriving(Clone, Eq)]
pub enum CSSFontWeight {
FontWeight100,
FontWeight200,
Expand Down Expand Up @@ -114,7 +118,7 @@ impl CSSFontWeight {
// the instance's properties.
//
// For now, the cases are differentiated with a typedef
#[deriving(Eq)]
#[deriving(Clone, Eq)]
pub struct FontStyle {
pt_size: float,
weight: CSSFontWeight,
Expand All @@ -139,7 +143,7 @@ struct ResolvedFont {
// It's used to swizzle/unswizzle gfx::Font instances when
// communicating across tasks, such as the display list between layout
// and render tasks.
#[deriving(Eq)]
#[deriving(Clone, Eq)]
pub struct FontDescriptor {
style: UsedFontStyle,
selector: FontSelector,
Expand All @@ -155,7 +159,7 @@ impl FontDescriptor {
}

// A FontSelector is a platform-specific strategy for serializing face names.
#[deriving(Eq)]
#[deriving(Clone, Eq)]
pub enum FontSelector {
SelectorPlatformIdentifier(~str),
}
Expand Down Expand Up @@ -206,6 +210,24 @@ pub struct RunMetrics {
bounding_box: Rect<Au>
}

impl RunMetrics {
pub fn new(advance: Au, ascent: Au, descent: Au) -> RunMetrics {
let bounds = Rect(Point2D(Au(0), -ascent),
Size2D(advance, ascent + descent));

// TODO(Issue #125): support loose and tight bounding boxes; using the
// ascent+descent and advance is sometimes too generous and
// looking at actual glyph extents can yield a tighter box.

RunMetrics {
advance_width: advance,
bounding_box: bounds,
ascent: ascent,
descent: descent,
}
}
}

/**
A font instance. Layout can use this to calculate glyph metrics
and the renderer can use it to render text.
Expand All @@ -218,6 +240,7 @@ pub struct Font {
metrics: FontMetrics,
backend: BackendType,
profiler_chan: ProfilerChan,
shape_cache: HashCache<~str, ARC<GlyphStore>>,
}

impl Font {
Expand Down Expand Up @@ -245,6 +268,7 @@ impl Font {
metrics: metrics,
backend: backend,
profiler_chan: profiler_chan,
shape_cache: HashCache::new(),
});
}

Expand All @@ -261,6 +285,7 @@ impl Font {
metrics: metrics,
backend: backend,
profiler_chan: profiler_chan,
shape_cache: HashCache::new(),
}
}

Expand Down Expand Up @@ -366,20 +391,22 @@ impl Font {
let mut azglyphs = ~[];
vec::reserve(&mut azglyphs, range.length());

for run.glyphs.iter_glyphs_for_char_range(range) |_i, glyph| {
let glyph_advance = glyph.advance_();
let glyph_offset = glyph.offset().get_or_default(Au::zero_point());

let azglyph = struct__AzGlyph {
mIndex: glyph.index() as uint32_t,
mPosition: struct__AzPoint {
x: (origin.x + glyph_offset.x).to_px() as AzFloat,
y: (origin.y + glyph_offset.y).to_px() as AzFloat
}
for run.iter_slices_for_range(range) |glyphs, _offset, slice_range| {
for glyphs.iter_glyphs_for_char_range(slice_range) |_i, glyph| {
let glyph_advance = glyph.advance_();
let glyph_offset = glyph.offset().get_or_default(Au::zero_point());

let azglyph = struct__AzGlyph {
mIndex: glyph.index() as uint32_t,
mPosition: struct__AzPoint {
x: (origin.x + glyph_offset.x).to_px() as AzFloat,
y: (origin.y + glyph_offset.y).to_px() as AzFloat
}
};
origin = Point2D(origin.x + glyph_advance, origin.y);
azglyphs.push(azglyph)
};
origin = Point2D(origin.x + glyph_advance, origin.y);
azglyphs.push(azglyph)
};
}

let azglyph_buf_len = azglyphs.len();
if azglyph_buf_len == 0 { return; } // Otherwise the Quartz backend will assert.
Expand All @@ -404,29 +431,34 @@ impl Font {
// TODO(Issue #199): alter advance direction for RTL
// TODO(Issue #98): using inter-char and inter-word spacing settings when measuring text
let mut advance = Au(0);
for run.glyphs.iter_glyphs_for_char_range(range) |_i, glyph| {
advance += glyph.advance_();
for run.iter_slices_for_range(range) |glyphs, _offset, slice_range| {
for glyphs.iter_glyphs_for_char_range(slice_range) |_i, glyph| {
advance += glyph.advance_();
}
}
let bounds = Rect(Point2D(Au(0), -self.metrics.ascent),
Size2D(advance, self.metrics.ascent + self.metrics.descent));

// TODO(Issue #125): support loose and tight bounding boxes; using the
// ascent+descent and advance is sometimes too generous and
// looking at actual glyph extents can yield a tighter box.
RunMetrics::new(advance, self.metrics.ascent, self.metrics.descent)
}

RunMetrics {
advance_width: advance,
bounding_box: bounds,
ascent: self.metrics.ascent,
descent: self.metrics.descent,
pub fn measure_text_for_slice(&self,
glyphs: &GlyphStore,
slice_range: &Range)
-> RunMetrics {
let mut advance = Au(0);
for glyphs.iter_glyphs_for_char_range(slice_range) |_i, glyph| {
advance += glyph.advance_();
}
RunMetrics::new(advance, self.metrics.ascent, self.metrics.descent)
}

pub fn shape_text(@mut self, text: &str, store: &mut GlyphStore) {
// TODO(Issue #229): use a more efficient strategy for repetitive shaping.
// For example, Gecko uses a per-"word" hashtable of shaper results.
let shaper = self.get_shaper();
shaper.shape_text(text, store);
pub fn shape_text(@mut self, text: ~str, is_whitespace: bool) -> ARC<GlyphStore> {
do profile(time::LayoutShapingCategory, self.profiler_chan.clone()) {
let shaper = self.get_shaper();
do self.shape_cache.find_or_create(&text) |txt| {
let mut glyphs = GlyphStore::new(text.char_len(), is_whitespace);
shaper.shape_text(*txt, &mut glyphs);
ARC(glyphs)
}
}
}

pub fn get_descriptor(&self) -> FontDescriptor {
Expand Down
8 changes: 3 additions & 5 deletions src/components/gfx/font_context.rs
Expand Up @@ -6,16 +6,14 @@ use font::{Font, FontDescriptor, FontGroup, FontHandleMethods, FontStyle,
SelectorPlatformIdentifier};
use font::{SpecifiedFontStyle, UsedFontStyle};
use font_list::FontList;
use servo_util::cache::Cache;
use servo_util::cache::LRUCache;
use servo_util::cache::{Cache, LRUCache};
use servo_util::time::ProfilerChan;

use platform::font::FontHandle;
use platform::font_context::FontContextHandle;

use azure::azure_hl::BackendType;
use std::hashmap::HashMap;
use std::str;
use std::result;

// TODO(Rust #3934): creating lots of new dummy styles is a workaround
Expand Down Expand Up @@ -90,7 +88,7 @@ impl<'self> FontContext {
None => {
debug!("font group cache miss");
let fg = self.create_font_group(style);
self.group_cache.insert(style, fg);
self.group_cache.insert(style.clone(), fg);
fg
}
}
Expand All @@ -107,7 +105,7 @@ impl<'self> FontContext {
let result = self.create_font_instance(desc);
match result {
Ok(font) => {
self.instance_cache.insert(desc, font);
self.instance_cache.insert(desc.clone(), font);
}, _ => {}
};
result
Expand Down
12 changes: 11 additions & 1 deletion src/components/gfx/text/glyph.rs
Expand Up @@ -507,20 +507,30 @@ impl<'self> GlyphInfo<'self> {
pub struct GlyphStore {
entry_buffer: ~[GlyphEntry],
detail_store: DetailedGlyphStore,
is_whitespace: bool,
}

impl<'self> GlyphStore {
// Initializes the glyph store, but doesn't actually shape anything.
// Use the set_glyph, set_glyphs() methods to store glyph data.
pub fn new(length: uint) -> GlyphStore {
pub fn new(length: uint, is_whitespace: bool) -> GlyphStore {
assert!(length > 0);

GlyphStore {
entry_buffer: vec::from_elem(length, GlyphEntry::initial()),
detail_store: DetailedGlyphStore::new(),
is_whitespace: is_whitespace,
}
}

pub fn char_len(&self) -> uint {
self.entry_buffer.len()
}

pub fn is_whitespace(&self) -> bool {
self.is_whitespace
}

pub fn finalize_changes(&mut self) {
self.detail_store.ensure_sorted();
}
Expand Down

0 comments on commit 74ab914

Please sign in to comment.