mirror of
https://github.com/servo/servo.git
synced 2025-08-06 06:00:15 +01:00
Cache shaped text at word granularity
This commit is contained in:
parent
0ac520631a
commit
677fce2546
6 changed files with 194 additions and 146 deletions
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -218,6 +240,7 @@ pub struct Font {
|
|||
metrics: FontMetrics,
|
||||
backend: BackendType,
|
||||
profiler_chan: ProfilerChan,
|
||||
shape_cache: HashCache<~str, ARC<GlyphStore>>,
|
||||
}
|
||||
|
||||
impl Font {
|
||||
|
@ -245,6 +268,7 @@ impl Font {
|
|||
metrics: metrics,
|
||||
backend: backend,
|
||||
profiler_chan: profiler_chan,
|
||||
shape_cache: HashCache::new(),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -261,6 +285,7 @@ impl Font {
|
|||
metrics: metrics,
|
||||
backend: backend,
|
||||
profiler_chan: profiler_chan,
|
||||
shape_cache: HashCache::new(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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());
|
||||
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
|
||||
}
|
||||
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.
|
||||
|
@ -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_();
|
||||
}
|
||||
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,
|
||||
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_();
|
||||
}
|
||||
}
|
||||
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 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, 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 {
|
||||
|
|
|
@ -14,7 +14,6 @@ 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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -4,18 +4,17 @@
|
|||
|
||||
use font_context::FontContext;
|
||||
use geometry::Au;
|
||||
use text::glyph::{BreakTypeNormal, GlyphStore};
|
||||
use text::glyph::GlyphStore;
|
||||
use font::{Font, FontDescriptor, RunMetrics};
|
||||
use servo_util::time;
|
||||
use servo_util::time::profile;
|
||||
use servo_util::range::Range;
|
||||
use extra::arc::ARC;
|
||||
|
||||
/// A text run.
|
||||
pub struct TextRun {
|
||||
text: ~str,
|
||||
font: @mut Font,
|
||||
underline: bool,
|
||||
glyphs: GlyphStore,
|
||||
glyphs: ~[ARC<GlyphStore>],
|
||||
}
|
||||
|
||||
/// 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,
|
||||
font: FontDescriptor,
|
||||
underline: bool,
|
||||
priv glyphs: GlyphStore,
|
||||
priv glyphs: ~[ARC<GlyphStore>],
|
||||
}
|
||||
|
||||
impl SendableTextRun {
|
||||
|
@ -37,24 +36,20 @@ impl SendableTextRun {
|
|||
text: copy self.text,
|
||||
font: font,
|
||||
underline: self.underline,
|
||||
glyphs: copy self.glyphs
|
||||
glyphs: self.glyphs.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'self> TextRun {
|
||||
pub fn new(font: @mut Font, text: ~str, underline: bool) -> TextRun {
|
||||
let mut glyph_store = GlyphStore::new(text.char_len());
|
||||
TextRun::compute_potential_breaks(text, &mut glyph_store);
|
||||
do profile(time::LayoutShapingCategory, font.profiler_chan.clone()) {
|
||||
font.shape_text(text, &mut glyph_store);
|
||||
}
|
||||
let glyphs = TextRun::break_and_shape(font, text);
|
||||
|
||||
let run = TextRun {
|
||||
text: text,
|
||||
font: font,
|
||||
underline: underline,
|
||||
glyphs: glyph_store,
|
||||
glyphs: glyphs,
|
||||
};
|
||||
return run;
|
||||
}
|
||||
|
@ -63,46 +58,59 @@ impl<'self> TextRun {
|
|||
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.
|
||||
|
||||
let mut glyphs = ~[];
|
||||
let mut byte_i = 0u;
|
||||
let mut char_j = 0u;
|
||||
let mut prev_is_whitespace = false;
|
||||
let mut cur_slice_is_whitespace = false;
|
||||
let mut byte_last_boundary = 0;
|
||||
while byte_i < text.len() {
|
||||
let range = text.char_range_at(byte_i);
|
||||
let ch = range.ch;
|
||||
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.
|
||||
if prev_is_whitespace {
|
||||
// Slices alternate between whitespace and non-whitespace,
|
||||
// representing line break opportunities.
|
||||
let can_break_before = if cur_slice_is_whitespace {
|
||||
match ch {
|
||||
' ' | '\t' | '\n' => {},
|
||||
' ' | '\t' | '\n' => false,
|
||||
_ => {
|
||||
glyphs.set_can_break_before(char_j, BreakTypeNormal);
|
||||
prev_is_whitespace = false;
|
||||
cur_slice_is_whitespace = false;
|
||||
true
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match ch {
|
||||
' ' | '\t' | '\n' => {
|
||||
glyphs.set_can_break_before(char_j, BreakTypeNormal);
|
||||
prev_is_whitespace = true;
|
||||
cur_slice_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;
|
||||
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 {
|
||||
|
@ -110,50 +118,80 @@ impl<'self> TextRun {
|
|||
text: copy self.text,
|
||||
font: self.font.get_descriptor(),
|
||||
underline: self.underline,
|
||||
glyphs: copy self.glyphs,
|
||||
glyphs: self.glyphs.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn char_len(&self) -> uint { self.glyphs.entry_buffer.len() }
|
||||
pub fn glyphs(&'self self) -> &'self GlyphStore { &self.glyphs }
|
||||
pub fn char_len(&self) -> uint {
|
||||
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 {
|
||||
for range.eachi |i| {
|
||||
if !self.glyphs.char_is_space(i) &&
|
||||
!self.glyphs.char_is_tab(i) &&
|
||||
!self.glyphs.char_is_newline(i) { return false; }
|
||||
for self.iter_slices_for_range(range) |slice_glyphs, _, _| {
|
||||
if !slice_glyphs.is_whitespace() { return false; }
|
||||
}
|
||||
return true;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn metrics_for_range(&self, range: &Range) -> RunMetrics {
|
||||
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 {
|
||||
let mut max_piece_width = Au(0);
|
||||
debug!("iterating outer range %?", range);
|
||||
for self.iter_indivisible_pieces_for_range(range) |piece_range| {
|
||||
debug!("iterated on %?", piece_range);
|
||||
let metrics = self.font.measure_text(self, piece_range);
|
||||
for self.iter_slices_for_range(range) |glyphs, offset, slice_range| {
|
||||
debug!("iterated on %?[%?]", offset, slice_range);
|
||||
let metrics = self.font.measure_text_for_slice(glyphs, slice_range);
|
||||
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 {
|
||||
let mut clump = Range::new(range.begin(), 0);
|
||||
let mut in_clump = false;
|
||||
|
||||
// clump non-linebreaks of nonzero length
|
||||
for range.eachi |i| {
|
||||
match (self.glyphs.char_is_newline(i), in_clump) {
|
||||
(false, true) => { clump.extend_by(1); }
|
||||
(false, false) => { in_clump = true; clump.reset(i, 1); }
|
||||
(true, false) => { /* chomp whitespace */ }
|
||||
(true, true) => {
|
||||
for self.iter_slices_for_range(range) |glyphs, offset, slice_range| {
|
||||
match (glyphs.is_whitespace(), in_clump) {
|
||||
(false, true) => { clump.extend_by(slice_range.length().to_int()); }
|
||||
(false, false) => {
|
||||
in_clump = true;
|
||||
clump = *slice_range;
|
||||
clump.shift_by(offset.to_int());
|
||||
}
|
||||
(true, false) => { /* chomp whitespace */ }
|
||||
(true, true) => {
|
||||
in_clump = false;
|
||||
// don't include the linebreak character itself in the clump.
|
||||
// The final whitespace clump is not included.
|
||||
if !f(&clump) { break }
|
||||
}
|
||||
}
|
||||
|
@ -167,28 +205,4 @@ impl<'self> TextRun {
|
|||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -289,51 +289,52 @@ impl RenderBox {
|
|||
text_box.range,
|
||||
max_width);
|
||||
|
||||
for text_box.run.iter_indivisible_pieces_for_range(
|
||||
&text_box.range) |piece_range| {
|
||||
debug!("split_to_width: considering piece (range=%?, remain_width=%?)",
|
||||
piece_range,
|
||||
for text_box.run.iter_slices_for_range(&text_box.range)
|
||||
|glyphs, offset, slice_range| {
|
||||
debug!("split_to_width: considering slice (offset=%?, range=%?, remain_width=%?)",
|
||||
offset,
|
||||
slice_range,
|
||||
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 should_continue: bool;
|
||||
|
||||
if advance <= remaining_width {
|
||||
should_continue = true;
|
||||
|
||||
if starts_line &&
|
||||
pieces_processed_count == 0 &&
|
||||
text_box.run.range_is_trimmable_whitespace(piece_range) {
|
||||
if starts_line && pieces_processed_count == 0 && glyphs.is_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 {
|
||||
debug!("split_to_width: case=enlarging span");
|
||||
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.
|
||||
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
|
||||
// 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 \
|
||||
whitespace, then split remainder");
|
||||
let right_range_end =
|
||||
text_box.range.end() - piece_range.end();
|
||||
right_range = Some(Range::new(piece_range.end(), right_range_end));
|
||||
text_box.range.end() - slice_end;
|
||||
right_range = Some(Range::new(slice_end, right_range_end));
|
||||
} else {
|
||||
debug!("split_to_width: case=skipping trimmable trailing \
|
||||
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
|
||||
// the right chunk.
|
||||
let right_range_end =
|
||||
text_box.range.end() - piece_range.begin();
|
||||
right_range = Some(Range::new(piece_range.begin(), right_range_end));
|
||||
text_box.range.end() - slice_begin;
|
||||
right_range = Some(Range::new(slice_begin, right_range_end));
|
||||
debug!("split_to_width: case=splitting remainder with right range=%?",
|
||||
right_range);
|
||||
}
|
||||
|
@ -449,13 +450,8 @@ impl RenderBox {
|
|||
let mut max_line_width = Au(0);
|
||||
for text_box.run.iter_natural_lines_for_range(&text_box.range)
|
||||
|line_range| {
|
||||
let mut line_width: Au = Au(0);
|
||||
for text_box.run.glyphs.iter_glyphs_for_char_range(line_range)
|
||||
|_, glyph| {
|
||||
line_width += glyph.advance_()
|
||||
}
|
||||
|
||||
max_line_width = Au::max(max_line_width, line_width);
|
||||
let line_metrics = text_box.run.metrics_for_range(line_range);
|
||||
max_line_width = Au::max(max_line_width, line_metrics.advance_width);
|
||||
}
|
||||
|
||||
max_line_width
|
||||
|
@ -857,10 +853,8 @@ impl RenderBox {
|
|||
GenericRenderBoxClass(*) => ~"GenericRenderBox",
|
||||
ImageRenderBoxClass(*) => ~"ImageRenderBox",
|
||||
TextRenderBoxClass(text_box) => {
|
||||
fmt!("TextRenderBox(text=%s)",
|
||||
text_box.run.text.slice(
|
||||
text_box.range.begin(),
|
||||
text_box.range.begin() + text_box.range.length()))
|
||||
fmt!("TextRenderBox(text=%s)", text_box.run.text.slice_chars(text_box.range.begin(),
|
||||
text_box.range.end()))
|
||||
}
|
||||
UnscannedTextRenderBoxClass(text_box) => {
|
||||
fmt!("UnscannedTextRenderBox(%s)", text_box.text)
|
||||
|
@ -870,5 +864,3 @@ impl RenderBox {
|
|||
fmt!("box b%?: %s", self.id(), representation)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -21,15 +21,16 @@ use servo_util::range::Range;
|
|||
/// Creates a TextRenderBox from a range and a text run.
|
||||
pub fn adapt_textbox_with_range(mut base: RenderBoxBase, run: @TextRun, range: Range)
|
||||
-> TextRenderBox {
|
||||
assert!(range.begin() < run.char_len());
|
||||
assert!(range.end() <= run.char_len());
|
||||
assert!(range.length() > 0);
|
||||
|
||||
debug!("Creating textbox with span: (strlen=%u, off=%u, len=%u) of textrun: %s",
|
||||
debug!("Creating textbox with span: (strlen=%u, off=%u, len=%u) of textrun (%s) (len=%u)",
|
||||
run.char_len(),
|
||||
range.begin(),
|
||||
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);
|
||||
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 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 range = Range::new(0, run.char_len());
|
||||
@mut adapt_textbox_with_range(*old_box_base, run, range)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue