mirror of
https://github.com/servo/servo.git
synced 2025-08-07 06:25:32 +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::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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue