diff options
author | Patrick Walton <pcwalton@mimiga.net> | 2015-08-06 17:51:57 -0700 |
---|---|---|
committer | Patrick Walton <pcwalton@mimiga.net> | 2015-08-08 00:01:31 -0700 |
commit | 11fdb503df410348f876794a39c4e27e5c295c53 (patch) | |
tree | 028ef8de2b492d128702f2ca24e67ed28e732df4 | |
parent | af4e0a246a286325ed6bd23062e2dbeaf8bc1d50 (diff) | |
download | servo-11fdb503df410348f876794a39c4e27e5c295c53.tar.gz servo-11fdb503df410348f876794a39c4e27e5c295c53.zip |
layout: Introduce infrastructure for tracking, backing up, and splitting
at the last known good split point, and use it for `white-space:
nowrap`.
Fixes overflowing tables on Wikipedia.
This infrastructure should form the basis of our fix for inline layout
of fragments that don't themselves constitute valid split points. That
will require some more work, however.
-rw-r--r-- | components/layout/inline.rs | 91 | ||||
-rw-r--r-- | tests/ref/basic.list | 1 | ||||
-rw-r--r-- | tests/ref/whitespace_nowrap_line_breaking_a.html | 22 | ||||
-rw-r--r-- | tests/ref/whitespace_nowrap_line_breaking_ref.html | 31 | ||||
-rw-r--r-- | tests/wpt/metadata-css/css21_dev/html4/inline-formatting-context-012.htm.ini | 3 |
5 files changed, 134 insertions, 14 deletions
diff --git a/components/layout/inline.rs b/components/layout/inline.rs index 52eb0879427..b293478656e 100644 --- a/components/layout/inline.rs +++ b/components/layout/inline.rs @@ -204,6 +204,9 @@ struct LineBreaker { pending_line: Line, /// The lines we've already committed. lines: Vec<Line>, + /// The index of the last known good line breaking opportunity. The opportunity will either + /// be inside this fragment (if it is splittable) or immediately prior to it. + last_known_line_breaking_opportunity: Option<FragmentIndex>, /// The current position in the block direction. cur_b: Au, /// The computed value of the indentation for the first line (`text-indent`, CSS 2.1 § 16.1). @@ -232,6 +235,7 @@ impl LineBreaker { floats: float_context, lines: Vec::new(), cur_b: Au(0), + last_known_line_breaking_opportunity: None, first_line_indentation: first_line_indentation, minimum_block_size_above_baseline: minimum_block_size_above_baseline, minimum_depth_below_baseline: minimum_depth_below_baseline, @@ -248,6 +252,7 @@ impl LineBreaker { /// Reinitializes the pending line to blank data. fn reset_line(&mut self) -> Line { + self.last_known_line_breaking_opportunity = None; mem::replace(&mut self.pending_line, Line::new(self.floats.writing_mode, self.minimum_block_size_above_baseline, self.minimum_depth_below_baseline)) @@ -272,7 +277,6 @@ impl LineBreaker { self.reflow_fragments(old_fragment_iter, flow, layout_context); // Perform unicode bidirectional layout. - let para_level = flow.base.writing_mode.to_bidi_level(); // The text within a fragment is at a single bidi embedding level (because we split @@ -537,12 +541,17 @@ impl LineBreaker { layout_context: &LayoutContext, flags: InlineReflowFlags) { // Determine initial placement for the fragment if we need to. - if self.pending_line_is_empty() { + // + // Also, determine whether we can legally break the line before, or inside, this fragment. + let fragment_is_line_break_opportunity = if self.pending_line_is_empty() { fragment.strip_leading_whitespace_if_necessary(); let (line_bounds, _) = self.initial_line_placement(flow, &fragment, self.cur_b); self.pending_line.bounds.start = line_bounds.start; self.pending_line.green_zone = line_bounds.size; - } + false + } else { + !flags.contains(NO_WRAP_INLINE_REFLOW_FLAG) + }; debug!("LineBreaker: trying to append to line {} (fragment size: {:?}, green zone: {:?}): \ {:?}", @@ -564,6 +573,11 @@ impl LineBreaker { return } + // Record the last known good line break opportunity if this is one. + if fragment_is_line_break_opportunity { + self.last_known_line_breaking_opportunity = Some(self.pending_line.range.end()) + } + // If we must flush the line after finishing this fragment due to `white-space: pre`, // detect that. let line_flush_mode = @@ -586,17 +600,29 @@ impl LineBreaker { return } - // If we can't split the fragment or aren't allowed to because of the wrapping mode, then - // just overflow. - if (!fragment.can_split() && self.pending_line_is_empty()) || - (flags.contains(NO_WRAP_INLINE_REFLOW_FLAG) && - !flags.contains(WRAP_ON_NEWLINE_INLINE_REFLOW_FLAG)) { + // If we can't split the fragment and we're at the start of the line, then just overflow. + if !fragment.can_split() && self.pending_line_is_empty() { debug!("LineBreaker: fragment can't split and line {} is empty, so overflowing", self.lines.len()); self.push_fragment_to_line(layout_context, fragment, LineFlushMode::No); return } + // If the wrapping mode prevents us from splitting, then back up and split at the last + // known good split point. + if flags.contains(NO_WRAP_INLINE_REFLOW_FLAG) && + !flags.contains(WRAP_ON_NEWLINE_INLINE_REFLOW_FLAG) { + debug!("LineBreaker: white-space: nowrap in effect; falling back to last known good \ + split point"); + if !self.split_line_at_last_known_good_position() { + // No line breaking opportunity exists at all for this line. Overflow. + self.push_fragment_to_line(layout_context, fragment, LineFlushMode::No) + } else { + self.work_list.push_front(fragment) + } + return + } + // Split it up! let available_inline_size = if !flags.contains(NO_WRAP_INLINE_REFLOW_FLAG) { green_zone.inline - self.pending_line.bounds.size.inline - indentation @@ -608,9 +634,21 @@ impl LineBreaker { let split_result = match fragment.calculate_split_position(available_inline_size, self.pending_line_is_empty()) { None => { - debug!("LineBreaker: fragment was unsplittable; deferring to next line"); - self.work_list.push_front(fragment); - self.flush_current_line(); + // We failed to split. Defer to the next line if we're allowed to; otherwise, + // rewind to the last line breaking opportunity. + if fragment_is_line_break_opportunity { + debug!("LineBreaker: fragment was unsplittable; deferring to next line"); + self.work_list.push_front(fragment); + self.flush_current_line(); + } else if self.split_line_at_last_known_good_position() { + // We split the line at a known-good prior position. Restart with the current + // fragment. + self.work_list.push_front(fragment) + } else { + // We failed to split and there is no known-good place on this line to split. + // Overflow. + self.push_fragment_to_line(layout_context, fragment, LineFlushMode::No) + } return } Some(split_result) => split_result, @@ -706,6 +744,37 @@ impl LineBreaker { self.new_fragments.push(fragment); } + fn split_line_at_last_known_good_position(&mut self) -> bool { + let last_known_line_breaking_opportunity = + match self.last_known_line_breaking_opportunity { + None => return false, + Some(last_known_line_breaking_opportunity) => last_known_line_breaking_opportunity, + }; + + for fragment_index in (last_known_line_breaking_opportunity.get().. + self.pending_line.range.end().get()).rev() { + debug_assert!(fragment_index == (self.new_fragments.len() as isize) - 1); + self.work_list.push_front(self.new_fragments.pop().unwrap()); + } + + // FIXME(pcwalton): This should actually attempt to split the last fragment if + // possible to do so, to handle cases like: + // + // (available width) + // +-------------+ + // The alphabet + // (<em>abcdefghijklmnopqrstuvwxyz</em>) + // + // Here, the last known-good split point is inside the fragment containing + // "The alphabet (", which has already been committed by the time we get to this + // point. Unfortunately, the existing splitting API (`calculate_split_position`) + // has no concept of "split right before the last non-whitespace position". We'll + // need to add that feature to the API to handle this case correctly. + self.pending_line.range.extend_to(last_known_line_breaking_opportunity); + self.flush_current_line(); + true + } + /// Returns the indentation that needs to be applied before the fragment we're reflowing. fn indentation_for_pending_fragment(&self) -> Au { if self.pending_line_is_empty() && self.lines.is_empty() { diff --git a/tests/ref/basic.list b/tests/ref/basic.list index 31b512cec4d..448e5cb1379 100644 --- a/tests/ref/basic.list +++ b/tests/ref/basic.list @@ -352,6 +352,7 @@ experimental == viewport_rule.html viewport_rule_ref.html == webgl-context/draw_arrays_simple.html webgl-context/draw_arrays_simple_ref.html == whitespace_nowrap_a.html whitespace_nowrap_ref.html +== whitespace_nowrap_line_breaking_a.html whitespace_nowrap_line_breaking_ref.html == whitespace_pre.html whitespace_pre_ref.html == width_nonreplaced_block_simple_a.html width_nonreplaced_block_simple_b.html == word_break_a.html word_break_ref.html diff --git a/tests/ref/whitespace_nowrap_line_breaking_a.html b/tests/ref/whitespace_nowrap_line_breaking_a.html new file mode 100644 index 00000000000..2b3ac4646d2 --- /dev/null +++ b/tests/ref/whitespace_nowrap_line_breaking_a.html @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<style> +body, html { + margin: 0; +} +em { + white-space: nowrap; +} +section { + background: gold; + width: 300px; +} +</style> +<section> + <em>Daniel Dennett</em> + <em>Michael Tye</em> + <em>David Chalmers</em> + <em>Patricia Churchland</em> + <em>Colin McGinn</em> +</section> + + diff --git a/tests/ref/whitespace_nowrap_line_breaking_ref.html b/tests/ref/whitespace_nowrap_line_breaking_ref.html new file mode 100644 index 00000000000..930da5fdc15 --- /dev/null +++ b/tests/ref/whitespace_nowrap_line_breaking_ref.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<style> +body, html { + margin: 0; +} +em { + white-space: nowrap; +} +section { + background: gold; + width: 300px; +} +#cover { + background: white; + position: absolute; + top: 0; + left: 300px; + right: 0; + bottom: 0; +} +</style> +<section> + <em>Daniel Dennett</em> + <em>Michael Tye</em> + <em>David Chalmers</em> + <em>Patricia Churchland</em> + <em>Colin McGinn</em> +</section> +<div id=cover> +</div> + diff --git a/tests/wpt/metadata-css/css21_dev/html4/inline-formatting-context-012.htm.ini b/tests/wpt/metadata-css/css21_dev/html4/inline-formatting-context-012.htm.ini deleted file mode 100644 index a67c595c67c..00000000000 --- a/tests/wpt/metadata-css/css21_dev/html4/inline-formatting-context-012.htm.ini +++ /dev/null @@ -1,3 +0,0 @@ -[inline-formatting-context-012.htm] - type: reftest - expected: FAIL |