/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 | | } |