Permalink
Browse files

Auto merge of #19688 - Manishearth:linebreak, r=<try>

Detect adjoining text fragments with no whitespace between them and avoid linebreaking there

First attempt at #874

<!-- Reviewable:start -->
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/19688)
<!-- Reviewable:end -->
  • Loading branch information...
bors-servo committed Jan 13, 2018
2 parents 6ca651c + 1026c8a commit ef5e71d433452798695bffeebc4d50901e01f176
View

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
@@ -15,7 +15,7 @@ use style::str::char_is_whitespace;
use text::glyph::{ByteIndex, GlyphStore};
use unicode_bidi as bidi;
use webrender_api;
use xi_unicode::LineBreakIterator;
use xi_unicode::LineBreakLeafIter;
thread_local! {
static INDEX_OF_FIRST_GLYPH_RUN_CACHE: Cell<Option<(*const TextRun, ByteIndex, usize)>> =
@@ -177,9 +177,11 @@ impl<'a> Iterator for CharacterSliceIterator<'a> {
}
impl<'a> TextRun {
pub fn new(font: &mut Font, text: String, options: &ShapingOptions, bidi_level: bidi::Level) -> TextRun {
let glyphs = TextRun::break_and_shape(font, &text, options);
TextRun {
/// Constructs a new text run. Also returns if there is a line break at the beginning
pub fn new(font: &mut Font, text: String, options: &ShapingOptions,
bidi_level: bidi::Level, breaker: &mut LineBreakLeafIter) -> (TextRun, bool) {
let (glyphs, break_at_zero) = TextRun::break_and_shape(font, &text, options, breaker);
(TextRun {
text: Arc::new(text),
font_metrics: font.metrics.clone(),
font_template: font.handle.template(),
@@ -188,15 +190,25 @@ impl<'a> TextRun {
glyphs: Arc::new(glyphs),
bidi_level: bidi_level,
extra_word_spacing: Au(0),
}
}, break_at_zero)
}
pub fn break_and_shape(font: &mut Font, text: &str, options: &ShapingOptions)
-> Vec<GlyphRun> {
pub fn break_and_shape(font: &mut Font, text: &str, options: &ShapingOptions,
breaker: &mut LineBreakLeafIter) -> (Vec<GlyphRun>, bool) {
let mut glyphs = vec!();
let mut slice = 0..0;
for (idx, _is_hard_break) in LineBreakIterator::new(text) {
let mut finished = false;
let mut break_at_zero = false;
while !finished {
let (idx, _is_hard_break) = breaker.next(text);
if idx == text.len() {
finished = true;
}
if idx == 0 {
break_at_zero = true;
}
// Extend the slice to the next UAX#14 line break opportunity.
slice.end = idx;
let word = &text[slice.clone()];
@@ -230,7 +242,7 @@ impl<'a> TextRun {
}
slice.start = whitespace.end;
}
glyphs
(glyphs, break_at_zero)
}
pub fn ascent(&self) -> Au {
@@ -46,3 +46,4 @@ style_traits = {path = "../style_traits"}
unicode-bidi = {version = "0.3", features = ["with_serde"]}
unicode-script = {version = "0.1", features = ["harfbuzz"]}
webrender_api = {git = "https://github.com/servo/webrender", features = ["ipc"]}
xi-unicode = "0.1.0"
@@ -479,6 +479,11 @@ bitflags! {
/// Is this fragment selected?
const SELECTED = 0x02;
/// Suppress line breaking between this and the previous fragment
///
/// This handles cases like Foo<span>bar</span>
const SUPPRESS_LINE_BREAK_BEFORE = 0x01;
}
}
@@ -1421,6 +1426,14 @@ impl Fragment {
}
}
pub fn suppress_line_break_before(&self) -> bool {
match self.specific {
SpecificFragmentInfo::ScannedText(ref st) =>
st.flags.contains(ScannedTextFlags::SUPPRESS_LINE_BREAK_BEFORE),
_ => false,
}
}
/// Computes the intrinsic inline-sizes of this fragment.
pub fn compute_intrinsic_inline_sizes(&mut self) -> IntrinsicISizesContribution {
let mut result = self.style_specified_intrinsic_inline_size();
@@ -566,7 +566,11 @@ impl LineBreaker {
self.pending_line.green_zone = line_bounds.size;
false
} else {
fragment.white_space().allow_wrap()
if fragment.suppress_line_break_before() {
false
} else {
fragment.white_space().allow_wrap()
}
};
debug!("LineBreaker: trying to append to line {} \
View
@@ -42,6 +42,7 @@ extern crate style_traits;
extern crate unicode_bidi;
extern crate unicode_script;
extern crate webrender_api;
extern crate xi_unicode;
#[macro_use]
pub mod layout_debug;
View
@@ -32,6 +32,7 @@ use style::properties::style_structs;
use style::values::generics::text::LineHeight;
use unicode_bidi as bidi;
use unicode_script::{Script, get_script};
use xi_unicode::LineBreakLeafIter;
/// Returns the concatenated text of a list of unscanned text fragments.
fn text(fragments: &LinkedList<Fragment>) -> String {
@@ -91,6 +92,15 @@ impl TextRunScanner {
let mut last_whitespace = false;
let mut paragraph_bytes_processed = 0;
// The first time we process a text run we will set this
// linebreaker. There is no way for the linebreaker to start
// with an empty state; you must give it its first input immediately.
//
// This linebreaker is shared across text runs, so we can know if
// there is a break at the beginning of a text run or clump, e.g.
// in the case of FooBar<span>Baz</span>
let mut linebreaker = None;
while !fragments.is_empty() {
// Create a clump.
split_first_fragment_at_newline_if_necessary(&mut fragments);
@@ -109,7 +119,8 @@ impl TextRunScanner {
&mut new_fragments,
&mut paragraph_bytes_processed,
bidi_levels,
last_whitespace);
last_whitespace,
&mut linebreaker);
}
debug!("TextRunScanner: complete.");
@@ -129,7 +140,8 @@ impl TextRunScanner {
out_fragments: &mut Vec<Fragment>,
paragraph_bytes_processed: &mut usize,
bidi_levels: Option<&[bidi::Level]>,
mut last_whitespace: bool)
mut last_whitespace: bool,
linebreaker: &mut Option<LineBreakLeafIter>)
-> bool {
debug!("TextRunScanner: flushing {} fragments in range", self.clump.len());
@@ -309,22 +321,30 @@ impl TextRunScanner {
flags: flags,
};
// FIXME(https://github.com/rust-lang/rust/issues/23338)
run_info_list.into_iter().map(|run_info| {
let mut result = Vec::with_capacity(run_info_list.len());
for run_info in run_info_list {
let mut options = options;
options.script = run_info.script;
if run_info.bidi_level.is_rtl() {
options.flags.insert(ShapingFlags::RTL_FLAG);
}
let mut font = fontgroup.fonts.get(run_info.font_index).unwrap().borrow_mut();
ScannedTextRun {
run: Arc::new(TextRun::new(&mut *font,
run_info.text,
&options,
run_info.bidi_level)),
insertion_point: run_info.insertion_point,
if linebreaker.is_none() {
debug_assert!(run_info.text.len() > 0);
*linebreaker = Some(LineBreakLeafIter::new(&run_info.text, 0));
}
}).collect::<Vec<_>>()
let (run, break_at_zero) = TextRun::new(&mut *font,
run_info.text,
&options,
run_info.bidi_level,
&mut linebreaker.as_mut()
.unwrap());
result.push((ScannedTextRun {
run: Arc::new(run),
insertion_point: run_info.insertion_point,
}, break_at_zero))
}
result
};
// Make new fragments with the runs and adjusted text indices.
@@ -351,12 +371,15 @@ impl TextRunScanner {
}
};
let mapping = mappings.next().unwrap();
let scanned_run = runs[mapping.text_run_index].clone();
let (scanned_run, break_at_zero) = runs[mapping.text_run_index].clone();
let mut byte_range = Range::new(ByteIndex(mapping.byte_range.begin() as isize),
ByteIndex(mapping.byte_range.length() as isize));
let mut flags = ScannedTextFlags::empty();
if !break_at_zero {
flags.insert(ScannedTextFlags::SUPPRESS_LINE_BREAK_BEFORE)
}
let text_size = old_fragment.border_box.size;
let requires_line_break_afterward_if_wrapping_on_newlines =

0 comments on commit ef5e71d

Please sign in to comment.