diff options
author | Oriol Brufau <obrufau@igalia.com> | 2024-09-16 10:20:22 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-09-16 08:20:22 +0000 |
commit | b12cebd1ac3b2bc809e5ac69f708cf61b515590d (patch) | |
tree | 6fc8bae0f36ce8f267608cf76244689a280fe7a2 | |
parent | 679afe519591c3c36036154afb1e9b6d73ffa1ac (diff) | |
download | servo-b12cebd1ac3b2bc809e5ac69f708cf61b515590d.tar.gz servo-b12cebd1ac3b2bc809e5ac69f708cf61b515590d.zip |
Small improvements for table border collapse (#33452)
We were only collapsing the borders from adjacent cells. This patch also
handles the borders from rows, row groups, columns, and column groups.
Additionally, it takes the border style into account in order to decide
which border wins.
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
11 files changed, 130 insertions, 33 deletions
diff --git a/components/layout_2020/style_ext.rs b/components/layout_2020/style_ext.rs index 53bf9e6df33..21283395789 100644 --- a/components/layout_2020/style_ext.rs +++ b/components/layout_2020/style_ext.rs @@ -16,7 +16,9 @@ use style::properties::ComputedValues; use style::servo::selector_parser::PseudoElement; use style::values::computed::basic_shape::ClipPath; use style::values::computed::image::Image as ComputedImageLayer; -use style::values::computed::{AlignItems, LengthPercentage, NonNegativeLengthPercentage, Size}; +use style::values::computed::{ + AlignItems, BorderStyle, LengthPercentage, NonNegativeLengthPercentage, Size, +}; use style::values::generics::box_::Perspective; use style::values::generics::length::MaxSize; use style::values::generics::position::{GenericAspectRatio, PreferredRatio}; @@ -250,6 +252,8 @@ pub(crate) trait ComputedValuesExt { &self, containing_block_writing_mode: WritingMode, ) -> LogicalSides<&LengthPercentage>; + fn border_style(&self, containing_block_writing_mode: WritingMode) + -> LogicalSides<BorderStyle>; fn border_width(&self, containing_block_writing_mode: WritingMode) -> LogicalSides<Au>; fn margin( &self, @@ -553,6 +557,22 @@ impl ComputedValuesExt for ComputedValues { ) } + fn border_style( + &self, + containing_block_writing_mode: WritingMode, + ) -> LogicalSides<BorderStyle> { + let border = self.get_border(); + LogicalSides::from_physical( + &PhysicalSides::new( + border.border_top_style, + border.border_right_style, + border.border_bottom_style, + border.border_left_style, + ), + containing_block_writing_mode, + ) + } + fn border_width(&self, containing_block_writing_mode: WritingMode) -> LogicalSides<Au> { let border = self.get_border(); LogicalSides::from_physical( diff --git a/components/layout_2020/table/layout.rs b/components/layout_2020/table/layout.rs index e52bab12400..ed4aef17bf4 100644 --- a/components/layout_2020/table/layout.rs +++ b/components/layout_2020/table/layout.rs @@ -2,6 +2,7 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use core::cmp::Ordering; use std::ops::Range; use app_units::{Au, MAX_AU}; @@ -16,7 +17,9 @@ use style::computed_values::table_layout::T as TableLayoutMode; use style::computed_values::visibility::T as Visibility; use style::logical_geometry::WritingMode; use style::properties::ComputedValues; -use style::values::computed::{LengthPercentage as ComputedLengthPercentage, Percentage}; +use style::values::computed::{ + BorderStyle, LengthPercentage as ComputedLengthPercentage, Percentage, +}; use style::values::generics::box_::{GenericVerticalAlign as VerticalAlign, VerticalAlignKeyword}; use style::values::generics::length::GenericLengthPercentageOrAuto::{Auto, LengthPercentage}; use style::Zero; @@ -92,11 +95,79 @@ struct ColumnLayout { has_originating_cells: bool, } +/// A calculated collapsed border. +#[derive(Clone, Debug, Eq, PartialEq)] +struct CollapsedBorder { + style: BorderStyle, + width: Au, +} + +impl Default for CollapsedBorder { + fn default() -> Self { + Self::new(BorderStyle::None, Au::zero()) + } +} + +impl CollapsedBorder { + fn new(style: BorderStyle, width: Au) -> Self { + Self { style, width } + } + + fn from_style(style: &ComputedValues, writing_mode: WritingMode) -> LogicalSides<Self> { + let border_style = style.border_style(writing_mode); + let border_width = style.border_width(writing_mode); + LogicalSides { + inline_start: Self::new(border_style.inline_start, border_width.inline_start), + inline_end: Self::new(border_style.inline_end, border_width.inline_end), + block_start: Self::new(border_style.block_start, border_width.block_start), + block_end: Self::new(border_style.block_end, border_width.block_end), + } + } + + fn max_assign(&mut self, other: Self) { + if *self < other { + *self = other; + } + } +} + +/// <https://drafts.csswg.org/css-tables/#border-specificity> +/// > Given two borders styles, the border style having the most specificity is the border style which… +/// > 1. … has the value "hidden" as border-style, if only one does +/// > 2. … has the biggest border-width, once converted into css pixels +/// > 3. … has the border-style which comes first in the following list: +/// > double, solid, dashed, dotted, ridge, outset, groove, inset, none +impl Ord for CollapsedBorder { + fn cmp(&self, other: &Self) -> Ordering { + let style_specificity = |border: &Self| match border.style { + BorderStyle::None => 0, + BorderStyle::Inset => 1, + BorderStyle::Groove => 2, + BorderStyle::Outset => 3, + BorderStyle::Ridge => 4, + BorderStyle::Dotted => 5, + BorderStyle::Dashed => 6, + BorderStyle::Solid => 7, + BorderStyle::Double => 8, + BorderStyle::Hidden => 9, + }; + ((self.style == BorderStyle::Hidden).cmp(&(other.style == BorderStyle::Hidden))) + .then_with(|| self.width.cmp(&other.width)) + .then_with(|| style_specificity(self).cmp(&style_specificity(other))) + } +} + +impl PartialOrd for CollapsedBorder { + fn partial_cmp(&self, other: &Self) -> Option<Ordering> { + Some(self.cmp(other)) + } +} + /// The calculated collapsed borders. #[derive(Clone, Debug, Default)] struct CollapsedBorders { - block: Vec<Au>, - inline: Vec<Au>, + block: Vec<CollapsedBorder>, + inline: Vec<CollapsedBorder>, } /// A helper struct that performs the layout of the box tree version @@ -2149,10 +2220,32 @@ impl<'a> TableLayout<'a> { } let mut collapsed_borders = CollapsedBorders { - block: vec![Au::zero(); self.table.size.height + 1], - inline: vec![Au::zero(); self.table.size.width + 1], + block: vec![Default::default(); self.table.size.height + 1], + inline: vec![Default::default(); self.table.size.width + 1], }; + let mut apply_border = + |style: &ComputedValues, block: &Range<usize>, inline: &Range<usize>| { + let border = CollapsedBorder::from_style(style, writing_mode); + collapsed_borders.block[block.start].max_assign(border.block_start); + collapsed_borders.block[block.end].max_assign(border.block_end); + collapsed_borders.inline[inline.start].max_assign(border.inline_start); + collapsed_borders.inline[inline.end].max_assign(border.inline_end); + }; + let all_rows = 0..self.table.size.height; + let all_columns = 0..self.table.size.width; + for column_group in &self.table.column_groups { + apply_border(&column_group.style, &all_rows, &column_group.track_range); + } + for (column_index, column) in self.table.columns.iter().enumerate() { + apply_border(&column.style, &all_rows, &(column_index..column_index + 1)); + } + for row_group in &self.table.row_groups { + apply_border(&row_group.style, &row_group.track_range, &all_columns); + } + for (row_index, row) in self.table.rows.iter().enumerate() { + apply_border(&row.style, &(row_index..row_index + 1), &all_columns); + } for row_index in 0..self.table.size.height { for column_index in 0..self.table.size.width { let cell = match self.table.slots[row_index][column_index] { @@ -2160,11 +2253,11 @@ impl<'a> TableLayout<'a> { _ => continue, }; - let border = cell.style.border_width(writing_mode); - collapsed_borders.block[row_index].max_assign(border.block_start); - collapsed_borders.block[row_index + cell.rowspan].max_assign(border.block_end); - collapsed_borders.inline[column_index].max_assign(border.inline_start); - collapsed_borders.inline[column_index + cell.colspan].max_assign(border.inline_end); + apply_border( + &cell.style, + &(row_index..row_index + cell.rowspan), + &(column_index..column_index + cell.colspan), + ); } } @@ -2180,10 +2273,10 @@ impl<'a> TableLayout<'a> { let end_x = coordinates.x + cell.colspan; let end_y = coordinates.y + cell.rowspan; let mut result = LogicalSides { - inline_start: collapsed_borders.inline[coordinates.x], - inline_end: collapsed_borders.inline[end_x], - block_start: collapsed_borders.block[coordinates.y], - block_end: collapsed_borders.block[end_y], + inline_start: collapsed_borders.inline[coordinates.x].width, + inline_end: collapsed_borders.inline[end_x].width, + block_start: collapsed_borders.block[coordinates.y].width, + block_end: collapsed_borders.block[end_y].width, }; if coordinates.x != 0 { diff --git a/tests/wpt/meta/css/CSS2/borders/border-conflict-style-101.xht.ini b/tests/wpt/meta/css/CSS2/borders/border-conflict-style-101.xht.ini deleted file mode 100644 index 1f31519bf1a..00000000000 --- a/tests/wpt/meta/css/CSS2/borders/border-conflict-style-101.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[border-conflict-style-101.xht] - expected: FAIL diff --git a/tests/wpt/meta/css/CSS2/borders/border-conflict-style-102.xht.ini b/tests/wpt/meta/css/CSS2/borders/border-conflict-style-102.xht.ini deleted file mode 100644 index c5c4965f0e5..00000000000 --- a/tests/wpt/meta/css/CSS2/borders/border-conflict-style-102.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[border-conflict-style-102.xht] - expected: FAIL diff --git a/tests/wpt/meta/css/CSS2/borders/border-conflict-style-103.xht.ini b/tests/wpt/meta/css/CSS2/borders/border-conflict-style-103.xht.ini deleted file mode 100644 index 1bf47e6123d..00000000000 --- a/tests/wpt/meta/css/CSS2/borders/border-conflict-style-103.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[border-conflict-style-103.xht] - expected: FAIL diff --git a/tests/wpt/meta/css/CSS2/borders/border-conflict-style-104.xht.ini b/tests/wpt/meta/css/CSS2/borders/border-conflict-style-104.xht.ini deleted file mode 100644 index 4228a8e88c8..00000000000 --- a/tests/wpt/meta/css/CSS2/borders/border-conflict-style-104.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[border-conflict-style-104.xht] - expected: FAIL diff --git a/tests/wpt/meta/css/CSS2/borders/border-conflict-style-105.xht.ini b/tests/wpt/meta/css/CSS2/borders/border-conflict-style-105.xht.ini deleted file mode 100644 index b1af0734d8f..00000000000 --- a/tests/wpt/meta/css/CSS2/borders/border-conflict-style-105.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[border-conflict-style-105.xht] - expected: FAIL diff --git a/tests/wpt/meta/css/CSS2/borders/border-conflict-style-106.xht.ini b/tests/wpt/meta/css/CSS2/borders/border-conflict-style-106.xht.ini deleted file mode 100644 index 5c16563b761..00000000000 --- a/tests/wpt/meta/css/CSS2/borders/border-conflict-style-106.xht.ini +++ /dev/null @@ -1,2 +0,0 @@ -[border-conflict-style-106.xht] - expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/border-writing-mode-dynamic-001.html.ini b/tests/wpt/meta/css/css-tables/border-writing-mode-dynamic-001.html.ini deleted file mode 100644 index bb496cd52d5..00000000000 --- a/tests/wpt/meta/css/css-tables/border-writing-mode-dynamic-001.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[border-writing-mode-dynamic-001.html] - [Table borders track writing mode changes] - expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/col_removal.html.ini b/tests/wpt/meta/css/css-tables/col_removal.html.ini deleted file mode 100644 index 2c3f82792b8..00000000000 --- a/tests/wpt/meta/css/css-tables/col_removal.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[col_removal.html] - [Table grid syncs after COL removal] - expected: FAIL diff --git a/tests/wpt/meta/css/css-tables/rules-groups.html.ini b/tests/wpt/meta/css/css-tables/rules-groups.html.ini new file mode 100644 index 00000000000..b8e00870260 --- /dev/null +++ b/tests/wpt/meta/css/css-tables/rules-groups.html.ini @@ -0,0 +1,2 @@ +[rules-groups.html] + expected: FAIL |