TableBordersPainting.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. /*
  2. * Copyright (c) 2023, the SerenityOS developers.
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <AK/HashMap.h>
  7. #include <AK/QuickSort.h>
  8. #include <AK/Traits.h>
  9. #include <LibWeb/Layout/TableFormattingContext.h>
  10. #include <LibWeb/Painting/PaintableBox.h>
  11. #include <LibWeb/Painting/TableBordersPainting.h>
  12. struct CellCoordinates {
  13. size_t row_index;
  14. size_t column_index;
  15. bool operator==(CellCoordinates const& other) const
  16. {
  17. return row_index == other.row_index && column_index == other.column_index;
  18. }
  19. };
  20. namespace AK {
  21. template<>
  22. struct Traits<CellCoordinates> : public GenericTraits<CellCoordinates> {
  23. static unsigned hash(CellCoordinates const& key) { return pair_int_hash(key.row_index, key.column_index); }
  24. };
  25. }
  26. namespace Web::Painting {
  27. static void collect_cell_boxes_with_collapsed_borders(Vector<PaintableBox const*>& cell_boxes, Layout::Node const& box)
  28. {
  29. box.for_each_child([&](auto& child) {
  30. if (child.display().is_table_cell() && child.computed_values().border_collapse() == CSS::BorderCollapse::Collapse) {
  31. VERIFY(is<Layout::Box>(child) && child.paintable());
  32. cell_boxes.append(static_cast<Layout::Box const&>(child).paintable_box());
  33. } else {
  34. collect_cell_boxes_with_collapsed_borders(cell_boxes, child);
  35. }
  36. });
  37. }
  38. enum class EdgeDirection {
  39. Horizontal,
  40. Vertical,
  41. };
  42. struct BorderEdgePaintingInfo {
  43. DevicePixelRect rect;
  44. PaintableBox::BorderDataWithElementKind border_data_with_element_kind;
  45. EdgeDirection direction;
  46. Optional<size_t> row;
  47. Optional<size_t> column;
  48. };
  49. static Optional<size_t> row_index_for_element_kind(size_t index, Painting::PaintableBox::ConflictingElementKind element_kind)
  50. {
  51. switch (element_kind) {
  52. case Painting::PaintableBox::ConflictingElementKind::Cell:
  53. case Painting::PaintableBox::ConflictingElementKind::Row:
  54. case Painting::PaintableBox::ConflictingElementKind::RowGroup: {
  55. return index;
  56. }
  57. default:
  58. return {};
  59. }
  60. }
  61. static Optional<size_t> column_index_for_element_kind(size_t index, Painting::PaintableBox::ConflictingElementKind element_kind)
  62. {
  63. switch (element_kind) {
  64. case Painting::PaintableBox::ConflictingElementKind::Cell:
  65. case Painting::PaintableBox::ConflictingElementKind::Column:
  66. case Painting::PaintableBox::ConflictingElementKind::ColumnGroup: {
  67. return index;
  68. }
  69. default:
  70. return {};
  71. }
  72. }
  73. static BorderEdgePaintingInfo make_right_cell_edge(
  74. PaintContext& context,
  75. CSSPixelRect const& right_cell_rect,
  76. CSSPixelRect const& cell_rect,
  77. PaintableBox::BordersDataWithElementKind const& borders_data,
  78. CellCoordinates const& coordinates)
  79. {
  80. DevicePixelRect right_border_rect = {
  81. context.rounded_device_pixels(right_cell_rect.x() - round(borders_data.right.border_data.width / 2)),
  82. context.rounded_device_pixels(cell_rect.y() - round(borders_data.top.border_data.width / 2)),
  83. context.rounded_device_pixels(borders_data.right.border_data.width),
  84. 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)),
  85. };
  86. return BorderEdgePaintingInfo {
  87. .rect = right_border_rect,
  88. .border_data_with_element_kind = borders_data.right,
  89. .direction = EdgeDirection::Vertical,
  90. .row = row_index_for_element_kind(coordinates.row_index, borders_data.right.element_kind),
  91. .column = column_index_for_element_kind(coordinates.column_index, borders_data.right.element_kind),
  92. };
  93. }
  94. static BorderEdgePaintingInfo make_down_cell_edge(
  95. PaintContext& context,
  96. CSSPixelRect const& down_cell_rect,
  97. CSSPixelRect const& cell_rect,
  98. PaintableBox::BordersDataWithElementKind const& borders_data,
  99. CellCoordinates const& coordinates)
  100. {
  101. DevicePixelRect down_border_rect = {
  102. context.rounded_device_pixels(cell_rect.x() - round(borders_data.left.border_data.width / 2)),
  103. context.rounded_device_pixels(down_cell_rect.y() - round(borders_data.bottom.border_data.width / 2)),
  104. 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)),
  105. context.rounded_device_pixels(borders_data.bottom.border_data.width),
  106. };
  107. return BorderEdgePaintingInfo {
  108. .rect = down_border_rect,
  109. .border_data_with_element_kind = borders_data.bottom,
  110. .direction = EdgeDirection::Horizontal,
  111. .row = row_index_for_element_kind(coordinates.row_index, borders_data.bottom.element_kind),
  112. .column = column_index_for_element_kind(coordinates.column_index, borders_data.bottom.element_kind),
  113. };
  114. }
  115. static BorderEdgePaintingInfo make_first_row_top_cell_edge(PaintContext& context, CSSPixelRect const& cell_rect, PaintableBox::BordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates)
  116. {
  117. DevicePixelRect top_border_rect = {
  118. context.rounded_device_pixels(cell_rect.x() - round(borders_data.left.border_data.width / 2)),
  119. context.rounded_device_pixels(cell_rect.y() - round(borders_data.top.border_data.width / 2)),
  120. context.rounded_device_pixels(cell_rect.width()),
  121. context.rounded_device_pixels(borders_data.top.border_data.width),
  122. };
  123. return BorderEdgePaintingInfo {
  124. .rect = top_border_rect,
  125. .border_data_with_element_kind = borders_data.top,
  126. .direction = EdgeDirection::Horizontal,
  127. .row = row_index_for_element_kind(coordinates.row_index, borders_data.top.element_kind),
  128. .column = column_index_for_element_kind(coordinates.column_index, borders_data.top.element_kind),
  129. };
  130. }
  131. static BorderEdgePaintingInfo make_last_row_bottom_cell_edge(PaintContext& context, CSSPixelRect const& cell_rect, PaintableBox::BordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates)
  132. {
  133. DevicePixelRect bottom_border_rect = {
  134. context.rounded_device_pixels(cell_rect.x() - round(borders_data.left.border_data.width / 2)),
  135. context.rounded_device_pixels(cell_rect.y() + cell_rect.height() - round(borders_data.bottom.border_data.width / 2)),
  136. context.rounded_device_pixels(cell_rect.width() + round(borders_data.left.border_data.width / 2) + round(borders_data.right.border_data.width / 2)),
  137. context.rounded_device_pixels(borders_data.bottom.border_data.width),
  138. };
  139. return BorderEdgePaintingInfo {
  140. .rect = bottom_border_rect,
  141. .border_data_with_element_kind = borders_data.bottom,
  142. .direction = EdgeDirection::Horizontal,
  143. .row = row_index_for_element_kind(coordinates.row_index, borders_data.bottom.element_kind),
  144. .column = column_index_for_element_kind(coordinates.column_index, borders_data.bottom.element_kind),
  145. };
  146. }
  147. static BorderEdgePaintingInfo make_first_column_left_cell_edge(PaintContext& context, CSSPixelRect const& cell_rect, PaintableBox::BordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates)
  148. {
  149. DevicePixelRect left_border_rect = {
  150. context.rounded_device_pixels(cell_rect.x() - round(borders_data.left.border_data.width / 2)),
  151. context.rounded_device_pixels(cell_rect.y() - round(borders_data.top.border_data.width / 2)),
  152. context.rounded_device_pixels(borders_data.left.border_data.width),
  153. context.rounded_device_pixels(cell_rect.height() + round(borders_data.top.border_data.width / 2)),
  154. };
  155. return BorderEdgePaintingInfo {
  156. .rect = left_border_rect,
  157. .border_data_with_element_kind = borders_data.left,
  158. .direction = EdgeDirection::Vertical,
  159. .row = row_index_for_element_kind(coordinates.row_index, borders_data.left.element_kind),
  160. .column = column_index_for_element_kind(coordinates.column_index, borders_data.left.element_kind),
  161. };
  162. }
  163. static BorderEdgePaintingInfo make_last_column_right_cell_edge(PaintContext& context, CSSPixelRect const& cell_rect, PaintableBox::BordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates)
  164. {
  165. DevicePixelRect right_border_rect = {
  166. context.rounded_device_pixels(cell_rect.x() + cell_rect.width() - round(borders_data.right.border_data.width / 2)),
  167. context.rounded_device_pixels(cell_rect.y() - round(borders_data.top.border_data.width / 2)),
  168. context.rounded_device_pixels(borders_data.right.border_data.width),
  169. context.rounded_device_pixels(cell_rect.height() + round(borders_data.top.border_data.width / 2) + round(borders_data.bottom.border_data.width / 2)),
  170. };
  171. return BorderEdgePaintingInfo {
  172. .rect = right_border_rect,
  173. .border_data_with_element_kind = borders_data.right,
  174. .direction = EdgeDirection::Vertical,
  175. .row = row_index_for_element_kind(coordinates.row_index, borders_data.right.element_kind),
  176. .column = column_index_for_element_kind(coordinates.column_index, borders_data.right.element_kind),
  177. };
  178. }
  179. static void paint_collected_edges(PaintContext& context, Vector<BorderEdgePaintingInfo>& border_edge_painting_info_list)
  180. {
  181. // This sorting step isn't part of the specification, but it matches the behavior of other browsers at border intersections, which aren't
  182. // part of border conflict resolution in the specification but it's still desirable to handle them in a way which is consistent with it.
  183. // See https://www.w3.org/TR/CSS22/tables.html#border-conflict-resolution for reference.
  184. quick_sort(border_edge_painting_info_list, [](auto const& a, auto const& b) {
  185. auto const& a_border_data = a.border_data_with_element_kind.border_data;
  186. auto const& b_border_data = b.border_data_with_element_kind.border_data;
  187. if (a_border_data.line_style == b_border_data.line_style && a_border_data.width == b_border_data.width) {
  188. if (b.border_data_with_element_kind.element_kind < a.border_data_with_element_kind.element_kind) {
  189. return true;
  190. } else if (b.border_data_with_element_kind.element_kind > a.border_data_with_element_kind.element_kind) {
  191. return false;
  192. }
  193. // Here the element kind is the same, thus the coordinates are either both set or not set.
  194. VERIFY(a.column.has_value() == b.column.has_value());
  195. VERIFY(a.row.has_value() == b.row.has_value());
  196. if (a.column.has_value()) {
  197. if (b.column.value() < a.column.value()) {
  198. return true;
  199. } else if (b.column.value() > a.column.value()) {
  200. return false;
  201. }
  202. }
  203. return a.row.has_value() ? b.row.value() < a.row.value() : false;
  204. }
  205. return Layout::TableFormattingContext::border_is_less_specific(a_border_data, b_border_data);
  206. });
  207. for (auto const& border_edge_painting_info : border_edge_painting_info_list) {
  208. auto const& border_data_with_element_kind = border_edge_painting_info.border_data_with_element_kind;
  209. CSSPixels width = border_data_with_element_kind.border_data.width;
  210. if (width <= 0)
  211. continue;
  212. auto color = border_data_with_element_kind.border_data.color;
  213. auto border_style = border_data_with_element_kind.border_data.line_style;
  214. auto p1 = border_edge_painting_info.rect.top_left();
  215. auto p2 = border_edge_painting_info.direction == EdgeDirection::Horizontal
  216. ? border_edge_painting_info.rect.top_right()
  217. : border_edge_painting_info.rect.bottom_left();
  218. if (border_style == CSS::LineStyle::Dotted) {
  219. Gfx::AntiAliasingPainter aa_painter { context.painter() };
  220. aa_painter.draw_line(p1.to_type<int>(), p2.to_type<int>(), color, width.to_double(), Gfx::Painter::LineStyle::Dotted);
  221. } else if (border_style == CSS::LineStyle::Dashed) {
  222. context.painter().draw_line(p1.to_type<int>(), p2.to_type<int>(), color, width.to_double(), Gfx::Painter::LineStyle::Dashed);
  223. } else {
  224. // FIXME: Support the remaining line styles instead of rendering them as solid.
  225. context.painter().fill_rect(Gfx::IntRect(border_edge_painting_info.rect.location(), border_edge_painting_info.rect.size()), color);
  226. }
  227. }
  228. }
  229. void paint_table_collapsed_borders(PaintContext& context, Layout::Node const& box)
  230. {
  231. // Partial implementation of painting according to the collapsing border model:
  232. // https://www.w3.org/TR/CSS22/tables.html#collapsing-borders
  233. Vector<PaintableBox const*> cell_boxes;
  234. collect_cell_boxes_with_collapsed_borders(cell_boxes, box);
  235. Vector<BorderEdgePaintingInfo> border_edge_painting_info_list;
  236. HashMap<CellCoordinates, PaintableBox const*> cell_coordinates_to_box;
  237. size_t row_count = 0;
  238. size_t column_count = 0;
  239. for (auto const cell_box : cell_boxes) {
  240. cell_coordinates_to_box.set(CellCoordinates {
  241. .row_index = cell_box->table_cell_coordinates()->row_index,
  242. .column_index = cell_box->table_cell_coordinates()->column_index },
  243. cell_box);
  244. row_count = max(row_count, cell_box->table_cell_coordinates()->row_index + cell_box->table_cell_coordinates()->row_span);
  245. column_count = max(column_count, cell_box->table_cell_coordinates()->column_index + cell_box->table_cell_coordinates()->column_span);
  246. }
  247. for (auto const cell_box : cell_boxes) {
  248. auto borders_data = cell_box->override_borders_data().has_value() ? cell_box->override_borders_data().value() : PaintableBox::BordersDataWithElementKind {
  249. .top = { .border_data = cell_box->box_model().border.top == 0 ? CSS::BorderData() : cell_box->computed_values().border_top(), .element_kind = PaintableBox::ConflictingElementKind::Cell },
  250. .right = { .border_data = cell_box->box_model().border.right == 0 ? CSS::BorderData() : cell_box->computed_values().border_right(), .element_kind = PaintableBox::ConflictingElementKind::Cell },
  251. .bottom = { .border_data = cell_box->box_model().border.bottom == 0 ? CSS::BorderData() : cell_box->computed_values().border_bottom(), .element_kind = PaintableBox::ConflictingElementKind::Cell },
  252. .left = { .border_data = cell_box->box_model().border.left == 0 ? CSS::BorderData() : cell_box->computed_values().border_left(), .element_kind = PaintableBox::ConflictingElementKind::Cell },
  253. };
  254. auto cell_rect = cell_box->absolute_border_box_rect();
  255. CellCoordinates right_cell_coordinates {
  256. .row_index = cell_box->table_cell_coordinates()->row_index,
  257. .column_index = cell_box->table_cell_coordinates()->column_index + cell_box->table_cell_coordinates()->column_span
  258. };
  259. auto maybe_right_cell = cell_coordinates_to_box.get(right_cell_coordinates);
  260. CellCoordinates down_cell_coordinates {
  261. .row_index = cell_box->table_cell_coordinates()->row_index + cell_box->table_cell_coordinates()->row_span,
  262. .column_index = cell_box->table_cell_coordinates()->column_index
  263. };
  264. auto maybe_down_cell = cell_coordinates_to_box.get(down_cell_coordinates);
  265. if (maybe_right_cell.has_value())
  266. 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));
  267. if (maybe_down_cell.has_value())
  268. 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));
  269. if (cell_box->table_cell_coordinates()->row_index == 0)
  270. border_edge_painting_info_list.append(make_first_row_top_cell_edge(context, cell_rect, borders_data,
  271. { .row_index = 0, .column_index = cell_box->table_cell_coordinates()->column_index }));
  272. if (cell_box->table_cell_coordinates()->row_index + cell_box->table_cell_coordinates()->row_span == row_count)
  273. border_edge_painting_info_list.append(make_last_row_bottom_cell_edge(context, cell_rect, borders_data,
  274. { .row_index = row_count - 1, .column_index = cell_box->table_cell_coordinates()->column_index }));
  275. if (cell_box->table_cell_coordinates()->column_index == 0)
  276. border_edge_painting_info_list.append(make_first_column_left_cell_edge(context, cell_rect, borders_data,
  277. { .row_index = cell_box->table_cell_coordinates()->row_index, .column_index = 0 }));
  278. if (cell_box->table_cell_coordinates()->column_index + cell_box->table_cell_coordinates()->column_span == column_count)
  279. border_edge_painting_info_list.append(make_last_column_right_cell_edge(context, cell_rect, borders_data,
  280. { .row_index = cell_box->table_cell_coordinates()->row_index, .column_index = column_count - 1 }));
  281. }
  282. paint_collected_edges(context, border_edge_painting_info_list);
  283. for (auto const cell_box : cell_boxes) {
  284. auto const& border_radii_data = cell_box->normalized_border_radii_data();
  285. auto top_left = border_radii_data.top_left.as_corner(context);
  286. auto top_right = border_radii_data.top_right.as_corner(context);
  287. auto bottom_right = border_radii_data.bottom_right.as_corner(context);
  288. auto bottom_left = border_radii_data.bottom_left.as_corner(context);
  289. if (!top_left && !top_right && !bottom_left && !bottom_right) {
  290. continue;
  291. } else {
  292. auto borders_data = cell_box->override_borders_data().has_value() ? PaintableBox::remove_element_kind_from_borders_data(cell_box->override_borders_data().value()) : BordersData {
  293. .top = cell_box->box_model().border.top == 0 ? CSS::BorderData() : cell_box->computed_values().border_top(),
  294. .right = cell_box->box_model().border.right == 0 ? CSS::BorderData() : cell_box->computed_values().border_right(),
  295. .bottom = cell_box->box_model().border.bottom == 0 ? CSS::BorderData() : cell_box->computed_values().border_bottom(),
  296. .left = cell_box->box_model().border.left == 0 ? CSS::BorderData() : cell_box->computed_values().border_left(),
  297. };
  298. paint_all_borders(context, cell_box->absolute_border_box_rect(), cell_box->normalized_border_radii_data(), borders_data);
  299. }
  300. }
  301. }
  302. }