GridFormattingContext.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. /*
  2. * Copyright (c) 2022, Martin Falisse <mfalisse@outlook.com>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include <LibWeb/DOM/Node.h>
  7. #include <LibWeb/Layout/Box.h>
  8. #include <LibWeb/Layout/GridFormattingContext.h>
  9. namespace Web::Layout {
  10. GridFormattingContext::GridFormattingContext(LayoutState& state, BlockContainer const& block_container, FormattingContext* parent)
  11. : BlockFormattingContext(state, block_container, parent)
  12. {
  13. }
  14. GridFormattingContext::~GridFormattingContext() = default;
  15. void GridFormattingContext::run(Box const& box, LayoutMode)
  16. {
  17. auto should_skip_is_anonymous_text_run = [&](Box& child_box) -> bool {
  18. if (child_box.is_anonymous() && !child_box.first_child_of_type<BlockContainer>()) {
  19. bool contains_only_white_space = true;
  20. child_box.for_each_in_subtree([&](auto const& node) {
  21. if (!is<TextNode>(node) || !static_cast<TextNode const&>(node).dom_node().data().is_whitespace()) {
  22. contains_only_white_space = false;
  23. return IterationDecision::Break;
  24. }
  25. return IterationDecision::Continue;
  26. });
  27. if (contains_only_white_space)
  28. return true;
  29. }
  30. return false;
  31. };
  32. auto maybe_add_column_to_occupation_grid = [](int needed_number_of_columns, Vector<Vector<bool>>& occupation_grid) -> void {
  33. int current_column_count = (int)occupation_grid[0].size();
  34. if (needed_number_of_columns <= current_column_count)
  35. return;
  36. for (auto& occupation_grid_row : occupation_grid)
  37. for (int idx = 0; idx < (needed_number_of_columns + 1) - current_column_count; idx++)
  38. occupation_grid_row.append(false);
  39. };
  40. auto maybe_add_row_to_occupation_grid = [](int needed_number_of_rows, Vector<Vector<bool>>& occupation_grid) -> void {
  41. if (needed_number_of_rows <= (int)occupation_grid.size())
  42. return;
  43. Vector<bool> new_occupation_grid_row;
  44. for (int idx = 0; idx < (int)occupation_grid[0].size(); idx++)
  45. new_occupation_grid_row.append(false);
  46. for (int idx = 0; idx < needed_number_of_rows - (int)occupation_grid.size(); idx++)
  47. occupation_grid.append(new_occupation_grid_row);
  48. };
  49. auto set_occupied_cells = [](int row_start, int row_end, int column_start, int column_end, Vector<Vector<bool>>& occupation_grid) -> void {
  50. for (int row_index = 0; row_index < (int)occupation_grid.size(); row_index++) {
  51. if (row_index >= row_start && row_index < row_end) {
  52. for (int column_index = 0; column_index < (int)occupation_grid[0].size(); column_index++) {
  53. if (column_index >= column_start && column_index < column_end) {
  54. occupation_grid[row_index][column_index] = true;
  55. }
  56. }
  57. }
  58. }
  59. };
  60. // https://drafts.csswg.org/css-grid/#overview-placement
  61. // 2.2. Placing Items
  62. // The contents of the grid container are organized into individual grid items (analogous to
  63. // flex items), which are then assigned to predefined areas in the grid. They can be explicitly
  64. // placed using coordinates through the grid-placement properties or implicitly placed into
  65. // empty areas using auto-placement.
  66. struct PositionedBox {
  67. Box const& box;
  68. int row { 0 };
  69. int row_span { 1 };
  70. int column { 0 };
  71. int column_span { 1 };
  72. float computed_height { 0 };
  73. };
  74. Vector<PositionedBox> positioned_boxes;
  75. Vector<Vector<bool>> occupation_grid;
  76. Vector<bool> occupation_grid_row;
  77. for (int column_index = 0; column_index < max((int)box.computed_values().grid_template_columns().size(), 1); column_index++)
  78. occupation_grid_row.append(false);
  79. for (int row_index = 0; row_index < max((int)box.computed_values().grid_template_rows().size(), 1); row_index++)
  80. occupation_grid.append(occupation_grid_row);
  81. Vector<Box const&> boxes_to_place;
  82. box.for_each_child_of_type<Box>([&](Box& child_box) {
  83. if (should_skip_is_anonymous_text_run(child_box))
  84. return IterationDecision::Continue;
  85. boxes_to_place.append(child_box);
  86. return IterationDecision::Continue;
  87. });
  88. // https://drafts.csswg.org/css-grid/#auto-placement-algo
  89. // 8.5. Grid Item Placement Algorithm
  90. // FIXME: 0. Generate anonymous grid items
  91. // 1. Position anything that's not auto-positioned.
  92. for (size_t i = 0; i < boxes_to_place.size(); i++) {
  93. auto const& child_box = boxes_to_place[i];
  94. if (child_box.computed_values().grid_row_start().is_auto()
  95. || child_box.computed_values().grid_row_end().is_auto()
  96. || child_box.computed_values().grid_column_start().is_auto()
  97. || child_box.computed_values().grid_column_end().is_auto())
  98. continue;
  99. int row_start = child_box.computed_values().grid_row_start().position();
  100. int row_end = child_box.computed_values().grid_row_end().position();
  101. int column_start = child_box.computed_values().grid_column_start().position();
  102. int column_end = child_box.computed_values().grid_column_end().position();
  103. int row_span = 1;
  104. int column_span = 1;
  105. // https://drafts.csswg.org/css-grid/#grid-placement-int
  106. // [ <integer [−∞,−1]> | <integer [1,∞]> ] && <custom-ident>?
  107. // Contributes the Nth grid line to the grid item’s placement. If a negative integer is given, it
  108. // instead counts in reverse, starting from the end edge of the explicit grid.
  109. if (row_end < 0)
  110. row_end = static_cast<int>(occupation_grid.size()) + row_end + 2;
  111. if (column_end < 0)
  112. column_end = static_cast<int>(occupation_grid[0].size()) + column_end + 2;
  113. // FIXME: If a name is given as a <custom-ident>, only lines with that name are counted. If not enough
  114. // lines with that name exist, all implicit grid lines are assumed to have that name for the purpose
  115. // of finding this position.
  116. // FIXME: An <integer> value of zero makes the declaration invalid.
  117. // https://drafts.csswg.org/css-grid/#grid-placement-errors
  118. // 8.3.1. Grid Placement Conflict Handling
  119. // If the placement for a grid item contains two lines, and the start line is further end-ward than
  120. // the end line, swap the two lines. If the start line is equal to the end line, remove the end
  121. // line.
  122. if (row_start > row_end) {
  123. auto temp = row_end;
  124. row_end = row_start;
  125. row_start = temp;
  126. }
  127. if (column_start > column_end) {
  128. auto temp = column_end;
  129. column_end = column_start;
  130. column_start = temp;
  131. }
  132. if (row_start != row_end)
  133. row_span = row_end - row_start;
  134. if (column_start != column_end)
  135. column_span = column_end - column_start;
  136. // FIXME: If the placement contains two spans, remove the one contributed by the end grid-placement
  137. // property.
  138. // FIXME: If the placement contains only a span for a named line, replace it with a span of 1.
  139. row_start -= 1;
  140. column_start -= 1;
  141. positioned_boxes.append({ child_box, row_start, row_span, column_start, column_span });
  142. maybe_add_row_to_occupation_grid(row_start + row_span, occupation_grid);
  143. maybe_add_column_to_occupation_grid(column_start + column_span, occupation_grid);
  144. set_occupied_cells(row_start, row_start + row_span, column_start, column_start + column_span, occupation_grid);
  145. boxes_to_place.remove(i);
  146. i--;
  147. }
  148. // 2. Process the items locked to a given row.
  149. // FIXME: Do "dense" packing
  150. for (size_t i = 0; i < boxes_to_place.size(); i++) {
  151. auto const& child_box = boxes_to_place[i];
  152. if (child_box.computed_values().grid_row_start().is_auto()
  153. || child_box.computed_values().grid_row_end().is_auto())
  154. continue;
  155. int row_start = child_box.computed_values().grid_row_start().position();
  156. int row_end = child_box.computed_values().grid_row_end().position();
  157. int row_span = 1;
  158. // https://drafts.csswg.org/css-grid/#grid-placement-int
  159. // [ <integer [−∞,−1]> | <integer [1,∞]> ] && <custom-ident>?
  160. // Contributes the Nth grid line to the grid item’s placement. If a negative integer is given, it
  161. // instead counts in reverse, starting from the end edge of the explicit grid.
  162. if (row_end < 0)
  163. row_end = static_cast<int>(occupation_grid.size()) + row_end + 2;
  164. // FIXME: If a name is given as a <custom-ident>, only lines with that name are counted. If not enough
  165. // lines with that name exist, all implicit grid lines are assumed to have that name for the purpose
  166. // of finding this position.
  167. // FIXME: An <integer> value of zero makes the declaration invalid.
  168. // https://drafts.csswg.org/css-grid/#grid-placement-errors
  169. // 8.3.1. Grid Placement Conflict Handling
  170. // If the placement for a grid item contains two lines, and the start line is further end-ward than
  171. // the end line, swap the two lines. If the start line is equal to the end line, remove the end
  172. // line.
  173. if (row_start > row_end) {
  174. auto temp = row_end;
  175. row_end = row_start;
  176. row_start = temp;
  177. }
  178. if (row_start != row_end)
  179. row_span = row_end - row_start;
  180. // FIXME: If the placement contains two spans, remove the one contributed by the end grid-placement
  181. // property.
  182. // FIXME: If the placement contains only a span for a named line, replace it with a span of 1.
  183. row_start -= 1;
  184. maybe_add_row_to_occupation_grid(row_start + row_span, occupation_grid);
  185. int column_start = 0;
  186. int column_span = 1;
  187. bool found_available_column = false;
  188. for (int column_index = column_start; column_index < (int)occupation_grid[0].size(); column_index++) {
  189. if (!occupation_grid[0][column_index]) {
  190. found_available_column = true;
  191. column_start = column_index;
  192. break;
  193. }
  194. }
  195. if (!found_available_column) {
  196. column_start = occupation_grid[0].size();
  197. maybe_add_column_to_occupation_grid(column_start + column_span, occupation_grid);
  198. }
  199. set_occupied_cells(row_start, row_start + row_span, column_start, column_start + column_span, occupation_grid);
  200. positioned_boxes.append({ child_box, row_start, row_span, column_start, column_span });
  201. boxes_to_place.remove(i);
  202. i--;
  203. }
  204. // 3. Determine the columns in the implicit grid.
  205. // 3.1. Start with the columns from the explicit grid.
  206. // 3.2. Among all the items with a definite column position (explicitly positioned items, items
  207. // positioned in the previous step, and items not yet positioned but with a definite column) add
  208. // columns to the beginning and end of the implicit grid as necessary to accommodate those items.
  209. // NOTE: "Explicitly positioned items" and "items positioned in the previous step" done in step 1
  210. // and 2, respectively. Adding columns for "items not yet positioned but with a definite column"
  211. // will be done in step 4.
  212. // 3.3. If the largest column span among all the items without a definite column position is larger
  213. // than the width of the implicit grid, add columns to the end of the implicit grid to accommodate
  214. // that column span.
  215. // 4. Position the remaining grid items.
  216. // For each grid item that hasn't been positioned by the previous steps, in order-modified document
  217. // order:
  218. }