Coverage Report

Created: 2026-11-13 08:07

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/serenity/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp
Line
Count
Source
1
/*
2
 * Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
3
 *
4
 * SPDX-License-Identifier: BSD-2-Clause
5
 */
6
7
#include <LibWeb/DOM/Node.h>
8
#include <LibWeb/HTML/BrowsingContext.h>
9
#include <LibWeb/HTML/HTMLTableColElement.h>
10
#include <LibWeb/Layout/BlockFormattingContext.h>
11
#include <LibWeb/Layout/Box.h>
12
#include <LibWeb/Layout/InlineFormattingContext.h>
13
#include <LibWeb/Layout/TableFormattingContext.h>
14
15
namespace Web::Layout {
16
17
TableFormattingContext::TableFormattingContext(LayoutState& state, LayoutMode layout_mode, Box const& root, FormattingContext* parent)
18
0
    : FormattingContext(Type::Table, layout_mode, state, root, parent)
19
0
{
20
0
}
21
22
0
TableFormattingContext::~TableFormattingContext() = default;
23
24
static inline bool is_table_column_group(Box const& box)
25
0
{
26
0
    return box.display().is_table_column_group();
27
0
}
28
29
static inline bool is_table_column(Box const& box)
30
0
{
31
0
    return box.display().is_table_column();
32
0
}
33
34
CSSPixels TableFormattingContext::run_caption_layout(CSS::CaptionSide phase)
35
0
{
36
0
    CSSPixels caption_height = 0;
37
0
    for (auto* child = table_box().first_child(); child; child = child->next_sibling()) {
38
0
        if (!child->display().is_table_caption() || child->computed_values().caption_side() != phase) {
39
0
            continue;
40
0
        }
41
        // The caption boxes are principal block-level boxes that retain their own content, padding, margin, and border areas,
42
        // and are rendered as normal block boxes inside the table wrapper box, as described in https://www.w3.org/TR/CSS22/tables.html#model
43
0
        auto caption_context = make<BlockFormattingContext>(m_state, m_layout_mode, *verify_cast<BlockContainer>(child), this);
44
0
        caption_context->run(*m_available_space);
45
0
        VERIFY(child->is_box());
46
0
        auto const& child_box = static_cast<Box const&>(*child);
47
        // FIXME: Since caption only has inline children, BlockFormattingContext doesn't resolve the vertical metrics.
48
        //        We need to do it manually here.
49
0
        caption_context->resolve_vertical_box_model_metrics(child_box);
50
0
        auto const& caption_state = m_state.get(child_box);
51
0
        if (phase == CSS::CaptionSide::Top) {
52
0
            m_state.get_mutable(table_box()).set_content_y(caption_state.content_height() + caption_state.margin_box_bottom());
53
0
        } else {
54
0
            m_state.get_mutable(child_box).set_content_y(
55
0
                m_state.get(table_box()).margin_box_height() + caption_state.margin_box_top());
56
0
        }
57
0
        caption_height += caption_state.margin_box_height();
58
0
    }
59
0
    return caption_height;
60
0
}
61
62
void TableFormattingContext::compute_constrainedness()
63
0
{
64
    // Definition of constrainedness: https://www.w3.org/TR/css-tables-3/#constrainedness
65
    // NB: The definition uses https://www.w3.org/TR/CSS21/visudet.html#propdef-width for width, which doesn't include
66
    //     keyword values. The remaining checks can be simplified to checking whether the size is a length.
67
0
    size_t column_index = 0;
68
0
    TableGrid::for_each_child_box_matching(table_box(), is_table_column_group, [&](auto& column_group_box) {
69
0
        TableGrid::for_each_child_box_matching(column_group_box, is_table_column, [&](auto& column_box) {
70
0
            auto const& computed_values = column_box.computed_values();
71
0
            if (computed_values.width().is_length()) {
72
0
                m_columns[column_index].is_constrained = true;
73
0
            }
74
0
            auto const& col_node = static_cast<HTML::HTMLTableColElement const&>(*column_box.dom_node());
75
0
            unsigned span = col_node.get_attribute_value(HTML::AttributeNames::span).to_number<unsigned>().value_or(1);
76
0
            column_index += span;
77
0
        });
78
0
    });
79
80
0
    for (auto& row : m_rows) {
81
0
        auto const& computed_values = row.box->computed_values();
82
0
        if (computed_values.height().is_length()) {
83
0
            row.is_constrained = true;
84
0
        }
85
0
    }
86
87
0
    for (auto& cell : m_cells) {
88
0
        auto const& computed_values = cell.box->computed_values();
89
0
        if (computed_values.width().is_length()) {
90
0
            m_columns[cell.column_index].is_constrained = true;
91
0
        }
92
93
0
        if (computed_values.height().is_length()) {
94
0
            m_rows[cell.row_index].is_constrained = true;
95
0
        }
96
0
    }
97
0
}
98
99
void TableFormattingContext::compute_cell_measures()
100
0
{
101
    // Implements https://www.w3.org/TR/css-tables-3/#computing-cell-measures.
102
0
    auto const& containing_block = m_state.get(*table_wrapper().containing_block());
103
104
0
    compute_constrainedness();
105
106
0
    for (auto& cell : m_cells) {
107
0
        auto const& computed_values = cell.box->computed_values();
108
0
        CSSPixels padding_top = computed_values.padding().top().to_px(cell.box, containing_block.content_height());
109
0
        CSSPixels padding_bottom = computed_values.padding().bottom().to_px(cell.box, containing_block.content_height());
110
0
        CSSPixels padding_left = computed_values.padding().left().to_px(cell.box, containing_block.content_width());
111
0
        CSSPixels padding_right = computed_values.padding().right().to_px(cell.box, containing_block.content_width());
112
113
0
        auto const& cell_state = m_state.get(cell.box);
114
0
        auto use_collapsing_borders_model = cell_state.override_borders_data().has_value();
115
        // Implement the collapsing border model https://www.w3.org/TR/CSS22/tables.html#collapsing-borders.
116
0
        CSSPixels border_top = use_collapsing_borders_model ? round(cell_state.border_top / 2) : computed_values.border_top().width;
117
0
        CSSPixels border_bottom = use_collapsing_borders_model ? round(cell_state.border_bottom / 2) : computed_values.border_bottom().width;
118
0
        CSSPixels border_left = use_collapsing_borders_model ? round(cell_state.border_left / 2) : computed_values.border_left().width;
119
0
        CSSPixels border_right = use_collapsing_borders_model ? round(cell_state.border_right / 2) : computed_values.border_right().width;
120
121
0
        auto min_content_width = calculate_min_content_width(cell.box);
122
0
        auto max_content_width = calculate_max_content_width(cell.box);
123
0
        auto min_content_height = calculate_min_content_height(cell.box, max_content_width);
124
0
        auto max_content_height = calculate_max_content_height(cell.box, min_content_width);
125
126
        // The outer min-content height of a table-cell is max(min-height, min-content height) adjusted by the cell intrinsic offsets.
127
0
        auto min_height = computed_values.min_height().to_px(cell.box, containing_block.content_height());
128
0
        auto cell_intrinsic_height_offsets = padding_top + padding_bottom + border_top + border_bottom;
129
0
        cell.outer_min_height = max(min_height, min_content_height) + cell_intrinsic_height_offsets;
130
        // The outer min-content width of a table-cell is max(min-width, min-content width) adjusted by the cell intrinsic offsets.
131
0
        auto min_width = computed_values.min_width().to_px(cell.box, containing_block.content_width());
132
0
        auto cell_intrinsic_width_offsets = padding_left + padding_right + border_left + border_right;
133
        // For fixed mode, according to https://www.w3.org/TR/css-tables-3/#computing-column-measures:
134
        // The min-content and max-content width of cells is considered zero unless they are directly specified as a length-percentage,
135
        // in which case they are resolved based on the table width (if it is definite, otherwise use 0).
136
0
        auto width_is_specified_length_or_percentage = computed_values.width().is_length() || computed_values.width().is_percentage();
137
0
        if (!use_fixed_mode_layout() || width_is_specified_length_or_percentage) {
138
0
            cell.outer_min_width = max(min_width, min_content_width) + cell_intrinsic_width_offsets;
139
0
        }
140
141
        // The tables specification isn't explicit on how to use the height and max-height CSS properties in the outer max-content formulas.
142
        // However, during this early phase we don't have enough information to resolve percentage sizes yet and the formulas for outer sizes
143
        // in the specification give enough clues to pick defaults in a way that makes sense.
144
0
        auto height = computed_values.height().is_length() ? computed_values.height().to_px(cell.box, containing_block.content_height()) : 0;
145
0
        auto max_height = computed_values.max_height().is_length() ? computed_values.max_height().to_px(cell.box, containing_block.content_height()) : CSSPixels::max();
146
0
        if (m_rows[cell.row_index].is_constrained) {
147
            // The outer max-content height of a table-cell in a constrained row is
148
            // max(min-height, height, min-content height, min(max-height, height)) adjusted by the cell intrinsic offsets.
149
            // NB: min(max-height, height) doesn't have any effect here, we can simplify the expression to max(min-height, height, min-content height).
150
0
            cell.outer_max_height = max(min_height, max(height, min_content_height)) + cell_intrinsic_height_offsets;
151
0
        } else {
152
            // The outer max-content height of a table-cell in a non-constrained row is
153
            // max(min-height, height, min-content height, min(max-height, max-content height)) adjusted by the cell intrinsic offsets.
154
0
            cell.outer_max_height = max(min_height, max(height, max(min_content_height, min(max_height, max_content_height)))) + cell_intrinsic_height_offsets;
155
0
        }
156
157
        // See the explanation for height and max_height above.
158
0
        auto width = computed_values.width().is_length() ? computed_values.width().to_px(cell.box, containing_block.content_width()) : 0;
159
0
        auto max_width = computed_values.max_width().is_length() ? computed_values.max_width().to_px(cell.box, containing_block.content_width()) : CSSPixels::max();
160
0
        if (use_fixed_mode_layout() && !width_is_specified_length_or_percentage) {
161
0
            continue;
162
0
        }
163
0
        if (m_columns[cell.column_index].is_constrained) {
164
            // The outer max-content width of a table-cell in a constrained column is
165
            // max(min-width, width, min-content width, min(max-width, width)) adjusted by the cell intrinsic offsets.
166
            // NB: min(max-width, width) doesn't have any effect here, we can simplify the expression to max(min-width, width, min-content width).
167
0
            cell.outer_max_width = max(min_width, max(width, min_content_width)) + cell_intrinsic_width_offsets;
168
0
        } else {
169
            // The outer max-content width of a table-cell in a non-constrained column is
170
            // max(min-width, width, min-content width, min(max-width, max-content width)) adjusted by the cell intrinsic offsets.
171
0
            cell.outer_max_width = max(min_width, max(width, max(min_content_width, min(max_width, max_content_width)))) + cell_intrinsic_width_offsets;
172
0
        }
173
0
    }
174
0
}
175
176
void TableFormattingContext::compute_outer_content_sizes()
177
0
{
178
0
    auto const& containing_block = m_state.get(*table_wrapper().containing_block());
179
180
0
    size_t column_index = 0;
181
0
    TableGrid::for_each_child_box_matching(table_box(), is_table_column_group, [&](auto& column_group_box) {
182
0
        TableGrid::for_each_child_box_matching(column_group_box, is_table_column, [&](auto& column_box) {
183
0
            auto const& computed_values = column_box.computed_values();
184
0
            auto min_width = computed_values.min_width().to_px(column_box, containing_block.content_width());
185
0
            auto max_width = computed_values.max_width().is_length() ? computed_values.max_width().to_px(column_box, containing_block.content_width()) : CSSPixels::max();
186
0
            auto width = computed_values.width().to_px(column_box, containing_block.content_width());
187
            // The outer min-content width of a table-column or table-column-group is max(min-width, width).
188
0
            m_columns[column_index].min_size = max(min_width, width);
189
            // The outer max-content width of a table-column or table-column-group is max(min-width, min(max-width, width)).
190
0
            m_columns[column_index].max_size = max(min_width, min(max_width, width));
191
0
            auto const& col_node = static_cast<HTML::HTMLTableColElement const&>(*column_box.dom_node());
192
0
            unsigned span = col_node.get_attribute_value(HTML::AttributeNames::span).to_number<unsigned>().value_or(1);
193
0
            column_index += span;
194
0
        });
195
0
    });
196
197
0
    for (auto& row : m_rows) {
198
0
        auto const& computed_values = row.box->computed_values();
199
0
        auto min_height = computed_values.min_height().to_px(row.box, containing_block.content_height());
200
0
        auto max_height = computed_values.max_height().is_length() ? computed_values.max_height().to_px(row.box, containing_block.content_height()) : CSSPixels::max();
201
0
        auto height = computed_values.height().to_px(row.box, containing_block.content_height());
202
        // The outer min-content height of a table-row or table-row-group is max(min-height, height).
203
0
        row.min_size = max(min_height, height);
204
        // The outer max-content height of a table-row or table-row-group is max(min-height, min(max-height, height)).
205
0
        row.max_size = max(min_height, min(max_height, height));
206
0
    }
207
0
}
208
209
template<>
210
void TableFormattingContext::initialize_table_measures<TableFormattingContext::Row>()
211
0
{
212
0
    auto const& containing_block = m_state.get(*table_wrapper().containing_block());
213
214
0
    for (auto& cell : m_cells) {
215
0
        auto const& computed_values = cell.box->computed_values();
216
0
        if (cell.row_span == 1) {
217
0
            auto specified_height = computed_values.height().to_px(cell.box, containing_block.content_height());
218
            // https://www.w3.org/TR/css-tables-3/#row-layout makes specified cell height part of the initialization formula for row table measures:
219
            // This is done by running the same algorithm as the column measurement, with the span=1 value being initialized (for min-content) with
220
            // the largest of the resulting height of the previous row layout, the height specified on the corresponding table-row (if any), and
221
            // the largest height specified on cells that span this row only (the algorithm starts by considering cells of span 2 on top of that assignment).
222
0
            m_rows[cell.row_index].min_size = max(m_rows[cell.row_index].min_size, max(cell.outer_min_height, specified_height));
223
0
            m_rows[cell.row_index].max_size = max(m_rows[cell.row_index].max_size, cell.outer_max_height);
224
0
        }
225
0
    }
226
0
}
227
228
template<>
229
void TableFormattingContext::initialize_table_measures<TableFormattingContext::Column>()
230
0
{
231
    // Implement the following parts of the specification, accounting for fixed layout mode:
232
    // https://www.w3.org/TR/css-tables-3/#min-content-width-of-a-column-based-on-cells-of-span-up-to-1
233
    // https://www.w3.org/TR/css-tables-3/#max-content-width-of-a-column-based-on-cells-of-span-up-to-1
234
0
    for (auto& cell : m_cells) {
235
0
        if (cell.column_span == 1 && (cell.row_index == 0 || !use_fixed_mode_layout())) {
236
0
            m_columns[cell.column_index].min_size = max(m_columns[cell.column_index].min_size, cell.outer_min_width);
237
0
            m_columns[cell.column_index].max_size = max(m_columns[cell.column_index].max_size, cell.outer_max_width);
238
0
        }
239
0
    }
240
0
}
241
242
template<class RowOrColumn>
243
void TableFormattingContext::compute_intrinsic_percentage(size_t max_cell_span)
244
0
{
245
0
    auto& rows_or_columns = table_rows_or_columns<RowOrColumn>();
246
247
    // https://www.w3.org/TR/css-tables-3/#intrinsic-percentage-width-of-a-column-based-on-cells-of-span-up-to-1
248
0
    initialize_intrinsic_percentages_from_rows_or_columns<RowOrColumn>();
249
0
    initialize_intrinsic_percentages_from_cells<RowOrColumn>();
250
251
    // Stores intermediate values for intrinsic percentage based on cells of span up to N for the iterative algorithm, to store them back at the end of the step.
252
0
    Vector<double> intrinsic_percentage_contribution_by_index;
253
0
    intrinsic_percentage_contribution_by_index.resize(rows_or_columns.size());
254
0
    for (size_t rc_index = 0; rc_index < rows_or_columns.size(); ++rc_index) {
255
0
        intrinsic_percentage_contribution_by_index[rc_index] = rows_or_columns[rc_index].intrinsic_percentage;
256
0
    }
257
258
0
    for (size_t current_span = 2; current_span <= max_cell_span; current_span++) {
259
        // https://www.w3.org/TR/css-tables-3/#intrinsic-percentage-width-of-a-column-based-on-cells-of-span-up-to-n-n--1
260
0
        for (auto& cell : m_cells) {
261
0
            auto cell_span_value = cell_span<RowOrColumn>(cell);
262
0
            if (cell_span_value != current_span) {
263
0
                continue;
264
0
            }
265
0
            auto cell_start_rc_index = cell_index<RowOrColumn>(cell);
266
0
            auto cell_end_rc_index = cell_start_rc_index + cell_span_value;
267
            // 1. Start with the percentage contribution of the cell.
268
0
            CSSPixels cell_contribution = CSSPixels::nearest_value_for(cell_percentage_contribution<RowOrColumn>(cell));
269
            // 2. Subtract the intrinsic percentage width of the column based on cells of span up to N-1 of all columns
270
            //    that the cell spans. If this gives a negative result, change it to 0%.
271
0
            for (auto rc_index = cell_start_rc_index; rc_index < cell_end_rc_index; rc_index++) {
272
0
                cell_contribution -= CSSPixels::nearest_value_for(rows_or_columns[rc_index].intrinsic_percentage);
273
0
                cell_contribution = max(cell_contribution, 0);
274
0
            }
275
            // Compute the sum of the non-spanning max-content sizes of all rows / columns spanned by the cell that have an intrinsic percentage
276
            // size of the row / column based on cells of span up to N-1 equal to 0%, to be used in step 3 of the cell contribution algorithm.
277
0
            CSSPixels width_sum_of_columns_with_zero_intrinsic_percentage = 0;
278
0
            size_t number_of_columns_with_zero_intrinsic_percentage = 0;
279
0
            for (auto rc_index = cell_start_rc_index; rc_index < cell_end_rc_index; rc_index++) {
280
0
                if (rows_or_columns[rc_index].intrinsic_percentage == 0) {
281
0
                    width_sum_of_columns_with_zero_intrinsic_percentage += rows_or_columns[rc_index].max_size;
282
0
                    ++number_of_columns_with_zero_intrinsic_percentage;
283
0
                }
284
0
            }
285
0
            for (size_t rc_index = cell_start_rc_index; rc_index < cell_end_rc_index; rc_index++) {
286
                // If the intrinsic percentage width of a column based on cells of span up to N-1 is greater than 0%, then the intrinsic percentage width of
287
                // the column based on cells of span up to N is the same as the intrinsic percentage width of the column based on cells of span up to N-1.
288
0
                if (rows_or_columns[rc_index].intrinsic_percentage > 0) {
289
0
                    continue;
290
0
                }
291
                // Otherwise, it is the largest of the contributions of the cells in the column whose colSpan is N,
292
                // where the contribution of a cell is the result of taking the following steps:
293
                // 1. Start with the percentage contribution of the cell.
294
                // 2. Subtract the intrinsic percentage width of the column based on cells of span up to N-1 of all columns
295
                //    that the cell spans. If this gives a negative result, change it to 0%.
296
                // 3. Multiply by the ratio of the column’s non-spanning max-content width to the sum of the non-spanning max-content widths of all
297
                //    columns spanned by the cell that have an intrinsic percentage width of the column based on cells of span up to N-1 equal to 0%.
298
0
                CSSPixels ajusted_cell_contribution;
299
0
                if (width_sum_of_columns_with_zero_intrinsic_percentage != 0) {
300
0
                    ajusted_cell_contribution = cell_contribution.scaled(rows_or_columns[rc_index].max_size / static_cast<double>(width_sum_of_columns_with_zero_intrinsic_percentage));
301
0
                } else {
302
                    // However, if this ratio is undefined because the denominator is zero, instead use the 1 divided by the number of columns
303
                    // spanned by the cell that have an intrinsic percentage width of the column based on cells of span up to N-1 equal to zero.
304
0
                    ajusted_cell_contribution = cell_contribution * 1 / number_of_columns_with_zero_intrinsic_percentage;
305
0
                }
306
0
                intrinsic_percentage_contribution_by_index[rc_index] = max(static_cast<double>(ajusted_cell_contribution), intrinsic_percentage_contribution_by_index[rc_index]);
307
0
            }
308
0
        }
309
0
        for (size_t rc_index = 0; rc_index < rows_or_columns.size(); ++rc_index) {
310
0
            rows_or_columns[rc_index].intrinsic_percentage = intrinsic_percentage_contribution_by_index[rc_index];
311
0
        }
312
0
    }
313
314
    // Clamp total intrinsic percentage to 100%: https://www.w3.org/TR/css-tables-3/#intrinsic-percentage-width-of-a-column
315
0
    double total_intrinsic_percentage = 0;
316
0
    for (auto& rows_or_column : rows_or_columns) {
317
0
        rows_or_column.intrinsic_percentage = max(min(100 - total_intrinsic_percentage, rows_or_column.intrinsic_percentage), 0);
318
0
        total_intrinsic_percentage += rows_or_column.intrinsic_percentage;
319
0
    }
320
0
}
Unexecuted instantiation: void Web::Layout::TableFormattingContext::compute_intrinsic_percentage<Web::Layout::TableFormattingContext::Column>(unsigned long)
Unexecuted instantiation: void Web::Layout::TableFormattingContext::compute_intrinsic_percentage<Web::Layout::TableGrid::Row>(unsigned long)
321
322
template<class RowOrColumn>
323
void TableFormattingContext::compute_table_measures()
324
0
{
325
0
    initialize_table_measures<RowOrColumn>();
326
327
0
    auto& rows_or_columns = table_rows_or_columns<RowOrColumn>();
328
329
0
    size_t max_cell_span = 1;
330
0
    for (auto& cell : m_cells) {
331
0
        max_cell_span = max(max_cell_span, cell_span<RowOrColumn>(cell));
332
0
    }
333
334
    // Since the intrinsic percentage specification uses non-spanning max-content size for the iterative algorithm,
335
    // run it before we compute the spanning max-content size with its own iterative algorithm for span up to N.
336
0
    compute_intrinsic_percentage<RowOrColumn>(max_cell_span);
337
338
0
    for (size_t current_span = 2; current_span <= max_cell_span; current_span++) {
339
        // https://www.w3.org/TR/css-tables-3/#min-content-width-of-a-column-based-on-cells-of-span-up-to-n-n--1
340
0
        Vector<Vector<CSSPixels>> cell_min_contributions_by_rc_index;
341
0
        cell_min_contributions_by_rc_index.resize(rows_or_columns.size());
342
        // https://www.w3.org/TR/css-tables-3/#max-content-width-of-a-column-based-on-cells-of-span-up-to-n-n--1
343
0
        Vector<Vector<CSSPixels>> cell_max_contributions_by_rc_index;
344
0
        cell_max_contributions_by_rc_index.resize(rows_or_columns.size());
345
0
        for (auto& cell : m_cells) {
346
0
            auto cell_span_value = cell_span<RowOrColumn>(cell);
347
0
            if (cell_span_value == current_span) {
348
                // Define the baseline max-content size as the sum of the max-content sizes based on cells of span up to N-1 of all columns that the cell spans.
349
0
                auto cell_start_rc_index = cell_index<RowOrColumn>(cell);
350
0
                auto cell_end_rc_index = cell_start_rc_index + cell_span_value;
351
0
                CSSPixels baseline_max_content_size = 0;
352
0
                for (auto rc_index = cell_start_rc_index; rc_index < cell_end_rc_index; rc_index++) {
353
0
                    baseline_max_content_size += rows_or_columns[rc_index].max_size;
354
0
                }
355
0
                CSSPixels baseline_min_content_size = 0;
356
0
                for (auto rc_index = cell_start_rc_index; rc_index < cell_end_rc_index; rc_index++) {
357
0
                    baseline_min_content_size += rows_or_columns[rc_index].min_size;
358
0
                }
359
360
                // Define the baseline border spacing as the sum of the horizontal border-spacing for any columns spanned by the cell, other than the one in which the cell originates.
361
0
                auto baseline_border_spacing = border_spacing<RowOrColumn>() * (cell_span_value - 1);
362
363
                // Add contribution from all rows / columns, since we've weighted the gap to the desired spanned size by the the
364
                // ratio of the max-content size based on cells of span up to N-1 of the row / column to the baseline max-content width.
365
0
                for (auto rc_index = cell_start_rc_index; rc_index < cell_end_rc_index; rc_index++) {
366
                    // The contribution of the cell is the sum of:
367
                    // the min-content size of the column based on cells of span up to N-1
368
0
                    auto cell_min_contribution = rows_or_columns[rc_index].min_size;
369
                    // the product of:
370
                    // - the ratio of:
371
                    //   - the max-content size of the row / column based on cells of span up to N-1 of the row / column minus the
372
                    //     min-content size of the row / column based on cells of span up to N-1 of the row / column, to
373
                    //   - the baseline max-content size minus the baseline min-content size
374
                    //   or zero if this ratio is undefined, and
375
                    // - the outer min-content size of the cell minus the baseline min-content size and the baseline border spacing, clamped
376
                    //   to be at least 0 and at most the difference between the baseline max-content size and the baseline min-content size
377
0
                    auto normalized_max_min_diff = baseline_max_content_size != baseline_min_content_size
378
0
                        ? (rows_or_columns[rc_index].max_size - rows_or_columns[rc_index].min_size) / static_cast<double>(baseline_max_content_size - baseline_min_content_size)
379
0
                        : 0;
380
0
                    auto clamped_diff_to_baseline_min = min(
381
0
                        max(cell_min_size<RowOrColumn>(cell) - baseline_min_content_size - baseline_border_spacing, 0),
382
0
                        baseline_max_content_size - baseline_min_content_size);
383
0
                    cell_min_contribution += CSSPixels::nearest_value_for(normalized_max_min_diff * clamped_diff_to_baseline_min);
384
                    // the product of:
385
                    // - the ratio of the max-content size based on cells of span up to N-1 of the column to the baseline max-content size
386
                    // - the outer min-content size of the cell minus the baseline max-content size and baseline border spacing, or 0 if this is negative
387
0
                    if (baseline_max_content_size != 0) {
388
0
                        cell_min_contribution += CSSPixels::nearest_value_for(rows_or_columns[rc_index].max_size / static_cast<double>(baseline_max_content_size))
389
0
                            * max(CSSPixels(0), cell_min_size<RowOrColumn>(cell) - baseline_max_content_size - baseline_border_spacing);
390
0
                    }
391
392
                    // The contribution of the cell is the sum of:
393
                    // the max-content size of the column based on cells of span up to N-1
394
0
                    auto cell_max_contribution = rows_or_columns[rc_index].max_size;
395
                    // and the product of:
396
                    // - the ratio of the max-content size based on cells of span up to N-1 of the column to the baseline max-content size
397
                    // - the outer max-content size of the cell minus the baseline max-content size and the baseline border spacing, or 0 if this is negative
398
0
                    if (baseline_max_content_size != 0) {
399
0
                        cell_max_contribution += CSSPixels::nearest_value_for(rows_or_columns[rc_index].max_size / static_cast<double>(baseline_max_content_size))
400
0
                            * max(CSSPixels(0), cell_max_size<RowOrColumn>(cell) - baseline_max_content_size - baseline_border_spacing);
401
0
                    }
402
0
                    cell_min_contributions_by_rc_index[rc_index].append(cell_min_contribution);
403
0
                    cell_max_contributions_by_rc_index[rc_index].append(cell_max_contribution);
404
0
                }
405
0
            }
406
0
        }
407
408
0
        for (size_t rc_index = 0; rc_index < rows_or_columns.size(); rc_index++) {
409
            // min-content size of a row / column based on cells of span up to N (N > 1) is
410
            // the largest of the min-content size of the row / column based on cells of span up to N-1 and
411
            // the contributions of the cells in the row / column whose rowSpan / colSpan is N
412
0
            for (auto min_contribution : cell_min_contributions_by_rc_index[rc_index])
413
0
                rows_or_columns[rc_index].min_size = max(rows_or_columns[rc_index].min_size, min_contribution);
414
415
            // max-content size of a row / column based on cells of span up to N (N > 1) is
416
            // the largest of the max-content size based on cells of span up to N-1 and the contributions of
417
            // the cells in the row / column whose rowSpan / colSpan is N
418
0
            for (auto max_contribution : cell_max_contributions_by_rc_index[rc_index])
419
0
                rows_or_columns[rc_index].max_size = max(rows_or_columns[rc_index].max_size, max_contribution);
420
0
        }
421
0
    }
422
0
}
Unexecuted instantiation: void Web::Layout::TableFormattingContext::compute_table_measures<Web::Layout::TableFormattingContext::Column>()
Unexecuted instantiation: void Web::Layout::TableFormattingContext::compute_table_measures<Web::Layout::TableGrid::Row>()
423
424
CSSPixels TableFormattingContext::compute_capmin()
425
0
{
426
    // The caption width minimum (CAPMIN) is the largest of the table captions min-content contribution:
427
    // https://drafts.csswg.org/css-tables-3/#computing-the-table-width
428
0
    CSSPixels capmin = 0;
429
0
    for (auto* child = table_box().first_child(); child; child = child->next_sibling()) {
430
0
        if (!child->display().is_table_caption()) {
431
0
            continue;
432
0
        }
433
0
        VERIFY(child->is_box());
434
0
        capmin = max(calculate_min_content_width(static_cast<Box const&>(*child)), capmin);
435
0
    }
436
0
    return capmin;
437
0
}
438
439
static bool width_is_auto_relative_to_state(CSS::Size const& width, LayoutState::UsedValues const& state)
440
0
{
441
0
    return width.is_auto() || (width.contains_percentage() && !state.has_definite_width());
442
0
}
443
444
void TableFormattingContext::compute_table_width()
445
0
{
446
    // https://drafts.csswg.org/css-tables-3/#computing-the-table-width
447
448
0
    auto& table_box_state = m_state.get_mutable(table_box());
449
450
0
    auto& computed_values = table_box().computed_values();
451
452
0
    auto width_of_table_containing_block = m_available_space->width;
453
454
    // Percentages on 'width' and 'height' on the table are relative to the table wrapper box's containing block,
455
    // not the table wrapper box itself.
456
0
    auto const& containing_block_state = m_state.get(*table_wrapper().containing_block());
457
0
    CSSPixels width_of_table_wrapper_containing_block = containing_block_state.content_width();
458
459
    // Compute undistributable space due to border spacing: https://www.w3.org/TR/css-tables-3/#computing-undistributable-space.
460
0
    auto undistributable_space = (m_columns.size() + 1) * border_spacing_horizontal();
461
462
    // The row/column-grid width minimum (GRIDMIN) width is the sum of the min-content width
463
    // of all the columns plus cell spacing or borders.
464
0
    CSSPixels grid_min = 0;
465
0
    for (auto& column : m_columns) {
466
0
        grid_min += column.min_size;
467
0
    }
468
0
    grid_min += undistributable_space;
469
470
    // The row/column-grid width maximum (GRIDMAX) width is the sum of the max-content width
471
    // of all the columns plus cell spacing or borders.
472
0
    CSSPixels grid_max = 0;
473
0
    for (auto& column : m_columns) {
474
0
        grid_max += column.max_size;
475
0
    }
476
0
    grid_max += undistributable_space;
477
478
    // The used min-width of a table is the greater of the resolved min-width, CAPMIN, and GRIDMIN.
479
0
    auto used_min_width = max(grid_min, compute_capmin());
480
0
    if (!computed_values.min_width().is_auto()) {
481
0
        used_min_width = max(used_min_width, computed_values.min_width().to_px(table_box(), width_of_table_wrapper_containing_block));
482
0
    }
483
484
0
    CSSPixels used_width;
485
0
    if (m_available_space->width.is_min_content()) {
486
0
        used_width = grid_min;
487
0
    } else if (m_available_space->width.is_max_content()) {
488
0
        used_width = grid_max;
489
0
    } else if (width_is_auto_relative_to_state(computed_values.width(), containing_block_state)) {
490
        // If the table-root has 'width: auto', the used width is the greater of
491
        // min(GRIDMAX, the table’s containing block width), the used min-width of the table.
492
0
        if (width_of_table_containing_block.is_definite())
493
0
            used_width = max(min(grid_max, width_of_table_containing_block.to_px_or_zero()), used_min_width);
494
0
        else
495
0
            used_width = max(grid_max, used_min_width);
496
        // https://www.w3.org/TR/CSS22/tables.html#auto-table-layout
497
        // A percentage value for a column width is relative to the table width. If the table has 'width: auto',
498
        // a percentage represents a constraint on the column's width, which a UA should try to satisfy.
499
0
        for (auto& cell : m_cells) {
500
0
            auto const& cell_width = cell.box->computed_values().width();
501
0
            if (cell_width.is_percentage()) {
502
0
                CSSPixels adjusted_used_width = undistributable_space;
503
0
                if (cell_width.percentage().value() != 0)
504
0
                    adjusted_used_width += CSSPixels::nearest_value_for(ceil(100 / cell_width.percentage().value() * cell.outer_max_width));
505
506
0
                if (width_of_table_containing_block.is_definite())
507
0
                    used_width = min(max(used_width, adjusted_used_width), width_of_table_containing_block.to_px_or_zero());
508
0
                else
509
0
                    used_width = max(used_width, adjusted_used_width);
510
0
            }
511
0
        }
512
0
    } else if (computed_values.width().is_max_content()) {
513
0
        used_width = grid_max;
514
0
    } else {
515
        // If the table-root’s width property has a computed value (resolving to
516
        // resolved-table-width) other than auto, the used width is the greater
517
        // of resolved-table-width, and the used min-width of the table.
518
0
        CSSPixels resolved_table_width = computed_values.width().to_px(table_box(), width_of_table_wrapper_containing_block);
519
        // Since used_width is content width, we need to subtract the border and padding spacing from the specified width for a consistent comparison.
520
0
        if (computed_values.box_sizing() == CSS::BoxSizing::BorderBox)
521
0
            resolved_table_width -= table_box_state.border_box_left() + table_box_state.border_box_right();
522
0
        used_width = max(resolved_table_width, used_min_width);
523
0
        if (!should_treat_max_width_as_none(table_box(), m_available_space->width))
524
0
            used_width = min(used_width, computed_values.max_width().to_px(table_box(), width_of_table_wrapper_containing_block));
525
0
    }
526
527
0
    table_box_state.set_content_width(used_width);
528
0
    auto& table_wrapper_box_state = m_state.get_mutable(table_wrapper());
529
0
    table_wrapper_box_state.set_content_width(table_box_state.border_box_width());
530
0
}
531
532
CSSPixels TableFormattingContext::compute_columns_total_used_width() const
533
0
{
534
0
    CSSPixels total_used_width = 0;
535
0
    for (auto& column : m_columns) {
536
0
        total_used_width += column.used_width;
537
0
    }
538
0
    return total_used_width;
539
0
}
540
541
static CSSPixels compute_columns_total_candidate_width(Vector<CSSPixels> const& candidate_widths)
542
0
{
543
0
    CSSPixels total_candidate_width = 0;
544
0
    for (auto width : candidate_widths) {
545
0
        total_candidate_width += width;
546
0
    }
547
0
    return total_candidate_width;
548
0
}
549
550
void TableFormattingContext::commit_candidate_column_widths(Vector<CSSPixels> const& candidate_widths)
551
0
{
552
0
    VERIFY(candidate_widths.size() == m_columns.size());
553
0
    for (size_t i = 0; i < m_columns.size(); ++i) {
554
0
        m_columns[i].used_width = candidate_widths[i];
555
0
    }
556
0
}
557
558
void TableFormattingContext::assign_columns_width_linear_combination(Vector<CSSPixels> const& candidate_widths, CSSPixels available_width)
559
0
{
560
0
    auto columns_total_candidate_width = compute_columns_total_candidate_width(candidate_widths);
561
0
    auto columns_total_used_width = compute_columns_total_used_width();
562
0
    if (columns_total_candidate_width == columns_total_used_width) {
563
0
        return;
564
0
    }
565
0
    auto candidate_weight = (available_width - columns_total_used_width) / static_cast<double>(columns_total_candidate_width - columns_total_used_width);
566
0
    for (size_t i = 0; i < m_columns.size(); ++i) {
567
0
        auto& column = m_columns[i];
568
0
        column.used_width = CSSPixels::nearest_value_for(candidate_weight * candidate_widths[i] + (1 - candidate_weight) * column.used_width);
569
0
    }
570
0
}
571
572
template<class ColumnFilter, class BaseWidthGetter>
573
bool TableFormattingContext::distribute_excess_width_proportionally_to_base_width(CSSPixels excess_width, ColumnFilter column_filter, BaseWidthGetter base_width_getter)
574
0
{
575
0
    bool found_matching_columns = false;
576
0
    CSSPixels total_base_width = 0;
577
0
    for (auto const& column : m_columns) {
578
0
        if (column_filter(column)) {
579
0
            total_base_width += base_width_getter(column);
580
0
            found_matching_columns = true;
581
0
        }
582
0
    }
583
0
    if (!found_matching_columns) {
584
0
        return false;
585
0
    }
586
0
    VERIFY(total_base_width > 0);
587
0
    for (auto& column : m_columns) {
588
0
        if (column_filter(column)) {
589
0
            column.used_width += CSSPixels::nearest_value_for(excess_width * base_width_getter(column) / static_cast<double>(total_base_width));
590
0
        }
591
0
    }
592
0
    return true;
593
0
}
Unexecuted instantiation: TableFormattingContext.cpp:bool Web::Layout::TableFormattingContext::distribute_excess_width_proportionally_to_base_width<Web::Layout::TableFormattingContext::distribute_excess_width_to_columns(Web::CSSPixels)::$_0, Web::Layout::TableFormattingContext::distribute_excess_width_to_columns(Web::CSSPixels)::$_1>(Web::CSSPixels, Web::Layout::TableFormattingContext::distribute_excess_width_to_columns(Web::CSSPixels)::$_0, Web::Layout::TableFormattingContext::distribute_excess_width_to_columns(Web::CSSPixels)::$_1)
Unexecuted instantiation: TableFormattingContext.cpp:bool Web::Layout::TableFormattingContext::distribute_excess_width_proportionally_to_base_width<Web::Layout::TableFormattingContext::distribute_excess_width_to_columns(Web::CSSPixels)::$_3, Web::Layout::TableFormattingContext::distribute_excess_width_to_columns(Web::CSSPixels)::$_4>(Web::CSSPixels, Web::Layout::TableFormattingContext::distribute_excess_width_to_columns(Web::CSSPixels)::$_3, Web::Layout::TableFormattingContext::distribute_excess_width_to_columns(Web::CSSPixels)::$_4)
Unexecuted instantiation: TableFormattingContext.cpp:bool Web::Layout::TableFormattingContext::distribute_excess_width_proportionally_to_base_width<Web::Layout::TableFormattingContext::distribute_excess_width_to_columns_fixed_mode(Web::CSSPixels)::$_1, Web::Layout::TableFormattingContext::distribute_excess_width_to_columns_fixed_mode(Web::CSSPixels)::$_2>(Web::CSSPixels, Web::Layout::TableFormattingContext::distribute_excess_width_to_columns_fixed_mode(Web::CSSPixels)::$_1, Web::Layout::TableFormattingContext::distribute_excess_width_to_columns_fixed_mode(Web::CSSPixels)::$_2)
594
595
template<class ColumnFilter>
596
bool TableFormattingContext::distribute_excess_width_equally(CSSPixels excess_width, ColumnFilter column_filter)
597
0
{
598
0
    size_t matching_column_count = 0;
599
0
    for (auto const& column : m_columns) {
600
0
        if (column_filter(column)) {
601
0
            ++matching_column_count;
602
0
        }
603
0
    }
604
0
    if (matching_column_count == 0) {
605
0
        return false;
606
0
    }
607
0
    for (auto& column : m_columns) {
608
0
        if (column_filter(column)) {
609
0
            column.used_width += excess_width / matching_column_count;
610
0
        }
611
0
    }
612
0
    return matching_column_count;
613
0
}
Unexecuted instantiation: TableFormattingContext.cpp:bool Web::Layout::TableFormattingContext::distribute_excess_width_equally<Web::Layout::TableFormattingContext::distribute_excess_width_to_columns(Web::CSSPixels)::$_2>(Web::CSSPixels, Web::Layout::TableFormattingContext::distribute_excess_width_to_columns(Web::CSSPixels)::$_2)
Unexecuted instantiation: TableFormattingContext.cpp:bool Web::Layout::TableFormattingContext::distribute_excess_width_equally<Web::Layout::TableFormattingContext::distribute_excess_width_to_columns(Web::CSSPixels)::$_6>(Web::CSSPixels, Web::Layout::TableFormattingContext::distribute_excess_width_to_columns(Web::CSSPixels)::$_6)
Unexecuted instantiation: TableFormattingContext.cpp:bool Web::Layout::TableFormattingContext::distribute_excess_width_equally<Web::Layout::TableFormattingContext::distribute_excess_width_to_columns(Web::CSSPixels)::$_7>(Web::CSSPixels, Web::Layout::TableFormattingContext::distribute_excess_width_to_columns(Web::CSSPixels)::$_7)
Unexecuted instantiation: TableFormattingContext.cpp:bool Web::Layout::TableFormattingContext::distribute_excess_width_equally<Web::Layout::TableFormattingContext::distribute_excess_width_to_columns_fixed_mode(Web::CSSPixels)::$_0>(Web::CSSPixels, Web::Layout::TableFormattingContext::distribute_excess_width_to_columns_fixed_mode(Web::CSSPixels)::$_0)
Unexecuted instantiation: TableFormattingContext.cpp:bool Web::Layout::TableFormattingContext::distribute_excess_width_equally<Web::Layout::TableFormattingContext::distribute_excess_width_to_columns_fixed_mode(Web::CSSPixels)::$_4>(Web::CSSPixels, Web::Layout::TableFormattingContext::distribute_excess_width_to_columns_fixed_mode(Web::CSSPixels)::$_4)
614
615
template<class ColumnFilter>
616
bool TableFormattingContext::distribute_excess_width_by_intrinsic_percentage(CSSPixels excess_width, ColumnFilter column_filter)
617
0
{
618
0
    bool found_matching_columns = false;
619
0
    double total_percentage_width = 0;
620
0
    for (auto const& column : m_columns) {
621
0
        if (column_filter(column)) {
622
0
            found_matching_columns = true;
623
0
            total_percentage_width += column.intrinsic_percentage;
624
0
        }
625
0
    }
626
0
    if (!found_matching_columns) {
627
0
        return false;
628
0
    }
629
0
    for (auto& column : m_columns) {
630
0
        if (column_filter(column)) {
631
0
            column.used_width += CSSPixels::nearest_value_for(excess_width * column.intrinsic_percentage / total_percentage_width);
632
0
        }
633
0
    }
634
0
    return true;
635
0
}
Unexecuted instantiation: TableFormattingContext.cpp:bool Web::Layout::TableFormattingContext::distribute_excess_width_by_intrinsic_percentage<Web::Layout::TableFormattingContext::distribute_excess_width_to_columns(Web::CSSPixels)::$_5>(Web::CSSPixels, Web::Layout::TableFormattingContext::distribute_excess_width_to_columns(Web::CSSPixels)::$_5)
Unexecuted instantiation: TableFormattingContext.cpp:bool Web::Layout::TableFormattingContext::distribute_excess_width_by_intrinsic_percentage<Web::Layout::TableFormattingContext::distribute_excess_width_to_columns_fixed_mode(Web::CSSPixels)::$_3>(Web::CSSPixels, Web::Layout::TableFormattingContext::distribute_excess_width_to_columns_fixed_mode(Web::CSSPixels)::$_3)
636
637
void TableFormattingContext::distribute_width_to_columns()
638
0
{
639
    // Implements https://www.w3.org/TR/css-tables-3/#width-distribution-algorithm
640
641
    // The total horizontal border spacing is defined for each table:
642
    // - For tables laid out in separated-borders mode containing at least one column, the horizontal component of the computed value of the border-spacing property times one plus the number of columns in the table
643
    // - Otherwise, 0
644
0
    auto total_horizontal_border_spacing = m_columns.is_empty() ? 0 : (m_columns.size() + 1) * border_spacing_horizontal();
645
646
    // The assignable table width is the used width of the table minus the total horizontal border spacing (if any).
647
    // This is the width that we will be able to allocate to the columns.
648
0
    CSSPixels const available_width = m_state.get(table_box()).content_width() - total_horizontal_border_spacing;
649
650
0
    Vector<CSSPixels> candidate_widths;
651
0
    candidate_widths.resize(m_columns.size());
652
653
    // 1. The min-content sizing-guess is the set of column width assignments where each column is assigned its min-content width.
654
0
    for (size_t i = 0; i < m_columns.size(); ++i) {
655
0
        auto& column = m_columns[i];
656
        // In fixed mode, the min-content width of percent-columns and auto-columns is considered to be zero:
657
        // https://www.w3.org/TR/css-tables-3/#width-distribution-in-fixed-mode
658
0
        if (use_fixed_mode_layout() && !column.is_constrained) {
659
0
            continue;
660
0
        }
661
0
        column.used_width = column.min_size;
662
0
        candidate_widths[i] = column.min_size;
663
0
    }
664
665
    // 2. The min-content-percentage sizing-guess is the set of column width assignments where:
666
    //    - each percent-column is assigned the larger of:
667
    //      - its intrinsic percentage width times the assignable width and
668
    //      - its min-content width.
669
    //    - all other columns are assigned their min-content width.
670
0
    for (size_t i = 0; i < m_columns.size(); ++i) {
671
0
        auto& column = m_columns[i];
672
0
        if (column.has_intrinsic_percentage) {
673
0
            candidate_widths[i] = max(column.min_size, CSSPixels::nearest_value_for(column.intrinsic_percentage / 100 * available_width));
674
0
        }
675
0
    }
676
677
    // If the assignable table width is less than or equal to the max-content sizing-guess, the used widths of the columns must be the
678
    // linear combination (with weights adding to 1) of the two consecutive sizing-guesses whose width sums bound the available width.
679
0
    if (available_width < compute_columns_total_candidate_width(candidate_widths)) {
680
0
        assign_columns_width_linear_combination(candidate_widths, available_width);
681
0
        return;
682
0
    } else {
683
0
        commit_candidate_column_widths(candidate_widths);
684
0
    }
685
686
    // 3. The min-content-specified sizing-guess is the set of column width assignments where:
687
    //    - each percent-column is assigned the larger of:
688
    //      - its intrinsic percentage width times the assignable width and
689
    //      - its min-content width
690
    //    - any other column that is constrained is assigned its max-content width
691
    //    - all other columns are assigned their min-content width.
692
0
    for (size_t i = 0; i < m_columns.size(); ++i) {
693
0
        auto& column = m_columns[i];
694
0
        if (column.is_constrained) {
695
0
            candidate_widths[i] = column.max_size;
696
0
        }
697
0
    }
698
699
0
    if (available_width < compute_columns_total_candidate_width(candidate_widths)) {
700
0
        assign_columns_width_linear_combination(candidate_widths, available_width);
701
0
        return;
702
0
    } else {
703
0
        commit_candidate_column_widths(candidate_widths);
704
0
    }
705
706
    // 4. The max-content sizing-guess is the set of column width assignments where:
707
    //    - each percent-column is assigned the larger of:
708
    //      - its intrinsic percentage width times the assignable width and
709
    //      - its min-content width
710
    //    - all other columns are assigned their max-content width.
711
0
    for (size_t i = 0; i < m_columns.size(); ++i) {
712
0
        auto& column = m_columns[i];
713
0
        if (!column.has_intrinsic_percentage) {
714
0
            candidate_widths[i] = column.max_size;
715
0
        }
716
0
    }
717
718
0
    if (available_width < compute_columns_total_candidate_width(candidate_widths)) {
719
0
        assign_columns_width_linear_combination(candidate_widths, available_width);
720
0
        return;
721
0
    } else {
722
0
        commit_candidate_column_widths(candidate_widths);
723
0
    }
724
725
    // Otherwise, the used widths of the columns are the result of starting from the max-content sizing-guess and distributing
726
    // the excess width to the columns of the table according to the rules for distributing excess width to columns (for used width).
727
0
    distribute_excess_width_to_columns(available_width);
728
0
}
729
730
void TableFormattingContext::distribute_excess_width_to_columns(CSSPixels available_width)
731
0
{
732
    // Implements https://www.w3.org/TR/css-tables-3/#distributing-width-to-columns
733
0
    auto columns_total_used_width = compute_columns_total_used_width();
734
0
    if (columns_total_used_width >= available_width) {
735
0
        return;
736
0
    }
737
0
    auto excess_width = available_width - columns_total_used_width;
738
0
    if (excess_width == 0) {
739
0
        return;
740
0
    }
741
742
0
    if (use_fixed_mode_layout()) {
743
0
        distribute_excess_width_to_columns_fixed_mode(excess_width);
744
0
        return;
745
0
    }
746
747
    // 1. If there are non-constrained columns that have originating cells with intrinsic percentage width of 0% and with nonzero
748
    //    max-content width (aka the columns allowed to grow by this rule), the distributed widths of the columns allowed to grow
749
    //    by this rule are increased in proportion to max-content width so the total increase adds to the excess width.
750
0
    if (distribute_excess_width_proportionally_to_base_width(
751
0
            excess_width,
752
0
            [](auto const& column) {
753
0
                return !column.is_constrained && column.has_originating_cells && column.intrinsic_percentage == 0 && column.max_size > 0;
754
0
            },
755
0
            [](auto const& column) { return column.max_size; })) {
756
0
        excess_width = available_width - compute_columns_total_used_width();
757
0
    }
758
0
    if (excess_width == 0) {
759
0
        return;
760
0
    }
761
    // 2. Otherwise, if there are non-constrained columns that have originating cells with intrinsic percentage width of 0% (aka the columns
762
    //    allowed to grow by this rule, which thanks to the previous rule must have zero max-content width), the distributed widths of the
763
    //    columns allowed to grow by this rule are increased by equal amounts so the total increase adds to the excess width.
764
0
    if (distribute_excess_width_equally(excess_width,
765
0
            [](auto const& column) {
766
0
                return !column.is_constrained && column.has_originating_cells && column.intrinsic_percentage == 0;
767
0
            })) {
768
0
        excess_width = available_width - compute_columns_total_used_width();
769
0
    }
770
0
    if (excess_width == 0) {
771
0
        return;
772
0
    }
773
    // 3. Otherwise, if there are constrained columns with intrinsic percentage width of 0% and with nonzero max-content width
774
    //    (aka the columns allowed to grow by this rule, which, due to other rules, must have originating cells), the distributed widths of the
775
    //    columns allowed to grow by this rule are increased in proportion to max-content width so the total increase adds to the excess width.
776
0
    if (distribute_excess_width_proportionally_to_base_width(
777
0
            excess_width,
778
0
            [](auto const& column) {
779
0
                return column.is_constrained && column.intrinsic_percentage == 0 && column.max_size > 0;
780
0
            },
781
0
            [](auto const& column) { return column.max_size; })) {
782
0
        excess_width = available_width - compute_columns_total_used_width();
783
0
    }
784
0
    if (excess_width == 0) {
785
0
        return;
786
0
    }
787
    // 4. Otherwise, if there are columns with intrinsic percentage width greater than 0% (aka the columns allowed to grow by this rule,
788
    //    which, due to other rules, must have originating cells), the distributed widths of the columns allowed to grow by this rule are
789
    //    increased in proportion to intrinsic percentage width so the total increase adds to the excess width.
790
0
    if (distribute_excess_width_by_intrinsic_percentage(excess_width, [](auto const& column) {
791
0
            return column.intrinsic_percentage > 0;
792
0
        })) {
793
0
        excess_width = available_width - compute_columns_total_used_width();
794
0
    }
795
0
    if (excess_width == 0) {
796
0
        return;
797
0
    }
798
    // 5. Otherwise, if there is any such column, the distributed widths of all columns that have originating cells are increased by equal amounts
799
    //    so the total increase adds to the excess width.
800
0
    if (distribute_excess_width_equally(
801
0
            excess_width,
802
0
            [](auto const& column) { return column.has_originating_cells; })) {
803
0
        excess_width = available_width - compute_columns_total_used_width();
804
0
    }
805
0
    if (excess_width == 0) {
806
0
        return;
807
0
    }
808
    // 6. Otherwise, the distributed widths of all columns are increased by equal amounts so the total increase adds to the excess width.
809
0
    distribute_excess_width_equally(excess_width, [](auto const&) { return true; });
810
0
}
811
812
void TableFormattingContext::distribute_excess_width_to_columns_fixed_mode(CSSPixels excess_width)
813
0
{
814
    // Implements the fixed mode for https://www.w3.org/TR/css-tables-3/#distributing-width-to-columns.
815
816
    // If there are any columns with no width specified, the excess width is distributed in equally to such columns
817
0
    if (distribute_excess_width_equally(excess_width, [](auto const& column) { return !column.is_constrained && !column.has_intrinsic_percentage; })) {
818
0
        return;
819
0
    }
820
    // otherwise, if there are columns with non-zero length widths from the base assignment, the excess width is distributed proportionally to width among those columns
821
0
    if (distribute_excess_width_proportionally_to_base_width(
822
0
            excess_width, [](auto const& column) { return column.used_width > 0; }, [](auto const& column) { return column.used_width; })) {
823
0
        return;
824
0
    }
825
    // otherwise, if there are columns with non-zero percentage widths from the base assignment, the excess width is distributed proportionally to percentage width among those columns
826
0
    if (distribute_excess_width_by_intrinsic_percentage(excess_width, [](auto const& column) { return column.intrinsic_percentage > 0; })) {
827
0
        return;
828
0
    }
829
    // otherwise, the excess width is distributed equally to the zero-sized columns
830
0
    distribute_excess_width_equally(excess_width, [](auto const& column) { return column.used_width == 0; });
831
0
}
832
833
void TableFormattingContext::compute_table_height()
834
0
{
835
    // First pass of row height calculation:
836
0
    for (auto& row : m_rows) {
837
0
        auto row_computed_height = row.box->computed_values().height();
838
0
        if (row_computed_height.is_length()) {
839
0
            auto height_of_containing_block = m_state.get(*row.box->containing_block()).content_height();
840
0
            auto row_used_height = row_computed_height.to_px(row.box, height_of_containing_block);
841
0
            row.base_height = max(row.base_height, row_used_height);
842
0
        }
843
0
    }
844
845
    // First pass of cells layout:
846
0
    for (auto& cell : m_cells) {
847
0
        auto& row = m_rows[cell.row_index];
848
0
        auto& cell_state = m_state.get_mutable(cell.box);
849
850
0
        CSSPixels span_width = 0;
851
0
        for (size_t i = 0; i < cell.column_span; ++i)
852
0
            span_width += m_columns[cell.column_index + i].used_width;
853
854
0
        auto width_of_containing_block = cell_state.containing_block_used_values()->content_width();
855
0
        auto height_of_containing_block = cell_state.containing_block_used_values()->content_height();
856
857
0
        cell_state.padding_top = cell.box->computed_values().padding().top().to_px(cell.box, width_of_containing_block);
858
0
        cell_state.padding_bottom = cell.box->computed_values().padding().bottom().to_px(cell.box, width_of_containing_block);
859
0
        cell_state.padding_left = cell.box->computed_values().padding().left().to_px(cell.box, width_of_containing_block);
860
0
        cell_state.padding_right = cell.box->computed_values().padding().right().to_px(cell.box, width_of_containing_block);
861
862
0
        if (cell.box->computed_values().border_collapse() == CSS::BorderCollapse::Separate) {
863
0
            cell_state.border_top = cell.box->computed_values().border_top().width;
864
0
            cell_state.border_bottom = cell.box->computed_values().border_bottom().width;
865
0
            cell_state.border_left = cell.box->computed_values().border_left().width;
866
0
            cell_state.border_right = cell.box->computed_values().border_right().width;
867
0
        }
868
869
0
        auto cell_computed_height = cell.box->computed_values().height();
870
0
        if (cell_computed_height.is_length()) {
871
0
            auto cell_used_height = cell_computed_height.to_px(cell.box, height_of_containing_block);
872
0
            cell_state.set_content_height(cell_used_height - cell_state.border_box_top() - cell_state.border_box_bottom());
873
874
0
            row.base_height = max(row.base_height, cell_used_height);
875
0
        }
876
877
        // Compute cell width as specified by https://www.w3.org/TR/css-tables-3/#bounding-box-assignment:
878
        // The position of any table-cell, table-track, or table-track-group box within the table is defined as the rectangle whose width/height is the sum of:
879
        // - the widths/heights of all spanned visible columns/rows
880
        // - the horizontal/vertical border-spacing times the amount of spanned visible columns/rows minus one
881
        // FIXME: Account for visibility.
882
0
        cell_state.set_content_width(span_width - cell_state.border_box_left() - cell_state.border_box_right() + (cell.column_span - 1) * border_spacing_horizontal());
883
0
        if (auto independent_formatting_context = layout_inside(cell.box, m_layout_mode, cell_state.available_inner_space_or_constraints_from(*m_available_space))) {
884
0
            cell_state.set_content_height(independent_formatting_context->automatic_content_height());
885
0
            independent_formatting_context->parent_context_did_dimension_child_root_box();
886
0
        }
887
888
0
        cell.baseline = box_baseline(cell.box);
889
890
        // Implements https://www.w3.org/TR/css-tables-3/#computing-the-table-height
891
892
        // The minimum height of a row is the maximum of:
893
        // - the computed height (if definite, percentages being considered 0px) of its corresponding table-row (if nay)
894
        // - the computed height of each cell spanning the current row exclusively (if definite, percentages being treated as 0px), and
895
        // - the minimum height (ROWMIN) required by the cells spanning the row.
896
        // Note that we've already applied the first rule at the top of the method.
897
0
        if (cell.row_span == 1) {
898
0
            row.base_height = max(row.base_height, cell_state.border_box_height());
899
0
        }
900
0
        row.base_height = max(row.base_height, m_rows[cell.row_index].min_size);
901
0
        row.baseline = max(row.baseline, cell.baseline);
902
0
    }
903
904
0
    CSSPixels sum_rows_height = 0;
905
0
    for (auto& row : m_rows) {
906
0
        sum_rows_height += row.base_height;
907
0
    }
908
909
0
    m_table_height = sum_rows_height;
910
911
0
    if (!table_box().computed_values().height().is_auto()) {
912
        // If the table has a height property with a value other than auto, it is treated as a minimum height for the
913
        // table grid, and will eventually be distributed to the height of the rows if their collective minimum height
914
        // ends up smaller than this number.
915
0
        CSSPixels height_of_table_containing_block = m_state.get(*table_wrapper().containing_block()).content_height();
916
0
        auto specified_table_height = table_box().computed_values().height().to_px(table_box(), height_of_table_containing_block);
917
0
        if (table_box().computed_values().box_sizing() == CSS::BoxSizing::BorderBox) {
918
0
            auto const& table_state = m_state.get(table_box());
919
0
            specified_table_height -= table_state.border_box_top() + table_state.border_box_bottom();
920
0
        }
921
0
        m_table_height = max(m_table_height, specified_table_height);
922
0
    }
923
924
0
    for (auto& row : m_rows) {
925
        // Reference size is the largest of
926
        // - its initial base height and
927
        // - its new base height (the one evaluated during the second layout pass, where percentages used in
928
        //   rowgroups/rows/cells' specified heights were resolved according to the table height, instead of
929
        //   being ignored as 0px).
930
931
        // Assign reference size to base size. Later, the reference size might change to a larger value during
932
        // the second pass of rows layout.
933
0
        row.reference_height = row.base_height;
934
0
    }
935
936
    // Second pass of rows height calculation:
937
    // At this point, percentage row height can be resolved because the final table height is calculated.
938
0
    for (auto& row : m_rows) {
939
0
        auto row_computed_height = row.box->computed_values().height();
940
0
        if (row_computed_height.is_percentage()) {
941
0
            auto row_used_height = row_computed_height.to_px(row.box, m_table_height);
942
0
            row.reference_height = max(row.reference_height, row_used_height);
943
0
        } else {
944
0
            continue;
945
0
        }
946
0
    }
947
948
    // Second pass cells layout:
949
    // At this point, percentage cell height can be resolved because the final table height is calculated.
950
0
    for (auto& cell : m_cells) {
951
0
        auto& row = m_rows[cell.row_index];
952
0
        auto& cell_state = m_state.get_mutable(cell.box);
953
954
0
        CSSPixels span_width = 0;
955
0
        for (size_t i = 0; i < cell.column_span; ++i)
956
0
            span_width += m_columns[cell.column_index + i].used_width;
957
958
0
        auto cell_computed_height = cell.box->computed_values().height();
959
0
        if (cell_computed_height.is_percentage()) {
960
0
            auto cell_used_height = cell_computed_height.to_px(cell.box, m_table_height);
961
0
            cell_state.set_content_height(cell_used_height - cell_state.border_box_top() - cell_state.border_box_bottom());
962
963
0
            row.reference_height = max(row.reference_height, cell_used_height);
964
0
        } else {
965
0
            continue;
966
0
        }
967
968
0
        cell_state.set_content_width(span_width - cell_state.border_box_left() - cell_state.border_box_right() + (cell.column_span - 1) * border_spacing_horizontal());
969
0
        if (auto independent_formatting_context = layout_inside(cell.box, m_layout_mode, cell_state.available_inner_space_or_constraints_from(*m_available_space))) {
970
0
            independent_formatting_context->parent_context_did_dimension_child_root_box();
971
0
        }
972
973
0
        cell.baseline = box_baseline(cell.box);
974
975
0
        row.reference_height = max(row.reference_height, cell_state.border_box_height());
976
0
        row.baseline = max(row.baseline, cell.baseline);
977
0
    }
978
0
}
979
980
void TableFormattingContext::distribute_height_to_rows()
981
0
{
982
0
    CSSPixels sum_reference_height = 0;
983
0
    for (auto& row : m_rows) {
984
0
        sum_reference_height += row.reference_height;
985
0
    }
986
987
0
    if (sum_reference_height == 0)
988
0
        return;
989
990
0
    Vector<Row&> rows_with_auto_height;
991
0
    for (auto& row : m_rows) {
992
0
        if (row.box->computed_values().height().is_auto()) {
993
0
            rows_with_auto_height.append(row);
994
0
        }
995
0
    }
996
997
0
    if (m_table_height <= sum_reference_height) {
998
        // If the table height is equal or smaller than sum of reference sizes, the final height assigned to each row
999
        // will be the weighted mean of the base and the reference size that yields the correct total height.
1000
1001
0
        for (auto& row : m_rows) {
1002
0
            auto weight = row.reference_height / static_cast<double>(sum_reference_height);
1003
0
            auto final_height = m_table_height * weight;
1004
0
            row.final_height = CSSPixels::nearest_value_for(final_height);
1005
0
        }
1006
0
    } else if (rows_with_auto_height.size() > 0) {
1007
        // Else, if the table owns any “auto-height” row (a row whose size is only determined by its content size and
1008
        // none of the specified heights), each non-auto-height row receives its reference height and auto-height rows
1009
        // receive their reference size plus some increment which is equal to the height missing to amount to the
1010
        // specified table height divided by the amount of such rows.
1011
1012
0
        for (auto& row : m_rows) {
1013
0
            row.final_height = row.reference_height;
1014
0
        }
1015
1016
0
        auto auto_height_rows_increment = (m_table_height - sum_reference_height) / rows_with_auto_height.size();
1017
0
        for (auto& row : rows_with_auto_height) {
1018
0
            row.final_height += auto_height_rows_increment;
1019
0
        }
1020
0
    } else {
1021
        // Else, all rows receive their reference size plus some increment which is equal to the height missing to
1022
        // amount to the specified table height divided by the amount of rows.
1023
1024
0
        auto increment = (m_table_height - sum_reference_height) / m_rows.size();
1025
0
        for (auto& row : m_rows) {
1026
0
            row.final_height = row.reference_height + increment;
1027
0
        }
1028
0
    }
1029
1030
    // Add undistributable space due to border spacing: https://www.w3.org/TR/css-tables-3/#computing-undistributable-space.
1031
0
    m_table_height += (m_rows.size() + 1) * border_spacing_vertical();
1032
0
}
1033
1034
void TableFormattingContext::position_row_boxes()
1035
0
{
1036
0
    auto const& table_state = m_state.get(table_box());
1037
1038
0
    CSSPixels row_top_offset = table_state.offset.y() + table_state.padding_top + border_spacing_vertical();
1039
0
    CSSPixels row_left_offset = table_state.border_left + table_state.padding_left + border_spacing_horizontal();
1040
0
    for (size_t y = 0; y < m_rows.size(); y++) {
1041
0
        auto& row = m_rows[y];
1042
0
        auto& row_state = m_state.get_mutable(row.box);
1043
0
        CSSPixels row_width = 0;
1044
0
        for (auto& column : m_columns) {
1045
0
            row_width += column.used_width;
1046
0
        }
1047
1048
0
        row_state.set_content_height(row.final_height);
1049
0
        row_state.set_content_width(row_width);
1050
0
        row_state.set_content_x(row_left_offset);
1051
0
        row_state.set_content_y(row_top_offset);
1052
0
        row_top_offset += row_state.content_height() + border_spacing_vertical();
1053
0
    }
1054
1055
0
    CSSPixels row_group_top_offset = table_state.border_top + table_state.padding_top;
1056
0
    CSSPixels row_group_left_offset = table_state.border_left + table_state.padding_left;
1057
0
    TableGrid::for_each_child_box_matching(table_box(), TableGrid::is_table_row_group, [&](auto& row_group_box) {
1058
0
        CSSPixels row_group_height = 0;
1059
0
        CSSPixels row_group_width = 0;
1060
1061
0
        auto& row_group_box_state = m_state.get_mutable(row_group_box);
1062
0
        row_group_box_state.set_content_x(row_group_left_offset);
1063
0
        row_group_box_state.set_content_y(row_group_top_offset);
1064
1065
0
        TableGrid::for_each_child_box_matching(row_group_box, TableGrid::is_table_row, [&](auto& row) {
1066
0
            auto const& row_state = m_state.get(row);
1067
0
            row_group_height += row_state.border_box_height();
1068
0
            row_group_width = max(row_group_width, row_state.border_box_width());
1069
0
        });
1070
1071
0
        row_group_box_state.set_content_height(row_group_height);
1072
0
        row_group_box_state.set_content_width(row_group_width);
1073
1074
0
        row_group_top_offset += row_group_height;
1075
0
    });
1076
1077
0
    auto total_content_height = max(row_top_offset, row_group_top_offset) - table_state.offset.y() - table_state.padding_top;
1078
0
    m_table_height = max(total_content_height, m_table_height);
1079
0
}
1080
1081
void TableFormattingContext::position_cell_boxes()
1082
0
{
1083
0
    CSSPixels left_column_offset = 0;
1084
0
    for (auto& column : m_columns) {
1085
0
        column.left_offset = left_column_offset;
1086
0
        left_column_offset += column.used_width;
1087
0
    }
1088
1089
0
    for (auto& cell : m_cells) {
1090
0
        auto& cell_state = m_state.get_mutable(cell.box);
1091
0
        auto& row_state = m_state.get(m_rows[cell.row_index].box);
1092
0
        auto const row_content_height = compute_row_content_height(cell);
1093
0
        auto const& vertical_align = cell.box->computed_values().vertical_align();
1094
        // The following image shows various alignment lines of a row:
1095
        // https://www.w3.org/TR/css-tables-3/images/cell-align-explainer.png
1096
0
        if (vertical_align.has<CSS::VerticalAlign>()) {
1097
0
            switch (vertical_align.get<CSS::VerticalAlign>()) {
1098
0
            case CSS::VerticalAlign::Middle: {
1099
0
                auto const height_diff = row_content_height - cell_state.border_box_height();
1100
0
                cell_state.padding_top += height_diff / 2;
1101
0
                cell_state.padding_bottom += height_diff / 2;
1102
0
                break;
1103
0
            }
1104
0
            case CSS::VerticalAlign::Top: {
1105
0
                cell_state.padding_bottom += row_content_height - cell_state.border_box_height();
1106
0
                break;
1107
0
            }
1108
0
            case CSS::VerticalAlign::Bottom: {
1109
0
                cell_state.padding_top += row_content_height - cell_state.border_box_height();
1110
0
                break;
1111
0
            }
1112
0
            case CSS::VerticalAlign::Baseline: {
1113
0
                cell_state.padding_top += m_rows[cell.row_index].baseline - cell.baseline;
1114
0
                cell_state.padding_bottom += row_content_height - cell_state.border_box_height();
1115
0
                break;
1116
0
            }
1117
0
            case CSS::VerticalAlign::Sub: {
1118
0
                dbgln("FIXME: Implement \"vertical-align: sub\" support for table cells");
1119
0
                break;
1120
0
            }
1121
0
            case CSS::VerticalAlign::Super: {
1122
0
                dbgln("FIXME: Implement \"vertical-align: super\" support for table cells");
1123
0
                break;
1124
0
            }
1125
0
            case CSS::VerticalAlign::TextBottom: {
1126
0
                dbgln("FIXME: Implement \"vertical-align: text-bottom\" support for table cells");
1127
0
                break;
1128
0
            }
1129
0
            case CSS::VerticalAlign::TextTop: {
1130
0
                dbgln("FIXME: Implement \"vertical-align: text-top\" support for table cells");
1131
0
                break;
1132
0
            }
1133
0
            default:
1134
0
                VERIFY_NOT_REACHED();
1135
0
            }
1136
0
        }
1137
1138
        // Compute cell position as specified by https://www.w3.org/TR/css-tables-3/#bounding-box-assignment:
1139
        // left/top location is the sum of:
1140
        // - for top: the height reserved for top captions (including margins), if any
1141
        // - the padding-left/padding-top and border-left-width/border-top-width of the table
1142
        // FIXME: Account for visibility.
1143
0
        cell_state.offset = row_state.offset.translated(
1144
0
            cell_state.border_box_left() + m_columns[cell.column_index].left_offset + cell.column_index * border_spacing_horizontal(),
1145
0
            cell_state.border_box_top());
1146
0
    }
1147
0
}
1148
1149
bool TableFormattingContext::use_fixed_mode_layout() const
1150
0
{
1151
    // Implements https://www.w3.org/TR/css-tables-3/#in-fixed-mode.
1152
    // A table-root is said to be laid out in fixed mode whenever the computed value of the table-layout property is equal to fixed, and the
1153
    // specified width of the table root is either a <length-percentage>, min-content or fit-content. When the specified width is not one of
1154
    // those values, or if the computed value of the table-layout property is auto, then the table-root is said to be laid out in auto mode.
1155
0
    auto const& width = table_box().computed_values().width();
1156
0
    return table_box().computed_values().table_layout() == CSS::TableLayout::Fixed && (width.is_length() || width.is_percentage() || width.is_min_content() || width.is_fit_content());
1157
0
}
1158
1159
bool TableFormattingContext::border_is_less_specific(const CSS::BorderData& a, const CSS::BorderData& b)
1160
0
{
1161
    // Implements criteria for steps 1, 2 and 3 of border conflict resolution algorithm, as described in
1162
    // https://www.w3.org/TR/CSS22/tables.html#border-conflict-resolution.
1163
0
    static HashMap<CSS::LineStyle, unsigned> const line_style_score = {
1164
0
        { CSS::LineStyle::Inset, 0 },
1165
0
        { CSS::LineStyle::Groove, 1 },
1166
0
        { CSS::LineStyle::Outset, 2 },
1167
0
        { CSS::LineStyle::Ridge, 3 },
1168
0
        { CSS::LineStyle::Dotted, 4 },
1169
0
        { CSS::LineStyle::Dashed, 5 },
1170
0
        { CSS::LineStyle::Solid, 6 },
1171
0
        { CSS::LineStyle::Double, 7 },
1172
0
    };
1173
1174
    // 1. Borders with the 'border-style' of 'hidden' take precedence over all other conflicting borders. Any border with this
1175
    //    value suppresses all borders at this location.
1176
0
    if (a.line_style == CSS::LineStyle::Hidden) {
1177
0
        return false;
1178
0
    }
1179
1180
0
    if (b.line_style == CSS::LineStyle::Hidden) {
1181
0
        return true;
1182
0
    }
1183
1184
    // 2. Borders with a style of 'none' have the lowest priority. Only if the border properties of all the elements meeting
1185
    //    at this edge are 'none' will the border be omitted (but note that 'none' is the default value for the border style.)
1186
0
    if (a.line_style == CSS::LineStyle::None) {
1187
0
        return true;
1188
0
    }
1189
0
    if (b.line_style == CSS::LineStyle::None) {
1190
0
        return false;
1191
0
    }
1192
    // 3. If none of the styles are 'hidden' and at least one of them is not 'none', then narrow borders are discarded in favor
1193
    //    of wider ones. If several have the same 'border-width' then styles are preferred in this order: 'double', 'solid',
1194
    //    'dashed', 'dotted', 'ridge', 'outset', 'groove', and the lowest: 'inset'.
1195
0
    if (a.width > b.width) {
1196
0
        return false;
1197
0
    } else if (a.width < b.width) {
1198
0
        return true;
1199
0
    }
1200
0
    if (*line_style_score.get(a.line_style) > *line_style_score.get(b.line_style)) {
1201
0
        return false;
1202
0
    } else if (*line_style_score.get(a.line_style) < *line_style_score.get(b.line_style)) {
1203
0
        return true;
1204
0
    }
1205
0
    return false;
1206
0
}
1207
1208
const CSS::BorderData& TableFormattingContext::border_data_conflicting_edge(TableFormattingContext::ConflictingEdge const& conflicting_edge)
1209
0
{
1210
0
    auto const& style = conflicting_edge.element->computed_values();
1211
0
    switch (conflicting_edge.side) {
1212
0
    case ConflictingSide::Top: {
1213
0
        return style.border_top();
1214
0
    }
1215
0
    case ConflictingSide::Bottom: {
1216
0
        return style.border_bottom();
1217
0
    }
1218
0
    case ConflictingSide::Left: {
1219
0
        return style.border_left();
1220
0
    }
1221
0
    case ConflictingSide::Right: {
1222
0
        return style.border_right();
1223
0
    }
1224
0
    default: {
1225
0
        VERIFY_NOT_REACHED();
1226
0
    }
1227
0
    }
1228
0
}
1229
1230
Painting::PaintableBox::BorderDataWithElementKind const TableFormattingContext::border_data_with_element_kind_from_conflicting_edge(ConflictingEdge const& conflicting_edge)
1231
0
{
1232
0
    auto const& border_data = border_data_conflicting_edge(conflicting_edge);
1233
0
    return { .border_data = border_data, .element_kind = conflicting_edge.element_kind };
1234
0
}
1235
1236
TableFormattingContext::ConflictingEdge const& TableFormattingContext::winning_conflicting_edge(TableFormattingContext::ConflictingEdge const& a, TableFormattingContext::ConflictingEdge const& b)
1237
0
{
1238
0
    auto a_border_data = border_data_conflicting_edge(a);
1239
0
    auto b_border_data = border_data_conflicting_edge(b);
1240
    // First check if step 4 of border conflict resolution applies, as described in https://www.w3.org/TR/CSS22/tables.html#border-conflict-resolution.
1241
0
    if (a_border_data.line_style == b_border_data.line_style && a_border_data.width == b_border_data.width) {
1242
        // 4. If border styles differ only in color, then a style set on a cell wins over one on a row, which wins over a
1243
        //    row group, column, column group and, lastly, table. When two elements of the same type conflict, then the one
1244
        //    further to the left (if the table's 'direction' is 'ltr'; right, if it is 'rtl') and further to the top wins.
1245
0
        if (static_cast<unsigned>(a.element_kind) < static_cast<unsigned>(b.element_kind)) {
1246
0
            return a;
1247
0
        } else if (static_cast<unsigned>(a.element_kind) > static_cast<unsigned>(b.element_kind)) {
1248
0
            return b;
1249
0
        }
1250
        // Here the element kind is the same, thus the coordinates are either both set or not set.
1251
0
        VERIFY(a.column.has_value() == b.column.has_value());
1252
0
        VERIFY(a.row.has_value() == b.row.has_value());
1253
0
        if (a.column.has_value()) {
1254
0
            if (a.column.value() < b.column.value()) {
1255
0
                return a;
1256
0
            } else if (a.column.value() > b.column.value()) {
1257
0
                return b;
1258
0
            }
1259
0
        }
1260
0
        if (a.row.has_value()) {
1261
0
            if (a.row.value() < b.row.value()) {
1262
0
                return a;
1263
0
            } else if (a.row.value() > b.row.value()) {
1264
0
                return b;
1265
0
            }
1266
0
        }
1267
0
        return a;
1268
0
    }
1269
    // Apply steps 1, 2 and 3 of the border conflict resolution algorithm.
1270
0
    return border_is_less_specific(a_border_data, b_border_data) ? b : a;
1271
0
}
1272
1273
void TableFormattingContext::border_conflict_resolution()
1274
0
{
1275
    // Implements border conflict resolution, as described in https://www.w3.org/TR/CSS22/tables.html#border-conflict-resolution.
1276
0
    BorderConflictFinder finder(this);
1277
0
    for (auto& cell : m_cells) {
1278
0
        auto& cell_state = m_state.get_mutable(cell.box);
1279
0
        cell_state.set_table_cell_coordinates(
1280
0
            Painting::PaintableBox::TableCellCoordinates {
1281
0
                .row_index = cell.row_index,
1282
0
                .column_index = cell.column_index,
1283
0
                .row_span = cell.row_span,
1284
0
                .column_span = cell.column_span });
1285
0
        if (cell.box->computed_values().border_collapse() == CSS::BorderCollapse::Separate) {
1286
0
            continue;
1287
0
        }
1288
0
        Painting::PaintableBox::BordersDataWithElementKind override_borders_data;
1289
0
        ConflictingEdge winning_edge_left {
1290
0
            .element = cell.box,
1291
0
            .element_kind = Painting::PaintableBox::ConflictingElementKind::Cell,
1292
0
            .side = ConflictingSide::Left,
1293
0
            .row = cell.row_index,
1294
0
            .column = cell.column_index,
1295
0
        };
1296
0
        for (auto const& conflicting_edge : finder.conflicting_edges(cell, ConflictingSide::Left)) {
1297
0
            winning_edge_left = winning_conflicting_edge(winning_edge_left, conflicting_edge);
1298
0
        }
1299
0
        override_borders_data.left = border_data_with_element_kind_from_conflicting_edge(winning_edge_left);
1300
0
        cell_state.border_left = override_borders_data.left.border_data.width;
1301
0
        ConflictingEdge winning_edge_right {
1302
0
            .element = cell.box,
1303
0
            .element_kind = Painting::PaintableBox::ConflictingElementKind::Cell,
1304
0
            .side = ConflictingSide::Right,
1305
0
            .row = cell.row_index,
1306
0
            .column = cell.column_index,
1307
0
        };
1308
0
        for (auto const& conflicting_edge : finder.conflicting_edges(cell, ConflictingSide::Right)) {
1309
0
            winning_edge_right = winning_conflicting_edge(winning_edge_right, conflicting_edge);
1310
0
        }
1311
0
        override_borders_data.right = border_data_with_element_kind_from_conflicting_edge(winning_edge_right);
1312
0
        cell_state.border_right = override_borders_data.right.border_data.width;
1313
0
        ConflictingEdge winning_edge_top {
1314
0
            .element = cell.box,
1315
0
            .element_kind = Painting::PaintableBox::ConflictingElementKind::Cell,
1316
0
            .side = ConflictingSide::Top,
1317
0
            .row = cell.row_index,
1318
0
            .column = cell.column_index,
1319
0
        };
1320
0
        for (auto const& conflicting_edge : finder.conflicting_edges(cell, ConflictingSide::Top)) {
1321
0
            winning_edge_top = winning_conflicting_edge(winning_edge_top, conflicting_edge);
1322
0
        }
1323
0
        override_borders_data.top = border_data_with_element_kind_from_conflicting_edge(winning_edge_top);
1324
0
        cell_state.border_top = override_borders_data.top.border_data.width;
1325
0
        ConflictingEdge winning_edge_bottom {
1326
0
            .element = cell.box,
1327
0
            .element_kind = Painting::PaintableBox::ConflictingElementKind::Cell,
1328
0
            .side = ConflictingSide::Bottom,
1329
0
            .row = cell.row_index,
1330
0
            .column = cell.column_index,
1331
0
        };
1332
0
        for (auto const& conflicting_edge : finder.conflicting_edges(cell, ConflictingSide::Bottom)) {
1333
0
            winning_edge_bottom = winning_conflicting_edge(winning_edge_bottom, conflicting_edge);
1334
0
        }
1335
0
        override_borders_data.bottom = border_data_with_element_kind_from_conflicting_edge(winning_edge_bottom);
1336
0
        cell_state.border_bottom = override_borders_data.bottom.border_data.width;
1337
0
        cell_state.set_override_borders_data(override_borders_data);
1338
0
    }
1339
0
}
1340
1341
CSSPixels TableFormattingContext::compute_row_content_height(Cell const& cell) const
1342
0
{
1343
0
    auto& row_state = m_state.get(m_rows[cell.row_index].box);
1344
0
    if (cell.row_span == 1) {
1345
0
        return row_state.content_height();
1346
0
    }
1347
    // The height of a cell is the sum of all spanned rows, as described in
1348
    // https://www.w3.org/TR/css-tables-3/#bounding-box-assignment
1349
1350
    // When the row span is greater than 1, the borders of inner rows within the span have to be
1351
    // included in the content height of the spanning cell. First top and final bottom borders are
1352
    // excluded to be consistent with the handling of row span 1 case above, which uses the content
1353
    // height (no top and bottom borders) of the row.
1354
0
    CSSPixels span_height = 0;
1355
0
    for (size_t i = 0; i < cell.row_span; ++i) {
1356
0
        auto const& row_state = m_state.get(m_rows[cell.row_index + i].box);
1357
0
        if (i == 0) {
1358
0
            span_height += row_state.content_height() + row_state.border_box_bottom();
1359
0
        } else if (i == cell.row_span - 1) {
1360
0
            span_height += row_state.border_box_top() + row_state.content_height();
1361
0
        } else {
1362
0
            span_height += row_state.border_box_height();
1363
0
        }
1364
0
    }
1365
1366
    // Compute cell height as specified by https://www.w3.org/TR/css-tables-3/#bounding-box-assignment:
1367
    // width/height is the sum of:
1368
    // - the widths/heights of all spanned visible columns/rows
1369
    // - the horizontal/vertical border-spacing times the amount of spanned visible columns/rows minus one
1370
    // FIXME: Account for visibility.
1371
0
    span_height += (cell.row_span - 1) * border_spacing_vertical();
1372
0
    return span_height;
1373
0
}
1374
1375
TableFormattingContext::BorderConflictFinder::BorderConflictFinder(TableFormattingContext const* context)
1376
0
    : m_context(context)
1377
0
{
1378
0
    collect_conflicting_col_elements();
1379
0
    collect_conflicting_row_group_elements();
1380
0
}
1381
1382
void TableFormattingContext::BorderConflictFinder::collect_conflicting_col_elements()
1383
0
{
1384
0
    m_col_elements_by_index.resize(m_context->m_columns.size());
1385
0
    for (auto* child = m_context->table_box().first_child(); child; child = child->next_sibling()) {
1386
0
        if (!child->display().is_table_column_group()) {
1387
0
            continue;
1388
0
        }
1389
0
        size_t column_index = 0;
1390
0
        for (auto* child_of_column_group = child->first_child(); child_of_column_group; child_of_column_group = child_of_column_group->next_sibling()) {
1391
0
            VERIFY(child_of_column_group->display().is_table_column());
1392
0
            auto const& col_node = static_cast<HTML::HTMLTableColElement const&>(*child_of_column_group->dom_node());
1393
0
            unsigned span = col_node.get_attribute_value(HTML::AttributeNames::span).to_number<unsigned>().value_or(1);
1394
0
            m_col_elements_by_index.resize(column_index + span);
1395
0
            for (size_t i = column_index; i < column_index + span; ++i) {
1396
0
                m_col_elements_by_index[i] = child_of_column_group;
1397
0
            }
1398
0
            column_index += span;
1399
0
        }
1400
0
    }
1401
0
}
1402
1403
void TableFormattingContext::BorderConflictFinder::collect_conflicting_row_group_elements()
1404
0
{
1405
0
    m_row_group_elements_by_index.resize(m_context->m_rows.size());
1406
0
    size_t current_row_index = 0;
1407
0
    TableGrid::for_each_child_box_matching(m_context->table_box(), TableGrid::is_table_row_group, [&](auto& row_group_box) {
1408
0
        auto start_row_index = current_row_index;
1409
0
        size_t row_count = 0;
1410
0
        TableGrid::for_each_child_box_matching(row_group_box, TableGrid::is_table_row, [&](auto&) {
1411
0
            ++row_count;
1412
0
        });
1413
0
        TableGrid::for_each_child_box_matching(row_group_box, TableGrid::is_table_row, [&](auto&) {
1414
0
            m_row_group_elements_by_index[current_row_index] = RowGroupInfo {
1415
0
                .row_group = &row_group_box, .start_index = start_row_index, .row_count = row_count
1416
0
            };
1417
0
            ++current_row_index;
1418
0
            return IterationDecision::Continue;
1419
0
        });
1420
0
    });
1421
0
}
1422
1423
void TableFormattingContext::BorderConflictFinder::collect_cell_conflicting_edges(Vector<ConflictingEdge>& result, Cell const& cell, TableFormattingContext::ConflictingSide edge) const
1424
0
{
1425
    // Right edge of the cell to the left.
1426
0
    if (cell.column_index >= cell.column_span && edge == ConflictingSide::Left) {
1427
0
        auto left_cell_column_index = cell.column_index - cell.column_span;
1428
0
        auto maybe_cell_to_left = m_context->m_cells_by_coordinate[cell.row_index][left_cell_column_index];
1429
0
        if (maybe_cell_to_left.has_value()) {
1430
0
            result.append({ maybe_cell_to_left->box, Painting::PaintableBox::ConflictingElementKind::Cell, ConflictingSide::Right, cell.row_index, left_cell_column_index });
1431
0
        }
1432
0
    }
1433
    // Left edge of the cell to the right.
1434
0
    if (cell.column_index + cell.column_span < m_context->m_cells_by_coordinate[cell.row_index].size() && edge == ConflictingSide::Right) {
1435
0
        auto right_cell_column_index = cell.column_index + cell.column_span;
1436
0
        auto maybe_cell_to_right = m_context->m_cells_by_coordinate[cell.row_index][right_cell_column_index];
1437
0
        if (maybe_cell_to_right.has_value()) {
1438
0
            result.append({ maybe_cell_to_right->box, Painting::PaintableBox::ConflictingElementKind::Cell, ConflictingSide::Left, cell.row_index, right_cell_column_index });
1439
0
        }
1440
0
    }
1441
    // Bottom edge of the cell above.
1442
0
    if (cell.row_index >= cell.row_span && edge == ConflictingSide::Top) {
1443
0
        auto above_cell_row_index = cell.row_index - cell.row_span;
1444
0
        auto maybe_cell_above = m_context->m_cells_by_coordinate[above_cell_row_index][cell.column_index];
1445
0
        if (maybe_cell_above.has_value()) {
1446
0
            result.append({ maybe_cell_above->box, Painting::PaintableBox::ConflictingElementKind::Cell, ConflictingSide::Bottom, above_cell_row_index, cell.column_index });
1447
0
        }
1448
0
    }
1449
    // Top edge of the cell below.
1450
0
    if (cell.row_index + cell.row_span < m_context->m_cells_by_coordinate.size() && edge == ConflictingSide::Bottom) {
1451
0
        auto below_cell_row_index = cell.row_index + cell.row_span;
1452
0
        auto maybe_cell_below = m_context->m_cells_by_coordinate[below_cell_row_index][cell.column_index];
1453
0
        if (maybe_cell_below.has_value()) {
1454
0
            result.append({ maybe_cell_below->box, Painting::PaintableBox::ConflictingElementKind::Cell, ConflictingSide::Top, below_cell_row_index, cell.column_index });
1455
0
        }
1456
0
    }
1457
0
}
1458
1459
void TableFormattingContext::BorderConflictFinder::collect_row_conflicting_edges(Vector<ConflictingEdge>& result, Cell const& cell, TableFormattingContext::ConflictingSide edge) const
1460
0
{
1461
    // Top edge of the row.
1462
0
    if (edge == ConflictingSide::Top) {
1463
0
        result.append({ m_context->m_rows[cell.row_index].box, Painting::PaintableBox::ConflictingElementKind::Row, ConflictingSide::Top, cell.row_index, {} });
1464
0
    }
1465
    // Bottom edge of the row.
1466
0
    if (edge == ConflictingSide::Bottom) {
1467
0
        result.append({ m_context->m_rows[cell.row_index].box, Painting::PaintableBox::ConflictingElementKind::Row, ConflictingSide::Bottom, cell.row_index, {} });
1468
0
    }
1469
    // Bottom edge of the row above.
1470
0
    if (cell.row_index >= cell.row_span && edge == ConflictingSide::Top) {
1471
0
        auto above_row_index = cell.row_index - cell.row_span;
1472
0
        result.append({ m_context->m_rows[above_row_index].box, Painting::PaintableBox::ConflictingElementKind::Row, ConflictingSide::Bottom, above_row_index, {} });
1473
0
    }
1474
    // Top edge of the row below.
1475
0
    if (cell.row_index + cell.row_span < m_context->m_rows.size() && edge == ConflictingSide::Bottom) {
1476
0
        auto below_row_index = cell.row_index + cell.row_span;
1477
0
        result.append({ m_context->m_rows[below_row_index].box, Painting::PaintableBox::ConflictingElementKind::Row, ConflictingSide::Top, below_row_index, {} });
1478
0
    }
1479
0
}
1480
1481
void TableFormattingContext::BorderConflictFinder::collect_row_group_conflicting_edges(Vector<ConflictingEdge>& result, Cell const& cell, TableFormattingContext::ConflictingSide edge) const
1482
0
{
1483
0
    auto const& maybe_row_group = m_row_group_elements_by_index[cell.row_index];
1484
    // Top edge of the row group.
1485
0
    if (maybe_row_group.has_value() && cell.row_index == maybe_row_group->start_index && edge == ConflictingSide::Top) {
1486
0
        result.append({ maybe_row_group->row_group, Painting::PaintableBox::ConflictingElementKind::RowGroup, ConflictingSide::Top, maybe_row_group->start_index, {} });
1487
0
    }
1488
    // Bottom edge of the row group above.
1489
0
    if (cell.row_index >= cell.row_span) {
1490
0
        auto const& maybe_row_group_above = m_row_group_elements_by_index[cell.row_index - cell.row_span];
1491
0
        if (maybe_row_group_above.has_value() && cell.row_index == maybe_row_group_above->start_index + maybe_row_group_above->row_count && edge == ConflictingSide::Top) {
1492
0
            result.append({ maybe_row_group_above->row_group, Painting::PaintableBox::ConflictingElementKind::RowGroup, ConflictingSide::Bottom, maybe_row_group_above->start_index, {} });
1493
0
        }
1494
0
    }
1495
    // Bottom edge of the row group.
1496
0
    if (maybe_row_group.has_value() && cell.row_index == maybe_row_group->start_index + maybe_row_group->row_count - 1 && edge == ConflictingSide::Bottom) {
1497
0
        result.append({ maybe_row_group->row_group, Painting::PaintableBox::ConflictingElementKind::RowGroup, ConflictingSide::Bottom, maybe_row_group->start_index, {} });
1498
0
    }
1499
    // Top edge of the row group below.
1500
0
    if (cell.row_index + cell.row_span < m_row_group_elements_by_index.size()) {
1501
0
        auto const& maybe_row_group_below = m_row_group_elements_by_index[cell.row_index + cell.row_span];
1502
0
        if (maybe_row_group_below.has_value() && cell.row_index + cell.row_span == maybe_row_group_below->start_index && edge == ConflictingSide::Bottom) {
1503
0
            result.append({ maybe_row_group_below->row_group, Painting::PaintableBox::ConflictingElementKind::RowGroup, ConflictingSide::Top, maybe_row_group_below->start_index, {} });
1504
0
        }
1505
0
    }
1506
0
}
1507
1508
void TableFormattingContext::BorderConflictFinder::collect_column_group_conflicting_edges(Vector<ConflictingEdge>& result, Cell const& cell, TableFormattingContext::ConflictingSide edge) const
1509
0
{
1510
    // Left edge of the column group.
1511
0
    if (m_col_elements_by_index[cell.column_index] && edge == ConflictingSide::Left) {
1512
0
        result.append({ m_col_elements_by_index[cell.column_index], Painting::PaintableBox::ConflictingElementKind::ColumnGroup, ConflictingSide::Left, {}, cell.column_index });
1513
0
    }
1514
    // Right edge of the column group to the left.
1515
0
    if (cell.column_index >= cell.column_span && m_col_elements_by_index[cell.column_index - cell.column_span] && edge == ConflictingSide::Left) {
1516
0
        auto left_column_index = cell.column_index - cell.column_span;
1517
0
        result.append({ m_col_elements_by_index[left_column_index], Painting::PaintableBox::ConflictingElementKind::ColumnGroup, ConflictingSide::Right, {}, left_column_index });
1518
0
    }
1519
    // Right edge of the column group.
1520
0
    if (m_col_elements_by_index[cell.column_index] && edge == ConflictingSide::Right) {
1521
0
        result.append({ m_col_elements_by_index[cell.column_index], Painting::PaintableBox::ConflictingElementKind::ColumnGroup, ConflictingSide::Right, {}, cell.column_index });
1522
0
    }
1523
    // Left edge of the column group to the right.
1524
0
    if (cell.column_index + cell.column_span < m_col_elements_by_index.size() && m_col_elements_by_index[cell.column_index + cell.column_span] && edge == ConflictingSide::Right) {
1525
0
        auto right_column_index = cell.column_index + cell.column_span;
1526
0
        result.append({ m_col_elements_by_index[right_column_index], Painting::PaintableBox::ConflictingElementKind::ColumnGroup, ConflictingSide::Left, {}, right_column_index });
1527
0
    }
1528
0
}
1529
1530
void TableFormattingContext::BorderConflictFinder::collect_table_box_conflicting_edges(Vector<ConflictingEdge>& result, Cell const& cell, TableFormattingContext::ConflictingSide edge) const
1531
0
{
1532
    // Top edge from column group or table. Left and right edges of the column group are handled in collect_column_group_conflicting_edges.
1533
0
    if (cell.row_index == 0 && edge == ConflictingSide::Top) {
1534
0
        if (m_col_elements_by_index[cell.column_index]) {
1535
0
            result.append({ m_col_elements_by_index[cell.column_index], Painting::PaintableBox::ConflictingElementKind::ColumnGroup, ConflictingSide::Top, {}, cell.column_index });
1536
0
        }
1537
0
        result.append({ &m_context->table_box(), Painting::PaintableBox::ConflictingElementKind::Table, ConflictingSide::Top, {}, {} });
1538
0
    }
1539
    // Bottom edge from column group or table. Left and right edges of the column group are handled in collect_column_group_conflicting_edges.
1540
0
    if (cell.row_index + cell.row_span == m_context->m_rows.size() && edge == ConflictingSide::Bottom) {
1541
0
        if (m_col_elements_by_index[cell.column_index]) {
1542
0
            result.append({ m_col_elements_by_index[cell.column_index], Painting::PaintableBox::ConflictingElementKind::ColumnGroup, ConflictingSide::Bottom, {}, cell.column_index });
1543
0
        }
1544
0
        result.append({ &m_context->table_box(), Painting::PaintableBox::ConflictingElementKind::Table, ConflictingSide::Bottom, {}, {} });
1545
0
    }
1546
    // Left edge from row group or table. Top and bottom edges of the row group are handled in collect_row_group_conflicting_edges.
1547
0
    if (cell.column_index == 0 && edge == ConflictingSide::Left) {
1548
0
        result.append({ m_context->m_rows[cell.row_index].box, Painting::PaintableBox::ConflictingElementKind::Row, ConflictingSide::Left, cell.row_index, {} });
1549
0
        if (m_row_group_elements_by_index[cell.row_index].has_value()) {
1550
0
            result.append({ m_row_group_elements_by_index[cell.row_index]->row_group, Painting::PaintableBox::ConflictingElementKind::RowGroup, ConflictingSide::Left, cell.row_index, {} });
1551
0
        }
1552
0
        result.append({ &m_context->table_box(), Painting::PaintableBox::ConflictingElementKind::Table, ConflictingSide::Left, {}, {} });
1553
0
    }
1554
    // Right edge from row group or table. Top and bottom edges of the row group are handled in collect_row_group_conflicting_edges.
1555
0
    if (cell.column_index + cell.column_span == m_context->m_columns.size() && edge == ConflictingSide::Right) {
1556
0
        result.append({ m_context->m_rows[cell.row_index].box, Painting::PaintableBox::ConflictingElementKind::Row, ConflictingSide::Right, cell.row_index, {} });
1557
0
        if (m_row_group_elements_by_index[cell.row_index].has_value()) {
1558
0
            result.append({ m_row_group_elements_by_index[cell.row_index]->row_group, Painting::PaintableBox::ConflictingElementKind::RowGroup, ConflictingSide::Right, cell.row_index, {} });
1559
0
        }
1560
0
        result.append({ &m_context->table_box(), Painting::PaintableBox::ConflictingElementKind::Table, ConflictingSide::Right, {}, {} });
1561
0
    }
1562
0
}
1563
1564
Vector<TableFormattingContext::ConflictingEdge> TableFormattingContext::BorderConflictFinder::conflicting_edges(
1565
    Cell const& cell, TableFormattingContext::ConflictingSide edge) const
1566
0
{
1567
0
    Vector<ConflictingEdge> result = {};
1568
0
    collect_cell_conflicting_edges(result, cell, edge);
1569
0
    collect_row_conflicting_edges(result, cell, edge);
1570
0
    collect_row_group_conflicting_edges(result, cell, edge);
1571
0
    collect_column_group_conflicting_edges(result, cell, edge);
1572
0
    collect_table_box_conflicting_edges(result, cell, edge);
1573
0
    return result;
1574
0
}
1575
1576
void TableFormattingContext::finish_grid_initialization(TableGrid const& table_grid)
1577
0
{
1578
0
    m_columns.resize(table_grid.column_count());
1579
0
    m_cells_by_coordinate.resize(m_rows.size());
1580
0
    for (auto& position_to_cell_row : m_cells_by_coordinate) {
1581
0
        position_to_cell_row.resize(table_grid.column_count());
1582
0
    }
1583
0
    for (auto const& cell : m_cells) {
1584
0
        m_cells_by_coordinate[cell.row_index][cell.column_index] = cell;
1585
0
        m_columns[cell.column_index].has_originating_cells = true;
1586
0
    }
1587
0
}
1588
1589
void TableFormattingContext::run_until_width_calculation(AvailableSpace const& available_space)
1590
0
{
1591
0
    m_available_space = available_space;
1592
1593
    // Determine the number of rows/columns the table requires.
1594
0
    finish_grid_initialization(TableGrid::calculate_row_column_grid(context_box(), m_cells, m_rows));
1595
1596
0
    border_conflict_resolution();
1597
1598
    // Compute the minimum width of each column.
1599
0
    compute_cell_measures();
1600
0
    compute_outer_content_sizes();
1601
0
    compute_table_measures<Column>();
1602
1603
    // https://www.w3.org/TR/css-tables-3/#row-layout
1604
    // Since during row layout the specified heights of cells in the row were ignored and cells that were spanning more than one rows
1605
    // have not been sized correctly, their height will need to be eventually distributed to the set of rows they spanned. This is done
1606
    // by running the same algorithm as the column measurement, with the span=1 value being initialized (for min-content) with the largest
1607
    // of the resulting height of the previous row layout, the height specified on the corresponding table-row (if any), and the largest
1608
    // height specified on cells that span this row only (the algorithm starts by considering cells of span 2 on top of that assignment).
1609
0
    compute_table_measures<Row>();
1610
1611
    // Compute the width of the table.
1612
0
    compute_table_width();
1613
0
}
1614
1615
void TableFormattingContext::run(AvailableSpace const& available_space)
1616
0
{
1617
0
    m_available_space = available_space;
1618
1619
0
    auto total_captions_height = run_caption_layout(CSS::CaptionSide::Top);
1620
1621
0
    run_until_width_calculation(available_space);
1622
1623
0
    if (available_space.width.is_intrinsic_sizing_constraint() && !available_space.height.is_intrinsic_sizing_constraint()) {
1624
0
        return;
1625
0
    }
1626
1627
    // Distribute the width of the table among columns.
1628
0
    distribute_width_to_columns();
1629
1630
0
    compute_table_height();
1631
1632
0
    distribute_height_to_rows();
1633
1634
0
    position_row_boxes();
1635
0
    position_cell_boxes();
1636
1637
0
    m_state.get_mutable(table_box()).set_content_height(m_table_height);
1638
1639
0
    total_captions_height += run_caption_layout(CSS::CaptionSide::Bottom);
1640
1641
    // Table captions are positioned between the table margins and its borders (outside the grid box borders) as described in
1642
    // https://www.w3.org/TR/css-tables-3/#bounding-box-assignment
1643
    // A visual representation of this model can be found at https://www.w3.org/TR/css-tables-3/images/table_container.png
1644
0
    m_state.get_mutable(table_box()).margin_bottom += total_captions_height;
1645
1646
0
    m_automatic_content_height = m_table_height;
1647
0
}
1648
1649
CSSPixels TableFormattingContext::automatic_content_width() const
1650
0
{
1651
0
    return greatest_child_width(context_box());
1652
0
}
1653
1654
CSSPixels TableFormattingContext::automatic_content_height() const
1655
0
{
1656
0
    return m_automatic_content_height;
1657
0
}
1658
1659
template<>
1660
size_t TableFormattingContext::cell_span<TableFormattingContext::Row>(TableFormattingContext::Cell const& cell)
1661
0
{
1662
0
    return cell.row_span;
1663
0
}
1664
1665
template<>
1666
size_t TableFormattingContext::cell_span<TableFormattingContext::Column>(TableFormattingContext::Cell const& cell)
1667
0
{
1668
0
    return cell.column_span;
1669
0
}
1670
1671
template<>
1672
size_t TableFormattingContext::cell_index<TableFormattingContext::Row>(TableFormattingContext::Cell const& cell)
1673
0
{
1674
0
    return cell.row_index;
1675
0
}
1676
1677
template<>
1678
size_t TableFormattingContext::cell_index<TableFormattingContext::Column>(TableFormattingContext::Cell const& cell)
1679
0
{
1680
0
    return cell.column_index;
1681
0
}
1682
1683
template<>
1684
CSSPixels TableFormattingContext::cell_min_size<TableFormattingContext::Row>(TableFormattingContext::Cell const& cell)
1685
0
{
1686
0
    return cell.outer_min_height;
1687
0
}
1688
1689
template<>
1690
CSSPixels TableFormattingContext::cell_min_size<TableFormattingContext::Column>(TableFormattingContext::Cell const& cell)
1691
0
{
1692
0
    return cell.outer_min_width;
1693
0
}
1694
1695
template<>
1696
CSSPixels TableFormattingContext::cell_max_size<TableFormattingContext::Row>(TableFormattingContext::Cell const& cell)
1697
0
{
1698
0
    return cell.outer_max_height;
1699
0
}
1700
1701
template<>
1702
CSSPixels TableFormattingContext::cell_max_size<TableFormattingContext::Column>(TableFormattingContext::Cell const& cell)
1703
0
{
1704
0
    return cell.outer_max_width;
1705
0
}
1706
1707
template<>
1708
double TableFormattingContext::cell_percentage_contribution<TableFormattingContext::Row>(TableFormattingContext::Cell const& cell)
1709
0
{
1710
    // Definition of percentage contribution: https://www.w3.org/TR/css-tables-3/#percentage-contribution
1711
0
    auto const& computed_values = cell.box->computed_values();
1712
0
    auto max_height_percentage = computed_values.max_height().is_percentage() ? computed_values.max_height().percentage().value() : static_cast<double>(INFINITY);
1713
0
    auto height_percentage = computed_values.height().is_percentage() ? computed_values.height().percentage().value() : 0;
1714
0
    return min(height_percentage, max_height_percentage);
1715
0
}
1716
1717
template<>
1718
double TableFormattingContext::cell_percentage_contribution<TableFormattingContext::Column>(TableFormattingContext::Cell const& cell)
1719
0
{
1720
    // Definition of percentage contribution: https://www.w3.org/TR/css-tables-3/#percentage-contribution
1721
0
    auto const& computed_values = cell.box->computed_values();
1722
0
    auto max_width_percentage = computed_values.max_width().is_percentage() ? computed_values.max_width().percentage().value() : static_cast<double>(INFINITY);
1723
0
    auto width_percentage = computed_values.width().is_percentage() ? computed_values.width().percentage().value() : 0;
1724
0
    return min(width_percentage, max_width_percentage);
1725
0
}
1726
1727
template<>
1728
bool TableFormattingContext::cell_has_intrinsic_percentage<TableFormattingContext::Row>(TableFormattingContext::Cell const& cell)
1729
0
{
1730
0
    return cell.box->computed_values().height().is_percentage();
1731
0
}
1732
1733
template<>
1734
bool TableFormattingContext::cell_has_intrinsic_percentage<TableFormattingContext::Column>(TableFormattingContext::Cell const& cell)
1735
0
{
1736
0
    return cell.box->computed_values().width().is_percentage();
1737
0
}
1738
1739
template<>
1740
void TableFormattingContext::initialize_intrinsic_percentages_from_rows_or_columns<TableFormattingContext::Row>()
1741
0
{
1742
0
    for (auto& row : m_rows) {
1743
0
        auto const& computed_values = row.box->computed_values();
1744
        // Definition of percentage contribution: https://www.w3.org/TR/css-tables-3/#percentage-contribution
1745
0
        auto max_height_percentage = computed_values.max_height().is_percentage() ? computed_values.max_height().percentage().value() : static_cast<double>(INFINITY);
1746
0
        auto height_percentage = computed_values.height().is_percentage() ? computed_values.height().percentage().value() : 0;
1747
0
        row.has_intrinsic_percentage = computed_values.max_height().is_percentage() || computed_values.height().is_percentage();
1748
0
        row.intrinsic_percentage = min(height_percentage, max_height_percentage);
1749
0
    }
1750
0
}
1751
1752
template<>
1753
void TableFormattingContext::initialize_intrinsic_percentages_from_rows_or_columns<TableFormattingContext::Column>()
1754
0
{
1755
0
    size_t column_index = 0;
1756
0
    TableGrid::for_each_child_box_matching(table_box(), is_table_column_group, [&](auto& column_group_box) {
1757
0
        TableGrid::for_each_child_box_matching(column_group_box, is_table_column, [&](auto& column_box) {
1758
0
            auto const& computed_values = column_box.computed_values();
1759
            // Definition of percentage contribution: https://www.w3.org/TR/css-tables-3/#percentage-contribution
1760
0
            auto max_width_percentage = computed_values.max_width().is_percentage() ? computed_values.max_width().percentage().value() : static_cast<double>(INFINITY);
1761
0
            auto width_percentage = computed_values.width().is_percentage() ? computed_values.width().percentage().value() : 0;
1762
0
            m_columns[column_index].has_intrinsic_percentage = computed_values.max_width().is_percentage() || computed_values.width().is_percentage();
1763
0
            m_columns[column_index].intrinsic_percentage = min(width_percentage, max_width_percentage);
1764
0
            auto const& col_node = static_cast<HTML::HTMLTableColElement const&>(*column_box.dom_node());
1765
0
            unsigned span = col_node.get_attribute_value(HTML::AttributeNames::span).to_number<unsigned>().value_or(1);
1766
0
            column_index += span;
1767
0
        });
1768
0
    });
1769
0
}
1770
1771
template<class RowOrColumn>
1772
void TableFormattingContext::initialize_intrinsic_percentages_from_cells()
1773
0
{
1774
0
    auto& rows_or_columns = table_rows_or_columns<RowOrColumn>();
1775
1776
0
    for (auto& cell : m_cells) {
1777
0
        auto cell_span_value = cell_span<RowOrColumn>(cell);
1778
0
        auto cell_start_rc_index = cell_index<RowOrColumn>(cell);
1779
0
        auto cell_end_rc_index = cell_start_rc_index + cell_span_value;
1780
0
        if (cell_has_intrinsic_percentage<RowOrColumn>(cell)) {
1781
0
            for (auto rc_index = cell_start_rc_index; rc_index < cell_end_rc_index; rc_index++) {
1782
0
                rows_or_columns[rc_index].has_intrinsic_percentage = true;
1783
0
            }
1784
0
            if (cell_span_value != 1) {
1785
0
                continue;
1786
0
            }
1787
0
        } else {
1788
0
            continue;
1789
0
        }
1790
0
        size_t rc_index = cell_index<RowOrColumn>(cell);
1791
0
        rows_or_columns[rc_index].has_intrinsic_percentage = true;
1792
0
        rows_or_columns[rc_index].intrinsic_percentage = max(cell_percentage_contribution<RowOrColumn>(cell), rows_or_columns[rc_index].intrinsic_percentage);
1793
0
    }
1794
0
}
Unexecuted instantiation: void Web::Layout::TableFormattingContext::initialize_intrinsic_percentages_from_cells<Web::Layout::TableFormattingContext::Column>()
Unexecuted instantiation: void Web::Layout::TableFormattingContext::initialize_intrinsic_percentages_from_cells<Web::Layout::TableGrid::Row>()
1795
1796
template<>
1797
CSSPixels TableFormattingContext::border_spacing<TableFormattingContext::Row>()
1798
0
{
1799
0
    return border_spacing_vertical();
1800
0
}
1801
1802
template<>
1803
CSSPixels TableFormattingContext::border_spacing<TableFormattingContext::Column>()
1804
0
{
1805
0
    return border_spacing_horizontal();
1806
0
}
1807
1808
CSSPixels TableFormattingContext::border_spacing_horizontal() const
1809
0
{
1810
0
    auto const& computed_values = table_box().computed_values();
1811
    // When a table is laid out in collapsed-borders mode, the border-spacing of the table-root is ignored (as if it was set to 0px):
1812
    // https://www.w3.org/TR/css-tables-3/#collapsed-style-overrides
1813
0
    if (computed_values.border_collapse() == CSS::BorderCollapse::Collapse)
1814
0
        return 0;
1815
0
    return computed_values.border_spacing_horizontal().to_px(table_box());
1816
0
}
1817
1818
CSSPixels TableFormattingContext::border_spacing_vertical() const
1819
0
{
1820
0
    auto const& computed_values = table_box().computed_values();
1821
    // When a table is laid out in collapsed-borders mode, the border-spacing of the table-root is ignored (as if it was set to 0px):
1822
    // https://www.w3.org/TR/css-tables-3/#collapsed-style-overrides
1823
0
    if (computed_values.border_collapse() == CSS::BorderCollapse::Collapse)
1824
0
        return 0;
1825
0
    return computed_values.border_spacing_vertical().to_px(table_box());
1826
0
}
1827
1828
template<>
1829
Vector<TableFormattingContext::Row>& TableFormattingContext::table_rows_or_columns()
1830
0
{
1831
0
    return m_rows;
1832
0
}
1833
1834
template<>
1835
Vector<TableFormattingContext::Column>& TableFormattingContext::table_rows_or_columns()
1836
0
{
1837
0
    return m_columns;
1838
0
}
1839
1840
}