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>,
}
#[derive(Clone, Copy, Debug)]
#[derive(Clone, Copy, Debug, Default)]
pub struct LogicalSides<T> {
pub inline_start: T,
pub inline_end: T,

View file

@ -45,11 +45,12 @@ use crate::{
};
fn detailed_layout_info(
border_style_color: Option<PhysicalSides<BorderStyleColor>>,
border_style_color: Option<LogicalSides<BorderStyleColor>>,
writing_mode: WritingMode,
) -> Option<SpecificLayoutInfo> {
Some(SpecificLayoutInfo::TableOrTableCell(Box::new(
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) {
if *self < other {
*self = other;
fn max_assign(&mut self, other: &Self) {
if *self < *other {
*self = other.clone();
}
}
}
@ -172,13 +173,25 @@ impl PartialOrd for CollapsedBorder {
impl Eq for CollapsedBorder {}
/// The calculated collapsed borders.
/// Represents a piecewise sequence of collapsed borders along a line.
#[derive(Clone, Debug, Default)]
struct CollapsedBorders {
block: Vec<CollapsedBorder>,
inline: Vec<CollapsedBorder>,
struct CollapsedBorderLine {
max_width: Au,
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
/// of a table into the fragment tree version. This implements
/// <https://drafts.csswg.org/css-tables/#table-layout-algorithm>
@ -279,14 +292,13 @@ impl<'a> TableLayout<'a> {
.percentages_relative_to(Au::zero());
let border = self
.get_collapsed_borders_for_cell(
cell,
TableSlotCoordinates::new(column_index, row_index),
)
.map_or_else(
|| cell.base.style.border_width(writing_mode),
|(border, _)| border,
);
.get_collapsed_border_widths_for_area(LogicalSides {
inline_start: column_index,
inline_end: column_index + cell.colspan,
block_start: row_index,
block_end: row_index + cell.rowspan,
})
.unwrap_or_else(|| cell.base.style.border_width(writing_mode));
let padding_border_sums = LogicalVec2 {
inline: padding.inline_sum() + border.inline_sum(),
@ -1195,22 +1207,23 @@ impl<'a> TableLayout<'a> {
return None;
};
let coordinates = TableSlotCoordinates::new(column_index, row_index);
let (border, detailed_layout_info) =
match self.get_collapsed_borders_for_cell(cell, coordinates) {
Some((border_width, border_style_color)) => {
let border_style_color = border_style_color
.to_physical(self.table.style.writing_mode);
(border_width, detailed_layout_info(Some(border_style_color)))
},
None => (
cell.base.style.border_width(
containing_block_for_table.style.writing_mode,
),
None,
),
};
let area = LogicalSides {
inline_start: column_index,
inline_end: column_index + cell.colspan,
block_start: row_index,
block_end: row_index + cell.rowspan,
};
let detailed_layout_info = detailed_layout_info(
self.get_collapsed_border_style_colors_for_area(area),
self.table.style.writing_mode,
);
let border = self
.get_collapsed_border_widths_for_area(area)
.unwrap_or_else(|| {
cell.base
.style
.border_width(containing_block_for_table.style.writing_mode)
});
let padding: LogicalSides<Au> = cell
.base
.style
@ -1861,20 +1874,13 @@ impl<'a> TableLayout<'a> {
assert_eq!(self.table.size.height, self.row_sizes.len());
assert_eq!(self.table.size.width, self.distributed_column_widths.len());
let border_style_color = self.collapsed_borders.as_ref().map(|collapsed_borders| {
LogicalSides {
inline_start: collapsed_borders.inline[0].style_color.clone(),
inline_end: collapsed_borders.inline[self.table.size.width]
.style_color
.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 border_style_color = self.get_collapsed_border_style_colors_for_area(LogicalSides {
inline_start: 0,
inline_end: self.table.size.width,
block_start: 0,
block_end: self.table.size.height,
});
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 {
let content_rect = LogicalRect {
@ -2211,17 +2217,29 @@ impl<'a> TableLayout<'a> {
}
let mut collapsed_borders = CollapsedBorders {
block: vec![Default::default(); self.table.size.height + 1],
inline: vec![Default::default(); self.table.size.width + 1],
block: vec![
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 =
|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);
collapsed_borders.block[block.start].max_assign(&border.block_start, inline);
collapsed_borders.block[block.end].max_assign(&border.block_end, inline);
collapsed_borders.inline[inline.start].max_assign(&border.inline_start, block);
collapsed_borders.inline[inline.end].max_assign(&border.inline_end, block);
};
let all_rows = 0..self.table.size.height;
let all_columns = 0..self.table.size.width;
@ -2256,47 +2274,61 @@ impl<'a> TableLayout<'a> {
self.collapsed_borders = Some(collapsed_borders);
}
fn get_collapsed_borders_for_cell(
fn get_collapsed_border_widths_for_area(
&self,
cell: &TableSlotCell,
coordinates: TableSlotCoordinates,
) -> Option<(LogicalSides<Au>, LogicalSides<BorderStyleColor>)> {
area: LogicalSides<usize>,
) -> Option<LogicalSides<Au>> {
let collapsed_borders = self.collapsed_borders.as_ref()?;
let end_x = coordinates.x + cell.colspan;
let end_y = coordinates.y + cell.rowspan;
let inline_start = &collapsed_borders.inline[coordinates.x];
let inline_end = &collapsed_borders.inline[end_x];
let block_start = &collapsed_borders.block[coordinates.y];
let block_end = &collapsed_borders.block[end_y];
let border_width = LogicalSides {
inline_start: if coordinates.x == 0 {
inline_start.width - self.pbm.border.inline_start
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];
Some(LogicalSides {
inline_start: if area.inline_start == 0 {
inline_start.max_width - self.pbm.border.inline_start
} else {
inline_start.width / 2
inline_start.max_width / 2
},
inline_end: if end_x == self.table.size.width {
inline_end.width - self.pbm.border.inline_end
inline_end: if area.inline_end == self.table.size.width {
inline_end.max_width - self.pbm.border.inline_end
} else {
inline_end.width / 2
inline_end.max_width / 2
},
block_start: if coordinates.y == 0 {
block_start.width - self.pbm.border.block_start
block_start: if area.block_start == 0 {
block_start.max_width - self.pbm.border.block_start
} else {
block_start.width / 2
block_start.max_width / 2
},
block_end: if end_y == self.table.size.height {
block_end.width - self.pbm.border.block_end
block_end: if area.block_end == self.table.size.height {
block_end.max_width - self.pbm.border.block_end
} 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(),
block_start: block_start.style_color.clone(),
block_end: block_end.style_color.clone(),
};
Some((border_width, border_style_color))
})
}
fn get_collapsed_border_style_colors_for_area(
&self,
area: LogicalSides<usize>,
) -> Option<LogicalSides<BorderStyleColor>> {
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