Cache shaped text at word granularity

This commit is contained in:
Seth Fowler 2013-06-26 15:45:47 -07:00
parent 0ac520631a
commit 677fce2546
6 changed files with 194 additions and 146 deletions

View file

@ -14,15 +14,19 @@ use std::result;
use std::ptr; use std::ptr;
use std::str; use std::str;
use std::vec; use std::vec;
use servo_util::cache::{Cache, HashCache};
use text::glyph::{GlyphStore, GlyphIndex}; use text::glyph::{GlyphStore, GlyphIndex};
use text::shaping::ShaperMethods; use text::shaping::ShaperMethods;
use text::{Shaper, TextRun}; use text::{Shaper, TextRun};
use extra::arc::ARC;
use azure::{AzFloat, AzScaledFontRef}; use azure::{AzFloat, AzScaledFontRef};
use azure::scaled_font::ScaledFont; use azure::scaled_font::ScaledFont;
use azure::azure_hl::{BackendType, ColorPattern}; use azure::azure_hl::{BackendType, ColorPattern};
use geom::{Point2D, Rect, Size2D}; use geom::{Point2D, Rect, Size2D};
use servo_util::time;
use servo_util::time::profile;
use servo_util::time::ProfilerChan; use servo_util::time::ProfilerChan;
// FontHandle encapsulates access to the platform's font API, // FontHandle encapsulates access to the platform's font API,
@ -206,6 +210,24 @@ pub struct RunMetrics {
bounding_box: Rect<Au> 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 A font instance. Layout can use this to calculate glyph metrics
and the renderer can use it to render text. and the renderer can use it to render text.
@ -218,6 +240,7 @@ pub struct Font {
metrics: FontMetrics, metrics: FontMetrics,
backend: BackendType, backend: BackendType,
profiler_chan: ProfilerChan, profiler_chan: ProfilerChan,
shape_cache: HashCache<~str, ARC<GlyphStore>>,
} }
impl Font { impl Font {
@ -245,6 +268,7 @@ impl Font {
metrics: metrics, metrics: metrics,
backend: backend, backend: backend,
profiler_chan: profiler_chan, profiler_chan: profiler_chan,
shape_cache: HashCache::new(),
}); });
} }
@ -261,6 +285,7 @@ impl Font {
metrics: metrics, metrics: metrics,
backend: backend, backend: backend,
profiler_chan: profiler_chan, profiler_chan: profiler_chan,
shape_cache: HashCache::new(),
} }
} }
@ -366,7 +391,8 @@ impl Font {
let mut azglyphs = ~[]; let mut azglyphs = ~[];
vec::reserve(&mut azglyphs, range.length()); vec::reserve(&mut azglyphs, range.length());
for run.glyphs.iter_glyphs_for_char_range(range) |_i, glyph| { 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_advance = glyph.advance_();
let glyph_offset = glyph.offset().get_or_default(Au::zero_point()); let glyph_offset = glyph.offset().get_or_default(Au::zero_point());
@ -380,6 +406,7 @@ impl Font {
origin = Point2D(origin.x + glyph_advance, origin.y); origin = Point2D(origin.x + glyph_advance, origin.y);
azglyphs.push(azglyph) azglyphs.push(azglyph)
}; };
}
let azglyph_buf_len = azglyphs.len(); let azglyph_buf_len = azglyphs.len();
if azglyph_buf_len == 0 { return; } // Otherwise the Quartz backend will assert. if azglyph_buf_len == 0 { return; } // Otherwise the Quartz backend will assert.
@ -404,29 +431,34 @@ impl Font {
// TODO(Issue #199): alter advance direction for RTL // TODO(Issue #199): alter advance direction for RTL
// TODO(Issue #98): using inter-char and inter-word spacing settings when measuring text // TODO(Issue #98): using inter-char and inter-word spacing settings when measuring text
let mut advance = Au(0); let mut advance = Au(0);
for run.glyphs.iter_glyphs_for_char_range(range) |_i, glyph| { 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_(); 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 {
advance_width: advance,
bounding_box: bounds,
ascent: self.metrics.ascent,
descent: self.metrics.descent,
} }
RunMetrics::new(advance, self.metrics.ascent, self.metrics.descent)
} }
pub fn shape_text(@mut self, text: &str, store: &mut GlyphStore) { pub fn measure_text_for_slice(&self,
// TODO(Issue #229): use a more efficient strategy for repetitive shaping. glyphs: &GlyphStore,
// For example, Gecko uses a per-"word" hashtable of shaper results. 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, is_whitespace: bool) -> ARC<GlyphStore> {
do profile(time::LayoutShapingCategory, self.profiler_chan.clone()) {
let shaper = self.get_shaper(); let shaper = self.get_shaper();
shaper.shape_text(text, store); 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 { pub fn get_descriptor(&self) -> FontDescriptor {

View file

@ -14,7 +14,6 @@ use platform::font_context::FontContextHandle;
use azure::azure_hl::BackendType; use azure::azure_hl::BackendType;
use std::hashmap::HashMap; use std::hashmap::HashMap;
use std::str;
use std::result; use std::result;
// TODO(Rust #3934): creating lots of new dummy styles is a workaround // TODO(Rust #3934): creating lots of new dummy styles is a workaround

View file

@ -507,20 +507,30 @@ impl<'self> GlyphInfo<'self> {
pub struct GlyphStore { pub struct GlyphStore {
entry_buffer: ~[GlyphEntry], entry_buffer: ~[GlyphEntry],
detail_store: DetailedGlyphStore, detail_store: DetailedGlyphStore,
is_whitespace: bool,
} }
impl<'self> GlyphStore { impl<'self> GlyphStore {
// Initializes the glyph store, but doesn't actually shape anything. // Initializes the glyph store, but doesn't actually shape anything.
// Use the set_glyph, set_glyphs() methods to store glyph data. // 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); assert!(length > 0);
GlyphStore { GlyphStore {
entry_buffer: vec::from_elem(length, GlyphEntry::initial()), entry_buffer: vec::from_elem(length, GlyphEntry::initial()),
detail_store: DetailedGlyphStore::new(), 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) { pub fn finalize_changes(&mut self) {
self.detail_store.ensure_sorted(); self.detail_store.ensure_sorted();
} }

View file

@ -4,18 +4,17 @@
use font_context::FontContext; use font_context::FontContext;
use geometry::Au; use geometry::Au;
use text::glyph::{BreakTypeNormal, GlyphStore}; use text::glyph::GlyphStore;
use font::{Font, FontDescriptor, RunMetrics}; use font::{Font, FontDescriptor, RunMetrics};
use servo_util::time;
use servo_util::time::profile;
use servo_util::range::Range; use servo_util::range::Range;
use extra::arc::ARC;
/// A text run. /// A text run.
pub struct TextRun { pub struct TextRun {
text: ~str, text: ~str,
font: @mut Font, font: @mut Font,
underline: bool, underline: bool,
glyphs: GlyphStore, glyphs: ~[ARC<GlyphStore>],
} }
/// This is a hack until TextRuns are normally sendable, or we instead use ARC<TextRun> everywhere. /// This is a hack until TextRuns are normally sendable, or we instead use ARC<TextRun> everywhere.
@ -23,7 +22,7 @@ pub struct SendableTextRun {
text: ~str, text: ~str,
font: FontDescriptor, font: FontDescriptor,
underline: bool, underline: bool,
priv glyphs: GlyphStore, priv glyphs: ~[ARC<GlyphStore>],
} }
impl SendableTextRun { impl SendableTextRun {
@ -37,24 +36,20 @@ impl SendableTextRun {
text: copy self.text, text: copy self.text,
font: font, font: font,
underline: self.underline, underline: self.underline,
glyphs: copy self.glyphs glyphs: self.glyphs.clone(),
} }
} }
} }
impl<'self> TextRun { impl<'self> TextRun {
pub fn new(font: @mut Font, text: ~str, underline: bool) -> TextRun { pub fn new(font: @mut Font, text: ~str, underline: bool) -> TextRun {
let mut glyph_store = GlyphStore::new(text.char_len()); let glyphs = TextRun::break_and_shape(font, text);
TextRun::compute_potential_breaks(text, &mut glyph_store);
do profile(time::LayoutShapingCategory, font.profiler_chan.clone()) {
font.shape_text(text, &mut glyph_store);
}
let run = TextRun { let run = TextRun {
text: text, text: text,
font: font, font: font,
underline: underline, underline: underline,
glyphs: glyph_store, glyphs: glyphs,
}; };
return run; return run;
} }
@ -63,46 +58,59 @@ impl<'self> TextRun {
self.font.teardown(); self.font.teardown();
} }
pub fn compute_potential_breaks(text: &str, glyphs: &mut GlyphStore) { pub fn break_and_shape(font: @mut Font, text: &str) -> ~[ARC<GlyphStore>] {
// TODO(Issue #230): do a better job. See Gecko's LineBreaker. // TODO(Issue #230): do a better job. See Gecko's LineBreaker.
let mut glyphs = ~[];
let mut byte_i = 0u; let mut byte_i = 0u;
let mut char_j = 0u; let mut cur_slice_is_whitespace = false;
let mut prev_is_whitespace = false; let mut byte_last_boundary = 0;
while byte_i < text.len() { while byte_i < text.len() {
let range = text.char_range_at(byte_i); let range = text.char_range_at(byte_i);
let ch = range.ch; let ch = range.ch;
let next = range.next; let next = range.next;
// set char properties.
match ch {
' ' => { glyphs.set_char_is_space(char_j); },
'\t' => { glyphs.set_char_is_tab(char_j); },
'\n' => { glyphs.set_char_is_newline(char_j); },
_ => {}
}
// set line break opportunities at whitespace/non-whitespace boundaries. // Slices alternate between whitespace and non-whitespace,
if prev_is_whitespace { // representing line break opportunities.
let can_break_before = if cur_slice_is_whitespace {
match ch { match ch {
' ' | '\t' | '\n' => {}, ' ' | '\t' | '\n' => false,
_ => { _ => {
glyphs.set_can_break_before(char_j, BreakTypeNormal); cur_slice_is_whitespace = false;
prev_is_whitespace = false; true
} }
} }
} else { } else {
match ch { match ch {
' ' | '\t' | '\n' => { ' ' | '\t' | '\n' => {
glyphs.set_can_break_before(char_j, BreakTypeNormal); cur_slice_is_whitespace = true;
prev_is_whitespace = true; true
}, },
_ => { } _ => false
} }
};
// Create a glyph store for this slice if it's nonempty.
if can_break_before && byte_i > byte_last_boundary {
let slice = text.slice(byte_last_boundary, byte_i).to_owned();
debug!("creating glyph store for slice %? (ws? %?), %? - %? in run %?",
slice, !cur_slice_is_whitespace, byte_last_boundary, byte_i, text);
glyphs.push(font.shape_text(slice, !cur_slice_is_whitespace));
byte_last_boundary = byte_i;
} }
byte_i = next; byte_i = next;
char_j += 1;
} }
// Create a glyph store for the final slice if it's nonempty.
if byte_i > byte_last_boundary {
let slice = text.slice(byte_last_boundary, text.len()).to_owned();
debug!("creating glyph store for final slice %? (ws? %?), %? - %? in run %?",
slice, cur_slice_is_whitespace, byte_last_boundary, text.len(), text);
glyphs.push(font.shape_text(slice, cur_slice_is_whitespace));
}
glyphs
} }
pub fn serialize(&self) -> SendableTextRun { pub fn serialize(&self) -> SendableTextRun {
@ -110,50 +118,80 @@ impl<'self> TextRun {
text: copy self.text, text: copy self.text,
font: self.font.get_descriptor(), font: self.font.get_descriptor(),
underline: self.underline, underline: self.underline,
glyphs: copy self.glyphs, glyphs: self.glyphs.clone(),
} }
} }
pub fn char_len(&self) -> uint { self.glyphs.entry_buffer.len() } pub fn char_len(&self) -> uint {
pub fn glyphs(&'self self) -> &'self GlyphStore { &self.glyphs } do self.glyphs.foldl(0u) |len, slice_glyphs| {
len + slice_glyphs.get().char_len()
}
}
pub fn glyphs(&'self self) -> &'self ~[ARC<GlyphStore>] { &self.glyphs }
pub fn range_is_trimmable_whitespace(&self, range: &Range) -> bool { pub fn range_is_trimmable_whitespace(&self, range: &Range) -> bool {
for range.eachi |i| { for self.iter_slices_for_range(range) |slice_glyphs, _, _| {
if !self.glyphs.char_is_space(i) && if !slice_glyphs.is_whitespace() { return false; }
!self.glyphs.char_is_tab(i) &&
!self.glyphs.char_is_newline(i) { return false; }
} }
return true; true
} }
pub fn metrics_for_range(&self, range: &Range) -> RunMetrics { pub fn metrics_for_range(&self, range: &Range) -> RunMetrics {
self.font.measure_text(self, range) self.font.measure_text(self, range)
} }
pub fn metrics_for_slice(&self, glyphs: &GlyphStore, slice_range: &Range) -> RunMetrics {
self.font.measure_text_for_slice(glyphs, slice_range)
}
pub fn min_width_for_range(&self, range: &Range) -> Au { pub fn min_width_for_range(&self, range: &Range) -> Au {
let mut max_piece_width = Au(0); let mut max_piece_width = Au(0);
debug!("iterating outer range %?", range); debug!("iterating outer range %?", range);
for self.iter_indivisible_pieces_for_range(range) |piece_range| { for self.iter_slices_for_range(range) |glyphs, offset, slice_range| {
debug!("iterated on %?", piece_range); debug!("iterated on %?[%?]", offset, slice_range);
let metrics = self.font.measure_text(self, piece_range); let metrics = self.font.measure_text_for_slice(glyphs, slice_range);
max_piece_width = Au::max(max_piece_width, metrics.advance_width); max_piece_width = Au::max(max_piece_width, metrics.advance_width);
} }
return max_piece_width; max_piece_width
}
pub fn iter_slices_for_range(&self,
range: &Range,
f: &fn(&GlyphStore, uint, &Range) -> bool)
-> bool {
let mut offset = 0;
for self.glyphs.each |slice_glyphs| {
// Determine the range of this slice that we need.
let slice_range = Range::new(offset, slice_glyphs.get().char_len());
let mut char_range = range.intersect(&slice_range);
char_range.shift_by(-(offset.to_int()));
let unwrapped_glyphs = slice_glyphs.get();
if !char_range.is_empty() {
if !f(unwrapped_glyphs, offset, &char_range) { break }
}
offset += unwrapped_glyphs.char_len();
}
true
} }
pub fn iter_natural_lines_for_range(&self, range: &Range, f: &fn(&Range) -> bool) -> bool { pub fn iter_natural_lines_for_range(&self, range: &Range, f: &fn(&Range) -> bool) -> bool {
let mut clump = Range::new(range.begin(), 0); let mut clump = Range::new(range.begin(), 0);
let mut in_clump = false; let mut in_clump = false;
// clump non-linebreaks of nonzero length for self.iter_slices_for_range(range) |glyphs, offset, slice_range| {
for range.eachi |i| { match (glyphs.is_whitespace(), in_clump) {
match (self.glyphs.char_is_newline(i), in_clump) { (false, true) => { clump.extend_by(slice_range.length().to_int()); }
(false, true) => { clump.extend_by(1); } (false, false) => {
(false, false) => { in_clump = true; clump.reset(i, 1); } in_clump = true;
clump = *slice_range;
clump.shift_by(offset.to_int());
}
(true, false) => { /* chomp whitespace */ } (true, false) => { /* chomp whitespace */ }
(true, true) => { (true, true) => {
in_clump = false; in_clump = false;
// don't include the linebreak character itself in the clump. // The final whitespace clump is not included.
if !f(&clump) { break } if !f(&clump) { break }
} }
} }
@ -167,28 +205,4 @@ impl<'self> TextRun {
true true
} }
pub fn iter_indivisible_pieces_for_range(&self, range: &Range, f: &fn(&Range) -> bool) -> bool {
let mut clump = Range::new(range.begin(), 0);
loop {
// extend clump to non-break-before characters.
while clump.end() < range.end()
&& self.glyphs.can_break_before(clump.end()) != BreakTypeNormal {
clump.extend_by(1);
}
// now clump.end() is break-before or range.end()
if !f(&clump) || clump.end() == range.end() {
break
}
// now clump includes one break-before character, or starts from range.end()
let end = clump.end(); // FIXME: borrow checker workaround
clump.reset(end, 1);
}
true
}
} }

View file

@ -289,51 +289,52 @@ impl RenderBox {
text_box.range, text_box.range,
max_width); max_width);
for text_box.run.iter_indivisible_pieces_for_range( for text_box.run.iter_slices_for_range(&text_box.range)
&text_box.range) |piece_range| { |glyphs, offset, slice_range| {
debug!("split_to_width: considering piece (range=%?, remain_width=%?)", debug!("split_to_width: considering slice (offset=%?, range=%?, remain_width=%?)",
piece_range, offset,
slice_range,
remaining_width); remaining_width);
let metrics = text_box.run.metrics_for_range(piece_range); let metrics = text_box.run.metrics_for_slice(glyphs, slice_range);
let advance = metrics.advance_width; let advance = metrics.advance_width;
let should_continue: bool; let should_continue: bool;
if advance <= remaining_width { if advance <= remaining_width {
should_continue = true; should_continue = true;
if starts_line && if starts_line && pieces_processed_count == 0 && glyphs.is_whitespace() {
pieces_processed_count == 0 &&
text_box.run.range_is_trimmable_whitespace(piece_range) {
debug!("split_to_width: case=skipping leading trimmable whitespace"); debug!("split_to_width: case=skipping leading trimmable whitespace");
left_range.shift_by(piece_range.length() as int); left_range.shift_by(slice_range.length() as int);
} else { } else {
debug!("split_to_width: case=enlarging span"); debug!("split_to_width: case=enlarging span");
remaining_width -= advance; remaining_width -= advance;
left_range.extend_by(piece_range.length() as int); left_range.extend_by(slice_range.length() as int);
} }
} else { // The advance is more than the remaining width. } else { // The advance is more than the remaining width.
should_continue = false; should_continue = false;
let slice_begin = offset + slice_range.begin();
let slice_end = offset + slice_range.end();
if text_box.run.range_is_trimmable_whitespace(piece_range) { if glyphs.is_whitespace() {
// If there are still things after the trimmable whitespace, create the // If there are still things after the trimmable whitespace, create the
// right chunk. // right chunk.
if piece_range.end() < text_box.range.end() { if slice_end < text_box.range.end() {
debug!("split_to_width: case=skipping trimmable trailing \ debug!("split_to_width: case=skipping trimmable trailing \
whitespace, then split remainder"); whitespace, then split remainder");
let right_range_end = let right_range_end =
text_box.range.end() - piece_range.end(); text_box.range.end() - slice_end;
right_range = Some(Range::new(piece_range.end(), right_range_end)); right_range = Some(Range::new(slice_end, right_range_end));
} else { } else {
debug!("split_to_width: case=skipping trimmable trailing \ debug!("split_to_width: case=skipping trimmable trailing \
whitespace"); whitespace");
} }
} else if piece_range.begin() < text_box.range.end() { } else if slice_begin < text_box.range.end() {
// There are still some things left over at the end of the line. Create // There are still some things left over at the end of the line. Create
// the right chunk. // the right chunk.
let right_range_end = let right_range_end =
text_box.range.end() - piece_range.begin(); text_box.range.end() - slice_begin;
right_range = Some(Range::new(piece_range.begin(), right_range_end)); right_range = Some(Range::new(slice_begin, right_range_end));
debug!("split_to_width: case=splitting remainder with right range=%?", debug!("split_to_width: case=splitting remainder with right range=%?",
right_range); right_range);
} }
@ -449,13 +450,8 @@ impl RenderBox {
let mut max_line_width = Au(0); let mut max_line_width = Au(0);
for text_box.run.iter_natural_lines_for_range(&text_box.range) for text_box.run.iter_natural_lines_for_range(&text_box.range)
|line_range| { |line_range| {
let mut line_width: Au = Au(0); let line_metrics = text_box.run.metrics_for_range(line_range);
for text_box.run.glyphs.iter_glyphs_for_char_range(line_range) max_line_width = Au::max(max_line_width, line_metrics.advance_width);
|_, glyph| {
line_width += glyph.advance_()
}
max_line_width = Au::max(max_line_width, line_width);
} }
max_line_width max_line_width
@ -857,10 +853,8 @@ impl RenderBox {
GenericRenderBoxClass(*) => ~"GenericRenderBox", GenericRenderBoxClass(*) => ~"GenericRenderBox",
ImageRenderBoxClass(*) => ~"ImageRenderBox", ImageRenderBoxClass(*) => ~"ImageRenderBox",
TextRenderBoxClass(text_box) => { TextRenderBoxClass(text_box) => {
fmt!("TextRenderBox(text=%s)", fmt!("TextRenderBox(text=%s)", text_box.run.text.slice_chars(text_box.range.begin(),
text_box.run.text.slice( text_box.range.end()))
text_box.range.begin(),
text_box.range.begin() + text_box.range.length()))
} }
UnscannedTextRenderBoxClass(text_box) => { UnscannedTextRenderBoxClass(text_box) => {
fmt!("UnscannedTextRenderBox(%s)", text_box.text) fmt!("UnscannedTextRenderBox(%s)", text_box.text)
@ -870,5 +864,3 @@ impl RenderBox {
fmt!("box b%?: %s", self.id(), representation) fmt!("box b%?: %s", self.id(), representation)
} }
} }

View file

@ -21,15 +21,16 @@ use servo_util::range::Range;
/// Creates a TextRenderBox from a range and a text run. /// Creates a TextRenderBox from a range and a text run.
pub fn adapt_textbox_with_range(mut base: RenderBoxBase, run: @TextRun, range: Range) pub fn adapt_textbox_with_range(mut base: RenderBoxBase, run: @TextRun, range: Range)
-> TextRenderBox { -> TextRenderBox {
assert!(range.begin() < run.char_len()); debug!("Creating textbox with span: (strlen=%u, off=%u, len=%u) of textrun (%s) (len=%u)",
assert!(range.end() <= run.char_len());
assert!(range.length() > 0);
debug!("Creating textbox with span: (strlen=%u, off=%u, len=%u) of textrun: %s",
run.char_len(), run.char_len(),
range.begin(), range.begin(),
range.length(), range.length(),
run.text); run.text,
run.char_len());
assert!(range.begin() < run.char_len());
assert!(range.end() <= run.char_len());
assert!(range.length() > 0);
let metrics = run.metrics_for_range(&range); let metrics = run.metrics_for_range(&range);
base.position.size = metrics.bounding_box.size; base.position.size = metrics.bounding_box.size;
@ -170,7 +171,7 @@ impl TextRunScanner {
let fontgroup = ctx.font_ctx.get_resolved_font_for_style(&font_style); let fontgroup = ctx.font_ctx.get_resolved_font_for_style(&font_style);
let run = @fontgroup.create_textrun(transformed_text, underline); let run = @fontgroup.create_textrun(transformed_text, underline);
debug!("TextRunScanner: pushing single text box in range: %?", self.clump); debug!("TextRunScanner: pushing single text box in range: %? (%?)", self.clump, text);
let new_box = do old_box.with_base |old_box_base| { let new_box = do old_box.with_base |old_box_base| {
let range = Range::new(0, run.char_len()); let range = Range::new(0, run.char_len());
@mut adapt_textbox_with_range(*old_box_base, run, range) @mut adapt_textbox_with_range(*old_box_base, run, range)