Simplify control flow of whitespace handling.

This commit is contained in:
Josh Matthews 2020-07-24 14:57:11 -04:00
parent d8b4dab4e3
commit 260347e5dc
3 changed files with 100 additions and 72 deletions

View file

@ -294,9 +294,15 @@ where
} }
fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, input: Cow<'dom, str>) { fn handle_text(&mut self, info: &NodeAndStyleInfo<Node>, input: Cow<'dom, str>) {
// Skip any leading whitespace as dictated by the node's style.
let white_space = info.style.get_inherited_text().white_space; let white_space = info.style.get_inherited_text().white_space;
let (leading_whitespace, mut input) = self.handle_leading_whitespace(&input, white_space); let (preserved_leading_whitespace, mut input) =
if leading_whitespace || !input.is_empty() { self.handle_leading_whitespace(&input, white_space);
if !preserved_leading_whitespace && input.is_empty() {
return;
}
// This text node should be pushed either to the next ongoing // This text node should be pushed either to the next ongoing
// inline level box with the parent style of that inline level box // inline level box with the parent style of that inline level box
// that will be ended, or directly to the ongoing inline formatting // that will be ended, or directly to the ongoing inline formatting
@ -322,7 +328,7 @@ where
output = new_text_run_contents.as_mut().unwrap(); output = new_text_run_contents.as_mut().unwrap();
} }
if leading_whitespace { if preserved_leading_whitespace {
output.push(' ') output.push(' ')
} }
@ -330,20 +336,32 @@ where
white_space.preserve_spaces(), white_space.preserve_spaces(),
white_space.preserve_newlines(), white_space.preserve_newlines(),
) { ) {
// All whitespace is significant, so we don't need to transform
// the input at all.
(true, true) => { (true, true) => {
output.push_str(input); output.push_str(input);
}, },
// There are no cases in CSS where where need to preserve spaces
// but not newlines.
(true, false) => unreachable!(), (true, false) => unreachable!(),
// Spaces are not significant, but newlines might be. We need
// to collapse non-significant whitespace as appropriate.
(false, preserve_newlines) => loop { (false, preserve_newlines) => loop {
if let Some(i) = input.bytes().position(|b| { // If there are any spaces that need preserving, split the string
b.is_ascii_whitespace() && (!preserve_newlines || b != b'\n') // that precedes them, collapse them into a single whitespace,
}) { // then process the remainder of the string independently.
if let Some(i) = input
.bytes()
.position(|b| b.is_ascii_whitespace() && (!preserve_newlines || b != b'\n'))
{
let (non_whitespace, rest) = input.split_at(i); let (non_whitespace, rest) = input.split_at(i);
output.push_str(non_whitespace); output.push_str(non_whitespace);
output.push(' '); output.push(' ');
// Find the first byte that is either significant whitespace or
// non-whitespace to continue processing it.
if let Some(i) = rest.bytes().position(|b| { if let Some(i) = rest.bytes().position(|b| {
!b.is_ascii_whitespace() || (preserve_newlines && b == b'\n') !b.is_ascii_whitespace() || (preserve_newlines && b == b'\n')
}) { }) {
@ -352,6 +370,7 @@ where
break; break;
} }
} else { } else {
// No whitespace found, so no transformation is required.
output.push_str(input); output.push_str(input);
break; break;
} }
@ -367,7 +386,6 @@ where
}))) })))
} }
} }
}
} }
impl<'dom, Node> BlockContainerBuilder<'dom, '_, Node> impl<'dom, Node> BlockContainerBuilder<'dom, '_, Node>

View file

@ -757,14 +757,20 @@ impl TextRun {
let mut advance_width = Length::zero(); let mut advance_width = Length::zero();
let mut last_break_opportunity = None; let mut last_break_opportunity = None;
let mut force_line_break = false; let mut force_line_break = false;
// Fit as many glyphs within a single line as possible.
loop { loop {
let next = runs.next(); let next = runs.next();
if next.as_ref().map_or(true, |run| { // If there are no more text runs we still need to check if the last
run.glyph_store.is_whitespace() || force_line_break // run was a forced line break
}) { if next
if advance_width > ifc.containing_block.inline_size - ifc.inline_position || .as_ref()
force_line_break .map_or(true, |run| run.glyph_store.is_whitespace())
{ {
// If this run exceeds the bounds of the containing block, then
// we need to attempt to break the line.
if advance_width > ifc.containing_block.inline_size - ifc.inline_position {
// Reset the text run iterator to the last whitespace if possible,
// to attempt to re-layout the most recent glyphs on a new line.
if let Some((len, width, iter)) = last_break_opportunity.take() { if let Some((len, width, iter)) = last_break_opportunity.take() {
glyphs.truncate(len); glyphs.truncate(len);
advance_width = width; advance_width = width;
@ -776,18 +782,24 @@ impl TextRun {
if let Some(run) = next { if let Some(run) = next {
if run.glyph_store.is_whitespace() { if run.glyph_store.is_whitespace() {
last_break_opportunity = Some((glyphs.len(), advance_width, runs.clone())); last_break_opportunity = Some((glyphs.len(), advance_width, runs.clone()));
if self.text.as_bytes().get(run.range.end().to_usize() - 1) == Some(&b'\n') // If this whitespace ends with a newline, we need to check if
{ // it's meaningful within the current style. If so, we force
force_line_break = self // a line break immediately.
.parent_style let last_byte = self.text.as_bytes().get(run.range.end().to_usize() - 1);
if last_byte == Some(&b'\n') &&
self.parent_style
.get_inherited_text() .get_inherited_text()
.white_space .white_space
.preserve_newlines(); .preserve_newlines()
{
force_line_break = true;
break;
} }
} }
glyphs.push(run.glyph_store.clone()); glyphs.push(run.glyph_store.clone());
advance_width += Length::from(run.glyph_store.total_advance()); advance_width += Length::from(run.glyph_store.total_advance());
} else { } else {
// No more runs, so we can end the line.
break; break;
} }
} }
@ -822,6 +834,7 @@ impl TextRun {
glyphs, glyphs,
text_decoration_line: ifc.current_nesting_level.text_decoration_line, text_decoration_line: ifc.current_nesting_level.text_decoration_line,
})); }));
// If this line is being broken because of a trailing newline, we can't ignore it.
if runs.as_slice().is_empty() && !force_line_break { if runs.as_slice().is_empty() && !force_line_break {
break; break;
} else { } else {

View file

@ -254,9 +254,6 @@
[clear: both] [clear: both]
expected: FAIL expected: FAIL
[list-style-image: url(http://localhost/)]
expected: FAIL
[outline-width: 0px] [outline-width: 0px]
expected: FAIL expected: FAIL