LibWeb: Add support for grid items with negative column-start in GFC

This changes grid items position storage type from unsigned to signed
integer so it can represent negative offsets and also updates placement
for grid items with specified column to correctly handle negative
offsets.
This commit is contained in:
Aliaksandr Kalenik 2023-06-07 12:00:01 +03:00 committed by Andreas Kling
parent 2c16e8371f
commit 0f1f95da46
Notes: sideshowbarker 2024-07-17 08:43:11 +09:00
4 changed files with 241 additions and 64 deletions

View File

@ -0,0 +1,71 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (0,0) content-size 800x600 [BFC] children: not-inline
BlockContainer <body> at (8,8) content-size 784x69.875 children: not-inline
Box <div.grid> at (8,8) content-size 784x69.875 [GFC] children: not-inline
BlockContainer <(anonymous)> (not painted) [BFC] children: inline
TextNode <#text>
BlockContainer <div.a> 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 <div.c> 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 <div.b> 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 <div.b> 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 <div.d> 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 <div.c> 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 <div.d> 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 <div.e> 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 <div.f> 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>

View File

@ -0,0 +1,38 @@
<style>
.grid {
display: grid;
grid-template-columns: auto;
}
.a {
grid-column: -3;
background-color: lightblue;
}
.b {
grid-column-start: -1;
background-color: lightcoral;
}
.c {
grid-column: 1;
background-color: lightsalmon;
}
.d {
grid-column: 3;
background-color: lightgreen;
}
</style>
<div class="grid">
<div class="a">a</div>
<div class="c">c</div>
<div class="b">b</div>
<div class="b">b</div>
<div class="d">d</div>
<div class="c">c</div>
<div class="d">d</div>
<div class="e">e</div>
<div class="f">f</div>
</div>

View File

@ -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;
}

View File

@ -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<GridPosition> 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<Box const> 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<GridPosition> 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<TemporaryTrack&> m_grid_rows_and_gaps;
Vector<TemporaryTrack&> 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<GridItem> m_grid_items;
Vector<JS::NonnullGCPtr<Box const>> m_boxes_to_place;