Turn white-space into a shorthand (#32146)

Bumps Stylo to servo/stylo#37

`white-space` is split into `white-space-collapse` and `text-wrap-mode`:

| white-space | white-space-collapse | text-wrap-mode |
| ----------- | -------------------- | -------------- |
| normal      | collapse             | wrap           |
| nowrap      | collapse             | nowrap         |
| pre-wrap    | preserve             | wrap           |
| pre         | preserve             | nowrap         |
| pre-line    | preserve-breaks      | wrap           |
| -           | preserve-breaks      | nowrap         |

Note this introduces a combination that wasn't previously possible,
but I think the existing logic can handle it well enough.

The old `allow_wrap()` is replaced by checking whether `text-wrap-mode`
is set to `wrap`.

The old `preserve_newlines()` is replaced by checking whether
`white-space-collapse` is *not* set to `collapse`.

The old `preserve_spaces()` is replaced by checking whether
`white-space-collapse` is set to `preserve`.
This commit is contained in:
Oriol Brufau 2024-04-29 12:40:44 +02:00 committed by GitHub
parent a1f8c19355
commit d490fdf83c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 141 additions and 454 deletions

View file

@ -77,10 +77,12 @@ use gfx::font::FontMetrics;
use gfx::text::glyph::GlyphStore;
use serde::Serialize;
use servo_arc::Arc;
use style::computed_values::text_wrap_mode::T as TextWrapMode;
use style::computed_values::vertical_align::T as VerticalAlign;
use style::computed_values::white_space::T as WhiteSpace;
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
use style::context::QuirksMode;
use style::logical_geometry::WritingMode;
use style::properties::style_structs::InheritedText;
use style::properties::ComputedValues;
use style::values::computed::{Clear, Length};
use style::values::generics::box_::VerticalAlignKeyword;
@ -659,11 +661,17 @@ pub(super) struct InlineFormattingContextState<'a, 'b> {
/// Whether or not this InlineFormattingContext has processed any in flow content at all.
had_inflow_content: bool,
/// The currently white-space setting of this line. This is stored on the
/// The currently white-space-collapse setting of this line. This is stored on the
/// [`InlineFormattingContextState`] because when a soft wrap opportunity is defined
/// by the boundary between two characters, the white-space property of their nearest
/// by the boundary between two characters, the white-space-collapse property of their
/// nearest common ancestor is used.
white_space_collapse: WhiteSpaceCollapse,
/// The currently text-wrap-mode setting of this line. This is stored on the
/// [`InlineFormattingContextState`] because when a soft wrap opportunity is defined
/// by the boundary between two characters, the text-wrap-mode property of their nearest
/// common ancestor is used.
white_space: WhiteSpace,
text_wrap_mode: TextWrapMode,
/// The offset of the first and last baselines in the inline formatting context that we
/// are laying out. This is used to propagate baselines to the ancestors of
@ -697,7 +705,9 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
Some(inline_box_state) => &inline_box_state.base.style,
None => self.containing_block.style,
};
self.white_space = style.get_inherited_text().white_space;
let style_text = style.get_inherited_text();
self.white_space_collapse = style_text.white_space_collapse;
self.text_wrap_mode = style_text.text_wrap_mode;
}
fn processing_br_element(&self) -> bool {
@ -1286,7 +1296,7 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
) {
let inline_advance = Length::from(glyph_store.total_advance());
let flags = if glyph_store.is_whitespace() {
SegmentContentFlags::from(text_run.parent_style.get_inherited_text().white_space)
SegmentContentFlags::from(text_run.parent_style.get_inherited_text())
} else {
SegmentContentFlags::empty()
};
@ -1394,7 +1404,7 @@ impl<'a, 'b> InlineFormattingContextState<'a, 'b> {
if self.current_line_segment.line_items.is_empty() {
return;
}
if !self.white_space.allow_wrap() {
if self.text_wrap_mode == TextWrapMode::Nowrap {
return;
}
@ -1498,13 +1508,13 @@ impl SegmentContentFlags {
}
}
impl From<WhiteSpace> for SegmentContentFlags {
fn from(white_space: WhiteSpace) -> Self {
impl From<&InheritedText> for SegmentContentFlags {
fn from(style_text: &InheritedText) -> Self {
let mut flags = Self::empty();
if !white_space.preserve_spaces() {
if style_text.white_space_collapse != WhiteSpaceCollapse::Preserve {
flags.insert(Self::COLLAPSIBLE_WHITESPACE);
}
if white_space.allow_wrap() {
if style_text.text_wrap_mode == TextWrapMode::Wrap {
flags.insert(Self::WRAPPABLE_WHITESPACE);
}
flags
@ -1633,6 +1643,7 @@ impl InlineFormattingContext {
.map(|font| font.borrow().metrics.clone())
});
let style_text = containing_block.style.get_inherited_text();
let mut ifc = InlineFormattingContextState {
positioning_context,
containing_block,
@ -1658,7 +1669,8 @@ impl InlineFormattingContext {
have_deferred_soft_wrap_opportunity: false,
prevent_soft_wrap_opportunity_before_next_atomic: false,
had_inflow_content: false,
white_space: containing_block.style.get_inherited_text().white_space,
white_space_collapse: style_text.white_space_collapse,
text_wrap_mode: style_text.text_wrap_mode,
baselines: Baselines::default(),
};
@ -2143,7 +2155,7 @@ impl IndependentFormattingContext {
&mut ifc.prevent_soft_wrap_opportunity_before_next_atomic,
false,
);
if ifc.white_space.allow_wrap() && !soft_wrap_opportunity_prevented {
if ifc.text_wrap_mode == TextWrapMode::Wrap && !soft_wrap_opportunity_prevented {
ifc.process_soft_wrap_opportunity();
}
@ -2403,10 +2415,9 @@ impl<'a> ContentSizesComputation<'a> {
continue;
}
let white_space =
text_run.parent_style.get_inherited_text().white_space;
if !white_space.preserve_spaces() {
// TODO: need to handle !white_space.allow_wrap().
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 {
@ -2416,7 +2427,7 @@ impl<'a> ContentSizesComputation<'a> {
}
continue;
}
if white_space.allow_wrap() {
if style_text.text_wrap_mode == TextWrapMode::Wrap {
self.commit_pending_whitespace();
self.line_break_opportunity();
self.current_line.max_content += advance;

View file

@ -9,6 +9,7 @@ use atomic_refcell::AtomicRef;
use gfx::font::FontMetrics;
use gfx::text::glyph::GlyphStore;
use servo_arc::Arc;
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
use style::properties::ComputedValues;
use style::values::computed::Length;
use style::values::generics::box_::{GenericVerticalAlign, VerticalAlignKeyword};
@ -151,11 +152,8 @@ pub(super) struct TextRunLineItem {
impl TextRunLineItem {
fn trim_whitespace_at_end(&mut self, whitespace_trimmed: &mut Length) -> bool {
if self
.parent_style
.get_inherited_text()
.white_space
.preserve_spaces()
if self.parent_style.get_inherited_text().white_space_collapse ==
WhiteSpaceCollapse::Preserve
{
return false;
}
@ -179,11 +177,8 @@ impl TextRunLineItem {
}
fn trim_whitespace_at_start(&mut self, whitespace_trimmed: &mut Length) -> bool {
if self
.parent_style
.get_inherited_text()
.white_space
.preserve_spaces()
if self.parent_style.get_inherited_text().white_space_collapse ==
WhiteSpaceCollapse::Preserve
{
return false;
}

View file

@ -16,7 +16,7 @@ use range::Range;
use serde::Serialize;
use servo_arc::Arc;
use style::computed_values::text_rendering::T as TextRendering;
use style::computed_values::white_space::T as WhiteSpace;
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
use style::computed_values::word_break::T as WordBreak;
use style::properties::ComputedValues;
use style::values::specified::text::TextTransformCase;
@ -189,8 +189,8 @@ impl TextRun {
/// Whether or not this [`TextRun`] has uncollapsible content. This is used
/// to determine if an [`super::InlineFormattingContext`] is considered empty or not.
pub(super) fn has_uncollapsible_content(&self) -> bool {
let white_space = self.parent_style.clone_white_space();
if white_space.preserve_spaces() && !self.text.is_empty() {
let white_space_collapse = self.parent_style.clone_white_space_collapse();
if white_space_collapse == WhiteSpaceCollapse::Preserve && !self.text.is_empty() {
return true;
}
@ -198,7 +198,7 @@ impl TextRun {
if !character.is_ascii_whitespace() {
return true;
}
if character == '\n' && white_space.preserve_newlines() {
if character == '\n' && white_space_collapse != WhiteSpaceCollapse::Collapse {
return true;
}
}
@ -291,10 +291,10 @@ impl TextRun {
// TODO: Eventually the text should come directly from the Cow strings of the DOM nodes.
let text = std::mem::take(&mut self.text);
let white_space = self.parent_style.clone_white_space();
let white_space_collapse = self.parent_style.clone_white_space_collapse();
let collapsed = WhitespaceCollapse::new(
text.as_str().chars(),
white_space,
white_space_collapse,
*last_inline_box_ended_with_collapsible_white_space,
);
@ -327,7 +327,7 @@ impl TextRun {
*on_word_boundary = character.is_whitespace();
*last_inline_box_ended_with_collapsible_white_space =
*on_word_boundary && !white_space.preserve_spaces();
*on_word_boundary && white_space_collapse != WhiteSpaceCollapse::Preserve;
let prevents_soft_wrap_opportunity =
char_prevents_soft_wrap_opportunity_when_before_or_after_atomic(character);
@ -437,11 +437,8 @@ impl TextRun {
if !run.glyph_store.is_whitespace() || run.range.length() != ByteIndex(1) {
return false;
}
if !self
.parent_style
.get_inherited_text()
.white_space
.preserve_newlines()
if self.parent_style.get_inherited_text().white_space_collapse ==
WhiteSpaceCollapse::Collapse
{
return false;
}
@ -526,7 +523,7 @@ fn preserve_segment_break() -> bool {
pub struct WhitespaceCollapse<InputIterator> {
char_iterator: InputIterator,
white_space: WhiteSpace,
white_space_collapse: WhiteSpaceCollapse,
/// Whether or not we should collapse white space completely at the start of the string.
/// This is true when the last character handled in our owning [`super::InlineFormattingContext`]
@ -555,12 +552,12 @@ pub struct WhitespaceCollapse<InputIterator> {
impl<InputIterator> WhitespaceCollapse<InputIterator> {
pub fn new(
char_iterator: InputIterator,
white_space: WhiteSpace,
white_space_collapse: WhiteSpaceCollapse,
trim_beginning_white_space: bool,
) -> Self {
Self {
char_iterator,
white_space,
white_space_collapse,
remove_collapsible_white_space_at_start: trim_beginning_white_space,
inside_white_space: false,
following_newline: false,
@ -594,7 +591,7 @@ where
// > characters are considered collapsible
// If whitespace is not considered collapsible, it is preserved entirely, which
// means that we can simply return the input string exactly.
if self.white_space.preserve_spaces() {
if self.white_space_collapse == WhiteSpaceCollapse::Preserve {
return self.char_iterator.next();
}
@ -623,7 +620,7 @@ where
//
// > When white-space is pre, pre-wrap, or pre-line, segment breaks are not
// > collapsible and are instead transformed into a preserved line feed"
if self.white_space == WhiteSpace::PreLine {
if self.white_space_collapse != WhiteSpaceCollapse::Collapse {
self.inside_white_space = false;
self.following_newline = true;
return Some(character);

View file

@ -4,52 +4,60 @@
mod text {
use layout_2020::flow::text_run::WhitespaceCollapse;
use style::computed_values::white_space::T as WhiteSpace;
use style::computed_values::white_space_collapse::T as WhiteSpaceCollapse;
#[test]
fn test_collapse_whitespace() {
let collapse = |input: &str, white_space, trim_beginning_white_space| {
WhitespaceCollapse::new(input.chars(), white_space, trim_beginning_white_space)
.collect::<String>()
let collapse = |input: &str, white_space_collapse, trim_beginning_white_space| {
WhitespaceCollapse::new(
input.chars(),
white_space_collapse,
trim_beginning_white_space,
)
.collect::<String>()
};
let output = collapse("H ", WhiteSpace::Normal, false);
let output = collapse("H ", WhiteSpaceCollapse::Collapse, false);
assert_eq!(output, "H ");
let output = collapse(" W", WhiteSpace::Normal, true);
let output = collapse(" W", WhiteSpaceCollapse::Collapse, true);
assert_eq!(output, "W");
let output = collapse(" W", WhiteSpace::Normal, false);
let output = collapse(" W", WhiteSpaceCollapse::Collapse, false);
assert_eq!(output, " W");
let output = collapse(" H W", WhiteSpace::Normal, false);
let output = collapse(" H W", WhiteSpaceCollapse::Collapse, false);
assert_eq!(output, " H W");
let output = collapse("\n H \n \t W", WhiteSpace::Normal, false);
let output = collapse("\n H \n \t W", WhiteSpaceCollapse::Collapse, false);
assert_eq!(output, " H W");
let output = collapse("\n H \n \t W \n", WhiteSpace::Pre, false);
let output = collapse("\n H \n \t W \n", WhiteSpaceCollapse::Preserve, false);
assert_eq!(output, "\n H \n \t W \n");
let output = collapse("\n H \n \t W \n ", WhiteSpace::PreLine, false);
let output = collapse(
"\n H \n \t W \n ",
WhiteSpaceCollapse::PreserveBreaks,
false,
);
assert_eq!(output, "\nH\nW\n");
let output = collapse("Hello \n World", WhiteSpace::PreLine, true);
let output = collapse("Hello \n World", WhiteSpaceCollapse::PreserveBreaks, true);
assert_eq!(output, "Hello\nWorld");
let output = collapse(" \n World", WhiteSpace::PreLine, true);
let output = collapse(" \n World", WhiteSpaceCollapse::PreserveBreaks, true);
assert_eq!(output, "\nWorld");
let output = collapse(" ", WhiteSpace::Normal, true);
let output = collapse(" ", WhiteSpaceCollapse::Collapse, true);
assert_eq!(output, "");
let output = collapse(" ", WhiteSpace::Normal, false);
let output = collapse(" ", WhiteSpaceCollapse::Collapse, false);
assert_eq!(output, " ");
let output = collapse("\n ", WhiteSpace::Normal, true);
let output = collapse("\n ", WhiteSpaceCollapse::Collapse, true);
assert_eq!(output, "");
let output = collapse("\n ", WhiteSpace::Normal, false);
let output = collapse("\n ", WhiteSpaceCollapse::Collapse, false);
assert_eq!(output, " ");
}
}