diff --git a/Tests/LibWeb/Layout/expected/grid/negative-grid-item-column-index.txt b/Tests/LibWeb/Layout/expected/grid/negative-grid-item-column-index.txt
new file mode 100644
index 0000000000..eb31b414ef
--- /dev/null
+++ b/Tests/LibWeb/Layout/expected/grid/negative-grid-item-column-index.txt
@@ -0,0 +1,71 @@
+Viewport <#document> at (0,0) content-size 800x600 children: not-inline
+ BlockContainer at (0,0) content-size 800x600 [BFC] children: not-inline
+ BlockContainer
at (8,8) content-size 784x69.875 children: not-inline
+ Box at (8,8) content-size 784x69.875 [GFC] children: not-inline
+ BlockContainer <(anonymous)> (not painted) [BFC] children: inline
+ TextNode <#text>
+ BlockContainer at (8,8) content-size 196x17.46875 [BFC] children: inline
+ line 0 width: 9.34375, height: 17.46875, bottom: 17.46875, baseline: 13.53125
+ frag 0 from TextNode start: 0, length: 1, rect: [8,8 9.34375x17.46875]
+ "a"
+ TextNode <#text>
+ BlockContainer <(anonymous)> (not painted) [BFC] children: inline
+ TextNode <#text>
+ BlockContainer at (204,8) content-size 196x17.46875 [BFC] children: inline
+ line 0 width: 8.890625, height: 17.46875, bottom: 17.46875, baseline: 13.53125
+ frag 0 from TextNode start: 0, length: 1, rect: [204,8 8.890625x17.46875]
+ "c"
+ TextNode <#text>
+ BlockContainer <(anonymous)> (not painted) [BFC] children: inline
+ TextNode <#text>
+ BlockContainer at (400,8) content-size 196x17.46875 [BFC] children: inline
+ line 0 width: 9.46875, height: 17.46875, bottom: 17.46875, baseline: 13.53125
+ frag 0 from TextNode start: 0, length: 1, rect: [400,8 9.46875x17.46875]
+ "b"
+ TextNode <#text>
+ BlockContainer <(anonymous)> (not painted) [BFC] children: inline
+ TextNode <#text>
+ BlockContainer at (400,25.46875) content-size 196x17.46875 [BFC] children: inline
+ line 0 width: 9.46875, height: 17.46875, bottom: 17.46875, baseline: 13.53125
+ frag 0 from TextNode start: 0, length: 1, rect: [400,25.46875 9.46875x17.46875]
+ "b"
+ TextNode <#text>
+ BlockContainer <(anonymous)> (not painted) [BFC] children: inline
+ TextNode <#text>
+ BlockContainer at (596,25.46875) content-size 196x17.46875 [BFC] children: inline
+ line 0 width: 7.859375, height: 17.46875, bottom: 17.46875, baseline: 13.53125
+ frag 0 from TextNode start: 0, length: 1, rect: [596,25.46875 7.859375x17.46875]
+ "d"
+ TextNode <#text>
+ BlockContainer <(anonymous)> (not painted) [BFC] children: inline
+ TextNode <#text>
+ BlockContainer at (204,42.9375) content-size 196x17.46875 [BFC] children: inline
+ line 0 width: 8.890625, height: 17.46875, bottom: 17.46875, baseline: 13.53125
+ frag 0 from TextNode start: 0, length: 1, rect: [204,42.9375 8.890625x17.46875]
+ "c"
+ TextNode <#text>
+ BlockContainer <(anonymous)> (not painted) [BFC] children: inline
+ TextNode <#text>
+ BlockContainer at (596,42.9375) content-size 196x17.46875 [BFC] children: inline
+ line 0 width: 7.859375, height: 17.46875, bottom: 17.46875, baseline: 13.53125
+ frag 0 from TextNode start: 0, length: 1, rect: [596,42.9375 7.859375x17.46875]
+ "d"
+ TextNode <#text>
+ BlockContainer <(anonymous)> (not painted) [BFC] children: inline
+ TextNode <#text>
+ BlockContainer at (8,60.40625) content-size 196x17.46875 [BFC] children: inline
+ line 0 width: 8.71875, height: 17.46875, bottom: 17.46875, baseline: 13.53125
+ frag 0 from TextNode start: 0, length: 1, rect: [8,60.40625 8.71875x17.46875]
+ "e"
+ TextNode <#text>
+ BlockContainer <(anonymous)> (not painted) [BFC] children: inline
+ TextNode <#text>
+ BlockContainer at (204,60.40625) content-size 196x17.46875 [BFC] children: inline
+ line 0 width: 6.4375, height: 17.46875, bottom: 17.46875, baseline: 13.53125
+ frag 0 from TextNode start: 0, length: 1, rect: [204,60.40625 6.4375x17.46875]
+ "f"
+ TextNode <#text>
+ BlockContainer <(anonymous)> (not painted) [BFC] children: inline
+ TextNode <#text>
+ BlockContainer <(anonymous)> at (8,77.875) content-size 784x0 children: inline
+ TextNode <#text>
diff --git a/Tests/LibWeb/Layout/input/grid/negative-grid-item-column-index.html b/Tests/LibWeb/Layout/input/grid/negative-grid-item-column-index.html
new file mode 100644
index 0000000000..3e6ed5b8bb
--- /dev/null
+++ b/Tests/LibWeb/Layout/input/grid/negative-grid-item-column-index.html
@@ -0,0 +1,38 @@
+
+
+
a
+
c
+
b
+
b
+
d
+
c
+
d
+
e
+
f
+
+
\ No newline at end of file
diff --git a/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp
index 21aca27197..9892f1d55a 100644
--- a/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp
+++ b/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp
@@ -344,7 +344,14 @@ void GridFormattingContext::place_item_with_row_position(Box const& child_box)
void GridFormattingContext::place_item_with_column_position(Box const& child_box, int& auto_placement_cursor_x, int& auto_placement_cursor_y)
{
- int column_start = child_box.computed_values().grid_column_start().raw_value() - 1;
+ int column_start;
+ if (child_box.computed_values().grid_column_start().raw_value() > 0) {
+ column_start = child_box.computed_values().grid_column_start().raw_value() - 1;
+ } else {
+ // NOTE: Negative indexes count from the end side of the explicit grid
+ column_start = m_explicit_columns_line_count + child_box.computed_values().grid_column_start().raw_value();
+ }
+
int column_end = child_box.computed_values().grid_column_end().raw_value() - 1;
// https://www.w3.org/TR/css-grid-2/#line-placement
@@ -485,33 +492,39 @@ void GridFormattingContext::place_item_with_no_declared_position(Box const& chil
else if (child_box.computed_values().grid_row_end().is_span())
row_span = child_box.computed_values().grid_row_end().raw_value();
auto found_unoccupied_area = false;
- for (size_t row_index = auto_placement_cursor_y; row_index < m_occupation_grid.row_count(); row_index++) {
- for (size_t column_index = auto_placement_cursor_x; column_index < m_occupation_grid.column_count(); column_index++) {
- if (column_span + column_index <= m_occupation_grid.column_count()) {
+
+ while (true) {
+ while (auto_placement_cursor_x <= m_occupation_grid.max_column_index()) {
+ if (auto_placement_cursor_x + column_span <= m_occupation_grid.max_column_index() + 1) {
auto found_all_available = true;
for (int span_index = 0; span_index < column_span; span_index++) {
- if (m_occupation_grid.is_occupied(column_index + span_index, row_index))
+ if (m_occupation_grid.is_occupied(auto_placement_cursor_x + span_index, auto_placement_cursor_y))
found_all_available = false;
}
if (found_all_available) {
found_unoccupied_area = true;
- column_start = column_index;
- row_start = row_index;
- goto finish;
+ column_start = auto_placement_cursor_x;
+ row_start = auto_placement_cursor_y;
+ break;
}
}
- }
- auto_placement_cursor_x = 0;
- auto_placement_cursor_y++;
- }
-finish:
- // 4.1.2.2. If a non-overlapping position was found in the previous step, set the item's row-start
- // and column-start lines to the cursor's position. Otherwise, increment the auto-placement cursor's
- // row position (creating new rows in the implicit grid as necessary), set its column position to the
- // start-most column line in the implicit grid, and return to the previous step.
- if (!found_unoccupied_area) {
- row_start = m_occupation_grid.row_count();
+ auto_placement_cursor_x++;
+ }
+
+ if (found_unoccupied_area) {
+ break;
+ }
+
+ // 4.1.2.2. If a non-overlapping position was found in the previous step, set the item's row-start
+ // and column-start lines to the cursor's position. Otherwise, increment the auto-placement cursor's
+ // row position (creating new rows in the implicit grid as necessary), set its column position to the
+ // start-most column line in the implicit grid, and return to the previous step.
+ if (!found_unoccupied_area) {
+ auto_placement_cursor_x = m_occupation_grid.min_column_index();
+ auto_placement_cursor_y++;
+ row_start = auto_placement_cursor_y;
+ }
}
m_occupation_grid.set_occupied(column_start, column_start + column_span, row_start, row_start + row_span);
@@ -554,11 +567,21 @@ void GridFormattingContext::initialize_grid_tracks_from_definition(AvailableSpac
void GridFormattingContext::initialize_grid_tracks_for_columns_and_rows(AvailableSpace const& available_space)
{
auto const& grid_computed_values = grid_container().computed_values();
- initialize_grid_tracks_from_definition(available_space, grid_computed_values.grid_template_columns().track_list(), m_grid_columns);
- initialize_grid_tracks_from_definition(available_space, grid_computed_values.grid_template_rows().track_list(), m_grid_rows);
auto const& grid_auto_columns = grid_computed_values.grid_auto_columns().track_list();
size_t implicit_column_index = 0;
+ // NOTE: If there are implicit tracks created by items with negative indexes they should prepend explicitly defined tracks
+ auto negative_index_implied_column_tracks_count = abs(m_occupation_grid.min_column_index());
+ for (int column_index = 0; column_index < negative_index_implied_column_tracks_count; column_index++) {
+ if (grid_auto_columns.size() > 0) {
+ auto size = grid_auto_columns[implicit_column_index % grid_auto_columns.size()];
+ m_grid_columns.append(TemporaryTrack(size.grid_size()));
+ } else {
+ m_grid_columns.append(TemporaryTrack());
+ }
+ implicit_column_index++;
+ }
+ initialize_grid_tracks_from_definition(available_space, grid_computed_values.grid_template_columns().track_list(), m_grid_columns);
for (size_t column_index = m_grid_columns.size(); column_index < m_occupation_grid.column_count(); column_index++) {
if (grid_auto_columns.size() > 0) {
auto size = grid_auto_columns[implicit_column_index % grid_auto_columns.size()];
@@ -571,6 +594,18 @@ void GridFormattingContext::initialize_grid_tracks_for_columns_and_rows(Availabl
auto const& grid_auto_rows = grid_computed_values.grid_auto_rows().track_list();
size_t implicit_row_index = 0;
+ // NOTE: If there are implicit tracks created by items with negative indexes they should prepend explicitly defined tracks
+ auto negative_index_implied_row_tracks_count = abs(m_occupation_grid.min_row_index());
+ for (int row_index = 0; row_index < negative_index_implied_row_tracks_count; row_index++) {
+ if (grid_auto_rows.size() > 0) {
+ auto size = grid_auto_rows[implicit_row_index % grid_auto_rows.size()];
+ m_grid_rows.append(TemporaryTrack(size.grid_size()));
+ } else {
+ m_grid_rows.append(TemporaryTrack());
+ }
+ implicit_row_index++;
+ }
+ initialize_grid_tracks_from_definition(available_space, grid_computed_values.grid_template_rows().track_list(), m_grid_rows);
for (size_t row_index = m_grid_rows.size(); row_index < m_occupation_grid.row_count(); row_index++) {
if (grid_auto_rows.size() > 0) {
auto size = grid_auto_rows[implicit_row_index % grid_auto_rows.size()];
@@ -1313,6 +1348,12 @@ void GridFormattingContext::place_grid_items(AvailableSpace const& available_spa
// FIXME: 4.2. For dense packing:
}
+
+ // NOTE: When final implicit grid sizes are known, we can offset their positions so leftmost grid track has 0 index.
+ for (auto& item : m_grid_items) {
+ item.set_raw_row(item.raw_row() - m_occupation_grid.min_row_index());
+ item.set_raw_column(item.raw_column() - m_occupation_grid.min_column_index());
+ }
}
void GridFormattingContext::determine_grid_container_height()
@@ -1369,6 +1410,12 @@ void GridFormattingContext::run(Box const& box, LayoutMode, AvailableSpace const
{
m_available_space = available_space;
+ auto const& grid_computed_values = grid_container().computed_values();
+
+ // NOTE: We store explicit grid sizes to later use in determining the position of items with negative index.
+ m_explicit_columns_line_count = get_count_of_tracks(grid_computed_values.grid_template_columns().track_list(), available_space) + 1;
+ m_explicit_rows_line_count = get_count_of_tracks(grid_computed_values.grid_template_rows().track_list(), available_space) + 1;
+
place_grid_items(available_space);
initialize_grid_tracks_for_columns_and_rows(available_space);
@@ -1566,33 +1613,31 @@ int GridFormattingContext::get_line_index_by_line_name(String const& needle, CSS
return -1;
}
-void OccupationGrid::set_occupied(size_t column_start, size_t column_end, size_t row_start, size_t row_end)
+void OccupationGrid::set_occupied(int column_start, int column_end, int row_start, int row_end)
{
- for (size_t row = row_start; row < row_end; row++) {
- for (size_t column = column_start; column < column_end; column++) {
- set_occupied(column, row);
+ for (int row_index = row_start; row_index < row_end; row_index++) {
+ for (int column_index = column_start; column_index < column_end; column_index++) {
+ m_min_column_index = min(m_min_column_index, column_index);
+ m_max_column_index = max(m_max_column_index, column_index);
+ m_min_row_index = min(m_min_row_index, row_index);
+ m_max_row_index = max(m_max_row_index, row_index);
+
+ m_occupation_grid.set(GridPosition { .row = row_index, .column = column_index });
}
}
}
-void OccupationGrid::set_occupied(size_t column_index, size_t row_index)
-{
- m_columns_count = max(m_columns_count, column_index + 1);
- m_rows_count = max(m_rows_count, row_index + 1);
- m_occupation_grid.try_set(GridPosition { .row = row_index, .column = column_index }).release_value_but_fixme_should_propagate_errors();
-}
-
-bool OccupationGrid::is_occupied(size_t column_index, size_t row_index) const
+bool OccupationGrid::is_occupied(int column_index, int row_index) const
{
return m_occupation_grid.contains(GridPosition { row_index, column_index });
}
-size_t GridItem::gap_adjusted_row(Box const& grid_box) const
+int GridItem::gap_adjusted_row(Box const& grid_box) const
{
return grid_box.computed_values().row_gap().is_auto() ? m_row : m_row * 2;
}
-size_t GridItem::gap_adjusted_column(Box const& grid_box) const
+int GridItem::gap_adjusted_column(Box const& grid_box) const
{
return grid_box.computed_values().column_gap().is_auto() ? m_column : m_column * 2;
}
diff --git a/Userland/Libraries/LibWeb/Layout/GridFormattingContext.h b/Userland/Libraries/LibWeb/Layout/GridFormattingContext.h
index e644688614..8a90600e08 100644
--- a/Userland/Libraries/LibWeb/Layout/GridFormattingContext.h
+++ b/Userland/Libraries/LibWeb/Layout/GridFormattingContext.h
@@ -17,31 +17,11 @@ enum class GridDimension {
};
struct GridPosition {
- size_t row;
- size_t column;
+ int row;
+ int column;
inline bool operator==(GridPosition const&) const = default;
};
-class OccupationGrid {
-public:
- OccupationGrid(size_t columns_count, size_t rows_count)
- : m_columns_count(columns_count)
- , m_rows_count(rows_count) {};
- OccupationGrid() {};
-
- void set_occupied(size_t column_start, size_t column_end, size_t row_start, size_t row_end);
- void set_occupied(size_t column_index, size_t row_index);
-
- size_t column_count() const { return m_columns_count; }
- size_t row_count() const { return m_rows_count; }
- bool is_occupied(size_t column_index, size_t row_index) const;
-
-private:
- HashTable m_occupation_grid;
- size_t m_columns_count { 0 };
- size_t m_rows_count { 0 };
-};
-
class GridItem {
public:
GridItem(Box const& box, int row, int row_span, int column, int column_span)
@@ -60,7 +40,7 @@ public:
return dimension == GridDimension::Column ? m_column_span : m_row_span;
}
- size_t raw_position(GridDimension const dimension) const
+ int raw_position(GridDimension const dimension) const
{
return dimension == GridDimension::Column ? m_column : m_row;
}
@@ -75,23 +55,63 @@ public:
}
}
- size_t raw_row() const { return m_row; }
- size_t raw_column() const { return m_column; }
+ int raw_row() const { return m_row; }
+ void set_raw_row(int row) { m_row = row; }
+
+ int raw_column() const { return m_column; }
+ void set_raw_column(int column) { m_column = column; }
size_t raw_row_span() const { return m_row_span; }
size_t raw_column_span() const { return m_column_span; }
- size_t gap_adjusted_row(Box const& grid_box) const;
- size_t gap_adjusted_column(Box const& grid_box) const;
+ int gap_adjusted_row(Box const& grid_box) const;
+ int gap_adjusted_column(Box const& grid_box) const;
private:
JS::NonnullGCPtr m_box;
- size_t m_row { 0 };
+ int m_row { 0 };
size_t m_row_span { 1 };
- size_t m_column { 0 };
+ int m_column { 0 };
size_t m_column_span { 1 };
};
+class OccupationGrid {
+public:
+ OccupationGrid(size_t columns_count, size_t rows_count)
+ {
+ m_max_column_index = max(0, columns_count - 1);
+ m_max_row_index = max(0, rows_count - 1);
+ };
+ OccupationGrid() {};
+
+ void set_occupied(int column_start, int column_end, int row_start, int row_end);
+
+ size_t column_count() const
+ {
+ return abs(m_min_column_index) + m_max_column_index + 1;
+ }
+
+ size_t row_count() const
+ {
+ return abs(m_min_row_index) + m_max_row_index + 1;
+ }
+
+ int min_column_index() const { return m_min_column_index; }
+ int max_column_index() const { return m_max_column_index; };
+ int min_row_index() const { return m_min_row_index; };
+ int max_row_index() const { return m_max_row_index; };
+
+ bool is_occupied(int column_index, int row_index) const;
+
+private:
+ HashTable m_occupation_grid;
+
+ int m_min_column_index { 0 };
+ int m_max_column_index { 0 };
+ int m_min_row_index { 0 };
+ int m_max_row_index { 0 };
+};
+
class GridFormattingContext final : public FormattingContext {
public:
explicit GridFormattingContext(LayoutState&, Box const& grid_container, FormattingContext* parent);
@@ -198,6 +218,9 @@ private:
Vector m_grid_rows_and_gaps;
Vector m_grid_columns_and_gaps;
+ size_t m_explicit_rows_line_count { 0 };
+ size_t m_explicit_columns_line_count { 0 };
+
OccupationGrid m_occupation_grid;
Vector m_grid_items;
Vector> m_boxes_to_place;