diff --git a/components/fonts/font.rs b/components/fonts/font.rs index 02cb6da8077..e423c546dae 100644 --- a/components/fonts/font.rs +++ b/components/fonts/font.rs @@ -291,15 +291,17 @@ bitflags! { #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] pub struct ShapingFlags: u8 { /// Set if the text is entirely whitespace. - const IS_WHITESPACE_SHAPING_FLAG = 0x01; + const IS_WHITESPACE_SHAPING_FLAG = 1 << 0; + /// Set if the text ends with whitespace. + const ENDS_WITH_WHITESPACE_SHAPING_FLAG = 1 << 1; /// Set if we are to ignore ligatures. - const IGNORE_LIGATURES_SHAPING_FLAG = 0x02; + const IGNORE_LIGATURES_SHAPING_FLAG = 1 << 2; /// Set if we are to disable kerning. - const DISABLE_KERNING_SHAPING_FLAG = 0x04; + const DISABLE_KERNING_SHAPING_FLAG = 1 << 3; /// Text direction is right-to-left. - const RTL_FLAG = 0x08; + const RTL_FLAG = 1 << 4; /// Set if word-break is set to keep-all. - const KEEP_ALL_FLAG = 0x10; + const KEEP_ALL_FLAG = 1 << 5; } } @@ -344,6 +346,9 @@ impl Font { options .flags .contains(ShapingFlags::IS_WHITESPACE_SHAPING_FLAG), + options + .flags + .contains(ShapingFlags::ENDS_WITH_WHITESPACE_SHAPING_FLAG), is_single_preserved_newline, options.flags.contains(ShapingFlags::RTL_FLAG), ); @@ -946,10 +951,10 @@ mod test { assert!(font.can_do_fast_shaping(text, &shaping_options)); - let mut expected_glyphs = GlyphStore::new(text.len(), false, false, false); + let mut expected_glyphs = GlyphStore::new(text.len(), false, false, false, false); font.shape_text_harfbuzz(text, &shaping_options, &mut expected_glyphs); - let mut glyphs = GlyphStore::new(text.len(), false, false, false); + let mut glyphs = GlyphStore::new(text.len(), false, false, false, false); font.shape_text_fast(text, &shaping_options, &mut glyphs); assert_eq!(glyphs.len(), expected_glyphs.len()); diff --git a/components/fonts/glyph.rs b/components/fonts/glyph.rs index 517afebfc64..83162da3619 100644 --- a/components/fonts/glyph.rs +++ b/components/fonts/glyph.rs @@ -446,6 +446,11 @@ pub struct GlyphStore { /// Whether or not this glyph store contains only glyphs for whitespace. is_whitespace: bool, + /// Whether or not this glyph store ends with whitespace glyphs. + /// Typically whitespace glyphs are placed in a separate store, + /// but that may not be the case with `white-space: break-spaces`. + ends_with_whitespace: bool, + /// Whether or not this glyph store contains only a single glyph for a single /// preserved newline. is_single_preserved_newline: bool, @@ -460,6 +465,7 @@ impl<'a> GlyphStore { pub fn new( length: usize, is_whitespace: bool, + ends_with_whitespace: bool, is_single_preserved_newline: bool, is_rtl: bool, ) -> GlyphStore { @@ -472,6 +478,7 @@ impl<'a> GlyphStore { total_word_separators: 0, has_detailed_glyphs: false, is_whitespace, + ends_with_whitespace, is_single_preserved_newline, is_rtl, } @@ -492,6 +499,11 @@ impl<'a> GlyphStore { self.is_whitespace } + #[inline] + pub fn ends_with_whitespace(&self) -> bool { + self.ends_with_whitespace + } + #[inline] pub fn total_word_separators(&self) -> usize { self.total_word_separators diff --git a/components/layout_2020/flow/inline/mod.rs b/components/layout_2020/flow/inline/mod.rs index 8653a3dff8c..22fc1a84173 100644 --- a/components/layout_2020/flow/inline/mod.rs +++ b/components/layout_2020/flow/inline/mod.rs @@ -2275,11 +2275,14 @@ struct ContentSizesComputation<'layout_data> { containing_block: &'layout_data IndefiniteContainingBlock<'layout_data>, paragraph: ContentSizes, current_line: ContentSizes, - /// Size for whitepsace pending to be added to this line. - pending_whitespace: Au, + /// Size for whitespace pending to be added to this line. + pending_whitespace: ContentSizes, + /// Whether or not the current line has seen any content (excluding collapsed whitespace), + /// when sizing under a min-content constraint. + had_content_yet_for_min_content: bool, /// Whether or not the current line has seen any content (excluding collapsed whitespace), /// when sizing under a max-content constraint. - had_content_yet: bool, + had_content_yet_for_max_content: bool, /// Stack of ending padding, margin, and border to add to the length /// when an inline box finishes. ending_inline_pbm_stack: Vec, @@ -2345,6 +2348,8 @@ impl<'layout_data> ContentSizesComputation<'layout_data> { for run in segment.runs.iter() { let advance = run.glyph_store.total_advance(); + let style_text = text_run.parent_style.get_inherited_text(); + let can_wrap = style_text.text_wrap_mode == TextWrapMode::Wrap; if run.glyph_store.is_whitespace() { // If this run is a forced line break, we *must* break the line @@ -2354,31 +2359,38 @@ impl<'layout_data> ContentSizesComputation<'layout_data> { self.current_line = ContentSizes::zero(); continue; } - - let style_text = text_run.parent_style.get_inherited_text(); - if style_text.white_space_collapse != WhiteSpaceCollapse::Preserve { - // TODO: need to handle TextWrapMode::Nowrap. - self.line_break_opportunity(); - // Discard any leading whitespace in the line. This will always be trimmed. - if self.had_content_yet { - // Wait to take into account other whitespace until we see more content. - // Whitespace at the end of the line will always be trimmed. - self.pending_whitespace += advance; + if !matches!( + style_text.white_space_collapse, + WhiteSpaceCollapse::Preserve | WhiteSpaceCollapse::BreakSpaces + ) { + if can_wrap { + self.line_break_opportunity(); + } else if self.had_content_yet_for_min_content { + self.pending_whitespace.min_content += advance; + } + if self.had_content_yet_for_max_content { + self.pending_whitespace.max_content += advance; } continue; } - if style_text.text_wrap_mode == TextWrapMode::Wrap { + if can_wrap { + self.pending_whitespace.max_content += advance; self.commit_pending_whitespace(); self.line_break_opportunity(); - self.current_line.max_content += advance; - self.had_content_yet = true; continue; } } self.commit_pending_whitespace(); self.add_inline_size(advance); - self.had_content_yet = true; + + // Typically whitespace glyphs are placed in a separate store, + // but for `white-space: break-spaces` we place the first whitespace + // with the preceding text. That prevents a line break before that + // first space, but we still need to allow a line break after it. + if can_wrap && run.glyph_store.ends_with_whitespace() { + self.line_break_opportunity(); + } } } }, @@ -2405,7 +2417,6 @@ impl<'layout_data> ContentSizesComputation<'layout_data> { self.commit_pending_whitespace(); self.current_line += outer; - self.had_content_yet = true; }, _ => {}, } @@ -2417,9 +2428,14 @@ impl<'layout_data> ContentSizesComputation<'layout_data> { } fn line_break_opportunity(&mut self) { + // Clear the pending whitespace, assuming that at the end of the line + // it needs to either hang or be removed. If that isn't the case, + // `commit_pending_whitespace()` should be called first. + self.pending_whitespace.min_content = Au::zero(); self.paragraph.min_content = std::cmp::max(self.paragraph.min_content, self.current_line.min_content); self.current_line.min_content = Au::zero(); + self.had_content_yet_for_min_content = false; } fn forced_line_break(&mut self) { @@ -2427,15 +2443,14 @@ impl<'layout_data> ContentSizesComputation<'layout_data> { self.paragraph.max_content = std::cmp::max(self.paragraph.max_content, self.current_line.max_content); self.current_line.max_content = Au::zero(); - self.had_content_yet = false; + self.had_content_yet_for_min_content = false; + self.had_content_yet_for_max_content = false; } fn commit_pending_whitespace(&mut self) { - // Only add the pending whitespace to the max-content size, because for the min-content - // we should wrap lines wherever is possible, so wrappable spaces shouldn't increase - // the length of the line (they will just be removed or hang at the end of the line). - self.current_line.max_content += self.pending_whitespace; - self.pending_whitespace = Au::zero(); + self.current_line += mem::take(&mut self.pending_whitespace); + self.had_content_yet_for_min_content = true; + self.had_content_yet_for_max_content = true; } /// Compute the [`ContentSizes`] of the given [`InlineFormattingContext`]. @@ -2449,8 +2464,9 @@ impl<'layout_data> ContentSizesComputation<'layout_data> { containing_block, paragraph: ContentSizes::zero(), current_line: ContentSizes::zero(), - pending_whitespace: Au::zero(), - had_content_yet: false, + pending_whitespace: ContentSizes::zero(), + had_content_yet_for_min_content: false, + had_content_yet_for_max_content: false, ending_inline_pbm_stack: Vec::new(), } .traverse(inline_formatting_context) diff --git a/components/layout_2020/flow/inline/text_run.rs b/components/layout_2020/flow/inline/text_run.rs index e9b9385d23f..250a7591248 100644 --- a/components/layout_2020/flow/inline/text_run.rs +++ b/components/layout_2020/flow/inline/text_run.rs @@ -217,6 +217,8 @@ impl TextRunSegment { continue; } + let mut options = *shaping_options; + // Extend the slice to the next UAX#14 line break opportunity. let mut slice = last_slice.end..*break_index; let word = &formatting_context_text[slice.clone()]; @@ -247,6 +249,9 @@ impl TextRunSegment { !can_break_anywhere { whitespace.start += first_white_space_character.len_utf8(); + options + .flags + .insert(ShapingFlags::ENDS_WITH_WHITESPACE_SHAPING_FLAG); } slice.end = whitespace.start; @@ -267,17 +272,17 @@ impl TextRunSegment { // Push the non-whitespace part of the range. if !slice.is_empty() { - self.shape_and_push_range(&slice, formatting_context_text, &font, shaping_options); + self.shape_and_push_range(&slice, formatting_context_text, &font, &options); } if whitespace.is_empty() { continue; } - let mut options = *shaping_options; - options - .flags - .insert(ShapingFlags::IS_WHITESPACE_SHAPING_FLAG); + options.flags.insert( + ShapingFlags::IS_WHITESPACE_SHAPING_FLAG | + ShapingFlags::ENDS_WITH_WHITESPACE_SHAPING_FLAG, + ); // If `white-space-collapse: break-spaces` is active, insert a line breaking opportunity // between each white space character in the white space that we trimmed off. diff --git a/tests/wpt/meta/MANIFEST.json b/tests/wpt/meta/MANIFEST.json index b202969a1d3..75343a4913c 100644 --- a/tests/wpt/meta/MANIFEST.json +++ b/tests/wpt/meta/MANIFEST.json @@ -566837,6 +566837,13 @@ null, {} ] + ], + "white-space-intrinsic-size-021.html": [ + "a209f72c30a5a48ed4c185f9d3962abeb68399fb", + [ + null, + {} + ] ] }, "word-break": { diff --git a/tests/wpt/meta/css/css-flexbox/flexbox_flex-formatting-interop.html.ini b/tests/wpt/meta/css/css-flexbox/flexbox_flex-formatting-interop.html.ini new file mode 100644 index 00000000000..0a5916356e7 --- /dev/null +++ b/tests/wpt/meta/css/css-flexbox/flexbox_flex-formatting-interop.html.ini @@ -0,0 +1,2 @@ +[flexbox_flex-formatting-interop.html] + expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/tentative/table-quirks.html.ini b/tests/wpt/meta/css/css-tables/tentative/table-quirks.html.ini index 89ebe4215c5..29dac1be5be 100644 --- a/tests/wpt/meta/css/css-tables/tentative/table-quirks.html.ini +++ b/tests/wpt/meta/css/css-tables/tentative/table-quirks.html.ini @@ -2,8 +2,5 @@ [table 1] expected: FAIL - [table 3] - expected: FAIL - [table 6] expected: FAIL diff --git a/tests/wpt/meta/css/css-text/white-space/white-space-intrinsic-size-002.html.ini b/tests/wpt/meta/css/css-text/white-space/white-space-intrinsic-size-002.html.ini deleted file mode 100644 index bddf7af6aae..00000000000 --- a/tests/wpt/meta/css/css-text/white-space/white-space-intrinsic-size-002.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[white-space-intrinsic-size-002.html] - expected: FAIL diff --git a/tests/wpt/mozilla/meta/css/white_space_intrinsic_sizes_a.html.ini b/tests/wpt/mozilla/meta/css/white_space_intrinsic_sizes_a.html.ini deleted file mode 100644 index 1330b71867f..00000000000 --- a/tests/wpt/mozilla/meta/css/white_space_intrinsic_sizes_a.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[white_space_intrinsic_sizes_a.html] - expected: FAIL diff --git a/tests/wpt/tests/css/css-text/white-space/white-space-intrinsic-size-021.html b/tests/wpt/tests/css/css-text/white-space/white-space-intrinsic-size-021.html new file mode 100644 index 00000000000..a209f72c30a --- /dev/null +++ b/tests/wpt/tests/css/css-text/white-space/white-space-intrinsic-size-021.html @@ -0,0 +1,266 @@ + + +CSS Text level 4 Test: intrinsic sizes of block containers with various 'white-space' values + + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
+
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
+
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
+
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
+
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
+
+
X É
+
X É
+
X É
+
X É
+
X É
+
X É
+
X É
+
X É
+
+
+
X É
+
X É
+
X É
+
X É
+
X É
+
X É
+
X É
+
X É
+
+ +
+ +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
+
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
+
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
+
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
+
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
X
+
+
+
X É
+
X É
+
X É
+
X É
+
X É
+
X É
+
X É
+
X É
+
+
+
X É
+
X É
+
X É
+
X É
+
X É
+
X É
+
X É
+
X É
+
+ +
+ + + + +