mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
Auto merge of #19688 - Manishearth:linebreak, r=mbrubeck
Detect adjoining text fragments with no line break opportunity between them First attempt at https://github.com/servo/servo/issues/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 -->
This commit is contained in:
commit
9c13073075
39 changed files with 172 additions and 81 deletions
|
@ -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"}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -712,13 +717,15 @@ impl Fragment {
|
|||
}
|
||||
|
||||
/// Transforms this fragment using the given `SplitInfo`, preserving all the other data.
|
||||
pub fn transform_with_split_info(&self, split: &SplitInfo, text_run: Arc<TextRun>)
|
||||
-> Fragment {
|
||||
///
|
||||
/// If this is the first half of a split, `first` is true
|
||||
pub fn transform_with_split_info(&self, split: &SplitInfo, text_run: Arc<TextRun>,
|
||||
first: bool) -> Fragment {
|
||||
let size = LogicalSize::new(self.style.writing_mode,
|
||||
split.inline_size,
|
||||
self.border_box.size.block);
|
||||
// Preserve the insertion point if it is in this fragment's range or it is at line end.
|
||||
let (flags, insertion_point) = match self.specific {
|
||||
let (mut flags, insertion_point) = match self.specific {
|
||||
SpecificFragmentInfo::ScannedText(ref info) => {
|
||||
match info.insertion_point {
|
||||
Some(index) if split.range.contains(index) => (info.flags, info.insertion_point),
|
||||
|
@ -729,6 +736,11 @@ impl Fragment {
|
|||
},
|
||||
_ => (ScannedTextFlags::empty(), None)
|
||||
};
|
||||
|
||||
if !first {
|
||||
flags.set(ScannedTextFlags::SUPPRESS_LINE_BREAK_BEFORE, false);
|
||||
}
|
||||
|
||||
let info = Box::new(ScannedTextFragmentInfo::new(
|
||||
text_run,
|
||||
split.range,
|
||||
|
@ -1421,6 +1433,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 +1641,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.
|
||||
|
|
|
@ -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 {} \
|
||||
|
@ -657,10 +670,10 @@ impl LineBreaker {
|
|||
};
|
||||
|
||||
inline_start_fragment = split_result.inline_start.as_ref().map(|x| {
|
||||
fragment.transform_with_split_info(x, split_result.text_run.clone())
|
||||
fragment.transform_with_split_info(x, split_result.text_run.clone(), true)
|
||||
});
|
||||
inline_end_fragment = split_result.inline_end.as_ref().map(|x| {
|
||||
fragment.transform_with_split_info(x, split_result.text_run.clone())
|
||||
fragment.transform_with_split_info(x, split_result.text_run.clone(), false)
|
||||
});
|
||||
|
||||
// Push the first fragment onto the line we're working on and start off the next line with
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 =
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue