diff options
author | Manish Goregaokar <manishsmail@gmail.com> | 2018-01-10 18:08:38 +0530 |
---|---|---|
committer | Manish Goregaokar <manishsmail@gmail.com> | 2018-01-24 12:51:33 +0530 |
commit | f3c81fcda8a16e9f3d7a30a9f67b0a03d618e630 (patch) | |
tree | e331b00e118054c627e1c5de4731b12716dd0781 /components/layout | |
parent | bda560d01b6a9452a9124957f39b99a701aac25c (diff) | |
download | servo-f3c81fcda8a16e9f3d7a30a9f67b0a03d618e630.tar.gz servo-f3c81fcda8a16e9f3d7a30a9f67b0a03d618e630.zip |
Share line breaking state across text runs
Fixes #874
Diffstat (limited to 'components/layout')
-rw-r--r-- | components/layout/Cargo.toml | 2 | ||||
-rw-r--r-- | components/layout/fragment.rs | 23 | ||||
-rw-r--r-- | components/layout/inline.rs | 17 | ||||
-rw-r--r-- | components/layout/lib.rs | 1 | ||||
-rw-r--r-- | components/layout/text.rs | 45 |
5 files changed, 74 insertions, 14 deletions
diff --git a/components/layout/Cargo.toml b/components/layout/Cargo.toml index 14532a1da52..dac80d51767 100644 --- a/components/layout/Cargo.toml +++ b/components/layout/Cargo.toml @@ -48,6 +48,8 @@ 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" [dev-dependencies] size_of_test = {path = "../size_of_test"} + diff --git a/components/layout/fragment.rs b/components/layout/fragment.rs index ae90f4d50d4..d11ed64a4be 100644 --- a/components/layout/fragment.rs +++ b/components/layout/fragment.rs @@ -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 = 0x04; } } @@ -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(); @@ -1621,6 +1634,16 @@ impl Fragment { } } + /// Does this fragment start on a glyph run boundary? + pub fn is_on_glyph_run_boundary(&self) -> bool { + let text_fragment_info = match self.specific { + SpecificFragmentInfo::ScannedText(ref text_fragment_info) + => text_fragment_info, + _ => return true, + }; + text_fragment_info.run.on_glyph_run_boundary(text_fragment_info.range.begin()) + } + /// Truncates this fragment to the given `max_inline_size`, using a character-based breaking /// strategy. The resulting fragment will have `SpecificFragmentInfo::TruncatedFragment`, /// preserving the original fragment for use in incremental reflow. diff --git a/components/layout/inline.rs b/components/layout/inline.rs index a934b95bafe..c5be5b33390 100644 --- a/components/layout/inline.rs +++ b/components/layout/inline.rs @@ -554,7 +554,6 @@ impl LineBreaker { layout_context: &LayoutContext) { // Undo any whitespace stripping from previous reflows. fragment.reset_text_range_and_inline_size(); - // Determine initial placement for the fragment if we need to. // // Also, determine whether we can legally break the line before, or @@ -566,7 +565,21 @@ impl LineBreaker { self.pending_line.green_zone = line_bounds.size; false } else { - fragment.white_space().allow_wrap() + // In case of Foo<span style="...">bar</span>, the line breaker will + // set the "suppress line break before" flag for the second fragment. + // + // In case of Foo<span>bar</span> the second fragment ("bar") will + // start _within_ a glyph run, so we also avoid breaking there + // + // is_on_glyph_run_boundary does a binary search, but this is ok + // because the result will be cached and reused in + // `calculate_split_position` later + if fragment.suppress_line_break_before() || + !fragment.is_on_glyph_run_boundary() { + false + } else { + fragment.white_space().allow_wrap() + } }; debug!("LineBreaker: trying to append to line {} \ diff --git a/components/layout/lib.rs b/components/layout/lib.rs index e19a5f81c9d..bb3a3dc20b2 100644 --- a/components/layout/lib.rs +++ b/components/layout/lib.rs @@ -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; diff --git a/components/layout/text.rs b/components/layout/text.rs index 0930389f37f..535c255bc76 100644 --- a/components/layout/text.rs +++ b/components/layout/text.rs @@ -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,26 @@ 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)), + + let (run, break_at_zero) = TextRun::new(&mut *font, + run_info.text, + &options, + run_info.bidi_level, + linebreaker); + result.push((ScannedTextRun { + run: Arc::new(run), insertion_point: run_info.insertion_point, - } - }).collect::<Vec<_>>() + }, break_at_zero)) + } + result }; // Make new fragments with the runs and adjusted text indices. @@ -351,12 +367,17 @@ 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 && mapping.byte_range.begin() == 0 { + // If this is the first segment of the text run, + // and the text run doesn't break at zero, suppress line breaks + flags.insert(ScannedTextFlags::SUPPRESS_LINE_BREAK_BEFORE) + } let text_size = old_fragment.border_box.size; let requires_line_break_afterward_if_wrapping_on_newlines = |