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;