diff --git a/Userland/Libraries/LibWeb/Painting/TableBordersPainting.cpp b/Userland/Libraries/LibWeb/Painting/TableBordersPainting.cpp index 28e1bfd674..df85b0d6c6 100644 --- a/Userland/Libraries/LibWeb/Painting/TableBordersPainting.cpp +++ b/Userland/Libraries/LibWeb/Painting/TableBordersPainting.cpp @@ -47,9 +47,27 @@ enum class EdgeDirection { Vertical, }; +struct DeviceBorderData { + Color color { Color::Transparent }; + CSS::LineStyle line_style { CSS::LineStyle::None }; + DevicePixels width { 0 }; +}; + +struct DeviceBorderDataWithElementKind { + DeviceBorderData border_data; + Painting::PaintableBox::ConflictingElementKind element_kind { Painting::PaintableBox::ConflictingElementKind::Cell }; +}; + +struct DeviceBordersDataWithElementKind { + DeviceBorderDataWithElementKind top; + DeviceBorderDataWithElementKind right; + DeviceBorderDataWithElementKind bottom; + DeviceBorderDataWithElementKind left; +}; + struct BorderEdgePaintingInfo { DevicePixelRect rect; - PaintableBox::BorderDataWithElementKind border_data_with_element_kind; + DeviceBorderDataWithElementKind border_data_with_element_kind; EdgeDirection direction; Optional row; Optional column; @@ -81,18 +99,29 @@ static Optional column_index_for_element_kind(size_t index, Painting::Pa } } +static DevicePixels half_ceil(DevicePixels width) +{ + return ceil(static_cast(width.value()) / 2); +} + +static DevicePixels half_floor(DevicePixels width) +{ + return floor(static_cast(width.value()) / 2); +} + static BorderEdgePaintingInfo make_right_cell_edge( - PaintContext& context, - CSSPixelRect const& right_cell_rect, - CSSPixelRect const& cell_rect, - PaintableBox::BordersDataWithElementKind const& borders_data, + DevicePixelRect const& right_cell_rect, + DevicePixelRect const& cell_rect, + DeviceBordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates) { + auto connect_top_offset = half_ceil(borders_data.top.border_data.width); + auto connect_excess_height = connect_top_offset + half_floor(borders_data.bottom.border_data.width); DevicePixelRect right_border_rect = { - 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)), + right_cell_rect.x() - half_ceil(borders_data.right.border_data.width), + cell_rect.y() - connect_top_offset, + borders_data.right.border_data.width, + max(cell_rect.height(), right_cell_rect.height()) + connect_excess_height, }; return BorderEdgePaintingInfo { .rect = right_border_rect, @@ -104,17 +133,18 @@ static BorderEdgePaintingInfo make_right_cell_edge( } static BorderEdgePaintingInfo make_down_cell_edge( - PaintContext& context, - CSSPixelRect const& down_cell_rect, - CSSPixelRect const& cell_rect, - PaintableBox::BordersDataWithElementKind const& borders_data, + DevicePixelRect const& down_cell_rect, + DevicePixelRect const& cell_rect, + DeviceBordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates) { + auto connect_left_offset = half_ceil(borders_data.left.border_data.width); + auto connect_excess_width = connect_left_offset + half_floor(borders_data.right.border_data.width); DevicePixelRect down_border_rect = { - 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), + cell_rect.x() - connect_left_offset, + down_cell_rect.y() - half_ceil(borders_data.bottom.border_data.width), + max(cell_rect.width(), down_cell_rect.width()) + connect_excess_width, + borders_data.bottom.border_data.width, }; return BorderEdgePaintingInfo { .rect = down_border_rect, @@ -125,13 +155,15 @@ static BorderEdgePaintingInfo make_down_cell_edge( }; } -static BorderEdgePaintingInfo make_first_row_top_cell_edge(PaintContext& context, CSSPixelRect const& cell_rect, PaintableBox::BordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates) +static BorderEdgePaintingInfo make_first_row_top_cell_edge(DevicePixelRect const& cell_rect, DeviceBordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates) { + auto connect_left_offset = half_ceil(borders_data.left.border_data.width.value()); + auto connect_excess_width = connect_left_offset + half_floor(borders_data.right.border_data.width.value()); DevicePixelRect top_border_rect = { - 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.border_data.width), + cell_rect.x() - connect_left_offset, + cell_rect.y() - half_ceil(borders_data.top.border_data.width.value()), + cell_rect.width() + connect_excess_width, + borders_data.top.border_data.width, }; return BorderEdgePaintingInfo { .rect = top_border_rect, @@ -142,13 +174,15 @@ static BorderEdgePaintingInfo make_first_row_top_cell_edge(PaintContext& context }; } -static BorderEdgePaintingInfo make_last_row_bottom_cell_edge(PaintContext& context, CSSPixelRect const& cell_rect, PaintableBox::BordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates) +static BorderEdgePaintingInfo make_last_row_bottom_cell_edge(DevicePixelRect const& cell_rect, DeviceBordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates) { + auto connect_left_offset = half_ceil(borders_data.left.border_data.width); + auto connect_excess_width = connect_left_offset + half_floor(borders_data.right.border_data.width); DevicePixelRect bottom_border_rect = { - 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), + cell_rect.x() - connect_left_offset, + cell_rect.y() + cell_rect.height() - half_ceil(borders_data.bottom.border_data.width), + cell_rect.width() + connect_excess_width, + borders_data.bottom.border_data.width, }; return BorderEdgePaintingInfo { .rect = bottom_border_rect, @@ -159,13 +193,15 @@ static BorderEdgePaintingInfo make_last_row_bottom_cell_edge(PaintContext& conte }; } -static BorderEdgePaintingInfo make_first_column_left_cell_edge(PaintContext& context, CSSPixelRect const& cell_rect, PaintableBox::BordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates) +static BorderEdgePaintingInfo make_first_column_left_cell_edge(DevicePixelRect const& cell_rect, DeviceBordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates) { + auto connect_top_offset = half_ceil(borders_data.top.border_data.width); + auto connect_excess_height = connect_top_offset + half_floor(borders_data.bottom.border_data.width); DevicePixelRect left_border_rect = { - 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)), + cell_rect.x() - half_ceil(borders_data.left.border_data.width), + cell_rect.y() - connect_top_offset, + borders_data.left.border_data.width, + cell_rect.height() + connect_excess_height, }; return BorderEdgePaintingInfo { .rect = left_border_rect, @@ -176,13 +212,15 @@ static BorderEdgePaintingInfo make_first_column_left_cell_edge(PaintContext& con }; } -static BorderEdgePaintingInfo make_last_column_right_cell_edge(PaintContext& context, CSSPixelRect const& cell_rect, PaintableBox::BordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates) +static BorderEdgePaintingInfo make_last_column_right_cell_edge(DevicePixelRect const& cell_rect, DeviceBordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates) { + auto connect_top_offset = half_ceil(borders_data.top.border_data.width); + auto connect_excess_height = connect_top_offset + half_floor(borders_data.bottom.border_data.width); DevicePixelRect right_border_rect = { - 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)), + cell_rect.x() + cell_rect.width() - half_ceil(borders_data.right.border_data.width), + cell_rect.y() - connect_top_offset, + borders_data.right.border_data.width, + cell_rect.height() + connect_excess_height, }; return BorderEdgePaintingInfo { .rect = right_border_rect, @@ -193,6 +231,15 @@ static BorderEdgePaintingInfo make_last_column_right_cell_edge(PaintContext& con }; } +static CSS::BorderData css_border_data_from_device_border_data(DeviceBorderData const& device_border_data) +{ + return CSS::BorderData { + .color = device_border_data.color, + .line_style = device_border_data.line_style, + .width = device_border_data.width.value(), + }; +} + static void paint_collected_edges(PaintContext& context, Vector& border_edge_painting_info_list) { // This sorting step isn't part of the specification, but it matches the behavior of other browsers at border intersections, which aren't @@ -219,12 +266,14 @@ static void paint_collected_edges(PaintContext& context, Vector(), p2.to_type(), color, width.to_double(), Gfx::Painter::LineStyle::Dotted); + aa_painter.draw_line(p1.to_type(), p2.to_type(), color, width.value(), Gfx::Painter::LineStyle::Dotted); } else if (border_style == CSS::LineStyle::Dashed) { - context.painter().draw_line(p1.to_type(), p2.to_type(), color, width.to_double(), Gfx::Painter::LineStyle::Dashed); + context.painter().draw_line(p1.to_type(), p2.to_type(), color, width.value(), Gfx::Painter::LineStyle::Dashed); } else { // FIXME: Support the remaining line styles instead of rendering them as solid. context.painter().fill_rect(Gfx::IntRect(border_edge_painting_info.rect.location(), border_edge_painting_info.rect.size()), color); @@ -246,6 +295,50 @@ static void paint_collected_edges(PaintContext& context, Vector snap_cells_to_device_coordinates(HashMap const& cell_coordinates_to_box, size_t row_count, size_t column_count, PaintContext const& context) +{ + Vector y_line_coordinates; + y_line_coordinates.resize(row_count + 1); + Vector x_line_coordinates; + x_line_coordinates.resize(column_count + 1); + for (auto const& kv : cell_coordinates_to_box) { + auto const& cell_box = kv.value; + auto start_row_index = cell_box->table_cell_coordinates()->row_index; + auto end_row_index = start_row_index + cell_box->table_cell_coordinates()->row_span; + auto cell_rect = cell_box->absolute_border_box_rect(); + y_line_coordinates[start_row_index] = max(context.rounded_device_pixels(cell_rect.y()), y_line_coordinates[start_row_index]); + y_line_coordinates[end_row_index] = max(context.rounded_device_pixels(cell_rect.y() + cell_rect.height()), y_line_coordinates[end_row_index]); + auto start_column_index = cell_box->table_cell_coordinates()->column_index; + auto end_column_index = start_column_index + cell_box->table_cell_coordinates()->column_span; + x_line_coordinates[start_column_index] = max(context.rounded_device_pixels(cell_rect.x()), x_line_coordinates[start_column_index]); + x_line_coordinates[end_column_index] = max(context.rounded_device_pixels(cell_rect.x() + cell_rect.width()), x_line_coordinates[end_column_index]); + } + HashMap cell_coordinates_to_device_rect; + for (auto const& kv : cell_coordinates_to_box) { + auto const& cell_box = kv.value; + auto start_row_index = cell_box->table_cell_coordinates()->row_index; + auto end_row_index = start_row_index + cell_box->table_cell_coordinates()->row_span; + auto height = y_line_coordinates[end_row_index] - y_line_coordinates[start_row_index]; + auto start_column_index = cell_box->table_cell_coordinates()->column_index; + auto end_column_index = start_column_index + cell_box->table_cell_coordinates()->column_span; + auto width = x_line_coordinates[end_column_index] - x_line_coordinates[start_column_index]; + cell_coordinates_to_device_rect.set(kv.key, DevicePixelRect { x_line_coordinates[start_column_index], y_line_coordinates[start_row_index], width, height }); + } + return cell_coordinates_to_device_rect; +} + +static DeviceBorderDataWithElementKind device_border_data_from_css_border_data(Painting::PaintableBox::BorderDataWithElementKind const& border_data_with_element_kind, PaintContext const& context) +{ + return DeviceBorderDataWithElementKind { + .border_data = { + .color = border_data_with_element_kind.border_data.color, + .line_style = border_data_with_element_kind.border_data.line_style, + .width = context.rounded_device_pixels(border_data_with_element_kind.border_data.width), + }, + .element_kind = border_data_with_element_kind.element_kind, + }; +} + void paint_table_collapsed_borders(PaintContext& context, Layout::Node const& box) { // Partial implementation of painting according to the collapsing border model: @@ -264,39 +357,46 @@ void paint_table_collapsed_borders(PaintContext& context, Layout::Node const& bo row_count = max(row_count, cell_box->table_cell_coordinates()->row_index + cell_box->table_cell_coordinates()->row_span); column_count = max(column_count, cell_box->table_cell_coordinates()->column_index + cell_box->table_cell_coordinates()->column_span); } + auto cell_coordinates_to_device_rect = snap_cells_to_device_coordinates(cell_coordinates_to_box, row_count, column_count, context); for (auto const cell_box : cell_boxes) { - auto borders_data = cell_box->override_borders_data().has_value() ? cell_box->override_borders_data().value() : PaintableBox::BordersDataWithElementKind { + auto css_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(); + DeviceBordersDataWithElementKind borders_data = { + .top = device_border_data_from_css_border_data(css_borders_data.top, context), + .right = device_border_data_from_css_border_data(css_borders_data.right, context), + .bottom = device_border_data_from_css_border_data(css_borders_data.bottom, context), + .left = device_border_data_from_css_border_data(css_borders_data.left, context), + }; + auto cell_rect = cell_coordinates_to_device_rect.get({ cell_box->table_cell_coordinates()->row_index, cell_box->table_cell_coordinates()->column_index }).value(); 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_right_cell = cell_coordinates_to_box.get(right_cell_coordinates); + auto maybe_right_cell = cell_coordinates_to_device_rect.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 }; - auto maybe_down_cell = cell_coordinates_to_box.get(down_cell_coordinates); + auto maybe_down_cell = cell_coordinates_to_device_rect.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, right_cell_coordinates)); + border_edge_painting_info_list.append(make_right_cell_edge(maybe_right_cell.value(), 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, down_cell_coordinates)); + border_edge_painting_info_list.append(make_down_cell_edge(maybe_down_cell.value(), 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(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(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(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(cell_rect, borders_data, { .row_index = cell_box->table_cell_coordinates()->row_index, .column_index = column_count - 1 })); } @@ -321,5 +421,4 @@ void paint_table_collapsed_borders(PaintContext& context, Layout::Node const& bo } } } - }