TableBordersPainting.cpp 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445
  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(Vector<PaintableBox const*>& cell_boxes, Layout::Node const& box)
  28. {
  29. box.for_each_child([&](auto& child) {
  30. if (child.display().is_table_cell()) {
  31. VERIFY(is<Layout::Box>(child));
  32. if (child.paintable())
  33. cell_boxes.append(static_cast<Layout::Box const&>(child).paintable_box());
  34. } else {
  35. collect_cell_boxes(cell_boxes, child);
  36. }
  37. });
  38. }
  39. enum class EdgeDirection {
  40. Horizontal,
  41. Vertical,
  42. };
  43. struct DeviceBorderData {
  44. Color color { Color::Transparent };
  45. CSS::LineStyle line_style { CSS::LineStyle::None };
  46. DevicePixels width { 0 };
  47. };
  48. struct DeviceBorderDataWithElementKind {
  49. DeviceBorderData border_data;
  50. Painting::PaintableBox::ConflictingElementKind element_kind { Painting::PaintableBox::ConflictingElementKind::Cell };
  51. };
  52. struct DeviceBordersDataWithElementKind {
  53. DeviceBorderDataWithElementKind top;
  54. DeviceBorderDataWithElementKind right;
  55. DeviceBorderDataWithElementKind bottom;
  56. DeviceBorderDataWithElementKind left;
  57. };
  58. struct BorderEdgePaintingInfo {
  59. DevicePixelRect rect;
  60. DeviceBorderDataWithElementKind border_data_with_element_kind;
  61. EdgeDirection direction;
  62. Optional<size_t> row;
  63. Optional<size_t> column;
  64. };
  65. static Optional<size_t> row_index_for_element_kind(size_t index, Painting::PaintableBox::ConflictingElementKind element_kind)
  66. {
  67. switch (element_kind) {
  68. case Painting::PaintableBox::ConflictingElementKind::Cell:
  69. case Painting::PaintableBox::ConflictingElementKind::Row:
  70. case Painting::PaintableBox::ConflictingElementKind::RowGroup: {
  71. return index;
  72. }
  73. default:
  74. return {};
  75. }
  76. }
  77. static Optional<size_t> column_index_for_element_kind(size_t index, Painting::PaintableBox::ConflictingElementKind element_kind)
  78. {
  79. switch (element_kind) {
  80. case Painting::PaintableBox::ConflictingElementKind::Cell:
  81. case Painting::PaintableBox::ConflictingElementKind::Column:
  82. case Painting::PaintableBox::ConflictingElementKind::ColumnGroup: {
  83. return index;
  84. }
  85. default:
  86. return {};
  87. }
  88. }
  89. static DevicePixels half_ceil(DevicePixels width)
  90. {
  91. return ceil(static_cast<double>(width.value()) / 2);
  92. }
  93. static DevicePixels half_floor(DevicePixels width)
  94. {
  95. return floor(static_cast<double>(width.value()) / 2);
  96. }
  97. static BorderEdgePaintingInfo make_right_cell_edge(
  98. DevicePixelRect const& right_cell_rect,
  99. DevicePixelRect const& cell_rect,
  100. DeviceBordersDataWithElementKind const& borders_data,
  101. CellCoordinates const& coordinates)
  102. {
  103. auto connect_top_offset = half_ceil(borders_data.top.border_data.width);
  104. auto connect_excess_height = connect_top_offset + half_floor(borders_data.bottom.border_data.width);
  105. DevicePixelRect right_border_rect = {
  106. right_cell_rect.x() - half_ceil(borders_data.right.border_data.width),
  107. cell_rect.y() - connect_top_offset,
  108. borders_data.right.border_data.width,
  109. max(cell_rect.height(), right_cell_rect.height()) + connect_excess_height,
  110. };
  111. return BorderEdgePaintingInfo {
  112. .rect = right_border_rect,
  113. .border_data_with_element_kind = borders_data.right,
  114. .direction = EdgeDirection::Vertical,
  115. .row = row_index_for_element_kind(coordinates.row_index, borders_data.right.element_kind),
  116. .column = column_index_for_element_kind(coordinates.column_index, borders_data.right.element_kind),
  117. };
  118. }
  119. static BorderEdgePaintingInfo make_down_cell_edge(
  120. DevicePixelRect const& down_cell_rect,
  121. DevicePixelRect const& cell_rect,
  122. DeviceBordersDataWithElementKind const& borders_data,
  123. CellCoordinates const& coordinates)
  124. {
  125. auto connect_left_offset = half_ceil(borders_data.left.border_data.width);
  126. auto connect_excess_width = connect_left_offset + half_floor(borders_data.right.border_data.width);
  127. DevicePixelRect down_border_rect = {
  128. cell_rect.x() - connect_left_offset,
  129. down_cell_rect.y() - half_ceil(borders_data.bottom.border_data.width),
  130. max(cell_rect.width(), down_cell_rect.width()) + connect_excess_width,
  131. borders_data.bottom.border_data.width,
  132. };
  133. return BorderEdgePaintingInfo {
  134. .rect = down_border_rect,
  135. .border_data_with_element_kind = borders_data.bottom,
  136. .direction = EdgeDirection::Horizontal,
  137. .row = row_index_for_element_kind(coordinates.row_index, borders_data.bottom.element_kind),
  138. .column = column_index_for_element_kind(coordinates.column_index, borders_data.bottom.element_kind),
  139. };
  140. }
  141. static BorderEdgePaintingInfo make_first_row_top_cell_edge(DevicePixelRect const& cell_rect, DeviceBordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates)
  142. {
  143. auto connect_left_offset = half_ceil(borders_data.left.border_data.width.value());
  144. auto connect_excess_width = connect_left_offset + half_floor(borders_data.right.border_data.width.value());
  145. DevicePixelRect top_border_rect = {
  146. cell_rect.x() - connect_left_offset,
  147. cell_rect.y() - half_ceil(borders_data.top.border_data.width.value()),
  148. cell_rect.width() + connect_excess_width,
  149. borders_data.top.border_data.width,
  150. };
  151. return BorderEdgePaintingInfo {
  152. .rect = top_border_rect,
  153. .border_data_with_element_kind = borders_data.top,
  154. .direction = EdgeDirection::Horizontal,
  155. .row = row_index_for_element_kind(coordinates.row_index, borders_data.top.element_kind),
  156. .column = column_index_for_element_kind(coordinates.column_index, borders_data.top.element_kind),
  157. };
  158. }
  159. static BorderEdgePaintingInfo make_last_row_bottom_cell_edge(DevicePixelRect const& cell_rect, DeviceBordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates)
  160. {
  161. auto connect_left_offset = half_ceil(borders_data.left.border_data.width);
  162. auto connect_excess_width = connect_left_offset + half_floor(borders_data.right.border_data.width);
  163. DevicePixelRect bottom_border_rect = {
  164. cell_rect.x() - connect_left_offset,
  165. cell_rect.y() + cell_rect.height() - half_ceil(borders_data.bottom.border_data.width),
  166. cell_rect.width() + connect_excess_width,
  167. borders_data.bottom.border_data.width,
  168. };
  169. return BorderEdgePaintingInfo {
  170. .rect = bottom_border_rect,
  171. .border_data_with_element_kind = borders_data.bottom,
  172. .direction = EdgeDirection::Horizontal,
  173. .row = row_index_for_element_kind(coordinates.row_index, borders_data.bottom.element_kind),
  174. .column = column_index_for_element_kind(coordinates.column_index, borders_data.bottom.element_kind),
  175. };
  176. }
  177. static BorderEdgePaintingInfo make_first_column_left_cell_edge(DevicePixelRect const& cell_rect, DeviceBordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates)
  178. {
  179. auto connect_top_offset = half_ceil(borders_data.top.border_data.width);
  180. auto connect_excess_height = connect_top_offset + half_floor(borders_data.bottom.border_data.width);
  181. DevicePixelRect left_border_rect = {
  182. cell_rect.x() - half_ceil(borders_data.left.border_data.width),
  183. cell_rect.y() - connect_top_offset,
  184. borders_data.left.border_data.width,
  185. cell_rect.height() + connect_excess_height,
  186. };
  187. return BorderEdgePaintingInfo {
  188. .rect = left_border_rect,
  189. .border_data_with_element_kind = borders_data.left,
  190. .direction = EdgeDirection::Vertical,
  191. .row = row_index_for_element_kind(coordinates.row_index, borders_data.left.element_kind),
  192. .column = column_index_for_element_kind(coordinates.column_index, borders_data.left.element_kind),
  193. };
  194. }
  195. static BorderEdgePaintingInfo make_last_column_right_cell_edge(DevicePixelRect const& cell_rect, DeviceBordersDataWithElementKind const& borders_data, CellCoordinates const& coordinates)
  196. {
  197. auto connect_top_offset = half_ceil(borders_data.top.border_data.width);
  198. auto connect_excess_height = connect_top_offset + half_floor(borders_data.bottom.border_data.width);
  199. DevicePixelRect right_border_rect = {
  200. cell_rect.x() + cell_rect.width() - half_ceil(borders_data.right.border_data.width),
  201. cell_rect.y() - connect_top_offset,
  202. borders_data.right.border_data.width,
  203. cell_rect.height() + connect_excess_height,
  204. };
  205. return BorderEdgePaintingInfo {
  206. .rect = right_border_rect,
  207. .border_data_with_element_kind = borders_data.right,
  208. .direction = EdgeDirection::Vertical,
  209. .row = row_index_for_element_kind(coordinates.row_index, borders_data.right.element_kind),
  210. .column = column_index_for_element_kind(coordinates.column_index, borders_data.right.element_kind),
  211. };
  212. }
  213. static CSS::BorderData css_border_data_from_device_border_data(DeviceBorderData const& device_border_data)
  214. {
  215. return CSS::BorderData {
  216. .color = device_border_data.color,
  217. .line_style = device_border_data.line_style,
  218. .width = device_border_data.width.value(),
  219. };
  220. }
  221. static void paint_collected_edges(PaintContext& context, Vector<BorderEdgePaintingInfo>& border_edge_painting_info_list)
  222. {
  223. // This sorting step isn't part of the specification, but it matches the behavior of other browsers at border intersections, which aren't
  224. // part of border conflict resolution in the specification but it's still desirable to handle them in a way which is consistent with it.
  225. // See https://www.w3.org/TR/CSS22/tables.html#border-conflict-resolution for reference.
  226. quick_sort(border_edge_painting_info_list, [](auto const& a, auto const& b) {
  227. auto const& a_border_data = a.border_data_with_element_kind.border_data;
  228. auto const& b_border_data = b.border_data_with_element_kind.border_data;
  229. if (a_border_data.line_style == b_border_data.line_style && a_border_data.width == b_border_data.width) {
  230. if (b.border_data_with_element_kind.element_kind < a.border_data_with_element_kind.element_kind) {
  231. return true;
  232. } else if (b.border_data_with_element_kind.element_kind > a.border_data_with_element_kind.element_kind) {
  233. return false;
  234. }
  235. // Here the element kind is the same, thus the coordinates are either both set or not set.
  236. VERIFY(a.column.has_value() == b.column.has_value());
  237. VERIFY(a.row.has_value() == b.row.has_value());
  238. if (a.column.has_value()) {
  239. if (b.column.value() < a.column.value()) {
  240. return true;
  241. } else if (b.column.value() > a.column.value()) {
  242. return false;
  243. }
  244. }
  245. return a.row.has_value() ? b.row.value() < a.row.value() : false;
  246. }
  247. return Layout::TableFormattingContext::border_is_less_specific(
  248. css_border_data_from_device_border_data(a_border_data),
  249. css_border_data_from_device_border_data(b_border_data));
  250. });
  251. for (auto const& border_edge_painting_info : border_edge_painting_info_list) {
  252. auto const& border_data_with_element_kind = border_edge_painting_info.border_data_with_element_kind;
  253. auto width = border_data_with_element_kind.border_data.width;
  254. if (width <= 0)
  255. continue;
  256. auto color = border_data_with_element_kind.border_data.color;
  257. auto border_style = border_data_with_element_kind.border_data.line_style;
  258. auto p1 = border_edge_painting_info.rect.top_left();
  259. auto p2 = border_edge_painting_info.direction == EdgeDirection::Horizontal
  260. ? border_edge_painting_info.rect.top_right()
  261. : border_edge_painting_info.rect.bottom_left();
  262. if (border_style == CSS::LineStyle::Dotted) {
  263. Gfx::AntiAliasingPainter aa_painter { context.painter() };
  264. aa_painter.draw_line(p1.to_type<int>(), p2.to_type<int>(), color, width.value(), Gfx::Painter::LineStyle::Dotted);
  265. } else if (border_style == CSS::LineStyle::Dashed) {
  266. context.painter().draw_line(p1.to_type<int>(), p2.to_type<int>(), color, width.value(), Gfx::Painter::LineStyle::Dashed);
  267. } else {
  268. // FIXME: Support the remaining line styles instead of rendering them as solid.
  269. context.painter().fill_rect(Gfx::IntRect(border_edge_painting_info.rect.location(), border_edge_painting_info.rect.size()), color);
  270. }
  271. }
  272. }
  273. static HashMap<CellCoordinates, DevicePixelRect> snap_cells_to_device_coordinates(HashMap<CellCoordinates, PaintableBox const*> const& cell_coordinates_to_box, size_t row_count, size_t column_count, PaintContext const& context)
  274. {
  275. Vector<DevicePixels> y_line_start_coordinates;
  276. Vector<DevicePixels> y_line_end_coordinates;
  277. y_line_start_coordinates.resize(row_count + 1);
  278. y_line_end_coordinates.resize(row_count + 1);
  279. Vector<DevicePixels> x_line_start_coordinates;
  280. Vector<DevicePixels> x_line_end_coordinates;
  281. x_line_start_coordinates.resize(column_count + 1);
  282. x_line_end_coordinates.resize(column_count + 1);
  283. for (auto const& kv : cell_coordinates_to_box) {
  284. auto const& cell_box = kv.value;
  285. auto start_row_index = cell_box->table_cell_coordinates()->row_index;
  286. auto end_row_index = start_row_index + cell_box->table_cell_coordinates()->row_span;
  287. auto cell_rect = cell_box->absolute_border_box_rect();
  288. y_line_start_coordinates[start_row_index] = max(context.rounded_device_pixels(cell_rect.y()), y_line_start_coordinates[start_row_index]);
  289. y_line_end_coordinates[end_row_index] = max(context.rounded_device_pixels(cell_rect.y() + cell_rect.height()), y_line_end_coordinates[end_row_index]);
  290. auto start_column_index = cell_box->table_cell_coordinates()->column_index;
  291. auto end_column_index = start_column_index + cell_box->table_cell_coordinates()->column_span;
  292. x_line_start_coordinates[start_column_index] = max(context.rounded_device_pixels(cell_rect.x()), x_line_start_coordinates[start_column_index]);
  293. x_line_end_coordinates[end_column_index] = max(context.rounded_device_pixels(cell_rect.x() + cell_rect.width()), x_line_end_coordinates[end_column_index]);
  294. }
  295. HashMap<CellCoordinates, DevicePixelRect> cell_coordinates_to_device_rect;
  296. for (auto const& kv : cell_coordinates_to_box) {
  297. auto const& cell_box = kv.value;
  298. auto start_row_index = cell_box->table_cell_coordinates()->row_index;
  299. auto end_row_index = start_row_index + cell_box->table_cell_coordinates()->row_span;
  300. auto height = y_line_end_coordinates[end_row_index] - y_line_start_coordinates[start_row_index];
  301. auto start_column_index = cell_box->table_cell_coordinates()->column_index;
  302. auto end_column_index = start_column_index + cell_box->table_cell_coordinates()->column_span;
  303. auto width = x_line_end_coordinates[end_column_index] - x_line_start_coordinates[start_column_index];
  304. cell_coordinates_to_device_rect.set(kv.key, DevicePixelRect { x_line_start_coordinates[start_column_index], y_line_start_coordinates[start_row_index], width, height });
  305. }
  306. return cell_coordinates_to_device_rect;
  307. }
  308. static DeviceBorderDataWithElementKind device_border_data_from_css_border_data(Painting::PaintableBox::BorderDataWithElementKind const& border_data_with_element_kind, PaintContext const& context)
  309. {
  310. return DeviceBorderDataWithElementKind {
  311. .border_data = {
  312. .color = border_data_with_element_kind.border_data.color,
  313. .line_style = border_data_with_element_kind.border_data.line_style,
  314. .width = context.rounded_device_pixels(border_data_with_element_kind.border_data.width),
  315. },
  316. .element_kind = border_data_with_element_kind.element_kind,
  317. };
  318. }
  319. static void paint_separate_cell_borders(PaintableBox const* cell_box, HashMap<CellCoordinates, DevicePixelRect> const& cell_coordinates_to_device_rect, PaintContext& context)
  320. {
  321. auto borders_data = cell_box->override_borders_data().has_value() ? PaintableBox::remove_element_kind_from_borders_data(cell_box->override_borders_data().value()) : BordersData {
  322. .top = cell_box->box_model().border.top == 0 ? CSS::BorderData() : cell_box->computed_values().border_top(),
  323. .right = cell_box->box_model().border.right == 0 ? CSS::BorderData() : cell_box->computed_values().border_right(),
  324. .bottom = cell_box->box_model().border.bottom == 0 ? CSS::BorderData() : cell_box->computed_values().border_bottom(),
  325. .left = cell_box->box_model().border.left == 0 ? CSS::BorderData() : cell_box->computed_values().border_left(),
  326. };
  327. auto cell_rect = cell_coordinates_to_device_rect.get({ cell_box->table_cell_coordinates()->row_index, cell_box->table_cell_coordinates()->column_index }).value();
  328. paint_all_borders(context, cell_rect, cell_box->normalized_border_radii_data(), borders_data);
  329. }
  330. void paint_table_borders(PaintContext& context, Layout::Node const& box)
  331. {
  332. // Partial implementation of painting according to the collapsing border model:
  333. // https://www.w3.org/TR/CSS22/tables.html#collapsing-borders
  334. Vector<PaintableBox const*> cell_boxes;
  335. collect_cell_boxes(cell_boxes, box);
  336. Vector<BorderEdgePaintingInfo> border_edge_painting_info_list;
  337. HashMap<CellCoordinates, PaintableBox const*> cell_coordinates_to_box;
  338. size_t row_count = 0;
  339. size_t column_count = 0;
  340. for (auto const cell_box : cell_boxes) {
  341. cell_coordinates_to_box.set(CellCoordinates {
  342. .row_index = cell_box->table_cell_coordinates()->row_index,
  343. .column_index = cell_box->table_cell_coordinates()->column_index },
  344. cell_box);
  345. row_count = max(row_count, cell_box->table_cell_coordinates()->row_index + cell_box->table_cell_coordinates()->row_span);
  346. column_count = max(column_count, cell_box->table_cell_coordinates()->column_index + cell_box->table_cell_coordinates()->column_span);
  347. }
  348. auto cell_coordinates_to_device_rect = snap_cells_to_device_coordinates(cell_coordinates_to_box, row_count, column_count, context);
  349. for (auto const cell_box : cell_boxes) {
  350. if (cell_box->computed_values().border_collapse() == CSS::BorderCollapse::Separate) {
  351. paint_separate_cell_borders(cell_box, cell_coordinates_to_device_rect, context);
  352. continue;
  353. }
  354. auto css_borders_data = cell_box->override_borders_data().has_value() ? cell_box->override_borders_data().value() : PaintableBox::BordersDataWithElementKind {
  355. .top = { .border_data = cell_box->box_model().border.top == 0 ? CSS::BorderData() : cell_box->computed_values().border_top(), .element_kind = PaintableBox::ConflictingElementKind::Cell },
  356. .right = { .border_data = cell_box->box_model().border.right == 0 ? CSS::BorderData() : cell_box->computed_values().border_right(), .element_kind = PaintableBox::ConflictingElementKind::Cell },
  357. .bottom = { .border_data = cell_box->box_model().border.bottom == 0 ? CSS::BorderData() : cell_box->computed_values().border_bottom(), .element_kind = PaintableBox::ConflictingElementKind::Cell },
  358. .left = { .border_data = cell_box->box_model().border.left == 0 ? CSS::BorderData() : cell_box->computed_values().border_left(), .element_kind = PaintableBox::ConflictingElementKind::Cell },
  359. };
  360. DeviceBordersDataWithElementKind borders_data = {
  361. .top = device_border_data_from_css_border_data(css_borders_data.top, context),
  362. .right = device_border_data_from_css_border_data(css_borders_data.right, context),
  363. .bottom = device_border_data_from_css_border_data(css_borders_data.bottom, context),
  364. .left = device_border_data_from_css_border_data(css_borders_data.left, context),
  365. };
  366. auto cell_rect = cell_coordinates_to_device_rect.get({ cell_box->table_cell_coordinates()->row_index, cell_box->table_cell_coordinates()->column_index }).value();
  367. CellCoordinates right_cell_coordinates {
  368. .row_index = cell_box->table_cell_coordinates()->row_index,
  369. .column_index = cell_box->table_cell_coordinates()->column_index + cell_box->table_cell_coordinates()->column_span
  370. };
  371. auto maybe_right_cell = cell_coordinates_to_device_rect.get(right_cell_coordinates);
  372. CellCoordinates down_cell_coordinates {
  373. .row_index = cell_box->table_cell_coordinates()->row_index + cell_box->table_cell_coordinates()->row_span,
  374. .column_index = cell_box->table_cell_coordinates()->column_index
  375. };
  376. auto maybe_down_cell = cell_coordinates_to_device_rect.get(down_cell_coordinates);
  377. if (maybe_right_cell.has_value())
  378. border_edge_painting_info_list.append(make_right_cell_edge(maybe_right_cell.value(), cell_rect, borders_data, right_cell_coordinates));
  379. if (maybe_down_cell.has_value())
  380. border_edge_painting_info_list.append(make_down_cell_edge(maybe_down_cell.value(), cell_rect, borders_data, down_cell_coordinates));
  381. if (cell_box->table_cell_coordinates()->row_index == 0)
  382. border_edge_painting_info_list.append(make_first_row_top_cell_edge(cell_rect, borders_data,
  383. { .row_index = 0, .column_index = cell_box->table_cell_coordinates()->column_index }));
  384. if (cell_box->table_cell_coordinates()->row_index + cell_box->table_cell_coordinates()->row_span == row_count)
  385. border_edge_painting_info_list.append(make_last_row_bottom_cell_edge(cell_rect, borders_data,
  386. { .row_index = row_count - 1, .column_index = cell_box->table_cell_coordinates()->column_index }));
  387. if (cell_box->table_cell_coordinates()->column_index == 0)
  388. border_edge_painting_info_list.append(make_first_column_left_cell_edge(cell_rect, borders_data,
  389. { .row_index = cell_box->table_cell_coordinates()->row_index, .column_index = 0 }));
  390. if (cell_box->table_cell_coordinates()->column_index + cell_box->table_cell_coordinates()->column_span == column_count)
  391. border_edge_painting_info_list.append(make_last_column_right_cell_edge(cell_rect, borders_data,
  392. { .row_index = cell_box->table_cell_coordinates()->row_index, .column_index = column_count - 1 }));
  393. }
  394. paint_collected_edges(context, border_edge_painting_info_list);
  395. for (auto const cell_box : cell_boxes) {
  396. auto const& border_radii_data = cell_box->normalized_border_radii_data();
  397. auto top_left = border_radii_data.top_left.as_corner(context);
  398. auto top_right = border_radii_data.top_right.as_corner(context);
  399. auto bottom_right = border_radii_data.bottom_right.as_corner(context);
  400. auto bottom_left = border_radii_data.bottom_left.as_corner(context);
  401. if (!top_left && !top_right && !bottom_left && !bottom_right) {
  402. continue;
  403. } else {
  404. auto borders_data = cell_box->override_borders_data().has_value() ? PaintableBox::remove_element_kind_from_borders_data(cell_box->override_borders_data().value()) : BordersData {
  405. .top = cell_box->box_model().border.top == 0 ? CSS::BorderData() : cell_box->computed_values().border_top(),
  406. .right = cell_box->box_model().border.right == 0 ? CSS::BorderData() : cell_box->computed_values().border_right(),
  407. .bottom = cell_box->box_model().border.bottom == 0 ? CSS::BorderData() : cell_box->computed_values().border_bottom(),
  408. .left = cell_box->box_model().border.left == 0 ? CSS::BorderData() : cell_box->computed_values().border_left(),
  409. };
  410. paint_all_borders(context, context.rounded_device_rect(cell_box->absolute_border_box_rect()), cell_box->normalized_border_radii_data(), borders_data);
  411. }
  412. }
  413. }
  414. }