mirror of
https://github.com/servo/servo.git
synced 2025-08-09 07:25:35 +01:00
layout: Implement overflow-wrap
/word-wrap
per CSS-TEXT § 6.2.
This property is used by approximately 55% of page loads. To implement the line breaking behavior, the "breaking strategy" has been cleaned up and abstracted. This should allow us to easily support other similar properties in the future, such as `text-overflow` and `word-break`.
This commit is contained in:
parent
1be7d7cced
commit
6943ddb93e
8 changed files with 356 additions and 139 deletions
|
@ -27,14 +27,14 @@ pub struct TextRun {
|
|||
#[deriving(Clone)]
|
||||
pub struct GlyphRun {
|
||||
/// The glyphs.
|
||||
glyph_store: Arc<GlyphStore>,
|
||||
pub glyph_store: Arc<GlyphStore>,
|
||||
/// The range of characters in the containing run.
|
||||
range: Range<CharIndex>,
|
||||
pub range: Range<CharIndex>,
|
||||
}
|
||||
|
||||
pub struct SliceIterator<'a> {
|
||||
pub struct NaturalWordSliceIterator<'a> {
|
||||
glyph_iter: Items<'a, GlyphRun>,
|
||||
range: Range<CharIndex>,
|
||||
range: Range<CharIndex>,
|
||||
}
|
||||
|
||||
struct CharIndexComparator;
|
||||
|
@ -51,10 +51,31 @@ impl Comparator<CharIndex,GlyphRun> for CharIndexComparator {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator<(&'a GlyphStore, CharIndex, Range<CharIndex>)> for SliceIterator<'a> {
|
||||
/// A "slice" of a text run is a series of contiguous glyphs that all belong to the same glyph
|
||||
/// store. Line breaking strategies yield these.
|
||||
pub struct TextRunSlice<'a> {
|
||||
/// The glyph store that the glyphs in this slice belong to.
|
||||
pub glyphs: &'a GlyphStore,
|
||||
/// The character index that this slice begins at, relative to the start of the *text run*.
|
||||
pub offset: CharIndex,
|
||||
/// The range that these glyphs encompass, relative to the start of the *glyph store*.
|
||||
pub range: Range<CharIndex>,
|
||||
}
|
||||
|
||||
impl<'a> TextRunSlice<'a> {
|
||||
/// Returns the range that these glyphs encompass, relative to the start of the *text run*.
|
||||
#[inline]
|
||||
pub fn text_run_range(&self) -> Range<CharIndex> {
|
||||
let mut range = self.range;
|
||||
range.shift_by(self.offset);
|
||||
range
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator<TextRunSlice<'a>> for NaturalWordSliceIterator<'a> {
|
||||
// inline(always) due to the inefficient rt failures messing up inline heuristics, I think.
|
||||
#[inline(always)]
|
||||
fn next(&mut self) -> Option<(&'a GlyphStore, CharIndex, Range<CharIndex>)> {
|
||||
fn next(&mut self) -> Option<TextRunSlice<'a>> {
|
||||
let slice_glyphs = self.glyph_iter.next();
|
||||
if slice_glyphs.is_none() {
|
||||
return None;
|
||||
|
@ -64,18 +85,58 @@ impl<'a> Iterator<(&'a GlyphStore, CharIndex, Range<CharIndex>)> for SliceIterat
|
|||
let mut char_range = self.range.intersect(&slice_glyphs.range);
|
||||
let slice_range_begin = slice_glyphs.range.begin();
|
||||
char_range.shift_by(-slice_range_begin);
|
||||
|
||||
if !char_range.is_empty() {
|
||||
return Some((&*slice_glyphs.glyph_store, slice_range_begin, char_range))
|
||||
Some(TextRunSlice {
|
||||
glyphs: &*slice_glyphs.glyph_store,
|
||||
offset: slice_range_begin,
|
||||
range: char_range,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CharacterSliceIterator<'a> {
|
||||
glyph_run: Option<&'a GlyphRun>,
|
||||
glyph_run_iter: Items<'a, GlyphRun>,
|
||||
range: Range<CharIndex>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator<TextRunSlice<'a>> for CharacterSliceIterator<'a> {
|
||||
// inline(always) due to the inefficient rt failures messing up inline heuristics, I think.
|
||||
#[inline(always)]
|
||||
fn next(&mut self) -> Option<TextRunSlice<'a>> {
|
||||
let glyph_run = match self.glyph_run {
|
||||
None => return None,
|
||||
Some(glyph_run) => glyph_run,
|
||||
};
|
||||
|
||||
debug_assert!(!self.range.is_empty());
|
||||
let index_to_return = self.range.begin();
|
||||
self.range.adjust_by(CharIndex(1), CharIndex(0));
|
||||
if self.range.is_empty() {
|
||||
// We're done.
|
||||
self.glyph_run = None
|
||||
} else if self.range.intersect(&glyph_run.range).is_empty() {
|
||||
// Move on to the next glyph run.
|
||||
self.glyph_run = self.glyph_run_iter.next();
|
||||
}
|
||||
|
||||
return None;
|
||||
let index_within_glyph_run = index_to_return - glyph_run.range.begin();
|
||||
Some(TextRunSlice {
|
||||
glyphs: &*glyph_run.glyph_store,
|
||||
offset: glyph_run.range.begin(),
|
||||
range: Range::new(index_within_glyph_run, CharIndex(1)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LineIterator<'a> {
|
||||
range: Range<CharIndex>,
|
||||
clump: Option<Range<CharIndex>>,
|
||||
slices: SliceIterator<'a>,
|
||||
range: Range<CharIndex>,
|
||||
clump: Option<Range<CharIndex>>,
|
||||
slices: NaturalWordSliceIterator<'a>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator<Range<CharIndex>> for LineIterator<'a> {
|
||||
|
@ -83,30 +144,30 @@ impl<'a> Iterator<Range<CharIndex>> for LineIterator<'a> {
|
|||
// Loop until we hit whitespace and are in a clump.
|
||||
loop {
|
||||
match self.slices.next() {
|
||||
Some((glyphs, offset, slice_range)) => {
|
||||
match (glyphs.is_whitespace(), self.clump) {
|
||||
Some(slice) => {
|
||||
match (slice.glyphs.is_whitespace(), self.clump) {
|
||||
(false, Some(ref mut c)) => {
|
||||
c.extend_by(slice_range.length());
|
||||
c.extend_by(slice.range.length());
|
||||
}
|
||||
(false, None) => {
|
||||
let mut c = slice_range;
|
||||
c.shift_by(offset);
|
||||
self.clump = Some(c);
|
||||
let mut range = slice.range;
|
||||
range.shift_by(slice.offset);
|
||||
self.clump = Some(range);
|
||||
}
|
||||
(true, None) => { /* chomp whitespace */ }
|
||||
(true, Some(c)) => {
|
||||
(true, Some(clump)) => {
|
||||
self.clump = None;
|
||||
// The final whitespace clump is not included.
|
||||
return Some(c);
|
||||
return Some(clump);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
None => {
|
||||
// flush any remaining chars as a line
|
||||
// Flush any remaining characters as a line.
|
||||
if self.clump.is_some() {
|
||||
let mut c = self.clump.take().unwrap();
|
||||
c.extend_to(self.range.end());
|
||||
return Some(c);
|
||||
let mut range = self.clump.take().unwrap();
|
||||
range.extend_to(self.range.end());
|
||||
return Some(range);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
|
@ -216,9 +277,7 @@ impl<'a> TextRun {
|
|||
}
|
||||
|
||||
pub fn range_is_trimmable_whitespace(&self, range: &Range<CharIndex>) -> bool {
|
||||
self.iter_slices_for_range(range).all(|(slice_glyphs, _, _)| {
|
||||
slice_glyphs.is_whitespace()
|
||||
})
|
||||
self.natural_word_slices_in_range(range).all(|slice| slice.glyphs.is_whitespace())
|
||||
}
|
||||
|
||||
pub fn ascent(&self) -> Au {
|
||||
|
@ -232,9 +291,9 @@ impl<'a> TextRun {
|
|||
pub fn advance_for_range(&self, range: &Range<CharIndex>) -> Au {
|
||||
// TODO(Issue #199): alter advance direction for RTL
|
||||
// TODO(Issue #98): using inter-char and inter-word spacing settings when measuring text
|
||||
self.iter_slices_for_range(range)
|
||||
.fold(Au(0), |advance, (glyphs, _, slice_range)| {
|
||||
advance + glyphs.advance_for_char_range(&slice_range)
|
||||
self.natural_word_slices_in_range(range)
|
||||
.fold(Au(0), |advance, slice| {
|
||||
advance + slice.glyphs.advance_for_char_range(&slice.range)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -252,33 +311,58 @@ impl<'a> TextRun {
|
|||
|
||||
pub fn min_width_for_range(&self, range: &Range<CharIndex>) -> Au {
|
||||
debug!("iterating outer range {}", range);
|
||||
self.iter_slices_for_range(range).fold(Au(0), |max_piece_width, (_, offset, slice_range)| {
|
||||
debug!("iterated on {}[{}]", offset, slice_range);
|
||||
Au::max(max_piece_width, self.advance_for_range(&slice_range))
|
||||
self.natural_word_slices_in_range(range).fold(Au(0), |max_piece_width, slice| {
|
||||
debug!("iterated on {}[{}]", slice.offset, slice.range);
|
||||
Au::max(max_piece_width, self.advance_for_range(&slice.range))
|
||||
})
|
||||
}
|
||||
|
||||
/// Returns the first glyph run containing the given character index.
|
||||
pub fn first_glyph_run_containing(&'a self, index: CharIndex) -> Option<&'a GlyphRun> {
|
||||
self.index_of_first_glyph_run_containing(index).map(|index| &self.glyphs[index])
|
||||
}
|
||||
|
||||
/// Returns the index of the first glyph run containing the given character index.
|
||||
fn index_of_first_glyph_run_containing(&self, index: CharIndex) -> Option<uint> {
|
||||
self.glyphs.as_slice().binary_search_index_by(&index, CharIndexComparator)
|
||||
}
|
||||
|
||||
pub fn iter_slices_for_range(&'a self, range: &Range<CharIndex>) -> SliceIterator<'a> {
|
||||
/// Returns an iterator that will iterate over all slices of glyphs that represent natural
|
||||
/// words in the given range.
|
||||
pub fn natural_word_slices_in_range(&'a self, range: &Range<CharIndex>)
|
||||
-> NaturalWordSliceIterator<'a> {
|
||||
let index = match self.index_of_first_glyph_run_containing(range.begin()) {
|
||||
None => self.glyphs.len(),
|
||||
Some(index) => index,
|
||||
};
|
||||
SliceIterator {
|
||||
NaturalWordSliceIterator {
|
||||
glyph_iter: self.glyphs.slice_from(index).iter(),
|
||||
range: *range,
|
||||
range: *range,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns an iterator that will iterate over all slices of glyphs that represent individual
|
||||
/// characters in the given range.
|
||||
pub fn character_slices_in_range(&'a self, range: &Range<CharIndex>)
|
||||
-> CharacterSliceIterator<'a> {
|
||||
let index = match self.index_of_first_glyph_run_containing(range.begin()) {
|
||||
None => self.glyphs.len(),
|
||||
Some(index) => index,
|
||||
};
|
||||
let mut glyph_run_iter = self.glyphs.slice_from(index).iter();
|
||||
let first_glyph_run = glyph_run_iter.next();
|
||||
CharacterSliceIterator {
|
||||
glyph_run: first_glyph_run,
|
||||
glyph_run_iter: glyph_run_iter,
|
||||
range: *range,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn iter_natural_lines_for_range(&'a self, range: &Range<CharIndex>) -> LineIterator<'a> {
|
||||
LineIterator {
|
||||
range: *range,
|
||||
clump: None,
|
||||
slices: self.iter_slices_for_range(range),
|
||||
range: *range,
|
||||
clump: None,
|
||||
slices: self.natural_word_slices_in_range(range),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue