layout: Allow different collapsed border style/color within a row/column (#35003)

We were previously using the same style and color for two collapsed
borders sharing a coordinate. Now such a line of collapsed borders can
be piecewise and have different colors and styles.

This still doesn't add support for piecewise border widths.

Also, since we are currently painting borders as part of the table and
cell boxes, and a box side can't have a piecewise border, this patch
only really works when:
 - There aren't spanning cells
 - The table has no assigned border (only the cells and tracks have it)

Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Oriol Brufau 2025-01-16 03:24:00 -08:00 committed by GitHub
parent a1326a7cf6
commit d58aa7fc04
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 116 additions and 100 deletions

View file

@ -40,7 +40,7 @@ pub struct LogicalRect<T> {
pub size: LogicalVec2<T>, pub size: LogicalVec2<T>,
} }
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug, Default)]
pub struct LogicalSides<T> { pub struct LogicalSides<T> {
pub inline_start: T, pub inline_start: T,
pub inline_end: T, pub inline_end: T,

View file

@ -45,11 +45,12 @@ use crate::{
}; };
fn detailed_layout_info( fn detailed_layout_info(
border_style_color: Option<PhysicalSides<BorderStyleColor>>, border_style_color: Option<LogicalSides<BorderStyleColor>>,
writing_mode: WritingMode,
) -> Option<SpecificLayoutInfo> { ) -> Option<SpecificLayoutInfo> {
Some(SpecificLayoutInfo::TableOrTableCell(Box::new( Some(SpecificLayoutInfo::TableOrTableCell(Box::new(
SpecificTableOrTableCellInfo { SpecificTableOrTableCellInfo {
border_style_color: border_style_color?, border_style_color: border_style_color?.to_physical(writing_mode),
}, },
))) )))
} }
@ -131,9 +132,9 @@ impl CollapsedBorder {
} }
} }
fn max_assign(&mut self, other: Self) { fn max_assign(&mut self, other: &Self) {
if *self < other { if *self < *other {
*self = other; *self = other.clone();
} }
} }
} }
@ -172,13 +173,25 @@ impl PartialOrd for CollapsedBorder {
impl Eq for CollapsedBorder {} impl Eq for CollapsedBorder {}
/// The calculated collapsed borders. /// Represents a piecewise sequence of collapsed borders along a line.
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug, Default)]
struct CollapsedBorders { struct CollapsedBorderLine {
block: Vec<CollapsedBorder>, max_width: Au,
inline: Vec<CollapsedBorder>, list: Vec<CollapsedBorder>,
} }
impl CollapsedBorderLine {
fn max_assign(&mut self, collapsed_border: &CollapsedBorder, range: &Range<usize>) {
self.max_width.max_assign(collapsed_border.width);
for index in range.clone() {
self.list[index].max_assign(collapsed_border)
}
}
}
/// The calculated collapsed borders.
type CollapsedBorders = LogicalVec2<Vec<CollapsedBorderLine>>;
/// A helper struct that performs the layout of the box tree version /// A helper struct that performs the layout of the box tree version
/// of a table into the fragment tree version. This implements /// of a table into the fragment tree version. This implements
/// <https://drafts.csswg.org/css-tables/#table-layout-algorithm> /// <https://drafts.csswg.org/css-tables/#table-layout-algorithm>
@ -279,14 +292,13 @@ impl<'a> TableLayout<'a> {
.percentages_relative_to(Au::zero()); .percentages_relative_to(Au::zero());
let border = self let border = self
.get_collapsed_borders_for_cell( .get_collapsed_border_widths_for_area(LogicalSides {
cell, inline_start: column_index,
TableSlotCoordinates::new(column_index, row_index), inline_end: column_index + cell.colspan,
) block_start: row_index,
.map_or_else( block_end: row_index + cell.rowspan,
|| cell.base.style.border_width(writing_mode), })
|(border, _)| border, .unwrap_or_else(|| cell.base.style.border_width(writing_mode));
);
let padding_border_sums = LogicalVec2 { let padding_border_sums = LogicalVec2 {
inline: padding.inline_sum() + border.inline_sum(), inline: padding.inline_sum() + border.inline_sum(),
@ -1195,22 +1207,23 @@ impl<'a> TableLayout<'a> {
return None; return None;
}; };
let coordinates = TableSlotCoordinates::new(column_index, row_index); let area = LogicalSides {
let (border, detailed_layout_info) = inline_start: column_index,
match self.get_collapsed_borders_for_cell(cell, coordinates) { inline_end: column_index + cell.colspan,
Some((border_width, border_style_color)) => { block_start: row_index,
let border_style_color = border_style_color block_end: row_index + cell.rowspan,
.to_physical(self.table.style.writing_mode); };
(border_width, detailed_layout_info(Some(border_style_color))) let detailed_layout_info = detailed_layout_info(
}, self.get_collapsed_border_style_colors_for_area(area),
None => ( self.table.style.writing_mode,
cell.base.style.border_width( );
containing_block_for_table.style.writing_mode, let border = self
), .get_collapsed_border_widths_for_area(area)
None, .unwrap_or_else(|| {
), cell.base
}; .style
.border_width(containing_block_for_table.style.writing_mode)
});
let padding: LogicalSides<Au> = cell let padding: LogicalSides<Au> = cell
.base .base
.style .style
@ -1861,20 +1874,13 @@ impl<'a> TableLayout<'a> {
assert_eq!(self.table.size.height, self.row_sizes.len()); assert_eq!(self.table.size.height, self.row_sizes.len());
assert_eq!(self.table.size.width, self.distributed_column_widths.len()); assert_eq!(self.table.size.width, self.distributed_column_widths.len());
let border_style_color = self.collapsed_borders.as_ref().map(|collapsed_borders| { let border_style_color = self.get_collapsed_border_style_colors_for_area(LogicalSides {
LogicalSides { inline_start: 0,
inline_start: collapsed_borders.inline[0].style_color.clone(), inline_end: self.table.size.width,
inline_end: collapsed_borders.inline[self.table.size.width] block_start: 0,
.style_color block_end: self.table.size.height,
.clone(),
block_start: collapsed_borders.block[0].style_color.clone(),
block_end: collapsed_borders.block[self.table.size.height]
.style_color
.clone(),
}
.to_physical(table_writing_mode)
}); });
let detailed_layout_info = detailed_layout_info(border_style_color); let detailed_layout_info = detailed_layout_info(border_style_color, table_writing_mode);
if self.table.size.width == 0 && self.table.size.height == 0 { if self.table.size.width == 0 && self.table.size.height == 0 {
let content_rect = LogicalRect { let content_rect = LogicalRect {
@ -2211,17 +2217,29 @@ impl<'a> TableLayout<'a> {
} }
let mut collapsed_borders = CollapsedBorders { let mut collapsed_borders = CollapsedBorders {
block: vec![Default::default(); self.table.size.height + 1], block: vec![
inline: vec![Default::default(); self.table.size.width + 1], CollapsedBorderLine {
max_width: Au::zero(),
list: vec![Default::default(); self.table.size.width],
};
self.table.size.height + 1
],
inline: vec![
CollapsedBorderLine {
max_width: Au::zero(),
list: vec![Default::default(); self.table.size.height],
};
self.table.size.width + 1
],
}; };
let mut apply_border = let mut apply_border =
|style: &ComputedValues, block: &Range<usize>, inline: &Range<usize>| { |style: &ComputedValues, block: &Range<usize>, inline: &Range<usize>| {
let border = CollapsedBorder::from_style(style, writing_mode); let border = CollapsedBorder::from_style(style, writing_mode);
collapsed_borders.block[block.start].max_assign(border.block_start); collapsed_borders.block[block.start].max_assign(&border.block_start, inline);
collapsed_borders.block[block.end].max_assign(border.block_end); collapsed_borders.block[block.end].max_assign(&border.block_end, inline);
collapsed_borders.inline[inline.start].max_assign(border.inline_start); collapsed_borders.inline[inline.start].max_assign(&border.inline_start, block);
collapsed_borders.inline[inline.end].max_assign(border.inline_end); collapsed_borders.inline[inline.end].max_assign(&border.inline_end, block);
}; };
let all_rows = 0..self.table.size.height; let all_rows = 0..self.table.size.height;
let all_columns = 0..self.table.size.width; let all_columns = 0..self.table.size.width;
@ -2256,47 +2274,61 @@ impl<'a> TableLayout<'a> {
self.collapsed_borders = Some(collapsed_borders); self.collapsed_borders = Some(collapsed_borders);
} }
fn get_collapsed_borders_for_cell( fn get_collapsed_border_widths_for_area(
&self, &self,
cell: &TableSlotCell, area: LogicalSides<usize>,
coordinates: TableSlotCoordinates, ) -> Option<LogicalSides<Au>> {
) -> Option<(LogicalSides<Au>, LogicalSides<BorderStyleColor>)> {
let collapsed_borders = self.collapsed_borders.as_ref()?; let collapsed_borders = self.collapsed_borders.as_ref()?;
let end_x = coordinates.x + cell.colspan; let inline_start = &collapsed_borders.inline[area.inline_start];
let end_y = coordinates.y + cell.rowspan; let inline_end = &collapsed_borders.inline[area.inline_end];
let inline_start = &collapsed_borders.inline[coordinates.x]; let block_start = &collapsed_borders.block[area.block_start];
let inline_end = &collapsed_borders.inline[end_x]; let block_end = &collapsed_borders.block[area.block_end];
let block_start = &collapsed_borders.block[coordinates.y]; Some(LogicalSides {
let block_end = &collapsed_borders.block[end_y]; inline_start: if area.inline_start == 0 {
let border_width = LogicalSides { inline_start.max_width - self.pbm.border.inline_start
inline_start: if coordinates.x == 0 {
inline_start.width - self.pbm.border.inline_start
} else { } else {
inline_start.width / 2 inline_start.max_width / 2
}, },
inline_end: if end_x == self.table.size.width { inline_end: if area.inline_end == self.table.size.width {
inline_end.width - self.pbm.border.inline_end inline_end.max_width - self.pbm.border.inline_end
} else { } else {
inline_end.width / 2 inline_end.max_width / 2
}, },
block_start: if coordinates.y == 0 { block_start: if area.block_start == 0 {
block_start.width - self.pbm.border.block_start block_start.max_width - self.pbm.border.block_start
} else { } else {
block_start.width / 2 block_start.max_width / 2
}, },
block_end: if end_y == self.table.size.height { block_end: if area.block_end == self.table.size.height {
block_end.width - self.pbm.border.block_end block_end.max_width - self.pbm.border.block_end
} else { } else {
block_end.width / 2 block_end.max_width / 2
}, },
}; })
let border_style_color = LogicalSides { }
inline_start: inline_start.style_color.clone(),
inline_end: inline_end.style_color.clone(), fn get_collapsed_border_style_colors_for_area(
block_start: block_start.style_color.clone(), &self,
block_end: block_end.style_color.clone(), area: LogicalSides<usize>,
}; ) -> Option<LogicalSides<BorderStyleColor>> {
Some((border_width, border_style_color)) let collapsed_borders = self.collapsed_borders.as_ref()?;
if self.table.size.width == 0 || self.table.size.height == 0 {
return Some(LogicalSides::default());
}
let inline_start = &collapsed_borders.inline[area.inline_start];
let inline_end = &collapsed_borders.inline[area.inline_end];
let block_start = &collapsed_borders.block[area.block_start];
let block_end = &collapsed_borders.block[area.block_end];
// This area may span multiple rows and columns, each of which can have different
// collapsed borders. However, we don't have support for one side of a box to have
// a piecewise border. Therefore, we just pick the first piece for the entire side.
Some(LogicalSides {
inline_start: inline_start.list[area.block_start].style_color.clone(),
inline_end: inline_end.list[area.block_start].style_color.clone(),
block_start: block_start.list[area.inline_start].style_color.clone(),
block_end: block_end.list[area.inline_start].style_color.clone(),
})
} }
} }

View file

@ -1,2 +0,0 @@
[border-bottom-applies-to-006.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[border-bottom-color-applies-to-006.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[border-bottom-width-applies-to-006.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[border-top-applies-to-006.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[border-top-color-applies-to-006.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[border-top-width-applies-to-006.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[border-collapse-dynamic-cell-002.xht]
expected: FAIL

View file

@ -1,2 +0,0 @@
[collapsed-border-remove-cell.html]
expected: FAIL