Coverage Report

Created: 2025-08-28 06:26

/src/serenity/Userland/Libraries/LibWeb/Layout/GridFormattingContext.h
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) 2023, Aliaksandr Kalenik <kalenik.aliaksandr@gmail.com>
3
 * Copyright (c) 2022-2023, Martin Falisse <mfalisse@outlook.com>
4
 *
5
 * SPDX-License-Identifier: BSD-2-Clause
6
 */
7
8
#pragma once
9
10
#include <LibWeb/CSS/Length.h>
11
#include <LibWeb/Layout/FormattingContext.h>
12
13
namespace Web::Layout {
14
15
enum class GridDimension {
16
    Row,
17
    Column
18
};
19
20
struct GridPosition {
21
    int row;
22
    int column;
23
0
    inline bool operator==(GridPosition const&) const = default;
24
};
25
26
struct GridItem {
27
    JS::NonnullGCPtr<Box const> box;
28
29
    int row;
30
    size_t row_span;
31
    int column;
32
    size_t column_span;
33
34
    [[nodiscard]] size_t span(GridDimension const dimension) const
35
0
    {
36
0
        return dimension == GridDimension::Column ? column_span : row_span;
37
0
    }
38
39
    [[nodiscard]] int raw_position(GridDimension const dimension) const
40
0
    {
41
0
        return dimension == GridDimension::Column ? column : row;
42
0
    }
43
44
    [[nodiscard]] CSSPixels add_margin_box_sizes(CSSPixels content_size, GridDimension dimension, LayoutState const& state) const
45
0
    {
46
0
        auto const& box_state = state.get(box);
47
0
        if (dimension == GridDimension::Column)
48
0
            return box_state.margin_box_left() + content_size + box_state.margin_box_right();
49
0
        return box_state.margin_box_top() + content_size + box_state.margin_box_bottom();
50
0
    }
51
52
    [[nodiscard]] int gap_adjusted_row() const;
53
    [[nodiscard]] int gap_adjusted_column() const;
54
};
55
56
enum class FoundUnoccupiedPlace {
57
    No,
58
    Yes
59
};
60
61
class OccupationGrid {
62
public:
63
    OccupationGrid(size_t columns_count, size_t rows_count)
64
0
    {
65
0
        m_max_column_index = max(0, columns_count - 1);
66
0
        m_max_row_index = max(0, rows_count - 1);
67
0
    }
68
0
    OccupationGrid() { }
69
70
    void set_occupied(int column_start, int column_end, int row_start, int row_end);
71
72
    size_t column_count() const
73
0
    {
74
0
        return abs(m_min_column_index) + m_max_column_index + 1;
75
0
    }
76
77
    size_t row_count() const
78
0
    {
79
0
        return abs(m_min_row_index) + m_max_row_index + 1;
80
0
    }
81
82
0
    void set_max_column_index(size_t max_column_index) { m_max_column_index = max_column_index; }
83
84
0
    int min_column_index() const { return m_min_column_index; }
85
0
    int max_column_index() const { return m_max_column_index; }
86
0
    int min_row_index() const { return m_min_row_index; }
87
0
    int max_row_index() const { return m_max_row_index; }
88
89
    bool is_occupied(int column_index, int row_index) const;
90
91
    FoundUnoccupiedPlace find_unoccupied_place(GridDimension dimension, int& column_index, int& row_index, int column_span, int row_span) const;
92
93
private:
94
    HashTable<GridPosition> m_occupation_grid;
95
96
    int m_min_column_index { 0 };
97
    int m_max_column_index { 0 };
98
    int m_min_row_index { 0 };
99
    int m_max_row_index { 0 };
100
};
101
102
class GridFormattingContext final : public FormattingContext {
103
public:
104
    explicit GridFormattingContext(LayoutState&, LayoutMode, Box const& grid_container, FormattingContext* parent);
105
    ~GridFormattingContext();
106
107
0
    virtual bool inhibits_floating() const override { return true; }
108
109
    virtual void run(AvailableSpace const& available_space) override;
110
    virtual CSSPixels automatic_content_width() const override;
111
    virtual CSSPixels automatic_content_height() const override;
112
113
0
    Box const& grid_container() const { return context_box(); }
114
115
private:
116
    CSS::JustifyItems justification_for_item(Box const& box) const;
117
    CSS::AlignItems alignment_for_item(Box const& box) const;
118
119
    void resolve_items_box_metrics(GridDimension const dimension);
120
121
    CSSPixels m_automatic_content_height { 0 };
122
123
    bool is_auto_positioned_track(CSS::GridTrackPlacement const&, CSS::GridTrackPlacement const&) const;
124
125
    struct GridTrack {
126
        CSS::GridSize min_track_sizing_function;
127
        CSS::GridSize max_track_sizing_function;
128
129
        CSSPixels base_size { 0 };
130
        bool base_size_frozen { false };
131
132
        Optional<CSSPixels> growth_limit { 0 };
133
        bool growth_limit_frozen { false };
134
        bool infinitely_growable { false };
135
136
        CSSPixels space_to_distribute { 0 };
137
        CSSPixels planned_increase { 0 };
138
        CSSPixels item_incurred_increase { 0 };
139
140
        bool is_gap { false };
141
142
        static GridTrack create_from_definition(CSS::ExplicitGridTrack const& definition);
143
        static GridTrack create_auto();
144
        static GridTrack create_gap(CSSPixels size);
145
    };
146
147
    struct GridArea {
148
        String name;
149
        size_t row_start { 0 };
150
        size_t row_end { 1 };
151
        size_t column_start { 0 };
152
        size_t column_end { 1 };
153
        bool invalid { false }; /* FIXME: Ignore ignore invalid areas during layout */
154
    };
155
156
    struct GridLine {
157
        Vector<String> names;
158
    };
159
    Vector<GridLine> m_row_lines;
160
    Vector<GridLine> m_column_lines;
161
162
    void init_grid_lines(GridDimension);
163
164
    Vector<GridTrack> m_grid_rows;
165
    Vector<GridTrack> m_grid_columns;
166
167
    bool has_gaps(GridDimension const dimension) const
168
0
    {
169
0
        if (dimension == GridDimension::Column) {
170
0
            return !grid_container().computed_values().column_gap().is_auto();
171
0
        } else {
172
0
            return !grid_container().computed_values().row_gap().is_auto();
173
0
        }
174
0
    }
175
176
    template<typename Callback>
177
    void for_each_spanned_track_by_item(GridItem const& item, GridDimension const dimension, Callback callback)
178
0
    {
179
0
        auto& tracks = dimension == GridDimension::Column ? m_grid_columns : m_grid_rows;
180
0
        auto& gaps = dimension == GridDimension::Column ? m_column_gap_tracks : m_row_gap_tracks;
181
0
        auto has_gaps = this->has_gaps(dimension);
182
0
        auto item_span = item.span(dimension);
183
0
        auto item_index = item.raw_position(dimension);
184
0
        for (size_t span = 0; span < item_span; span++) {
185
0
            auto track_index = item_index + span;
186
0
            if (track_index >= tracks.size())
187
0
                break;
188
189
0
            auto& track = tracks[track_index];
190
0
            callback(track);
191
192
0
            auto is_last_spanned_track = span == item_span - 1;
193
0
            if (has_gaps && !is_last_spanned_track) {
194
0
                auto& gap = gaps[track_index];
195
0
                callback(gap);
196
0
            }
197
0
        }
198
0
    }
Unexecuted instantiation: GridFormattingContext.cpp:void Web::Layout::GridFormattingContext::for_each_spanned_track_by_item<Web::Layout::GridFormattingContext::expand_flexible_tracks(Web::Layout::GridDimension)::$_1::operator()() const::{lambda(Web::Layout::GridFormattingContext::GridTrack&)#1}>(Web::Layout::GridItem const&, Web::Layout::GridDimension, Web::Layout::GridFormattingContext::expand_flexible_tracks(Web::Layout::GridDimension)::$_1::operator()() const::{lambda(Web::Layout::GridFormattingContext::GridTrack&)#1})
Unexecuted instantiation: GridFormattingContext.cpp:void Web::Layout::GridFormattingContext::for_each_spanned_track_by_item<Web::Layout::GridFormattingContext::increase_sizes_to_accommodate_spanning_items_crossing_content_sized_tracks(Web::Layout::GridDimension, unsigned long)::$_1>(Web::Layout::GridItem const&, Web::Layout::GridDimension, Web::Layout::GridFormattingContext::increase_sizes_to_accommodate_spanning_items_crossing_content_sized_tracks(Web::Layout::GridDimension, unsigned long)::$_1)
Unexecuted instantiation: GridFormattingContext.cpp:void Web::Layout::GridFormattingContext::for_each_spanned_track_by_item<Web::Layout::GridFormattingContext::increase_sizes_to_accommodate_spanning_items_crossing_flexible_tracks(Web::Layout::GridDimension)::$_1>(Web::Layout::GridItem const&, Web::Layout::GridDimension, Web::Layout::GridFormattingContext::increase_sizes_to_accommodate_spanning_items_crossing_flexible_tracks(Web::Layout::GridDimension)::$_1)
199
200
    template<typename Callback>
201
    void for_each_spanned_track_by_item(GridItem const& item, GridDimension const dimension, Callback callback) const
202
0
    {
203
0
        auto& tracks = dimension == GridDimension::Column ? m_grid_columns : m_grid_rows;
204
0
        auto& gaps = dimension == GridDimension::Column ? m_column_gap_tracks : m_row_gap_tracks;
205
0
        auto has_gaps = this->has_gaps(dimension);
206
0
        auto item_span = item.span(dimension);
207
0
        auto item_index = item.raw_position(dimension);
208
0
        for (size_t span = 0; span < item_span; span++) {
209
0
            auto track_index = item_index + span;
210
0
            if (track_index >= tracks.size())
211
0
                break;
212
213
0
            auto& track = tracks[track_index];
214
0
            callback(track);
215
216
0
            auto is_last_spanned_track = span == item_span - 1;
217
0
            if (has_gaps && !is_last_spanned_track) {
218
0
                auto& gap = gaps[track_index];
219
0
                callback(gap);
220
0
            }
221
0
        }
222
0
    }
223
224
    Vector<GridTrack> m_row_gap_tracks;
225
    Vector<GridTrack> m_column_gap_tracks;
226
227
    Vector<GridTrack&> m_grid_rows_and_gaps;
228
    Vector<GridTrack&> m_grid_columns_and_gaps;
229
230
    size_t m_explicit_rows_line_count { 0 };
231
    size_t m_explicit_columns_line_count { 0 };
232
233
    OccupationGrid m_occupation_grid;
234
    Vector<GridItem> m_grid_items;
235
236
    Optional<AvailableSpace> m_available_space;
237
238
    void determine_grid_container_height();
239
    void determine_intrinsic_size_of_grid_container(AvailableSpace const& available_space);
240
241
    void layout_absolutely_positioned_element(Box const&, AvailableSpace const&);
242
    virtual void parent_context_did_dimension_child_root_box() override;
243
244
    void resolve_grid_item_widths();
245
    void resolve_grid_item_heights();
246
247
    void resolve_track_spacing(GridDimension const dimension);
248
249
    AvailableSize get_free_space(AvailableSpace const&, GridDimension const) const;
250
251
    Optional<int> get_line_index_by_line_name(GridDimension dimension, String const&);
252
    CSSPixels resolve_definite_track_size(CSS::GridSize const&, AvailableSpace const&);
253
    int count_of_repeated_auto_fill_or_fit_tracks(GridDimension, CSS::ExplicitGridTrack const& repeated_track);
254
255
    void build_grid_areas();
256
257
    struct PlacementPosition {
258
        int start { 0 };
259
        int end { 0 };
260
        size_t span { 1 };
261
    };
262
    PlacementPosition resolve_grid_position(Box const& child_box, GridDimension const dimension);
263
264
    void place_grid_items();
265
    void place_item_with_row_and_column_position(Box const& child_box);
266
    void place_item_with_row_position(Box const& child_box);
267
    void place_item_with_column_position(Box const& child_box, int& auto_placement_cursor_x, int& auto_placement_cursor_y);
268
    void place_item_with_no_declared_position(Box const& child_box, int& auto_placement_cursor_x, int& auto_placement_cursor_y);
269
    void record_grid_placement(GridItem);
270
271
    void initialize_grid_tracks_from_definition(GridDimension);
272
    void initialize_grid_tracks_for_columns_and_rows();
273
    void initialize_gap_tracks(AvailableSpace const&);
274
275
    void collapse_auto_fit_tracks_if_needed(GridDimension const);
276
277
    enum class SpaceDistributionPhase {
278
        AccommodateMinimumContribution,
279
        AccommodateMinContentContribution,
280
        AccommodateMaxContentContribution
281
    };
282
283
    template<typename Match>
284
    void distribute_extra_space_across_spanned_tracks_base_size(GridDimension dimension, CSSPixels item_size_contribution, SpaceDistributionPhase phase, Vector<GridTrack&>& spanned_tracks, Match matcher);
285
286
    template<typename Match>
287
    void distribute_extra_space_across_spanned_tracks_growth_limit(CSSPixels item_size_contribution, Vector<GridTrack&>& spanned_tracks, Match matcher);
288
289
    void initialize_track_sizes(GridDimension);
290
    void resolve_intrinsic_track_sizes(GridDimension);
291
    void increase_sizes_to_accommodate_spanning_items_crossing_content_sized_tracks(GridDimension, size_t span);
292
    void increase_sizes_to_accommodate_spanning_items_crossing_flexible_tracks(GridDimension);
293
    void maximize_tracks_using_available_size(AvailableSpace const& available_space, GridDimension dimension);
294
    void maximize_tracks(GridDimension);
295
    void expand_flexible_tracks(GridDimension);
296
    void stretch_auto_tracks(GridDimension);
297
    void run_track_sizing(GridDimension);
298
299
    CSSPixels calculate_grid_container_maximum_size(GridDimension const) const;
300
301
    CSS::Size const& get_item_preferred_size(GridItem const&, GridDimension const) const;
302
303
    CSSPixels calculate_min_content_size(GridItem const&, GridDimension const) const;
304
    CSSPixels calculate_max_content_size(GridItem const&, GridDimension const) const;
305
306
    CSSPixels calculate_min_content_contribution(GridItem const&, GridDimension const) const;
307
    CSSPixels calculate_max_content_contribution(GridItem const&, GridDimension const) const;
308
309
    CSSPixels calculate_limited_min_content_contribution(GridItem const&, GridDimension const) const;
310
    CSSPixels calculate_limited_max_content_contribution(GridItem const&, GridDimension const) const;
311
312
    CSSPixels containing_block_size_for_item(GridItem const&, GridDimension const) const;
313
    AvailableSpace get_available_space_for_item(GridItem const&) const;
314
315
    CSSPixelRect get_grid_area_rect(GridItem const&) const;
316
317
    CSSPixels content_size_suggestion(GridItem const&, GridDimension const) const;
318
    Optional<CSSPixels> specified_size_suggestion(GridItem const&, GridDimension const) const;
319
    CSSPixels content_based_minimum_size(GridItem const&, GridDimension const) const;
320
    CSSPixels automatic_minimum_size(GridItem const&, GridDimension const) const;
321
    CSSPixels calculate_minimum_contribution(GridItem const&, GridDimension const) const;
322
};
323
324
}