diff --git a/Userland/Libraries/LibWeb/Layout/LayoutState.h b/Userland/Libraries/LibWeb/Layout/LayoutState.h index 8d15abbe28..17d3601473 100644 --- a/Userland/Libraries/LibWeb/Layout/LayoutState.h +++ b/Userland/Libraries/LibWeb/Layout/LayoutState.h @@ -119,7 +119,7 @@ struct LayoutState { void add_floating_descendant(Box const& box) { m_floating_descendants.set(&box); } auto const& floating_descendants() const { return m_floating_descendants; } - void set_override_borders_data(Painting::BordersData const& override_borders_data) { m_override_borders_data = override_borders_data; } + void set_override_borders_data(Painting::PaintableBox::BordersDataWithElementKind const& override_borders_data) { m_override_borders_data = override_borders_data; } auto const& override_borders_data() const { return m_override_borders_data; } void set_table_cell_coordinates(Painting::PaintableBox::TableCellCoordinates const& table_cell_coordinates) { m_table_cell_coordinates = table_cell_coordinates; } @@ -146,7 +146,7 @@ struct LayoutState { HashTable> m_floating_descendants; - Optional m_override_borders_data; + Optional m_override_borders_data; Optional m_table_cell_coordinates; }; diff --git a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp index eb470c228f..fdfb5c7c5b 100644 --- a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp @@ -1182,7 +1182,8 @@ void TableFormattingContext::position_cell_boxes() bool TableFormattingContext::border_is_less_specific(const CSS::BorderData& a, const CSS::BorderData& b) { - // Implements criteria for steps 1, 2 and 3 of border conflict resolution algorithm. + // Implements criteria for steps 1, 2 and 3 of border conflict resolution algorithm, as described in + // https://www.w3.org/TR/CSS22/tables.html#border-conflict-resolution. static HashMap const line_style_score = { { CSS::LineStyle::Inset, 0 }, { CSS::LineStyle::Groove, 1 }, @@ -1194,6 +1195,8 @@ bool TableFormattingContext::border_is_less_specific(const CSS::BorderData& a, c { CSS::LineStyle::Double, 7 }, }; + // 1. Borders with the 'border-style' of 'hidden' take precedence over all other conflicting borders. Any border with this + // value suppresses all borders at this location. if (a.line_style == CSS::LineStyle::Hidden) { return false; } @@ -1202,12 +1205,17 @@ bool TableFormattingContext::border_is_less_specific(const CSS::BorderData& a, c return true; } + // 2. Borders with a style of 'none' have the lowest priority. Only if the border properties of all the elements meeting + // at this edge are 'none' will the border be omitted (but note that 'none' is the default value for the border style.) if (a.line_style == CSS::LineStyle::None) { return true; } if (b.line_style == CSS::LineStyle::None) { return false; } + // 3. If none of the styles are 'hidden' and at least one of them is not 'none', then narrow borders are discarded in favor + // of wider ones. If several have the same 'border-width' then styles are preferred in this order: 'double', 'solid', + // 'dashed', 'dotted', 'ridge', 'outset', 'groove', and the lowest: 'inset'. if (a.width > b.width) { return false; } else if (a.width < b.width) { @@ -1221,11 +1229,6 @@ bool TableFormattingContext::border_is_less_specific(const CSS::BorderData& a, c return false; } -static const CSS::BorderData& winning_border_style(const CSS::BorderData& a, const CSS::BorderData& b) -{ - return TableFormattingContext::border_is_less_specific(a, b) ? b : a; -} - const CSS::BorderData& TableFormattingContext::border_data_conflicting_edge(TableFormattingContext::ConflictingEdge const& conflicting_edge) { auto const& style = conflicting_edge.element->computed_values(); @@ -1248,10 +1251,52 @@ const CSS::BorderData& TableFormattingContext::border_data_conflicting_edge(Tabl } } +const Painting::PaintableBox::BorderDataWithElementKind TableFormattingContext::border_data_with_element_kind_from_conflicting_edge(ConflictingEdge const& conflicting_edge) +{ + auto const& border_data = border_data_conflicting_edge(conflicting_edge); + return { .border_data = border_data, .element_kind = conflicting_edge.element_kind }; +} + +TableFormattingContext::ConflictingEdge const& TableFormattingContext::winning_conflicting_edge(TableFormattingContext::ConflictingEdge const& a, TableFormattingContext::ConflictingEdge const& b) +{ + auto a_border_data = border_data_conflicting_edge(a); + auto b_border_data = border_data_conflicting_edge(b); + // First check if step 4 of border conflict resolution applies, as described in https://www.w3.org/TR/CSS22/tables.html#border-conflict-resolution. + if (a_border_data.line_style == b_border_data.line_style && a_border_data.width == b_border_data.width) { + // 4. If border styles differ only in color, then a style set on a cell wins over one on a row, which wins over a + // row group, column, column group and, lastly, table. When two elements of the same type conflict, then the one + // further to the left (if the table's 'direction' is 'ltr'; right, if it is 'rtl') and further to the top wins. + if (static_cast(a.element_kind) < static_cast(b.element_kind)) { + return a; + } else if (static_cast(a.element_kind) > static_cast(b.element_kind)) { + return b; + } + // Here the element kind is the same, thus the coordinates are either both set or not set. + VERIFY(a.column.has_value() == b.column.has_value()); + VERIFY(a.row.has_value() == b.row.has_value()); + if (a.column.has_value()) { + if (a.column.value() < b.column.value()) { + return a; + } else if (a.column.value() > b.column.value()) { + return b; + } + } + if (a.row.has_value()) { + if (a.row.value() < b.row.value()) { + return a; + } else if (a.row.value() > b.row.value()) { + return b; + } + } + return a; + } + // Apply steps 1, 2 and 3 of the border conflict resolution algorithm. + return border_is_less_specific(a_border_data, b_border_data) ? b : a; +} + void TableFormattingContext::border_conflict_resolution() { - // Partially implements border conflict resolution, as described in - // https://www.w3.org/TR/CSS22/tables.html#border-conflict-resolution + // Implements border conflict resolution, as described in https://www.w3.org/TR/CSS22/tables.html#border-conflict-resolution. BorderConflictFinder finder(this); for (auto& cell : m_cells) { auto& cell_state = m_state.get_mutable(cell.box); @@ -1264,36 +1309,55 @@ void TableFormattingContext::border_conflict_resolution() if (cell.box->computed_values().border_collapse() == CSS::BorderCollapse::Separate) { continue; } - // Execute steps 1, 2 and 3 of the algorithm for each edge. - Painting::BordersData override_borders_data; - auto const& cell_style = cell.box->computed_values(); - auto winning_border_left = cell_style.border_left(); - for (auto const conflicting_edge : finder.conflicting_edges(cell, ConflictingSide::Left)) { - winning_border_left = winning_border_style(winning_border_left, border_data_conflicting_edge(conflicting_edge)); + Painting::PaintableBox::BordersDataWithElementKind override_borders_data; + ConflictingEdge winning_edge_left { + .element = cell.box, + .element_kind = Painting::PaintableBox::ConflictingElementKind::Cell, + .side = ConflictingSide::Left, + .row = cell.row_index, + .column = cell.column_index, + }; + for (auto const& conflicting_edge : finder.conflicting_edges(cell, ConflictingSide::Left)) { + winning_edge_left = winning_conflicting_edge(winning_edge_left, conflicting_edge); } - override_borders_data.left = winning_border_left; - cell_state.border_left = winning_border_left.width; - auto winning_border_right = cell_style.border_right(); - for (auto const conflicting_edge : finder.conflicting_edges(cell, ConflictingSide::Right)) { - winning_border_right = winning_border_style(winning_border_right, border_data_conflicting_edge(conflicting_edge)); + override_borders_data.left = border_data_with_element_kind_from_conflicting_edge(winning_edge_left); + cell_state.border_left = override_borders_data.left.border_data.width; + ConflictingEdge winning_edge_right { + .element = cell.box, + .element_kind = Painting::PaintableBox::ConflictingElementKind::Cell, + .side = ConflictingSide::Right, + .row = cell.row_index, + .column = cell.column_index, + }; + for (auto const& conflicting_edge : finder.conflicting_edges(cell, ConflictingSide::Right)) { + winning_edge_right = winning_conflicting_edge(winning_edge_right, conflicting_edge); } - override_borders_data.right = winning_border_right; - cell_state.border_right = winning_border_right.width; - auto winning_border_top = cell_style.border_top(); - for (auto const conflicting_edge : finder.conflicting_edges(cell, ConflictingSide::Top)) { - winning_border_top = winning_border_style(winning_border_top, border_data_conflicting_edge(conflicting_edge)); + override_borders_data.right = border_data_with_element_kind_from_conflicting_edge(winning_edge_right); + cell_state.border_right = override_borders_data.right.border_data.width; + ConflictingEdge winning_edge_top { + .element = cell.box, + .element_kind = Painting::PaintableBox::ConflictingElementKind::Cell, + .side = ConflictingSide::Top, + .row = cell.row_index, + .column = cell.column_index, + }; + for (auto const& conflicting_edge : finder.conflicting_edges(cell, ConflictingSide::Top)) { + winning_edge_top = winning_conflicting_edge(winning_edge_top, conflicting_edge); } - override_borders_data.top = winning_border_top; - cell_state.border_top = winning_border_top.width; - auto winning_border_bottom = cell_style.border_bottom(); - for (auto const conflicting_edge : finder.conflicting_edges(cell, ConflictingSide::Bottom)) { - winning_border_bottom = winning_border_style(winning_border_bottom, border_data_conflicting_edge(conflicting_edge)); + override_borders_data.top = border_data_with_element_kind_from_conflicting_edge(winning_edge_top); + cell_state.border_top = override_borders_data.top.border_data.width; + ConflictingEdge winning_edge_bottom { + .element = cell.box, + .element_kind = Painting::PaintableBox::ConflictingElementKind::Cell, + .side = ConflictingSide::Bottom, + .row = cell.row_index, + .column = cell.column_index, + }; + for (auto const& conflicting_edge : finder.conflicting_edges(cell, ConflictingSide::Bottom)) { + winning_edge_bottom = winning_conflicting_edge(winning_edge_bottom, conflicting_edge); } - override_borders_data.bottom = winning_border_bottom; - cell_state.border_bottom = override_borders_data.bottom.width; - // FIXME: 4. If border styles differ only in color, then a style set on a cell wins over one on a row, which wins over a - // row group, column, column group and, lastly, table. When two elements of the same type conflict, then the one - // further to the left (if the table's 'direction' is 'ltr'; right, if it is 'rtl') and further to the top wins. + override_borders_data.bottom = border_data_with_element_kind_from_conflicting_edge(winning_edge_bottom); + cell_state.border_bottom = override_borders_data.bottom.border_data.width; cell_state.set_override_borders_data(override_borders_data); } } @@ -1384,97 +1448,105 @@ Vector TableFormattingContext::BorderCo { Vector result = {}; if (cell.column_index >= cell.column_span && edge == ConflictingSide::Left) { - auto maybe_cell_to_left = m_context->m_cells_by_coordinate[cell.row_index][cell.column_index - cell.column_span]; + auto left_cell_column_index = cell.column_index - cell.column_span; + auto maybe_cell_to_left = m_context->m_cells_by_coordinate[cell.row_index][left_cell_column_index]; if (maybe_cell_to_left.has_value()) { - result.append({ maybe_cell_to_left->box, ConflictingSide::Right }); + result.append({ maybe_cell_to_left->box, Painting::PaintableBox::ConflictingElementKind::Cell, ConflictingSide::Right, cell.row_index, left_cell_column_index }); } } if (cell.column_index + cell.column_span < m_context->m_cells_by_coordinate[cell.row_index].size() && edge == ConflictingSide::Right) { - auto maybe_cell_to_right = m_context->m_cells_by_coordinate[cell.row_index][cell.column_index + cell.column_span]; + auto right_cell_column_index = cell.column_index + cell.column_span; + auto maybe_cell_to_right = m_context->m_cells_by_coordinate[cell.row_index][right_cell_column_index]; if (maybe_cell_to_right.has_value()) { - result.append({ maybe_cell_to_right->box, ConflictingSide::Left }); + result.append({ maybe_cell_to_right->box, Painting::PaintableBox::ConflictingElementKind::Cell, ConflictingSide::Left, cell.row_index, right_cell_column_index }); } } if (cell.row_index >= cell.row_span && edge == ConflictingSide::Top) { - auto maybe_cell_above = m_context->m_cells_by_coordinate[cell.row_index - cell.row_span][cell.column_index]; + auto above_cell_row_index = cell.row_index - cell.row_span; + auto maybe_cell_above = m_context->m_cells_by_coordinate[above_cell_row_index][cell.column_index]; if (maybe_cell_above.has_value()) { - result.append({ maybe_cell_above->box, ConflictingSide::Bottom }); + result.append({ maybe_cell_above->box, Painting::PaintableBox::ConflictingElementKind::Cell, ConflictingSide::Bottom, above_cell_row_index, cell.column_index }); } } if (cell.row_index + cell.row_span < m_context->m_cells_by_coordinate.size() && edge == ConflictingSide::Bottom) { - auto maybe_cell_below = m_context->m_cells_by_coordinate[cell.row_index + cell.row_span][cell.column_index]; + auto below_cell_row_index = cell.row_index + cell.row_span; + auto maybe_cell_below = m_context->m_cells_by_coordinate[below_cell_row_index][cell.column_index]; if (maybe_cell_below.has_value()) { - result.append({ maybe_cell_below->box, ConflictingSide::Top }); + result.append({ maybe_cell_below->box, Painting::PaintableBox::ConflictingElementKind::Cell, ConflictingSide::Top, below_cell_row_index, cell.column_index }); } } if (edge == ConflictingSide::Top) { - result.append({ m_context->m_rows[cell.row_index].box, ConflictingSide::Top }); + result.append({ m_context->m_rows[cell.row_index].box, Painting::PaintableBox::ConflictingElementKind::Row, ConflictingSide::Top, cell.row_index, {} }); } if (edge == ConflictingSide::Bottom) { - result.append({ m_context->m_rows[cell.row_index].box, ConflictingSide::Bottom }); + result.append({ m_context->m_rows[cell.row_index].box, Painting::PaintableBox::ConflictingElementKind::Row, ConflictingSide::Bottom, cell.row_index, {} }); } if (cell.row_index >= cell.row_span && edge == ConflictingSide::Top) { - result.append({ m_context->m_rows[cell.row_index - cell.row_span].box, ConflictingSide::Bottom }); + auto above_row_index = cell.row_index - cell.row_span; + result.append({ m_context->m_rows[above_row_index].box, Painting::PaintableBox::ConflictingElementKind::Row, ConflictingSide::Bottom, above_row_index, {} }); } if (cell.row_index + cell.row_span < m_context->m_rows.size() && edge == ConflictingSide::Bottom) { - result.append({ m_context->m_rows[cell.row_index + cell.row_span].box, ConflictingSide::Top }); + auto below_row_index = cell.row_index + cell.row_span; + result.append({ m_context->m_rows[below_row_index].box, Painting::PaintableBox::ConflictingElementKind::Row, ConflictingSide::Top, below_row_index, {} }); } auto const& maybe_row_group = m_row_group_elements_by_index[cell.row_index]; if (maybe_row_group.has_value() && cell.row_index == maybe_row_group->start_index && edge == ConflictingSide::Top) { - result.append({ maybe_row_group->row_group, ConflictingSide::Top }); + result.append({ maybe_row_group->row_group, Painting::PaintableBox::ConflictingElementKind::RowGroup, ConflictingSide::Top, maybe_row_group->start_index, {} }); } if (cell.row_index >= cell.row_span) { auto const& maybe_row_group_above = m_row_group_elements_by_index[cell.row_index - cell.row_span]; if (maybe_row_group_above.has_value() && cell.row_index == maybe_row_group_above->start_index + maybe_row_group_above->row_count && edge == ConflictingSide::Top) { - result.append({ maybe_row_group_above->row_group, ConflictingSide::Bottom }); + result.append({ maybe_row_group_above->row_group, Painting::PaintableBox::ConflictingElementKind::RowGroup, ConflictingSide::Bottom, maybe_row_group_above->start_index, {} }); } } if (maybe_row_group.has_value() && cell.row_index == maybe_row_group->start_index + maybe_row_group->row_count - 1 && edge == ConflictingSide::Bottom) { - result.append({ maybe_row_group->row_group, ConflictingSide::Bottom }); + result.append({ maybe_row_group->row_group, Painting::PaintableBox::ConflictingElementKind::RowGroup, ConflictingSide::Bottom, maybe_row_group->start_index, {} }); } if (cell.row_index + cell.row_span < m_row_group_elements_by_index.size()) { auto const& maybe_row_group_below = m_row_group_elements_by_index[cell.row_index + cell.row_span]; if (maybe_row_group_below.has_value() && cell.row_index + cell.row_span == maybe_row_group_below->start_index && edge == ConflictingSide::Bottom) { - result.append({ maybe_row_group_below->row_group, ConflictingSide::Top }); + result.append({ maybe_row_group_below->row_group, Painting::PaintableBox::ConflictingElementKind::RowGroup, ConflictingSide::Top, maybe_row_group_below->start_index, {} }); } } if (m_col_elements_by_index[cell.column_index] && edge == ConflictingSide::Left) { - result.append({ m_col_elements_by_index[cell.column_index], ConflictingSide::Left }); + result.append({ m_col_elements_by_index[cell.column_index], Painting::PaintableBox::ConflictingElementKind::ColumnGroup, ConflictingSide::Left, {}, cell.column_index }); } if (cell.column_index >= cell.column_span && m_col_elements_by_index[cell.column_index - cell.column_span] && edge == ConflictingSide::Left) { - result.append({ m_col_elements_by_index[cell.column_index - cell.column_span], ConflictingSide::Right }); + auto left_column_index = cell.column_index - cell.column_span; + result.append({ m_col_elements_by_index[left_column_index], Painting::PaintableBox::ConflictingElementKind::ColumnGroup, ConflictingSide::Right, {}, left_column_index }); } if (m_col_elements_by_index[cell.column_index] && edge == ConflictingSide::Right) { - result.append({ m_col_elements_by_index[cell.column_index], ConflictingSide::Right }); + result.append({ m_col_elements_by_index[cell.column_index], Painting::PaintableBox::ConflictingElementKind::ColumnGroup, ConflictingSide::Right, {}, cell.column_index }); } if (cell.column_index + cell.column_span < m_col_elements_by_index.size() && m_col_elements_by_index[cell.column_index + cell.column_span] && edge == ConflictingSide::Right) { - result.append({ m_col_elements_by_index[cell.column_index + cell.column_span], ConflictingSide::Left }); + auto right_column_index = cell.column_index + cell.column_span; + result.append({ m_col_elements_by_index[right_column_index], Painting::PaintableBox::ConflictingElementKind::ColumnGroup, ConflictingSide::Left, {}, right_column_index }); } if (cell.row_index == 0 && edge == ConflictingSide::Top) { if (m_col_elements_by_index[cell.column_index]) { - result.append({ m_col_elements_by_index[cell.column_index], ConflictingSide::Top }); + result.append({ m_col_elements_by_index[cell.column_index], Painting::PaintableBox::ConflictingElementKind::ColumnGroup, ConflictingSide::Top, {}, cell.column_index }); } - result.append({ &m_context->table_box(), ConflictingSide::Top }); + result.append({ &m_context->table_box(), Painting::PaintableBox::ConflictingElementKind::Table, ConflictingSide::Top, {}, {} }); } if (cell.row_index == m_context->m_rows.size() - 1 && edge == ConflictingSide::Bottom) { if (m_col_elements_by_index[cell.column_index]) { - result.append({ m_col_elements_by_index[cell.column_index], ConflictingSide::Bottom }); + result.append({ m_col_elements_by_index[cell.column_index], Painting::PaintableBox::ConflictingElementKind::ColumnGroup, ConflictingSide::Bottom, {}, cell.column_index }); } - result.append({ &m_context->table_box(), ConflictingSide::Bottom }); + result.append({ &m_context->table_box(), Painting::PaintableBox::ConflictingElementKind::Table, ConflictingSide::Bottom, {}, {} }); } if (cell.column_index == 0 && edge == ConflictingSide::Left) { - result.append({ m_context->m_rows[cell.row_index].box, ConflictingSide::Left }); + result.append({ m_context->m_rows[cell.row_index].box, Painting::PaintableBox::ConflictingElementKind::Row, ConflictingSide::Left, cell.row_index, {} }); if (m_row_group_elements_by_index[cell.row_index].has_value()) { - result.append({ m_row_group_elements_by_index[cell.row_index]->row_group, ConflictingSide::Left }); + result.append({ m_row_group_elements_by_index[cell.row_index]->row_group, Painting::PaintableBox::ConflictingElementKind::RowGroup, ConflictingSide::Left, cell.row_index, {} }); } - result.append({ &m_context->table_box(), ConflictingSide::Left }); + result.append({ &m_context->table_box(), Painting::PaintableBox::ConflictingElementKind::Table, ConflictingSide::Left, {}, {} }); } if (cell.column_index == m_context->m_columns.size() - 1 && edge == ConflictingSide::Right) { - result.append({ m_context->m_rows[cell.row_index].box, ConflictingSide::Right }); + result.append({ m_context->m_rows[cell.row_index].box, Painting::PaintableBox::ConflictingElementKind::Row, ConflictingSide::Right, cell.row_index, {} }); if (m_row_group_elements_by_index[cell.row_index].has_value()) { - result.append({ m_row_group_elements_by_index[cell.row_index]->row_group, ConflictingSide::Right }); + result.append({ m_row_group_elements_by_index[cell.row_index]->row_group, Painting::PaintableBox::ConflictingElementKind::RowGroup, ConflictingSide::Right, cell.row_index, {} }); } - result.append({ &m_context->table_box(), ConflictingSide::Right }); + result.append({ &m_context->table_box(), Painting::PaintableBox::ConflictingElementKind::Table, ConflictingSide::Right, {}, {} }); } return result; } diff --git a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h index c3e0b9b489..f2c9738c90 100644 --- a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h @@ -157,10 +157,16 @@ private: struct ConflictingEdge { Node const* element; + Painting::PaintableBox::ConflictingElementKind element_kind; ConflictingSide side; + Optional row; + Optional column; }; + static TableFormattingContext::ConflictingEdge const& winning_conflicting_edge(TableFormattingContext::ConflictingEdge const& a, TableFormattingContext::ConflictingEdge const& b); + static const CSS::BorderData& border_data_conflicting_edge(ConflictingEdge const& conflicting_edge); + static const Painting::PaintableBox::BorderDataWithElementKind border_data_with_element_kind_from_conflicting_edge(ConflictingEdge const& conflicting_edge); class BorderConflictFinder { public: diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp index 4d1d6ebf80..ac358abb59 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.cpp @@ -224,9 +224,19 @@ void PaintableBox::paint(PaintContext& context, PaintPhase phase) const } } +BordersData PaintableBox::remove_element_kind_from_borders_data(PaintableBox::BordersDataWithElementKind borders_data) +{ + return { + .top = borders_data.top.border_data, + .right = borders_data.right.border_data, + .bottom = borders_data.bottom.border_data, + .left = borders_data.left.border_data, + }; +} + void PaintableBox::paint_border(PaintContext& context) const { - auto borders_data = m_override_borders_data.has_value() ? m_override_borders_data.value() : BordersData { + auto borders_data = m_override_borders_data.has_value() ? remove_element_kind_from_borders_data(m_override_borders_data.value()) : BordersData { .top = box_model().border.top == 0 ? CSS::BorderData() : computed_values().border_top(), .right = box_model().border.right == 0 ? CSS::BorderData() : computed_values().border_right(), .bottom = box_model().border.bottom == 0 ? CSS::BorderData() : computed_values().border_bottom(), diff --git a/Userland/Libraries/LibWeb/Painting/PaintableBox.h b/Userland/Libraries/LibWeb/Painting/PaintableBox.h index 8b388c8945..99f39a59d5 100644 --- a/Userland/Libraries/LibWeb/Painting/PaintableBox.h +++ b/Userland/Libraries/LibWeb/Painting/PaintableBox.h @@ -130,8 +130,31 @@ public: bool is_out_of_view(PaintContext&) const; - void set_override_borders_data(BordersData const& override_borders_data) { m_override_borders_data = override_borders_data; } - Optional const& override_borders_data() const { return m_override_borders_data; } + enum class ConflictingElementKind { + Cell, + Row, + RowGroup, + Column, + ColumnGroup, + Table, + }; + + struct BorderDataWithElementKind { + CSS::BorderData border_data; + ConflictingElementKind element_kind; + }; + + struct BordersDataWithElementKind { + BorderDataWithElementKind top; + BorderDataWithElementKind right; + BorderDataWithElementKind bottom; + BorderDataWithElementKind left; + }; + + void set_override_borders_data(BordersDataWithElementKind const& override_borders_data) { m_override_borders_data = override_borders_data; } + Optional const& override_borders_data() const { return m_override_borders_data; } + + static BordersData remove_element_kind_from_borders_data(PaintableBox::BordersDataWithElementKind borders_data); struct TableCellCoordinates { size_t row_index; @@ -182,7 +205,7 @@ private: mutable bool m_clipping_overflow { false }; Optional mutable m_overflow_corner_radius_clipper; - Optional m_override_borders_data; + Optional m_override_borders_data; Optional m_table_cell_coordinates; }; diff --git a/Userland/Libraries/LibWeb/Painting/TableBordersPainting.cpp b/Userland/Libraries/LibWeb/Painting/TableBordersPainting.cpp index c94763f1f7..28e1bfd674 100644 --- a/Userland/Libraries/LibWeb/Painting/TableBordersPainting.cpp +++ b/Userland/Libraries/LibWeb/Painting/TableBordersPainting.cpp @@ -49,97 +49,147 @@ enum class EdgeDirection { struct BorderEdgePaintingInfo { DevicePixelRect rect; - CSS::BorderData border_data; + PaintableBox::BorderDataWithElementKind border_data_with_element_kind; EdgeDirection direction; + Optional row; + Optional column; }; -static BorderEdgePaintingInfo make_right_cell_edge(PaintContext& context, CSSPixelRect const& right_cell_rect, CSSPixelRect const& cell_rect, BordersData const& borders_data) +static Optional row_index_for_element_kind(size_t index, Painting::PaintableBox::ConflictingElementKind element_kind) +{ + switch (element_kind) { + case Painting::PaintableBox::ConflictingElementKind::Cell: + case Painting::PaintableBox::ConflictingElementKind::Row: + case Painting::PaintableBox::ConflictingElementKind::RowGroup: { + return index; + } + default: + return {}; + } +} + +static Optional column_index_for_element_kind(size_t index, Painting::PaintableBox::ConflictingElementKind element_kind) +{ + switch (element_kind) { + case Painting::PaintableBox::ConflictingElementKind::Cell: + case Painting::PaintableBox::ConflictingElementKind::Column: + case Painting::PaintableBox::ConflictingElementKind::ColumnGroup: { + return index; + } + default: + return {}; + } +} + +static BorderEdgePaintingInfo make_right_cell_edge( + PaintContext& context, + CSSPixelRect const& right_cell_rect, + CSSPixelRect const& cell_rect, + PaintableBox::BordersDataWithElementKind const& borders_data, + CellCoordinates const& coordinates) { DevicePixelRect right_border_rect = { - context.rounded_device_pixels(right_cell_rect.x() - round(borders_data.right.width / 2)), - context.rounded_device_pixels(cell_rect.y() - round(borders_data.top.width / 2)), - context.rounded_device_pixels(borders_data.right.width), - context.rounded_device_pixels(max(cell_rect.height(), right_cell_rect.height()) + round(borders_data.top.width / 2) + round(borders_data.bottom.width / 2)), + context.rounded_device_pixels(right_cell_rect.x() - round(borders_data.right.border_data.width / 2)), + context.rounded_device_pixels(cell_rect.y() - round(borders_data.top.border_data.width / 2)), + context.rounded_device_pixels(borders_data.right.border_data.width), + context.rounded_device_pixels(max(cell_rect.height(), right_cell_rect.height()) + round(borders_data.top.border_data.width / 2) + round(borders_data.bottom.border_data.width / 2)), }; return BorderEdgePaintingInfo { .rect = right_border_rect, - .border_data = borders_data.right, + .border_data_with_element_kind = borders_data.right, .direction = EdgeDirection::Vertical, + .row = row_index_for_element_kind(coordinates.row_index, borders_data.right.element_kind), + .column = column_index_for_element_kind(coordinates.column_index, borders_data.right.element_kind), }; } -static BorderEdgePaintingInfo make_down_cell_edge(PaintContext& context, CSSPixelRect const& down_cell_rect, CSSPixelRect const& cell_rect, BordersData const& borders_data) +static BorderEdgePaintingInfo make_down_cell_edge( + PaintContext& context, + CSSPixelRect const& down_cell_rect, + CSSPixelRect const& cell_rect, + PaintableBox::BordersDataWithElementKind const& borders_data, + CellCoordinates const& coordinates) { DevicePixelRect down_border_rect = { - context.rounded_device_pixels(cell_rect.x() - round(borders_data.left.width / 2)), - context.rounded_device_pixels(down_cell_rect.y() - round(borders_data.bottom.width / 2)), - context.rounded_device_pixels(max(cell_rect.width(), down_cell_rect.width()) + round(borders_data.left.width / 2) + round(borders_data.right.width / 2)), - context.rounded_device_pixels(borders_data.bottom.width), + context.rounded_device_pixels(cell_rect.x() - round(borders_data.left.border_data.width / 2)), + context.rounded_device_pixels(down_cell_rect.y() - round(borders_data.bottom.border_data.width / 2)), + context.rounded_device_pixels(max(cell_rect.width(), down_cell_rect.width()) + round(borders_data.left.border_data.width / 2) + round(borders_data.right.border_data.width / 2)), + context.rounded_device_pixels(borders_data.bottom.border_data.width), }; return BorderEdgePaintingInfo { .rect = down_border_rect, - .border_data = borders_data.bottom, + .border_data_with_element_kind = borders_data.bottom, .direction = EdgeDirection::Horizontal, + .row = row_index_for_element_kind(coordinates.row_index, borders_data.bottom.element_kind), + .column = column_index_for_element_kind(coordinates.column_index, borders_data.bottom.element_kind), }; } -static BorderEdgePaintingInfo make_first_row_top_cell_edge(PaintContext& context, CSSPixelRect const& cell_rect, BordersData const& borders_data) +static BorderEdgePaintingInfo make_first_row_top_cell_edge(PaintContext& context, CSSPixelRect const& cell_rect, PaintableBox::BordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates) { DevicePixelRect top_border_rect = { - context.rounded_device_pixels(cell_rect.x() - round(borders_data.left.width / 2)), - context.rounded_device_pixels(cell_rect.y() - round(borders_data.top.width / 2)), + context.rounded_device_pixels(cell_rect.x() - round(borders_data.left.border_data.width / 2)), + context.rounded_device_pixels(cell_rect.y() - round(borders_data.top.border_data.width / 2)), context.rounded_device_pixels(cell_rect.width()), - context.rounded_device_pixels(borders_data.top.width), + context.rounded_device_pixels(borders_data.top.border_data.width), }; return BorderEdgePaintingInfo { .rect = top_border_rect, - .border_data = borders_data.top, + .border_data_with_element_kind = borders_data.top, .direction = EdgeDirection::Horizontal, + .row = row_index_for_element_kind(coordinates.row_index, borders_data.top.element_kind), + .column = column_index_for_element_kind(coordinates.column_index, borders_data.top.element_kind), }; } -static BorderEdgePaintingInfo make_last_row_bottom_cell_edge(PaintContext& context, CSSPixelRect const& cell_rect, BordersData const& borders_data) +static BorderEdgePaintingInfo make_last_row_bottom_cell_edge(PaintContext& context, CSSPixelRect const& cell_rect, PaintableBox::BordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates) { DevicePixelRect bottom_border_rect = { - context.rounded_device_pixels(cell_rect.x() - round(borders_data.left.width / 2)), - context.rounded_device_pixels(cell_rect.y() + cell_rect.height() - round(borders_data.bottom.width / 2)), - context.rounded_device_pixels(cell_rect.width() + round(borders_data.left.width / 2) + round(borders_data.right.width / 2)), - context.rounded_device_pixels(borders_data.bottom.width), + context.rounded_device_pixels(cell_rect.x() - round(borders_data.left.border_data.width / 2)), + context.rounded_device_pixels(cell_rect.y() + cell_rect.height() - round(borders_data.bottom.border_data.width / 2)), + context.rounded_device_pixels(cell_rect.width() + round(borders_data.left.border_data.width / 2) + round(borders_data.right.border_data.width / 2)), + context.rounded_device_pixels(borders_data.bottom.border_data.width), }; return BorderEdgePaintingInfo { .rect = bottom_border_rect, - .border_data = borders_data.bottom, + .border_data_with_element_kind = borders_data.bottom, .direction = EdgeDirection::Horizontal, + .row = row_index_for_element_kind(coordinates.row_index, borders_data.bottom.element_kind), + .column = column_index_for_element_kind(coordinates.column_index, borders_data.bottom.element_kind), }; } -static BorderEdgePaintingInfo make_first_column_left_cell_edge(PaintContext& context, CSSPixelRect const& cell_rect, BordersData const& borders_data) +static BorderEdgePaintingInfo make_first_column_left_cell_edge(PaintContext& context, CSSPixelRect const& cell_rect, PaintableBox::BordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates) { DevicePixelRect left_border_rect = { - context.rounded_device_pixels(cell_rect.x() - round(borders_data.left.width / 2)), - context.rounded_device_pixels(cell_rect.y() - round(borders_data.top.width / 2)), - context.rounded_device_pixels(borders_data.left.width), - context.rounded_device_pixels(cell_rect.height() + round(borders_data.top.width / 2)), + context.rounded_device_pixels(cell_rect.x() - round(borders_data.left.border_data.width / 2)), + context.rounded_device_pixels(cell_rect.y() - round(borders_data.top.border_data.width / 2)), + context.rounded_device_pixels(borders_data.left.border_data.width), + context.rounded_device_pixels(cell_rect.height() + round(borders_data.top.border_data.width / 2)), }; return BorderEdgePaintingInfo { .rect = left_border_rect, - .border_data = borders_data.left, + .border_data_with_element_kind = borders_data.left, .direction = EdgeDirection::Vertical, + .row = row_index_for_element_kind(coordinates.row_index, borders_data.left.element_kind), + .column = column_index_for_element_kind(coordinates.column_index, borders_data.left.element_kind), }; } -static BorderEdgePaintingInfo make_last_column_right_cell_edge(PaintContext& context, CSSPixelRect const& cell_rect, BordersData const& borders_data) +static BorderEdgePaintingInfo make_last_column_right_cell_edge(PaintContext& context, CSSPixelRect const& cell_rect, PaintableBox::BordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates) { DevicePixelRect right_border_rect = { - context.rounded_device_pixels(cell_rect.x() + cell_rect.width() - round(borders_data.right.width / 2)), - context.rounded_device_pixels(cell_rect.y() - round(borders_data.top.width / 2)), - context.rounded_device_pixels(borders_data.right.width), - context.rounded_device_pixels(cell_rect.height() + round(borders_data.top.width / 2) + round(borders_data.bottom.width / 2)), + context.rounded_device_pixels(cell_rect.x() + cell_rect.width() - round(borders_data.right.border_data.width / 2)), + context.rounded_device_pixels(cell_rect.y() - round(borders_data.top.border_data.width / 2)), + context.rounded_device_pixels(borders_data.right.border_data.width), + context.rounded_device_pixels(cell_rect.height() + round(borders_data.top.border_data.width / 2) + round(borders_data.bottom.border_data.width / 2)), }; return BorderEdgePaintingInfo { .rect = right_border_rect, - .border_data = borders_data.right, + .border_data_with_element_kind = borders_data.right, .direction = EdgeDirection::Vertical, + .row = row_index_for_element_kind(coordinates.row_index, borders_data.right.element_kind), + .column = column_index_for_element_kind(coordinates.column_index, borders_data.right.element_kind), }; } @@ -147,17 +197,38 @@ static void paint_collected_edges(PaintContext& context, Vector a.border_data_with_element_kind.element_kind) { + return false; + } + // Here the element kind is the same, thus the coordinates are either both set or not set. + VERIFY(a.column.has_value() == b.column.has_value()); + VERIFY(a.row.has_value() == b.row.has_value()); + if (a.column.has_value()) { + if (b.column.value() < a.column.value()) { + return true; + } else if (b.column.value() > a.column.value()) { + return false; + } + } + return a.row.has_value() ? b.row.value() < a.row.value() : false; + } + return Layout::TableFormattingContext::border_is_less_specific(a_border_data, b_border_data); }); for (auto const& border_edge_painting_info : border_edge_painting_info_list) { - auto const& border_data = border_edge_painting_info.border_data; - CSSPixels width = border_data.width; + auto const& border_data_with_element_kind = border_edge_painting_info.border_data_with_element_kind; + CSSPixels width = border_data_with_element_kind.border_data.width; if (width <= 0) continue; - auto color = border_data.color; - auto border_style = border_data.line_style; + auto color = border_data_with_element_kind.border_data.color; + auto border_style = border_data_with_element_kind.border_data.line_style; auto p1 = border_edge_painting_info.rect.top_left(); auto p2 = border_edge_painting_info.direction == EdgeDirection::Horizontal ? border_edge_painting_info.rect.top_right() @@ -194,31 +265,39 @@ void paint_table_collapsed_borders(PaintContext& context, Layout::Node const& bo column_count = max(column_count, cell_box->table_cell_coordinates()->column_index + cell_box->table_cell_coordinates()->column_span); } for (auto const cell_box : cell_boxes) { - auto borders_data = cell_box->override_borders_data().has_value() ? cell_box->override_borders_data().value() : BordersData { - .top = cell_box->box_model().border.top == 0 ? CSS::BorderData() : cell_box->computed_values().border_top(), - .right = cell_box->box_model().border.right == 0 ? CSS::BorderData() : cell_box->computed_values().border_right(), - .bottom = cell_box->box_model().border.bottom == 0 ? CSS::BorderData() : cell_box->computed_values().border_bottom(), - .left = cell_box->box_model().border.left == 0 ? CSS::BorderData() : cell_box->computed_values().border_left(), + auto borders_data = cell_box->override_borders_data().has_value() ? cell_box->override_borders_data().value() : PaintableBox::BordersDataWithElementKind { + .top = { .border_data = cell_box->box_model().border.top == 0 ? CSS::BorderData() : cell_box->computed_values().border_top(), .element_kind = PaintableBox::ConflictingElementKind::Cell }, + .right = { .border_data = cell_box->box_model().border.right == 0 ? CSS::BorderData() : cell_box->computed_values().border_right(), .element_kind = PaintableBox::ConflictingElementKind::Cell }, + .bottom = { .border_data = cell_box->box_model().border.bottom == 0 ? CSS::BorderData() : cell_box->computed_values().border_bottom(), .element_kind = PaintableBox::ConflictingElementKind::Cell }, + .left = { .border_data = cell_box->box_model().border.left == 0 ? CSS::BorderData() : cell_box->computed_values().border_left(), .element_kind = PaintableBox::ConflictingElementKind::Cell }, }; auto cell_rect = cell_box->absolute_border_box_rect(); - auto maybe_right_cell = cell_coordinates_to_box.get(CellCoordinates { + CellCoordinates right_cell_coordinates { .row_index = cell_box->table_cell_coordinates()->row_index, - .column_index = cell_box->table_cell_coordinates()->column_index + cell_box->table_cell_coordinates()->column_span }); - auto maybe_down_cell = cell_coordinates_to_box.get(CellCoordinates { + .column_index = cell_box->table_cell_coordinates()->column_index + cell_box->table_cell_coordinates()->column_span + }; + auto maybe_right_cell = cell_coordinates_to_box.get(right_cell_coordinates); + CellCoordinates down_cell_coordinates { .row_index = cell_box->table_cell_coordinates()->row_index + cell_box->table_cell_coordinates()->row_span, - .column_index = cell_box->table_cell_coordinates()->column_index }); + .column_index = cell_box->table_cell_coordinates()->column_index + }; + auto maybe_down_cell = cell_coordinates_to_box.get(down_cell_coordinates); if (maybe_right_cell.has_value()) - border_edge_painting_info_list.append(make_right_cell_edge(context, maybe_right_cell.value()->absolute_border_box_rect(), cell_rect, borders_data)); + border_edge_painting_info_list.append(make_right_cell_edge(context, maybe_right_cell.value()->absolute_border_box_rect(), cell_rect, borders_data, right_cell_coordinates)); if (maybe_down_cell.has_value()) - border_edge_painting_info_list.append(make_down_cell_edge(context, maybe_down_cell.value()->absolute_border_box_rect(), cell_rect, borders_data)); + border_edge_painting_info_list.append(make_down_cell_edge(context, maybe_down_cell.value()->absolute_border_box_rect(), cell_rect, borders_data, down_cell_coordinates)); if (cell_box->table_cell_coordinates()->row_index == 0) - border_edge_painting_info_list.append(make_first_row_top_cell_edge(context, cell_rect, borders_data)); + border_edge_painting_info_list.append(make_first_row_top_cell_edge(context, cell_rect, borders_data, + { .row_index = 0, .column_index = cell_box->table_cell_coordinates()->column_index })); if (cell_box->table_cell_coordinates()->row_index + cell_box->table_cell_coordinates()->row_span == row_count) - border_edge_painting_info_list.append(make_last_row_bottom_cell_edge(context, cell_rect, borders_data)); + border_edge_painting_info_list.append(make_last_row_bottom_cell_edge(context, cell_rect, borders_data, + { .row_index = row_count - 1, .column_index = cell_box->table_cell_coordinates()->column_index })); if (cell_box->table_cell_coordinates()->column_index == 0) - border_edge_painting_info_list.append(make_first_column_left_cell_edge(context, cell_rect, borders_data)); + border_edge_painting_info_list.append(make_first_column_left_cell_edge(context, cell_rect, borders_data, + { .row_index = cell_box->table_cell_coordinates()->row_index, .column_index = 0 })); if (cell_box->table_cell_coordinates()->column_index + cell_box->table_cell_coordinates()->column_span == column_count) - border_edge_painting_info_list.append(make_last_column_right_cell_edge(context, cell_rect, borders_data)); + border_edge_painting_info_list.append(make_last_column_right_cell_edge(context, cell_rect, borders_data, + { .row_index = cell_box->table_cell_coordinates()->row_index, .column_index = column_count - 1 })); } paint_collected_edges(context, border_edge_painting_info_list); @@ -232,7 +311,7 @@ void paint_table_collapsed_borders(PaintContext& context, Layout::Node const& bo if (!top_left && !top_right && !bottom_left && !bottom_right) { continue; } else { - auto borders_data = cell_box->override_borders_data().has_value() ? cell_box->override_borders_data().value() : BordersData { + auto borders_data = cell_box->override_borders_data().has_value() ? PaintableBox::remove_element_kind_from_borders_data(cell_box->override_borders_data().value()) : BordersData { .top = cell_box->box_model().border.top == 0 ? CSS::BorderData() : cell_box->computed_values().border_top(), .right = cell_box->box_model().border.right == 0 ? CSS::BorderData() : cell_box->computed_values().border_right(), .bottom = cell_box->box_model().border.bottom == 0 ? CSS::BorderData() : cell_box->computed_values().border_bottom(),