/src/serenity/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2021-2024, Andreas Kling <kling@serenityos.org> |
3 | | * Copyright (c) 2021, Tobias Christiansen <tobyase@serenityos.org> |
4 | | * |
5 | | * SPDX-License-Identifier: BSD-2-Clause |
6 | | */ |
7 | | |
8 | | #include "InlineFormattingContext.h" |
9 | | #include <AK/QuickSort.h> |
10 | | #include <AK/StdLibExtras.h> |
11 | | #include <LibWeb/Layout/BlockContainer.h> |
12 | | #include <LibWeb/Layout/BlockFormattingContext.h> |
13 | | #include <LibWeb/Layout/Box.h> |
14 | | #include <LibWeb/Layout/FlexFormattingContext.h> |
15 | | #include <LibWeb/Layout/ReplacedBox.h> |
16 | | #include <LibWeb/Layout/TextNode.h> |
17 | | #include <LibWeb/Layout/Viewport.h> |
18 | | |
19 | | namespace Web::Layout { |
20 | | |
21 | | CSSPixels FlexFormattingContext::get_pixel_width(Box const& box, CSS::Size const& size) const |
22 | 0 | { |
23 | 0 | return calculate_inner_width(box, containing_block_width_as_available_size(box), size); |
24 | 0 | } |
25 | | |
26 | | CSSPixels FlexFormattingContext::get_pixel_height(Box const& box, CSS::Size const& size) const |
27 | 0 | { |
28 | 0 | return calculate_inner_height(box, containing_block_height_as_available_size(box), size); |
29 | 0 | } |
30 | | |
31 | | FlexFormattingContext::FlexFormattingContext(LayoutState& state, LayoutMode layout_mode, Box const& flex_container, FormattingContext* parent) |
32 | 0 | : FormattingContext(Type::Flex, layout_mode, state, flex_container, parent) |
33 | 0 | , m_flex_container_state(m_state.get_mutable(flex_container)) |
34 | 0 | , m_flex_direction(flex_container.computed_values().flex_direction()) |
35 | 0 | { |
36 | 0 | } |
37 | | |
38 | 0 | FlexFormattingContext::~FlexFormattingContext() = default; |
39 | | |
40 | | CSSPixels FlexFormattingContext::automatic_content_width() const |
41 | 0 | { |
42 | 0 | return m_flex_container_state.content_width(); |
43 | 0 | } |
44 | | |
45 | | CSSPixels FlexFormattingContext::automatic_content_height() const |
46 | 0 | { |
47 | 0 | return m_flex_container_state.content_height(); |
48 | 0 | } |
49 | | |
50 | | void FlexFormattingContext::run(AvailableSpace const& available_space) |
51 | 0 | { |
52 | | // This implements https://www.w3.org/TR/css-flexbox-1/#layout-algorithm |
53 | | |
54 | | // 1. Generate anonymous flex items |
55 | 0 | generate_anonymous_flex_items(); |
56 | | |
57 | | // 2. Determine the available main and cross space for the flex items |
58 | 0 | determine_available_space_for_items(available_space); |
59 | |
|
60 | 0 | { |
61 | | // https://drafts.csswg.org/css-flexbox-1/#definite-sizes |
62 | | // 3. If a single-line flex container has a definite cross size, |
63 | | // the automatic preferred outer cross size of any stretched flex items is the flex container’s inner cross size |
64 | | // (clamped to the flex item’s min and max cross size) and is considered definite. |
65 | 0 | if (is_single_line() && has_definite_cross_size(m_flex_container_state)) { |
66 | 0 | auto flex_container_inner_cross_size = inner_cross_size(m_flex_container_state); |
67 | 0 | for (auto& item : m_flex_items) { |
68 | 0 | if (!flex_item_is_stretched(item)) |
69 | 0 | continue; |
70 | 0 | auto item_min_cross_size = has_cross_min_size(item.box) ? specified_cross_min_size(item.box) : 0; |
71 | 0 | auto item_max_cross_size = has_cross_max_size(item.box) ? specified_cross_max_size(item.box) : CSSPixels::max(); |
72 | 0 | auto item_preferred_outer_cross_size = css_clamp(flex_container_inner_cross_size, item_min_cross_size, item_max_cross_size); |
73 | 0 | auto item_inner_cross_size = item_preferred_outer_cross_size - item.margins.cross_before - item.margins.cross_after - item.padding.cross_before - item.padding.cross_after - item.borders.cross_before - item.borders.cross_after; |
74 | 0 | set_cross_size(item.box, item_inner_cross_size); |
75 | 0 | set_has_definite_cross_size(item); |
76 | 0 | } |
77 | 0 | } |
78 | 0 | } |
79 | | |
80 | | // 3. Determine the flex base size and hypothetical main size of each item |
81 | 0 | for (auto& item : m_flex_items) { |
82 | 0 | if (item.box->is_replaced_box()) { |
83 | | // FIXME: Get rid of prepare_for_replaced_layout() and make replaced elements figure out their intrinsic size lazily. |
84 | 0 | static_cast<ReplacedBox&>(*item.box).prepare_for_replaced_layout(); |
85 | 0 | } |
86 | 0 | determine_flex_base_size_and_hypothetical_main_size(item); |
87 | 0 | } |
88 | |
|
89 | 0 | if (available_space.width.is_intrinsic_sizing_constraint() || available_space.height.is_intrinsic_sizing_constraint()) { |
90 | | // We're computing intrinsic size for the flex container. This happens at the end of run(). |
91 | 0 | } else { |
92 | | // 4. Determine the main size of the flex container |
93 | | // Determine the main size of the flex container using the rules of the formatting context in which it participates. |
94 | | // NOTE: The automatic block size of a block-level flex container is its max-content size. |
95 | | |
96 | | // NOTE: We've already handled this in the parent formatting context. |
97 | | // Specifically, all formatting contexts will have assigned width & height to the flex container |
98 | | // before this formatting context runs. |
99 | 0 | } |
100 | | |
101 | | // 5. Collect flex items into flex lines: |
102 | | // After this step no additional items are to be added to flex_lines or any of its items! |
103 | 0 | collect_flex_items_into_flex_lines(); |
104 | | |
105 | | // 6. Resolve the flexible lengths |
106 | 0 | resolve_flexible_lengths(); |
107 | | |
108 | | // Cross Size Determination |
109 | | // 7. Determine the hypothetical cross size of each item |
110 | 0 | for (auto& item : m_flex_items) { |
111 | 0 | determine_hypothetical_cross_size_of_item(item, false); |
112 | 0 | } |
113 | | |
114 | | // 8. Calculate the cross size of each flex line. |
115 | 0 | calculate_cross_size_of_each_flex_line(); |
116 | | |
117 | | // 9. Handle 'align-content: stretch'. |
118 | 0 | handle_align_content_stretch(); |
119 | | |
120 | | // 10. Collapse visibility:collapse items. |
121 | | // FIXME: This |
122 | | |
123 | | // 11. Determine the used cross size of each flex item. |
124 | 0 | determine_used_cross_size_of_each_flex_item(); |
125 | | |
126 | | // 12. Distribute any remaining free space. |
127 | 0 | distribute_any_remaining_free_space(); |
128 | | |
129 | | // 13. Resolve cross-axis auto margins. |
130 | 0 | resolve_cross_axis_auto_margins(); |
131 | | |
132 | | // 14. Align all flex items along the cross-axis |
133 | 0 | align_all_flex_items_along_the_cross_axis(); |
134 | | |
135 | | // 15. Determine the flex container’s used cross size |
136 | | // NOTE: This is handled by the parent formatting context. |
137 | |
|
138 | 0 | { |
139 | | // https://drafts.csswg.org/css-flexbox-1/#definite-sizes |
140 | | // 4. Once the cross size of a flex line has been determined, |
141 | | // the cross sizes of items in auto-sized flex containers are also considered definite for the purpose of layout. |
142 | 0 | auto const& flex_container_computed_cross_size = is_row_layout() ? flex_container().computed_values().height() : flex_container().computed_values().width(); |
143 | 0 | if (flex_container_computed_cross_size.is_auto()) { |
144 | 0 | for (auto& item : m_flex_items) { |
145 | 0 | set_cross_size(item.box, item.cross_size.value()); |
146 | 0 | set_has_definite_cross_size(item); |
147 | 0 | } |
148 | 0 | } |
149 | 0 | } |
150 | |
|
151 | 0 | { |
152 | | // NOTE: We re-resolve cross sizes here, now that we can resolve percentages. |
153 | | |
154 | | // 7. Determine the hypothetical cross size of each item |
155 | 0 | for (auto& item : m_flex_items) { |
156 | 0 | determine_hypothetical_cross_size_of_item(item, true); |
157 | 0 | } |
158 | | |
159 | | // 11. Determine the used cross size of each flex item. |
160 | 0 | determine_used_cross_size_of_each_flex_item(); |
161 | 0 | } |
162 | | |
163 | | // 16. Align all flex lines (per align-content) |
164 | 0 | align_all_flex_lines(); |
165 | |
|
166 | 0 | if (available_space.width.is_intrinsic_sizing_constraint() || available_space.height.is_intrinsic_sizing_constraint()) { |
167 | | // We're computing intrinsic size for the flex container. |
168 | 0 | determine_intrinsic_size_of_flex_container(); |
169 | 0 | } else { |
170 | | // This is a normal layout (not intrinsic sizing). |
171 | | // AD-HOC: Finally, layout the inside of all flex items. |
172 | 0 | copy_dimensions_from_flex_items_to_boxes(); |
173 | 0 | for (auto& item : m_flex_items) { |
174 | 0 | if (auto independent_formatting_context = layout_inside(item.box, LayoutMode::Normal, item.used_values.available_inner_space_or_constraints_from(m_available_space_for_items->space))) |
175 | 0 | independent_formatting_context->parent_context_did_dimension_child_root_box(); |
176 | |
|
177 | 0 | compute_inset(item.box); |
178 | 0 | } |
179 | 0 | } |
180 | 0 | } |
181 | | |
182 | | void FlexFormattingContext::parent_context_did_dimension_child_root_box() |
183 | 0 | { |
184 | 0 | if (m_layout_mode != LayoutMode::Normal) |
185 | 0 | return; |
186 | | |
187 | 0 | flex_container().for_each_child_of_type<Box>([&](Layout::Box& box) { |
188 | 0 | if (box.is_absolutely_positioned()) { |
189 | 0 | auto& cb_state = m_state.get(*box.containing_block()); |
190 | 0 | auto available_width = AvailableSize::make_definite(cb_state.content_width() + cb_state.padding_left + cb_state.padding_right); |
191 | 0 | auto available_height = AvailableSize::make_definite(cb_state.content_height() + cb_state.padding_top + cb_state.padding_bottom); |
192 | 0 | layout_absolutely_positioned_element(box, AvailableSpace(available_width, available_height)); |
193 | 0 | } |
194 | 0 | return IterationDecision::Continue; |
195 | 0 | }); |
196 | 0 | } |
197 | | |
198 | | // https://www.w3.org/TR/css-flexbox-1/#flex-direction-property |
199 | | bool FlexFormattingContext::is_direction_reverse() const |
200 | 0 | { |
201 | 0 | switch (flex_container().computed_values().direction()) { |
202 | 0 | case CSS::Direction::Ltr: |
203 | 0 | return m_flex_direction == CSS::FlexDirection::ColumnReverse || m_flex_direction == CSS::FlexDirection::RowReverse; |
204 | 0 | case CSS::Direction::Rtl: |
205 | 0 | return m_flex_direction == CSS::FlexDirection::ColumnReverse || m_flex_direction == CSS::FlexDirection::Row; |
206 | 0 | default: |
207 | 0 | VERIFY_NOT_REACHED(); |
208 | 0 | } |
209 | 0 | } |
210 | | |
211 | | void FlexFormattingContext::populate_specified_margins(FlexItem& item, CSS::FlexDirection flex_direction) const |
212 | 0 | { |
213 | 0 | auto width_of_containing_block = m_flex_container_state.content_width(); |
214 | | // FIXME: This should also take reverse-ness into account |
215 | 0 | if (flex_direction == CSS::FlexDirection::Row || flex_direction == CSS::FlexDirection::RowReverse) { |
216 | 0 | item.borders.main_before = item.box->computed_values().border_left().width; |
217 | 0 | item.borders.main_after = item.box->computed_values().border_right().width; |
218 | 0 | item.borders.cross_before = item.box->computed_values().border_top().width; |
219 | 0 | item.borders.cross_after = item.box->computed_values().border_bottom().width; |
220 | |
|
221 | 0 | item.padding.main_before = item.box->computed_values().padding().left().to_px(item.box, width_of_containing_block); |
222 | 0 | item.padding.main_after = item.box->computed_values().padding().right().to_px(item.box, width_of_containing_block); |
223 | 0 | item.padding.cross_before = item.box->computed_values().padding().top().to_px(item.box, width_of_containing_block); |
224 | 0 | item.padding.cross_after = item.box->computed_values().padding().bottom().to_px(item.box, width_of_containing_block); |
225 | |
|
226 | 0 | item.margins.main_before = item.box->computed_values().margin().left().to_px(item.box, width_of_containing_block); |
227 | 0 | item.margins.main_after = item.box->computed_values().margin().right().to_px(item.box, width_of_containing_block); |
228 | 0 | item.margins.cross_before = item.box->computed_values().margin().top().to_px(item.box, width_of_containing_block); |
229 | 0 | item.margins.cross_after = item.box->computed_values().margin().bottom().to_px(item.box, width_of_containing_block); |
230 | |
|
231 | 0 | item.margins.main_before_is_auto = item.box->computed_values().margin().left().is_auto(); |
232 | 0 | item.margins.main_after_is_auto = item.box->computed_values().margin().right().is_auto(); |
233 | 0 | item.margins.cross_before_is_auto = item.box->computed_values().margin().top().is_auto(); |
234 | 0 | item.margins.cross_after_is_auto = item.box->computed_values().margin().bottom().is_auto(); |
235 | 0 | } else { |
236 | 0 | item.borders.main_before = item.box->computed_values().border_top().width; |
237 | 0 | item.borders.main_after = item.box->computed_values().border_bottom().width; |
238 | 0 | item.borders.cross_before = item.box->computed_values().border_left().width; |
239 | 0 | item.borders.cross_after = item.box->computed_values().border_right().width; |
240 | |
|
241 | 0 | item.padding.main_before = item.box->computed_values().padding().top().to_px(item.box, width_of_containing_block); |
242 | 0 | item.padding.main_after = item.box->computed_values().padding().bottom().to_px(item.box, width_of_containing_block); |
243 | 0 | item.padding.cross_before = item.box->computed_values().padding().left().to_px(item.box, width_of_containing_block); |
244 | 0 | item.padding.cross_after = item.box->computed_values().padding().right().to_px(item.box, width_of_containing_block); |
245 | |
|
246 | 0 | item.margins.main_before = item.box->computed_values().margin().top().to_px(item.box, width_of_containing_block); |
247 | 0 | item.margins.main_after = item.box->computed_values().margin().bottom().to_px(item.box, width_of_containing_block); |
248 | 0 | item.margins.cross_before = item.box->computed_values().margin().left().to_px(item.box, width_of_containing_block); |
249 | 0 | item.margins.cross_after = item.box->computed_values().margin().right().to_px(item.box, width_of_containing_block); |
250 | |
|
251 | 0 | item.margins.main_before_is_auto = item.box->computed_values().margin().top().is_auto(); |
252 | 0 | item.margins.main_after_is_auto = item.box->computed_values().margin().bottom().is_auto(); |
253 | 0 | item.margins.cross_before_is_auto = item.box->computed_values().margin().left().is_auto(); |
254 | 0 | item.margins.cross_after_is_auto = item.box->computed_values().margin().right().is_auto(); |
255 | 0 | } |
256 | 0 | } |
257 | | |
258 | | // https://www.w3.org/TR/css-flexbox-1/#flex-items |
259 | | void FlexFormattingContext::generate_anonymous_flex_items() |
260 | 0 | { |
261 | | // More like, sift through the already generated items. |
262 | | // After this step no items are to be added or removed from flex_items! |
263 | | // It holds every item we need to consider and there should be nothing in the following |
264 | | // calculations that could change that. |
265 | | // This is particularly important since we take references to the items stored in flex_items |
266 | | // later, whose addresses won't be stable if we added or removed any items. |
267 | 0 | HashMap<int, Vector<FlexItem>> order_item_bucket; |
268 | |
|
269 | 0 | flex_container().for_each_child_of_type<Box>([&](Box& child_box) { |
270 | 0 | if (can_skip_is_anonymous_text_run(child_box)) |
271 | 0 | return IterationDecision::Continue; |
272 | | |
273 | | // Skip any "out-of-flow" children |
274 | 0 | if (child_box.is_out_of_flow(*this)) |
275 | 0 | return IterationDecision::Continue; |
276 | | |
277 | 0 | child_box.set_flex_item(true); |
278 | 0 | FlexItem item = { child_box, m_state.get_mutable(child_box) }; |
279 | 0 | populate_specified_margins(item, m_flex_direction); |
280 | |
|
281 | 0 | auto& order_bucket = order_item_bucket.ensure(child_box.computed_values().order()); |
282 | 0 | order_bucket.append(move(item)); |
283 | |
|
284 | 0 | return IterationDecision::Continue; |
285 | 0 | }); |
286 | |
|
287 | 0 | auto keys = order_item_bucket.keys(); |
288 | |
|
289 | 0 | if (is_direction_reverse()) { |
290 | 0 | quick_sort(keys, [](auto& a, auto& b) { return a > b; }); |
291 | 0 | } else { |
292 | 0 | quick_sort(keys, [](auto& a, auto& b) { return a < b; }); |
293 | 0 | } |
294 | |
|
295 | 0 | for (auto key : keys) { |
296 | 0 | auto order_bucket = order_item_bucket.get(key); |
297 | 0 | if (order_bucket.has_value()) { |
298 | 0 | auto& items = order_bucket.value(); |
299 | 0 | for (auto item : items) { |
300 | 0 | m_flex_items.append(move(item)); |
301 | 0 | } |
302 | 0 | } |
303 | 0 | } |
304 | 0 | } |
305 | | |
306 | | bool FlexFormattingContext::has_definite_main_size(LayoutState::UsedValues const& used_values) const |
307 | 0 | { |
308 | 0 | return is_row_layout() ? used_values.has_definite_width() : used_values.has_definite_height(); |
309 | 0 | } |
310 | | |
311 | | CSSPixels FlexFormattingContext::inner_main_size(LayoutState::UsedValues const& used_values) const |
312 | 0 | { |
313 | 0 | return is_row_layout() ? used_values.content_width() : used_values.content_height(); |
314 | 0 | } |
315 | | |
316 | | CSSPixels FlexFormattingContext::inner_cross_size(LayoutState::UsedValues const& used_values) const |
317 | 0 | { |
318 | 0 | return is_row_layout() ? used_values.content_height() : used_values.content_width(); |
319 | 0 | } |
320 | | |
321 | | bool FlexFormattingContext::has_main_min_size(Box const& box) const |
322 | 0 | { |
323 | 0 | auto const& value = is_row_layout() ? box.computed_values().min_width() : box.computed_values().min_height(); |
324 | 0 | return !value.is_auto(); |
325 | 0 | } |
326 | | |
327 | | bool FlexFormattingContext::has_cross_min_size(Box const& box) const |
328 | 0 | { |
329 | 0 | auto const& value = is_row_layout() ? box.computed_values().min_height() : box.computed_values().min_width(); |
330 | 0 | return !value.is_auto(); |
331 | 0 | } |
332 | | |
333 | | bool FlexFormattingContext::has_definite_cross_size(LayoutState::UsedValues const& used_values) const |
334 | 0 | { |
335 | 0 | return is_row_layout() ? used_values.has_definite_height() : used_values.has_definite_width(); |
336 | 0 | } |
337 | | |
338 | | CSSPixels FlexFormattingContext::specified_main_min_size(Box const& box) const |
339 | 0 | { |
340 | 0 | return is_row_layout() |
341 | 0 | ? get_pixel_width(box, box.computed_values().min_width()) |
342 | 0 | : get_pixel_height(box, box.computed_values().min_height()); |
343 | 0 | } |
344 | | |
345 | | CSSPixels FlexFormattingContext::specified_cross_min_size(Box const& box) const |
346 | 0 | { |
347 | 0 | return is_row_layout() |
348 | 0 | ? get_pixel_height(box, box.computed_values().min_height()) |
349 | 0 | : get_pixel_width(box, box.computed_values().min_width()); |
350 | 0 | } |
351 | | |
352 | | bool FlexFormattingContext::has_main_max_size(Box const& box) const |
353 | 0 | { |
354 | 0 | return !should_treat_main_max_size_as_none(box); |
355 | 0 | } |
356 | | |
357 | | bool FlexFormattingContext::has_cross_max_size(Box const& box) const |
358 | 0 | { |
359 | 0 | return !should_treat_cross_max_size_as_none(box); |
360 | 0 | } |
361 | | |
362 | | CSSPixels FlexFormattingContext::specified_main_max_size(Box const& box) const |
363 | 0 | { |
364 | 0 | return is_row_layout() |
365 | 0 | ? get_pixel_width(box, box.computed_values().max_width()) |
366 | 0 | : get_pixel_height(box, box.computed_values().max_height()); |
367 | 0 | } |
368 | | |
369 | | CSSPixels FlexFormattingContext::specified_cross_max_size(Box const& box) const |
370 | 0 | { |
371 | 0 | return is_row_layout() |
372 | 0 | ? get_pixel_height(box, box.computed_values().max_height()) |
373 | 0 | : get_pixel_width(box, box.computed_values().max_width()); |
374 | 0 | } |
375 | | |
376 | | bool FlexFormattingContext::is_cross_auto(Box const& box) const |
377 | 0 | { |
378 | 0 | auto& cross_length = is_row_layout() ? box.computed_values().height() : box.computed_values().width(); |
379 | 0 | return cross_length.is_auto(); |
380 | 0 | } |
381 | | |
382 | | void FlexFormattingContext::set_has_definite_main_size(FlexItem& item) |
383 | 0 | { |
384 | 0 | if (is_row_layout()) |
385 | 0 | item.used_values.set_has_definite_width(true); |
386 | 0 | else |
387 | 0 | item.used_values.set_has_definite_height(true); |
388 | 0 | } |
389 | | |
390 | | void FlexFormattingContext::set_has_definite_cross_size(FlexItem& item) |
391 | 0 | { |
392 | 0 | if (is_row_layout()) |
393 | 0 | item.used_values.set_has_definite_height(true); |
394 | 0 | else |
395 | 0 | item.used_values.set_has_definite_width(true); |
396 | 0 | } |
397 | | |
398 | | void FlexFormattingContext::set_main_size(Box const& box, CSSPixels size) |
399 | 0 | { |
400 | 0 | if (is_row_layout()) |
401 | 0 | m_state.get_mutable(box).set_content_width(size); |
402 | 0 | else |
403 | 0 | m_state.get_mutable(box).set_content_height(size); |
404 | 0 | } |
405 | | |
406 | | void FlexFormattingContext::set_cross_size(Box const& box, CSSPixels size) |
407 | 0 | { |
408 | 0 | if (is_row_layout()) |
409 | 0 | m_state.get_mutable(box).set_content_height(size); |
410 | 0 | else |
411 | 0 | m_state.get_mutable(box).set_content_width(size); |
412 | 0 | } |
413 | | |
414 | | void FlexFormattingContext::set_offset(Box const& box, CSSPixels main_offset, CSSPixels cross_offset) |
415 | 0 | { |
416 | 0 | if (is_row_layout()) |
417 | 0 | m_state.get_mutable(box).offset = CSSPixelPoint { main_offset, cross_offset }; |
418 | 0 | else |
419 | 0 | m_state.get_mutable(box).offset = CSSPixelPoint { cross_offset, main_offset }; |
420 | 0 | } |
421 | | |
422 | | void FlexFormattingContext::set_main_axis_first_margin(FlexItem& item, CSSPixels margin) |
423 | 0 | { |
424 | 0 | item.margins.main_before = margin; |
425 | 0 | if (is_row_layout()) |
426 | 0 | m_state.get_mutable(item.box).margin_left = margin; |
427 | 0 | else |
428 | 0 | m_state.get_mutable(item.box).margin_top = margin; |
429 | 0 | } |
430 | | |
431 | | void FlexFormattingContext::set_main_axis_second_margin(FlexItem& item, CSSPixels margin) |
432 | 0 | { |
433 | 0 | item.margins.main_after = margin; |
434 | 0 | if (is_row_layout()) |
435 | 0 | m_state.get_mutable(item.box).margin_right = margin; |
436 | 0 | else |
437 | 0 | m_state.get_mutable(item.box).margin_bottom = margin; |
438 | 0 | } |
439 | | |
440 | | // https://drafts.csswg.org/css-flexbox-1/#algo-available |
441 | | void FlexFormattingContext::determine_available_space_for_items(AvailableSpace const& available_space) |
442 | 0 | { |
443 | 0 | if (is_row_layout()) { |
444 | 0 | m_available_space_for_items = AxisAgnosticAvailableSpace { |
445 | 0 | .main = available_space.width, |
446 | 0 | .cross = available_space.height, |
447 | 0 | .space = { available_space.width, available_space.height }, |
448 | 0 | }; |
449 | 0 | } else { |
450 | 0 | m_available_space_for_items = AxisAgnosticAvailableSpace { |
451 | 0 | .main = available_space.height, |
452 | 0 | .cross = available_space.width, |
453 | 0 | .space = { available_space.width, available_space.height }, |
454 | 0 | }; |
455 | 0 | } |
456 | 0 | } |
457 | | |
458 | | // https://drafts.csswg.org/css-flexbox-1/#propdef-flex-basis |
459 | | CSS::FlexBasis FlexFormattingContext::used_flex_basis_for_item(FlexItem const& item) const |
460 | 0 | { |
461 | 0 | auto flex_basis = item.box->computed_values().flex_basis(); |
462 | |
|
463 | 0 | if (flex_basis.has<CSS::Size>() && flex_basis.get<CSS::Size>().is_auto()) { |
464 | | // https://drafts.csswg.org/css-flexbox-1/#valdef-flex-basis-auto |
465 | | // When specified on a flex item, the auto keyword retrieves the value of the main size property as the used flex-basis. |
466 | | // If that value is itself auto, then the used value is content. |
467 | 0 | auto const& main_size = is_row_layout() ? item.box->computed_values().width() : item.box->computed_values().height(); |
468 | |
|
469 | 0 | if (main_size.is_auto()) { |
470 | 0 | flex_basis = CSS::FlexBasisContent {}; |
471 | 0 | } else { |
472 | 0 | flex_basis = main_size; |
473 | 0 | } |
474 | 0 | } |
475 | | |
476 | | // For example, percentage values of flex-basis are resolved against the flex item’s containing block |
477 | | // (i.e. its flex container); and if that containing block’s size is indefinite, |
478 | | // the used value for flex-basis is content. |
479 | 0 | if (flex_basis.has<CSS::Size>() |
480 | 0 | && flex_basis.get<CSS::Size>().is_percentage() |
481 | 0 | && !has_definite_main_size(m_flex_container_state)) { |
482 | 0 | flex_basis = CSS::FlexBasisContent {}; |
483 | 0 | } |
484 | |
|
485 | 0 | return flex_basis; |
486 | 0 | } |
487 | | |
488 | | CSSPixels FlexFormattingContext::calculate_main_size_from_cross_size_and_aspect_ratio(CSSPixels cross_size, CSSPixelFraction aspect_ratio) const |
489 | 0 | { |
490 | 0 | if (is_row_layout()) |
491 | 0 | return cross_size * aspect_ratio; |
492 | 0 | return cross_size / aspect_ratio; |
493 | 0 | } |
494 | | |
495 | | CSSPixels FlexFormattingContext::calculate_cross_size_from_main_size_and_aspect_ratio(CSSPixels main_size, CSSPixelFraction aspect_ratio) const |
496 | 0 | { |
497 | 0 | if (is_row_layout()) |
498 | 0 | return main_size / aspect_ratio; |
499 | 0 | return main_size * aspect_ratio; |
500 | 0 | } |
501 | | |
502 | | // This function takes a size in the main axis and adjusts it according to the aspect ratio of the box |
503 | | // if the min/max constraints in the cross axis forces us to come up with a new main axis size. |
504 | | CSSPixels FlexFormattingContext::adjust_main_size_through_aspect_ratio_for_cross_size_min_max_constraints(Box const& box, CSSPixels main_size, CSS::Size const& min_cross_size, CSS::Size const& max_cross_size) const |
505 | 0 | { |
506 | 0 | if (!should_treat_cross_max_size_as_none(box)) { |
507 | 0 | auto max_cross_size_px = max_cross_size.to_px(box, !is_row_layout() ? m_flex_container_state.content_width() : m_flex_container_state.content_height()); |
508 | 0 | main_size = min(main_size, calculate_main_size_from_cross_size_and_aspect_ratio(max_cross_size_px, box.preferred_aspect_ratio().value())); |
509 | 0 | } |
510 | |
|
511 | 0 | if (!min_cross_size.is_auto()) { |
512 | 0 | auto min_cross_size_px = min_cross_size.to_px(box, !is_row_layout() ? m_flex_container_state.content_width() : m_flex_container_state.content_height()); |
513 | 0 | main_size = max(main_size, calculate_main_size_from_cross_size_and_aspect_ratio(min_cross_size_px, box.preferred_aspect_ratio().value())); |
514 | 0 | } |
515 | |
|
516 | 0 | return main_size; |
517 | 0 | } |
518 | | |
519 | | CSSPixels FlexFormattingContext::adjust_cross_size_through_aspect_ratio_for_main_size_min_max_constraints(Box const& box, CSSPixels cross_size, CSS::Size const& min_main_size, CSS::Size const& max_main_size) const |
520 | 0 | { |
521 | 0 | if (!should_treat_main_max_size_as_none(box)) { |
522 | 0 | auto max_main_size_px = max_main_size.to_px(box, is_row_layout() ? m_flex_container_state.content_width() : m_flex_container_state.content_height()); |
523 | 0 | cross_size = min(cross_size, calculate_cross_size_from_main_size_and_aspect_ratio(max_main_size_px, box.preferred_aspect_ratio().value())); |
524 | 0 | } |
525 | |
|
526 | 0 | if (!min_main_size.is_auto()) { |
527 | 0 | auto min_main_size_px = min_main_size.to_px(box, is_row_layout() ? m_flex_container_state.content_width() : m_flex_container_state.content_height()); |
528 | 0 | cross_size = max(cross_size, calculate_cross_size_from_main_size_and_aspect_ratio(min_main_size_px, box.preferred_aspect_ratio().value())); |
529 | 0 | } |
530 | |
|
531 | 0 | return cross_size; |
532 | 0 | } |
533 | | |
534 | | // https://www.w3.org/TR/css-flexbox-1/#algo-main-item |
535 | | void FlexFormattingContext::determine_flex_base_size_and_hypothetical_main_size(FlexItem& item) |
536 | 0 | { |
537 | 0 | auto& child_box = item.box; |
538 | |
|
539 | 0 | item.flex_base_size = [&] { |
540 | 0 | item.used_flex_basis = used_flex_basis_for_item(item); |
541 | |
|
542 | 0 | item.used_flex_basis_is_definite = [&](CSS::FlexBasis const& flex_basis) -> bool { |
543 | 0 | if (!flex_basis.has<CSS::Size>()) |
544 | 0 | return false; |
545 | 0 | auto const& size = flex_basis.get<CSS::Size>(); |
546 | 0 | if (size.is_auto() || size.is_min_content() || size.is_max_content() || size.is_fit_content()) |
547 | 0 | return false; |
548 | 0 | if (size.is_length()) |
549 | 0 | return true; |
550 | | |
551 | 0 | bool can_resolve_percentages = is_row_layout() |
552 | 0 | ? m_flex_container_state.has_definite_width() |
553 | 0 | : m_flex_container_state.has_definite_height(); |
554 | |
|
555 | 0 | if (size.is_calculated()) { |
556 | 0 | auto const& calc_value = size.calculated(); |
557 | 0 | if (calc_value.resolves_to_percentage()) |
558 | 0 | return can_resolve_percentages; |
559 | 0 | if (calc_value.resolves_to_length()) { |
560 | 0 | if (calc_value.contains_percentage()) |
561 | 0 | return can_resolve_percentages; |
562 | 0 | return true; |
563 | 0 | } |
564 | 0 | return false; |
565 | 0 | } |
566 | 0 | VERIFY(size.is_percentage()); |
567 | 0 | return can_resolve_percentages; |
568 | 0 | }(*item.used_flex_basis); |
569 | | |
570 | | // A. If the item has a definite used flex basis, that’s the flex base size. |
571 | 0 | if (item.used_flex_basis_is_definite) { |
572 | 0 | auto const& size = item.used_flex_basis->get<CSS::Size>(); |
573 | 0 | if (is_row_layout()) |
574 | 0 | return get_pixel_width(child_box, size); |
575 | 0 | return get_pixel_height(child_box, size); |
576 | 0 | } |
577 | | |
578 | | // AD-HOC: If we're sizing the flex container under a min-content constraint in the main axis, |
579 | | // flex items resolve percentages in the main axis to 0. |
580 | 0 | if (m_available_space_for_items->main.is_min_content() |
581 | 0 | && computed_main_size(item.box).contains_percentage()) { |
582 | 0 | return CSSPixels(0); |
583 | 0 | } |
584 | | |
585 | | // B. If the flex item has ... |
586 | | // - an intrinsic aspect ratio, |
587 | | // - a used flex basis of content, and |
588 | | // - a definite cross size, |
589 | 0 | if (item.box->has_preferred_aspect_ratio() |
590 | 0 | && item.used_flex_basis->has<CSS::FlexBasisContent>() |
591 | 0 | && has_definite_cross_size(item)) { |
592 | | // flex_base_size is calculated from definite cross size and intrinsic aspect ratio |
593 | 0 | return adjust_main_size_through_aspect_ratio_for_cross_size_min_max_constraints( |
594 | 0 | item.box, |
595 | 0 | calculate_main_size_from_cross_size_and_aspect_ratio(inner_cross_size(item), item.box->preferred_aspect_ratio().value()), |
596 | 0 | computed_cross_min_size(item.box), |
597 | 0 | computed_cross_max_size(item.box)); |
598 | 0 | } |
599 | | |
600 | | // C. If the used flex basis is content or depends on its available space, |
601 | | // and the flex container is being sized under a min-content or max-content constraint |
602 | | // (e.g. when performing automatic table layout [CSS21]), size the item under that constraint. |
603 | | // The flex base size is the item’s resulting main size. |
604 | 0 | if (item.used_flex_basis->has<CSS::FlexBasisContent>() && m_available_space_for_items->main.is_intrinsic_sizing_constraint()) { |
605 | 0 | if (m_available_space_for_items->main.is_min_content()) |
606 | 0 | return calculate_min_content_main_size(item); |
607 | 0 | return calculate_max_content_main_size(item); |
608 | 0 | } |
609 | | |
610 | | // D. Otherwise, if the used flex basis is content or depends on its available space, |
611 | | // the available main size is infinite, and the flex item’s inline axis is parallel to the main axis, |
612 | | // lay the item out using the rules for a box in an orthogonal flow [CSS3-WRITING-MODES]. |
613 | | // The flex base size is the item’s max-content main size. |
614 | 0 | if (item.used_flex_basis->has<CSS::FlexBasisContent>() |
615 | | // FIXME: && main_size is infinite && inline axis is parallel to the main axis |
616 | 0 | && false && false) { |
617 | 0 | TODO(); |
618 | | // Use rules for a flex_container in orthogonal flow |
619 | 0 | } |
620 | | |
621 | | // E. Otherwise, size the item into the available space using its used flex basis in place of its main size, |
622 | | // treating a value of content as max-content. If a cross size is needed to determine the main size |
623 | | // (e.g. when the flex item’s main size is in its block axis) and the flex item’s cross size is auto and not definite, |
624 | | // in this calculation use fit-content as the flex item’s cross size. |
625 | | // The flex base size is the item’s resulting main size. |
626 | | |
627 | 0 | if (auto* size = item.used_flex_basis->get_pointer<CSS::Size>()) { |
628 | 0 | if (size->is_fit_content()) |
629 | 0 | return calculate_fit_content_main_size(item); |
630 | 0 | if (size->is_max_content()) |
631 | 0 | return calculate_max_content_main_size(item); |
632 | 0 | if (size->is_min_content()) |
633 | 0 | return calculate_min_content_main_size(item); |
634 | 0 | } |
635 | | |
636 | | // NOTE: If the flex item has a definite main size, just use that as the flex base size. |
637 | 0 | if (has_definite_main_size(item)) |
638 | 0 | return inner_main_size(item); |
639 | | |
640 | | // NOTE: There's a fundamental problem with many CSS specifications in that they neglect to mention |
641 | | // which width to provide when calculating the intrinsic height of a box in various situations. |
642 | | // Spec bug: https://github.com/w3c/csswg-drafts/issues/2890 |
643 | | |
644 | | // NOTE: This is one of many situations where that causes trouble: if this is a flex column layout, |
645 | | // we may need to calculate the intrinsic height of a flex item. This requires a width, but a |
646 | | // width won't be determined until later on in the flex layout algorithm. |
647 | | // In the specific case above (E), the spec mentions using `fit-content` in place of `auto` |
648 | | // if "a cross size is needed to determine the main size", so that's exactly what we do. |
649 | | |
650 | | // NOTE: Finding a suitable width for intrinsic height determination actually happens elsewhere, |
651 | | // in the various helpers that calculate the intrinsic sizes of a flex item, |
652 | | // e.g. calculate_min_content_main_size(). |
653 | | |
654 | 0 | if (item.used_flex_basis->has<CSS::FlexBasisContent>()) |
655 | 0 | return calculate_max_content_main_size(item); |
656 | | |
657 | 0 | return calculate_fit_content_main_size(item); |
658 | 0 | }(); |
659 | | |
660 | | // AD-HOC: This is not mentioned in the spec, but if the item has an aspect ratio, we may need |
661 | | // to adjust the main size in these ways: |
662 | | // - using stretch-fit main size if the flex basis is indefinite, there is no |
663 | | // intrinsic size and no cross size to resolve the ratio against. |
664 | | // - in response to cross size min/max constraints. |
665 | 0 | if (item.box->has_natural_aspect_ratio()) { |
666 | 0 | if (!item.used_flex_basis_is_definite && !item.box->has_natural_width() && !item.box->has_natural_height() && !has_definite_cross_size(item)) { |
667 | 0 | item.flex_base_size = inner_main_size(m_flex_container_state); |
668 | 0 | } |
669 | 0 | item.flex_base_size = adjust_main_size_through_aspect_ratio_for_cross_size_min_max_constraints(child_box, item.flex_base_size, computed_cross_min_size(child_box), computed_cross_max_size(child_box)); |
670 | 0 | } |
671 | | |
672 | | // The hypothetical main size is the item’s flex base size clamped according to its used min and max main sizes (and flooring the content box size at zero). |
673 | 0 | auto clamp_min = has_main_min_size(child_box) ? specified_main_min_size(child_box) : automatic_minimum_size(item); |
674 | 0 | auto clamp_max = has_main_max_size(child_box) ? specified_main_max_size(child_box) : CSSPixels::max(); |
675 | 0 | item.hypothetical_main_size = max(CSSPixels(0), css_clamp(item.flex_base_size, clamp_min, clamp_max)); |
676 | | |
677 | | // NOTE: At this point, we set the hypothetical main size as the flex item's *temporary* main size. |
678 | | // The size may change again when we resolve flexible lengths, but this is necessary in order for |
679 | | // descendants of this flex item to resolve percentage sizes against something. |
680 | | // |
681 | | // The spec just barely hand-waves about this, but it seems to *roughly* match what other engines do. |
682 | | // See "Note" section here: https://drafts.csswg.org/css-flexbox-1/#definite-sizes |
683 | 0 | if (is_row_layout()) |
684 | 0 | item.used_values.set_temporary_content_width(item.hypothetical_main_size); |
685 | 0 | else |
686 | 0 | item.used_values.set_temporary_content_height(item.hypothetical_main_size); |
687 | 0 | } |
688 | | |
689 | | // https://drafts.csswg.org/css-flexbox-1/#min-size-auto |
690 | | CSSPixels FlexFormattingContext::automatic_minimum_size(FlexItem const& item) const |
691 | 0 | { |
692 | | // To provide a more reasonable default minimum size for flex items, |
693 | | // the used value of a main axis automatic minimum size on a flex item that is not a scroll container is its content-based minimum size; |
694 | | // for scroll containers the automatic minimum size is zero, as usual. |
695 | 0 | if (!item.box->is_scroll_container()) |
696 | 0 | return content_based_minimum_size(item); |
697 | 0 | return 0; |
698 | 0 | } |
699 | | |
700 | | // https://drafts.csswg.org/css-flexbox-1/#specified-size-suggestion |
701 | | Optional<CSSPixels> FlexFormattingContext::specified_size_suggestion(FlexItem const& item) const |
702 | 0 | { |
703 | | // If the item’s preferred main size is definite and not automatic, |
704 | | // then the specified size suggestion is that size. It is otherwise undefined. |
705 | 0 | if (has_definite_main_size(item) && !should_treat_main_size_as_auto(item.box)) { |
706 | | // NOTE: We use get_pixel_{width,height} to ensure that CSS box-sizing is respected. |
707 | 0 | return is_row_layout() ? get_pixel_width(item.box, computed_main_size(item.box)) : get_pixel_height(item.box, computed_main_size(item.box)); |
708 | 0 | } |
709 | 0 | return {}; |
710 | 0 | } |
711 | | |
712 | | // https://drafts.csswg.org/css-flexbox-1/#content-size-suggestion |
713 | | CSSPixels FlexFormattingContext::content_size_suggestion(FlexItem const& item) const |
714 | 0 | { |
715 | 0 | auto suggestion = calculate_min_content_main_size(item); |
716 | |
|
717 | 0 | if (item.box->has_preferred_aspect_ratio()) { |
718 | 0 | suggestion = adjust_main_size_through_aspect_ratio_for_cross_size_min_max_constraints(item.box, suggestion, computed_cross_min_size(item.box), computed_cross_max_size(item.box)); |
719 | 0 | } |
720 | |
|
721 | 0 | return suggestion; |
722 | 0 | } |
723 | | |
724 | | // https://drafts.csswg.org/css-flexbox-1/#transferred-size-suggestion |
725 | | Optional<CSSPixels> FlexFormattingContext::transferred_size_suggestion(FlexItem const& item) const |
726 | 0 | { |
727 | | // If the item has a preferred aspect ratio and its preferred cross size is definite, |
728 | | // then the transferred size suggestion is that size |
729 | | // (clamped by its minimum and maximum cross sizes if they are definite), converted through the aspect ratio. |
730 | 0 | if (item.box->has_preferred_aspect_ratio() && has_definite_cross_size(item)) { |
731 | 0 | auto aspect_ratio = item.box->preferred_aspect_ratio().value(); |
732 | 0 | return adjust_main_size_through_aspect_ratio_for_cross_size_min_max_constraints( |
733 | 0 | item.box, |
734 | 0 | calculate_main_size_from_cross_size_and_aspect_ratio(inner_cross_size(item), aspect_ratio), |
735 | 0 | computed_cross_min_size(item.box), |
736 | 0 | computed_cross_max_size(item.box)); |
737 | 0 | } |
738 | | |
739 | | // It is otherwise undefined. |
740 | 0 | return {}; |
741 | 0 | } |
742 | | |
743 | | // https://drafts.csswg.org/css-flexbox-1/#content-based-minimum-size |
744 | | CSSPixels FlexFormattingContext::content_based_minimum_size(FlexItem const& item) const |
745 | 0 | { |
746 | 0 | auto unclamped_size = [&] { |
747 | | // The content-based minimum size of a flex item is the smaller of its specified size suggestion |
748 | | // and its content size suggestion if its specified size suggestion exists; |
749 | 0 | if (auto specified_size_suggestion = this->specified_size_suggestion(item); specified_size_suggestion.has_value()) { |
750 | 0 | return min(specified_size_suggestion.value(), content_size_suggestion(item)); |
751 | 0 | } |
752 | | |
753 | | // otherwise, the smaller of its transferred size suggestion and its content size suggestion |
754 | | // if the element is replaced and its transferred size suggestion exists; |
755 | 0 | if (item.box->is_replaced_box()) { |
756 | 0 | if (auto transferred_size_suggestion = this->transferred_size_suggestion(item); transferred_size_suggestion.has_value()) { |
757 | 0 | return min(transferred_size_suggestion.value(), content_size_suggestion(item)); |
758 | 0 | } |
759 | 0 | } |
760 | | |
761 | | // otherwise its content size suggestion. |
762 | 0 | return content_size_suggestion(item); |
763 | 0 | }(); |
764 | | |
765 | | // In all cases, the size is clamped by the maximum main size if it’s definite. |
766 | 0 | if (has_main_max_size(item.box)) { |
767 | 0 | return min(unclamped_size, specified_main_max_size(item.box)); |
768 | 0 | } |
769 | 0 | return unclamped_size; |
770 | 0 | } |
771 | | |
772 | | // https://www.w3.org/TR/css-flexbox-1/#algo-line-break |
773 | | void FlexFormattingContext::collect_flex_items_into_flex_lines() |
774 | 0 | { |
775 | | // If the flex container is single-line, collect all the flex items into a single flex line. |
776 | 0 | if (is_single_line()) { |
777 | 0 | FlexLine line; |
778 | 0 | for (auto& item : m_flex_items) { |
779 | 0 | if (is_direction_reverse()) { |
780 | 0 | line.items.prepend(item); |
781 | 0 | } else { |
782 | 0 | line.items.append(item); |
783 | 0 | } |
784 | 0 | } |
785 | 0 | m_flex_lines.append(move(line)); |
786 | 0 | return; |
787 | 0 | } |
788 | | |
789 | | // Otherwise, starting from the first uncollected item, collect consecutive items one by one |
790 | | // until the first time that the next collected item would not fit into the flex container’s inner main size |
791 | | // (or until a forced break is encountered, see §10 Fragmenting Flex Layout). |
792 | | // If the very first uncollected item wouldn't fit, collect just it into the line. |
793 | | |
794 | | // For this step, the size of a flex item is its outer hypothetical main size. (Note: This can be negative.) |
795 | | |
796 | | // Repeat until all flex items have been collected into flex lines. |
797 | | |
798 | 0 | FlexLine line; |
799 | 0 | CSSPixels line_main_size = 0; |
800 | 0 | for (auto& item : m_flex_items) { |
801 | 0 | auto const outer_hypothetical_main_size = item.outer_hypothetical_main_size(); |
802 | 0 | if (!line.items.is_empty() && (line_main_size + outer_hypothetical_main_size) > m_available_space_for_items->main) { |
803 | 0 | m_flex_lines.append(move(line)); |
804 | 0 | line = {}; |
805 | 0 | line_main_size = 0; |
806 | 0 | } |
807 | |
|
808 | 0 | if (is_direction_reverse()) { |
809 | 0 | line.items.prepend(item); |
810 | 0 | } else { |
811 | 0 | line.items.append(item); |
812 | 0 | } |
813 | |
|
814 | 0 | line_main_size += outer_hypothetical_main_size; |
815 | | // CSS-FLEXBOX-2: Account for gap between flex items. |
816 | 0 | line_main_size += main_gap(); |
817 | 0 | } |
818 | 0 | m_flex_lines.append(move(line)); |
819 | |
|
820 | 0 | if (flex_container().computed_values().flex_wrap() == CSS::FlexWrap::WrapReverse) |
821 | 0 | m_flex_lines.reverse(); |
822 | 0 | } |
823 | | |
824 | | // https://drafts.csswg.org/css-flexbox-1/#resolve-flexible-lengths |
825 | | void FlexFormattingContext::resolve_flexible_lengths_for_line(FlexLine& line) |
826 | 0 | { |
827 | | // AD-HOC: The spec tells us to use the "flex container’s inner main size" in this algorithm, |
828 | | // but that doesn't work when we're sizing under a max-content constraint. |
829 | | // In that case, there is effectively infinite size available in the main axis, |
830 | | // but the inner main size has not been assigned yet. |
831 | | // We solve this by calculating our own "available main size" here, which is essentially |
832 | | // infinity under max-content, 0 under min-content, and the inner main size otherwise. |
833 | 0 | AvailableSize available_main_size { AvailableSize::make_indefinite() }; |
834 | 0 | if (m_available_space_for_items->main.is_intrinsic_sizing_constraint()) |
835 | 0 | available_main_size = m_available_space_for_items->main; |
836 | 0 | else |
837 | 0 | available_main_size = AvailableSize::make_definite(inner_main_size(m_flex_container_state)); |
838 | | |
839 | | // 1. Determine the used flex factor. |
840 | | |
841 | | // Sum the outer hypothetical main sizes of all items on the line. |
842 | | // If the sum is less than the flex container’s inner main size, |
843 | | // use the flex grow factor for the rest of this algorithm; otherwise, use the flex shrink factor |
844 | 0 | enum FlexFactor { |
845 | 0 | FlexGrowFactor, |
846 | 0 | FlexShrinkFactor |
847 | 0 | }; |
848 | 0 | auto used_flex_factor = [&]() -> FlexFactor { |
849 | 0 | CSSPixels sum = 0; |
850 | 0 | for (auto const& item : line.items) { |
851 | 0 | sum += item.outer_hypothetical_main_size(); |
852 | 0 | } |
853 | | // CSS-FLEXBOX-2: Account for gap between flex items. |
854 | 0 | sum += main_gap() * (line.items.size() - 1); |
855 | | // AD-HOC: Note that we're using our own "available main size" explained above |
856 | | // instead of the flex container’s inner main size. |
857 | 0 | if (sum < available_main_size) |
858 | 0 | return FlexFactor::FlexGrowFactor; |
859 | 0 | return FlexFactor::FlexShrinkFactor; |
860 | 0 | }(); |
861 | | |
862 | | // 2. Each item in the flex line has a target main size, initially set to its flex base size. |
863 | | // Each item is initially unfrozen and may become frozen. |
864 | 0 | for (auto& item : line.items) { |
865 | 0 | item.target_main_size = item.flex_base_size; |
866 | 0 | item.frozen = false; |
867 | 0 | } |
868 | | |
869 | | // 3. Size inflexible items. |
870 | |
|
871 | 0 | for (FlexItem& item : line.items) { |
872 | 0 | if (used_flex_factor == FlexFactor::FlexGrowFactor) { |
873 | 0 | item.flex_factor = item.box->computed_values().flex_grow(); |
874 | 0 | } else if (used_flex_factor == FlexFactor::FlexShrinkFactor) { |
875 | 0 | item.flex_factor = item.box->computed_values().flex_shrink(); |
876 | 0 | } |
877 | | // Freeze, setting its target main size to its hypothetical main size… |
878 | | // - any item that has a flex factor of zero |
879 | | // - if using the flex grow factor: any item that has a flex base size greater than its hypothetical main size |
880 | | // - if using the flex shrink factor: any item that has a flex base size smaller than its hypothetical main size |
881 | 0 | if (item.flex_factor.value() == 0 |
882 | 0 | || (used_flex_factor == FlexFactor::FlexGrowFactor && item.flex_base_size > item.hypothetical_main_size) |
883 | 0 | || (used_flex_factor == FlexFactor::FlexShrinkFactor && item.flex_base_size < item.hypothetical_main_size)) { |
884 | 0 | item.frozen = true; |
885 | 0 | item.target_main_size = item.hypothetical_main_size; |
886 | 0 | } |
887 | 0 | } |
888 | | |
889 | | // 4. Calculate initial free space |
890 | | |
891 | | // Sum the outer sizes of all items on the line, and subtract this from the flex container’s inner main size. |
892 | | // For frozen items, use their outer target main size; for other items, use their outer flex base size. |
893 | 0 | auto calculate_remaining_free_space = [&]() -> Optional<CSSPixels> { |
894 | | // AD-HOC: If the container is sized under max-content constraints, then remaining_free_space won't have |
895 | | // a value to avoid leaking an infinite value into layout calculations. |
896 | 0 | if (available_main_size.is_intrinsic_sizing_constraint()) |
897 | 0 | return {}; |
898 | 0 | CSSPixels sum = 0; |
899 | 0 | for (auto const& item : line.items) { |
900 | 0 | if (item.frozen) |
901 | 0 | sum += item.outer_target_main_size(); |
902 | 0 | else |
903 | 0 | sum += item.outer_flex_base_size(); |
904 | 0 | } |
905 | | // CSS-FLEXBOX-2: Account for gap between flex items. |
906 | 0 | sum += main_gap() * (line.items.size() - 1); |
907 | | |
908 | | // AD-HOC: Note that we're using our own "available main size" explained above |
909 | | // instead of the flex container’s inner main size. |
910 | 0 | return available_main_size.to_px_or_zero() - sum; |
911 | 0 | }; |
912 | 0 | auto const initial_free_space = calculate_remaining_free_space(); |
913 | | |
914 | | // 5. Loop |
915 | 0 | while (true) { |
916 | | // a. Check for flexible items. |
917 | | // If all the flex items on the line are frozen, free space has been distributed; exit this loop. |
918 | 0 | if (all_of(line.items, [](auto const& item) { return item.frozen; })) { |
919 | 0 | break; |
920 | 0 | } |
921 | | |
922 | | // b. Calculate the remaining free space as for initial free space, above. |
923 | 0 | line.remaining_free_space = calculate_remaining_free_space(); |
924 | | |
925 | | // If the sum of the unfrozen flex items’ flex factors is less than one, multiply the initial free space by this sum. |
926 | 0 | if (auto sum_of_flex_factor_of_unfrozen_items = line.sum_of_flex_factor_of_unfrozen_items(); sum_of_flex_factor_of_unfrozen_items < 1 && initial_free_space.has_value()) { |
927 | 0 | auto value = CSSPixels::nearest_value_for(initial_free_space.value() * sum_of_flex_factor_of_unfrozen_items); |
928 | | // If the magnitude of this value is less than the magnitude of the remaining free space, use this as the remaining free space. |
929 | 0 | if (abs(value) < abs(line.remaining_free_space.value())) |
930 | 0 | line.remaining_free_space = value; |
931 | 0 | } |
932 | | |
933 | | // AD-HOC: We allow the remaining free space to be infinite, but we can't let infinity |
934 | | // leak into the layout geometry, so we treat infinity as zero when used in arithmetic. |
935 | 0 | auto remaining_free_space_or_zero_if_infinite = line.remaining_free_space.has_value() ? line.remaining_free_space.value() : 0; |
936 | | |
937 | | // c. If the remaining free space is non-zero, distribute it proportional to the flex factors: |
938 | 0 | if (line.remaining_free_space != 0) { |
939 | | // If using the flex grow factor |
940 | 0 | if (used_flex_factor == FlexFactor::FlexGrowFactor) { |
941 | | // For every unfrozen item on the line, |
942 | | // find the ratio of the item’s flex grow factor to the sum of the flex grow factors of all unfrozen items on the line. |
943 | 0 | auto sum_of_flex_factor_of_unfrozen_items = line.sum_of_flex_factor_of_unfrozen_items(); |
944 | 0 | for (auto& item : line.items) { |
945 | 0 | if (item.frozen) |
946 | 0 | continue; |
947 | 0 | double ratio = item.flex_factor.value() / sum_of_flex_factor_of_unfrozen_items; |
948 | | // Set the item’s target main size to its flex base size plus a fraction of the remaining free space proportional to the ratio. |
949 | 0 | item.target_main_size = item.flex_base_size + remaining_free_space_or_zero_if_infinite.scaled(ratio); |
950 | 0 | } |
951 | 0 | } |
952 | | // If using the flex shrink factor |
953 | 0 | else if (used_flex_factor == FlexFactor::FlexShrinkFactor) { |
954 | | // For every unfrozen item on the line, multiply its flex shrink factor by its inner flex base size, and note this as its scaled flex shrink factor. |
955 | 0 | for (auto& item : line.items) { |
956 | 0 | if (item.frozen) |
957 | 0 | continue; |
958 | 0 | item.scaled_flex_shrink_factor = item.flex_factor.value() * item.flex_base_size.to_double(); |
959 | 0 | } |
960 | 0 | auto sum_of_scaled_flex_shrink_factors_of_all_unfrozen_items_on_line = line.sum_of_scaled_flex_shrink_factor_of_unfrozen_items(); |
961 | 0 | for (auto& item : line.items) { |
962 | 0 | if (item.frozen) |
963 | 0 | continue; |
964 | | // Find the ratio of the item’s scaled flex shrink factor to the sum of the scaled flex shrink factors of all unfrozen items on the line. |
965 | 0 | double ratio = 1.0; |
966 | 0 | if (sum_of_scaled_flex_shrink_factors_of_all_unfrozen_items_on_line != 0) |
967 | 0 | ratio = item.scaled_flex_shrink_factor / sum_of_scaled_flex_shrink_factors_of_all_unfrozen_items_on_line; |
968 | | |
969 | | // Set the item’s target main size to its flex base size minus a fraction of the absolute value of the remaining free space proportional to the ratio. |
970 | | // (Note this may result in a negative inner main size; it will be corrected in the next step.) |
971 | 0 | item.target_main_size = item.flex_base_size - abs(remaining_free_space_or_zero_if_infinite).scaled(ratio); |
972 | 0 | } |
973 | 0 | } |
974 | 0 | } |
975 | | |
976 | | // d. Fix min/max violations. |
977 | 0 | CSSPixels total_violation = 0; |
978 | | |
979 | | // Clamp each non-frozen item’s target main size by its used min and max main sizes and floor its content-box size at zero. |
980 | 0 | for (auto& item : line.items) { |
981 | 0 | if (item.frozen) |
982 | 0 | continue; |
983 | 0 | auto used_min_main_size = has_main_min_size(item.box) |
984 | 0 | ? specified_main_min_size(item.box) |
985 | 0 | : automatic_minimum_size(item); |
986 | |
|
987 | 0 | auto used_max_main_size = has_main_max_size(item.box) |
988 | 0 | ? specified_main_max_size(item.box) |
989 | 0 | : CSSPixels::max(); |
990 | |
|
991 | 0 | auto original_target_main_size = item.target_main_size; |
992 | 0 | item.target_main_size = css_clamp(item.target_main_size, used_min_main_size, used_max_main_size); |
993 | 0 | item.target_main_size = max(item.target_main_size, CSSPixels(0)); |
994 | | |
995 | | // If the item’s target main size was made smaller by this, it’s a max violation. |
996 | 0 | if (item.target_main_size < original_target_main_size) |
997 | 0 | item.is_max_violation = true; |
998 | | |
999 | | // If the item’s target main size was made larger by this, it’s a min violation. |
1000 | 0 | if (item.target_main_size > original_target_main_size) |
1001 | 0 | item.is_min_violation = true; |
1002 | |
|
1003 | 0 | total_violation += item.target_main_size - original_target_main_size; |
1004 | 0 | } |
1005 | | |
1006 | | // e. Freeze over-flexed items. |
1007 | | // The total violation is the sum of the adjustments from the previous step ∑(clamped size - unclamped size). |
1008 | | |
1009 | | // If the total violation is: |
1010 | | // Zero |
1011 | | // Freeze all items. |
1012 | 0 | if (total_violation == 0) { |
1013 | 0 | for (auto& item : line.items) { |
1014 | 0 | if (!item.frozen) |
1015 | 0 | item.frozen = true; |
1016 | 0 | } |
1017 | 0 | } |
1018 | | // Positive |
1019 | | // Freeze all the items with min violations. |
1020 | 0 | else if (total_violation > 0) { |
1021 | 0 | for (auto& item : line.items) { |
1022 | 0 | if (!item.frozen && item.is_min_violation) |
1023 | 0 | item.frozen = true; |
1024 | 0 | } |
1025 | 0 | } |
1026 | | // Negative |
1027 | | // Freeze all the items with max violations. |
1028 | 0 | else { |
1029 | 0 | for (auto& item : line.items) { |
1030 | 0 | if (!item.frozen && item.is_max_violation) |
1031 | 0 | item.frozen = true; |
1032 | 0 | } |
1033 | 0 | } |
1034 | | // NOTE: This freezes at least one item, ensuring that the loop makes progress and eventually terminates. |
1035 | | |
1036 | | // f. Return to the start of this loop. |
1037 | 0 | } |
1038 | | |
1039 | | // NOTE: Calculate the remaining free space once again here, since it's needed later when aligning items. |
1040 | 0 | line.remaining_free_space = calculate_remaining_free_space(); |
1041 | | |
1042 | | // 6. Set each item’s used main size to its target main size. |
1043 | 0 | for (auto& item : line.items) { |
1044 | 0 | item.main_size = item.target_main_size; |
1045 | 0 | set_main_size(item.box, item.target_main_size); |
1046 | | |
1047 | | // https://drafts.csswg.org/css-flexbox-1/#definite-sizes |
1048 | | // 1. If the flex container has a definite main size, then the post-flexing main sizes of its flex items are treated as definite. |
1049 | | // 2. If a flex item’s flex basis is definite, then its post-flexing main size is also definite. |
1050 | 0 | if (has_definite_main_size(m_flex_container_state) || item.used_flex_basis_is_definite) |
1051 | 0 | set_has_definite_main_size(item); |
1052 | 0 | } |
1053 | 0 | } |
1054 | | |
1055 | | // https://drafts.csswg.org/css-flexbox-1/#resolve-flexible-lengths |
1056 | | void FlexFormattingContext::resolve_flexible_lengths() |
1057 | 0 | { |
1058 | 0 | for (auto& line : m_flex_lines) { |
1059 | 0 | resolve_flexible_lengths_for_line(line); |
1060 | 0 | } |
1061 | 0 | } |
1062 | | |
1063 | | // https://www.w3.org/TR/css-flexbox-1/#hypothetical-cross-size |
1064 | | void FlexFormattingContext::determine_hypothetical_cross_size_of_item(FlexItem& item, bool resolve_percentage_min_max_sizes) |
1065 | 0 | { |
1066 | | // Determine the hypothetical cross size of each item by performing layout |
1067 | | // as if it were an in-flow block-level box with the used main size |
1068 | | // and the given available space, treating auto as fit-content. |
1069 | |
|
1070 | 0 | auto const& computed_min_size = this->computed_cross_min_size(item.box); |
1071 | 0 | auto const& computed_max_size = this->computed_cross_max_size(item.box); |
1072 | |
|
1073 | 0 | auto clamp_min = (!computed_min_size.is_auto() && (resolve_percentage_min_max_sizes || !computed_min_size.contains_percentage())) ? specified_cross_min_size(item.box) : 0; |
1074 | 0 | auto clamp_max = (!should_treat_cross_max_size_as_none(item.box) && (resolve_percentage_min_max_sizes || !computed_max_size.contains_percentage())) ? specified_cross_max_size(item.box) : CSSPixels::max(); |
1075 | | |
1076 | | // If we have a definite cross size, this is easy! No need to perform layout, we can just use it as-is. |
1077 | 0 | if (has_definite_cross_size(item)) { |
1078 | | // To avoid subtracting padding and border twice for `box-sizing: border-box` only min and max clamp should happen on a second pass |
1079 | 0 | if (resolve_percentage_min_max_sizes) { |
1080 | 0 | item.hypothetical_cross_size = css_clamp(item.hypothetical_cross_size, clamp_min, clamp_max); |
1081 | 0 | return; |
1082 | 0 | } |
1083 | | |
1084 | 0 | item.hypothetical_cross_size = css_clamp(inner_cross_size(item), clamp_min, clamp_max); |
1085 | 0 | return; |
1086 | 0 | } |
1087 | | |
1088 | 0 | if (item.box->has_preferred_aspect_ratio()) { |
1089 | 0 | if (item.used_flex_basis_is_definite) { |
1090 | 0 | item.hypothetical_cross_size = calculate_cross_size_from_main_size_and_aspect_ratio(item.main_size.value(), item.box->preferred_aspect_ratio().value()); |
1091 | 0 | return; |
1092 | 0 | } |
1093 | 0 | item.hypothetical_cross_size = inner_cross_size(m_flex_container_state); |
1094 | 0 | return; |
1095 | 0 | } |
1096 | | |
1097 | 0 | auto computed_cross_size = [&]() -> CSS::Size { |
1098 | | // "... treating auto as fit-content" |
1099 | 0 | if (should_treat_cross_size_as_auto(item.box)) |
1100 | 0 | return CSS::Size::make_fit_content(); |
1101 | 0 | return this->computed_cross_size(item.box); |
1102 | 0 | }(); |
1103 | |
|
1104 | 0 | if (computed_cross_size.is_min_content()) { |
1105 | 0 | item.hypothetical_cross_size = css_clamp(calculate_min_content_cross_size(item), clamp_min, clamp_max); |
1106 | 0 | return; |
1107 | 0 | } |
1108 | | |
1109 | 0 | if (computed_cross_size.is_max_content()) { |
1110 | 0 | item.hypothetical_cross_size = css_clamp(calculate_max_content_cross_size(item), clamp_min, clamp_max); |
1111 | 0 | return; |
1112 | 0 | } |
1113 | | |
1114 | 0 | if (computed_cross_size.is_fit_content()) { |
1115 | 0 | CSSPixels fit_content_cross_size = 0; |
1116 | 0 | if (is_row_layout()) { |
1117 | 0 | auto available_width = item.main_size.has_value() ? AvailableSize::make_definite(item.main_size.value()) : AvailableSize::make_indefinite(); |
1118 | 0 | auto available_height = AvailableSize::make_indefinite(); |
1119 | 0 | fit_content_cross_size = calculate_fit_content_height(item.box, AvailableSpace(available_width, available_height)); |
1120 | 0 | } else { |
1121 | 0 | fit_content_cross_size = calculate_fit_content_width(item.box, m_available_space_for_items->space); |
1122 | 0 | } |
1123 | |
|
1124 | 0 | item.hypothetical_cross_size = css_clamp(fit_content_cross_size, clamp_min, clamp_max); |
1125 | 0 | return; |
1126 | 0 | } |
1127 | | |
1128 | | // For indefinite cross sizes, we perform a throwaway layout and then measure it. |
1129 | 0 | LayoutState throwaway_state(&m_state); |
1130 | |
|
1131 | 0 | auto& box_state = throwaway_state.get_mutable(item.box); |
1132 | 0 | if (is_row_layout()) { |
1133 | 0 | box_state.set_content_width(item.main_size.value()); |
1134 | 0 | } else { |
1135 | 0 | box_state.set_content_height(item.main_size.value()); |
1136 | 0 | } |
1137 | | |
1138 | | // Item has definite main size, layout with that as the used main size. |
1139 | 0 | auto independent_formatting_context = create_independent_formatting_context_if_needed(throwaway_state, LayoutMode::Normal, item.box); |
1140 | | // NOTE: Flex items should always create an independent formatting context! |
1141 | 0 | VERIFY(independent_formatting_context); |
1142 | | |
1143 | 0 | auto available_width = is_row_layout() ? AvailableSize::make_definite(item.main_size.value()) : AvailableSize::make_indefinite(); |
1144 | 0 | auto available_height = is_row_layout() ? AvailableSize::make_indefinite() : AvailableSize::make_definite(item.main_size.value()); |
1145 | |
|
1146 | 0 | independent_formatting_context->run(AvailableSpace(available_width, available_height)); |
1147 | |
|
1148 | 0 | auto automatic_cross_size = is_row_layout() ? independent_formatting_context->automatic_content_height() |
1149 | 0 | : independent_formatting_context->automatic_content_width(); |
1150 | |
|
1151 | 0 | item.hypothetical_cross_size = css_clamp(automatic_cross_size, clamp_min, clamp_max); |
1152 | 0 | } |
1153 | | |
1154 | | // https://www.w3.org/TR/css-flexbox-1/#algo-cross-line |
1155 | | void FlexFormattingContext::calculate_cross_size_of_each_flex_line() |
1156 | 0 | { |
1157 | | // If the flex container is single-line and has a definite cross size, the cross size of the flex line is the flex container’s inner cross size. |
1158 | 0 | if (is_single_line() && has_definite_cross_size(m_flex_container_state)) { |
1159 | 0 | m_flex_lines[0].cross_size = inner_cross_size(m_flex_container_state); |
1160 | 0 | return; |
1161 | 0 | } |
1162 | | |
1163 | | // Otherwise, for each flex line: |
1164 | 0 | for (auto& flex_line : m_flex_lines) { |
1165 | | // FIXME: 1. Collect all the flex items whose inline-axis is parallel to the main-axis, whose align-self is baseline, |
1166 | | // and whose cross-axis margins are both non-auto. Find the largest of the distances between each item’s baseline |
1167 | | // and its hypothetical outer cross-start edge, and the largest of the distances between each item’s baseline |
1168 | | // and its hypothetical outer cross-end edge, and sum these two values. |
1169 | | |
1170 | | // 2. Among all the items not collected by the previous step, find the largest outer hypothetical cross size. |
1171 | 0 | CSSPixels largest_hypothetical_cross_size = 0; |
1172 | 0 | for (auto& item : flex_line.items) { |
1173 | 0 | if (largest_hypothetical_cross_size < item.hypothetical_cross_size_with_margins()) |
1174 | 0 | largest_hypothetical_cross_size = item.hypothetical_cross_size_with_margins(); |
1175 | 0 | } |
1176 | | |
1177 | | // 3. The used cross-size of the flex line is the largest of the numbers found in the previous two steps and zero. |
1178 | 0 | flex_line.cross_size = max(CSSPixels(0), largest_hypothetical_cross_size); |
1179 | 0 | } |
1180 | | |
1181 | | // If the flex container is single-line, then clamp the line’s cross-size to be within the container’s computed min and max cross sizes. |
1182 | | // Note that if CSS 2.1’s definition of min/max-width/height applied more generally, this behavior would fall out automatically. |
1183 | | // AD-HOC: We don't do this when the flex container is being sized under a min-content or max-content constraint. |
1184 | 0 | if (is_single_line() && !m_available_space_for_items->cross.is_intrinsic_sizing_constraint()) { |
1185 | 0 | auto const& computed_min_size = this->computed_cross_min_size(flex_container()); |
1186 | 0 | auto const& computed_max_size = this->computed_cross_max_size(flex_container()); |
1187 | 0 | auto cross_min_size = (!computed_min_size.is_auto() && !computed_min_size.contains_percentage()) ? specified_cross_min_size(flex_container()) : 0; |
1188 | 0 | auto cross_max_size = (!computed_max_size.is_none() && !computed_max_size.contains_percentage()) ? specified_cross_max_size(flex_container()) : CSSPixels::max(); |
1189 | 0 | m_flex_lines[0].cross_size = css_clamp(m_flex_lines[0].cross_size, cross_min_size, cross_max_size); |
1190 | 0 | } |
1191 | 0 | } |
1192 | | |
1193 | | // https://www.w3.org/TR/css-flexbox-1/#algo-stretch |
1194 | | void FlexFormattingContext::determine_used_cross_size_of_each_flex_item() |
1195 | 0 | { |
1196 | 0 | for (auto& flex_line : m_flex_lines) { |
1197 | 0 | for (auto& item : flex_line.items) { |
1198 | | // If a flex item has align-self: stretch, its computed cross size property is auto, |
1199 | | // and neither of its cross-axis margins are auto, the used outer cross size is the used cross size of its flex line, |
1200 | | // clamped according to the item’s used min and max cross sizes. |
1201 | 0 | auto flex_item_alignment = alignment_for_item(item.box); |
1202 | 0 | if ((flex_item_alignment == CSS::AlignItems::Stretch || flex_item_alignment == CSS::AlignItems::Normal) |
1203 | 0 | && is_cross_auto(item.box) |
1204 | 0 | && !item.margins.cross_before_is_auto |
1205 | 0 | && !item.margins.cross_after_is_auto) { |
1206 | 0 | auto unclamped_cross_size = flex_line.cross_size |
1207 | 0 | - item.margins.cross_before - item.margins.cross_after |
1208 | 0 | - item.padding.cross_before - item.padding.cross_after |
1209 | 0 | - item.borders.cross_before - item.borders.cross_after; |
1210 | |
|
1211 | 0 | auto const& computed_min_size = computed_cross_min_size(item.box); |
1212 | 0 | auto const& computed_max_size = computed_cross_max_size(item.box); |
1213 | 0 | auto cross_min_size = (!computed_min_size.is_auto() && !computed_min_size.contains_percentage()) ? specified_cross_min_size(item.box) : 0; |
1214 | 0 | auto cross_max_size = (!should_treat_cross_max_size_as_none(item.box) && !computed_max_size.contains_percentage()) ? specified_cross_max_size(item.box) : CSSPixels::max(); |
1215 | |
|
1216 | 0 | item.cross_size = css_clamp(unclamped_cross_size, cross_min_size, cross_max_size); |
1217 | 0 | } else { |
1218 | | // Otherwise, the used cross size is the item’s hypothetical cross size. |
1219 | 0 | item.cross_size = item.hypothetical_cross_size; |
1220 | 0 | } |
1221 | 0 | } |
1222 | 0 | } |
1223 | 0 | } |
1224 | | |
1225 | | // https://www.w3.org/TR/css-flexbox-1/#algo-main-align |
1226 | | void FlexFormattingContext::distribute_any_remaining_free_space() |
1227 | 0 | { |
1228 | 0 | for (auto& flex_line : m_flex_lines) { |
1229 | | // 12.1. |
1230 | 0 | CSSPixels used_main_space = 0; |
1231 | 0 | size_t auto_margins = 0; |
1232 | 0 | for (auto& item : flex_line.items) { |
1233 | 0 | used_main_space += item.main_size.value(); |
1234 | 0 | if (item.margins.main_before_is_auto) |
1235 | 0 | ++auto_margins; |
1236 | |
|
1237 | 0 | if (item.margins.main_after_is_auto) |
1238 | 0 | ++auto_margins; |
1239 | |
|
1240 | 0 | used_main_space += item.margins.main_before + item.margins.main_after |
1241 | 0 | + item.borders.main_before + item.borders.main_after |
1242 | 0 | + item.padding.main_before + item.padding.main_after; |
1243 | 0 | } |
1244 | | |
1245 | | // CSS-FLEXBOX-2: Account for gap between flex items. |
1246 | 0 | used_main_space += main_gap() * (flex_line.items.size() - 1); |
1247 | |
|
1248 | 0 | if (flex_line.remaining_free_space.has_value() && flex_line.remaining_free_space.value() > 0 && auto_margins > 0) { |
1249 | 0 | CSSPixels size_per_auto_margin = flex_line.remaining_free_space.value() / auto_margins; |
1250 | 0 | for (auto& item : flex_line.items) { |
1251 | 0 | if (item.margins.main_before_is_auto) |
1252 | 0 | set_main_axis_first_margin(item, size_per_auto_margin); |
1253 | 0 | if (item.margins.main_after_is_auto) |
1254 | 0 | set_main_axis_second_margin(item, size_per_auto_margin); |
1255 | 0 | } |
1256 | 0 | } else { |
1257 | 0 | for (auto& item : flex_line.items) { |
1258 | 0 | if (item.margins.main_before_is_auto) |
1259 | 0 | set_main_axis_first_margin(item, 0); |
1260 | 0 | if (item.margins.main_after_is_auto) |
1261 | 0 | set_main_axis_second_margin(item, 0); |
1262 | 0 | } |
1263 | 0 | } |
1264 | | |
1265 | | // 12.2. |
1266 | 0 | CSSPixels space_between_items = 0; |
1267 | 0 | CSSPixels initial_offset = 0; |
1268 | 0 | auto number_of_items = flex_line.items.size(); |
1269 | |
|
1270 | 0 | if (auto_margins == 0 && number_of_items > 0) { |
1271 | 0 | switch (flex_container().computed_values().justify_content()) { |
1272 | 0 | case CSS::JustifyContent::Start: |
1273 | 0 | case CSS::JustifyContent::Left: |
1274 | 0 | initial_offset = 0; |
1275 | 0 | break; |
1276 | 0 | case CSS::JustifyContent::Stretch: |
1277 | 0 | case CSS::JustifyContent::Normal: |
1278 | 0 | case CSS::JustifyContent::FlexStart: |
1279 | 0 | if (is_direction_reverse()) { |
1280 | 0 | initial_offset = inner_main_size(m_flex_container_state); |
1281 | 0 | } else { |
1282 | 0 | initial_offset = 0; |
1283 | 0 | } |
1284 | 0 | break; |
1285 | 0 | case CSS::JustifyContent::End: |
1286 | 0 | initial_offset = inner_main_size(m_flex_container_state); |
1287 | 0 | break; |
1288 | 0 | case CSS::JustifyContent::Right: |
1289 | 0 | if (is_row_layout()) { |
1290 | 0 | initial_offset = inner_main_size(m_flex_container_state); |
1291 | 0 | } else { |
1292 | 0 | initial_offset = 0; |
1293 | 0 | } |
1294 | 0 | break; |
1295 | 0 | case CSS::JustifyContent::FlexEnd: |
1296 | 0 | if (is_direction_reverse()) { |
1297 | 0 | initial_offset = 0; |
1298 | 0 | } else { |
1299 | 0 | initial_offset = inner_main_size(m_flex_container_state); |
1300 | 0 | } |
1301 | 0 | break; |
1302 | 0 | case CSS::JustifyContent::Center: |
1303 | 0 | initial_offset = (inner_main_size(m_flex_container_state) - used_main_space) / 2; |
1304 | 0 | if (is_direction_reverse()) { |
1305 | 0 | initial_offset = inner_main_size(m_flex_container_state) - initial_offset; |
1306 | 0 | } |
1307 | 0 | break; |
1308 | 0 | case CSS::JustifyContent::SpaceBetween: |
1309 | 0 | if (is_direction_reverse()) { |
1310 | 0 | initial_offset = inner_main_size(m_flex_container_state); |
1311 | 0 | } else { |
1312 | 0 | initial_offset = 0; |
1313 | 0 | } |
1314 | 0 | if (flex_line.remaining_free_space.has_value() && number_of_items > 1) |
1315 | 0 | space_between_items = max(CSSPixels(0), flex_line.remaining_free_space.value() / (number_of_items - 1)); |
1316 | 0 | break; |
1317 | 0 | case CSS::JustifyContent::SpaceAround: |
1318 | 0 | if (flex_line.remaining_free_space.has_value()) |
1319 | 0 | space_between_items = max(CSSPixels(0), flex_line.remaining_free_space.value() / number_of_items); |
1320 | 0 | if (is_direction_reverse()) { |
1321 | 0 | initial_offset = inner_main_size(m_flex_container_state) - space_between_items / 2; |
1322 | 0 | } else { |
1323 | 0 | initial_offset = space_between_items / 2; |
1324 | 0 | } |
1325 | 0 | break; |
1326 | 0 | case CSS::JustifyContent::SpaceEvenly: |
1327 | 0 | if (flex_line.remaining_free_space.has_value()) |
1328 | 0 | space_between_items = max(CSSPixels(0), flex_line.remaining_free_space.value() / (number_of_items + 1)); |
1329 | 0 | if (is_direction_reverse()) { |
1330 | 0 | initial_offset = inner_main_size(m_flex_container_state) - space_between_items; |
1331 | 0 | } else { |
1332 | 0 | initial_offset = space_between_items; |
1333 | 0 | } |
1334 | 0 | break; |
1335 | 0 | } |
1336 | 0 | } |
1337 | | |
1338 | | // For reverse, we use FlexRegionRenderCursor::Right |
1339 | | // to indicate the cursor offset is the end and render backwards |
1340 | | // Otherwise the cursor offset is the 'start' of the region or initial offset |
1341 | 0 | enum class FlexRegionRenderCursor { |
1342 | 0 | Left, |
1343 | 0 | Right |
1344 | 0 | }; |
1345 | 0 | auto flex_region_render_cursor = FlexRegionRenderCursor::Left; |
1346 | |
|
1347 | 0 | if (auto_margins == 0) { |
1348 | 0 | switch (flex_container().computed_values().justify_content()) { |
1349 | 0 | case CSS::JustifyContent::Start: |
1350 | 0 | case CSS::JustifyContent::Left: |
1351 | 0 | flex_region_render_cursor = FlexRegionRenderCursor::Left; |
1352 | 0 | break; |
1353 | 0 | case CSS::JustifyContent::Normal: |
1354 | 0 | case CSS::JustifyContent::FlexStart: |
1355 | 0 | case CSS::JustifyContent::Center: |
1356 | 0 | case CSS::JustifyContent::SpaceAround: |
1357 | 0 | case CSS::JustifyContent::SpaceBetween: |
1358 | 0 | case CSS::JustifyContent::SpaceEvenly: |
1359 | 0 | case CSS::JustifyContent::Stretch: |
1360 | 0 | if (is_direction_reverse()) { |
1361 | 0 | flex_region_render_cursor = FlexRegionRenderCursor::Right; |
1362 | 0 | } |
1363 | 0 | break; |
1364 | 0 | case CSS::JustifyContent::End: |
1365 | 0 | flex_region_render_cursor = FlexRegionRenderCursor::Right; |
1366 | 0 | break; |
1367 | 0 | case CSS::JustifyContent::Right: |
1368 | 0 | if (is_row_layout()) { |
1369 | 0 | flex_region_render_cursor = FlexRegionRenderCursor::Right; |
1370 | 0 | } else { |
1371 | 0 | flex_region_render_cursor = FlexRegionRenderCursor::Left; |
1372 | 0 | } |
1373 | 0 | break; |
1374 | 0 | case CSS::JustifyContent::FlexEnd: |
1375 | 0 | if (!is_direction_reverse()) { |
1376 | 0 | flex_region_render_cursor = FlexRegionRenderCursor::Right; |
1377 | 0 | } |
1378 | 0 | break; |
1379 | 0 | default: |
1380 | 0 | break; |
1381 | 0 | } |
1382 | 0 | } |
1383 | | |
1384 | 0 | CSSPixels cursor_offset = initial_offset; |
1385 | |
|
1386 | 0 | auto place_item = [&](FlexItem& item) { |
1387 | | // CSS-FLEXBOX-2: Account for gap between items. |
1388 | 0 | auto amount_of_main_size_used = item.main_size.value() |
1389 | 0 | + item.margins.main_before |
1390 | 0 | + item.borders.main_before |
1391 | 0 | + item.padding.main_before |
1392 | 0 | + item.margins.main_after |
1393 | 0 | + item.borders.main_after |
1394 | 0 | + item.padding.main_after |
1395 | 0 | + space_between_items |
1396 | 0 | + main_gap(); |
1397 | |
|
1398 | 0 | if (is_direction_reverse() && flex_region_render_cursor == FlexRegionRenderCursor::Right) { |
1399 | 0 | item.main_offset = cursor_offset - item.main_size.value() - item.margins.main_after - item.borders.main_after - item.padding.main_after; |
1400 | 0 | cursor_offset -= amount_of_main_size_used; |
1401 | 0 | } else if (flex_region_render_cursor == FlexRegionRenderCursor::Right) { |
1402 | 0 | cursor_offset -= amount_of_main_size_used; |
1403 | 0 | item.main_offset = cursor_offset + item.margins.main_before + item.borders.main_before + item.padding.main_before; |
1404 | 0 | } else { |
1405 | 0 | item.main_offset = cursor_offset + item.margins.main_before + item.borders.main_before + item.padding.main_before; |
1406 | 0 | cursor_offset += amount_of_main_size_used; |
1407 | 0 | } |
1408 | 0 | }; |
1409 | |
|
1410 | 0 | if (flex_region_render_cursor == FlexRegionRenderCursor::Right) { |
1411 | 0 | for (ssize_t i = flex_line.items.size() - 1; i >= 0; --i) { |
1412 | 0 | auto& item = flex_line.items[i]; |
1413 | 0 | place_item(item); |
1414 | 0 | } |
1415 | 0 | } else { |
1416 | 0 | for (size_t i = 0; i < flex_line.items.size(); ++i) { |
1417 | 0 | auto& item = flex_line.items[i]; |
1418 | 0 | place_item(item); |
1419 | 0 | } |
1420 | 0 | } |
1421 | 0 | } |
1422 | 0 | } |
1423 | | |
1424 | | void FlexFormattingContext::dump_items() const |
1425 | 0 | { |
1426 | 0 | dbgln("\033[34;1mflex-container\033[0m {}, direction: {}, current-size: {}x{}", flex_container().debug_description(), is_row_layout() ? "row" : "column", m_flex_container_state.content_width(), m_flex_container_state.content_height()); |
1427 | 0 | for (size_t i = 0; i < m_flex_lines.size(); ++i) { |
1428 | 0 | dbgln("{} flex-line #{}:", flex_container().debug_description(), i); |
1429 | 0 | for (size_t j = 0; j < m_flex_lines[i].items.size(); ++j) { |
1430 | 0 | auto& item = m_flex_lines[i].items[j]; |
1431 | 0 | dbgln("{} flex-item #{}: {} (main:{}, cross:{})", flex_container().debug_description(), j, item.box->debug_description(), item.main_size.value_or(-1), item.cross_size.value_or(-1)); |
1432 | 0 | } |
1433 | 0 | } |
1434 | 0 | } |
1435 | | |
1436 | | CSS::AlignItems FlexFormattingContext::alignment_for_item(Box const& box) const |
1437 | 0 | { |
1438 | 0 | switch (box.computed_values().align_self()) { |
1439 | 0 | case CSS::AlignSelf::Auto: |
1440 | 0 | return flex_container().computed_values().align_items(); |
1441 | 0 | case CSS::AlignSelf::End: |
1442 | 0 | return CSS::AlignItems::End; |
1443 | 0 | case CSS::AlignSelf::Normal: |
1444 | 0 | return CSS::AlignItems::Normal; |
1445 | 0 | case CSS::AlignSelf::SelfStart: |
1446 | 0 | return CSS::AlignItems::SelfStart; |
1447 | 0 | case CSS::AlignSelf::SelfEnd: |
1448 | 0 | return CSS::AlignItems::SelfEnd; |
1449 | 0 | case CSS::AlignSelf::FlexStart: |
1450 | 0 | return CSS::AlignItems::FlexStart; |
1451 | 0 | case CSS::AlignSelf::FlexEnd: |
1452 | 0 | return CSS::AlignItems::FlexEnd; |
1453 | 0 | case CSS::AlignSelf::Center: |
1454 | 0 | return CSS::AlignItems::Center; |
1455 | 0 | case CSS::AlignSelf::Baseline: |
1456 | 0 | return CSS::AlignItems::Baseline; |
1457 | 0 | case CSS::AlignSelf::Start: |
1458 | 0 | return CSS::AlignItems::Start; |
1459 | 0 | case CSS::AlignSelf::Stretch: |
1460 | 0 | return CSS::AlignItems::Stretch; |
1461 | 0 | case CSS::AlignSelf::Safe: |
1462 | 0 | return CSS::AlignItems::Safe; |
1463 | 0 | case CSS::AlignSelf::Unsafe: |
1464 | 0 | return CSS::AlignItems::Unsafe; |
1465 | 0 | default: |
1466 | 0 | VERIFY_NOT_REACHED(); |
1467 | 0 | } |
1468 | 0 | } |
1469 | | |
1470 | | void FlexFormattingContext::align_all_flex_items_along_the_cross_axis() |
1471 | 0 | { |
1472 | | // FIXME: Take better care of margins |
1473 | 0 | for (auto& flex_line : m_flex_lines) { |
1474 | 0 | for (auto& item : flex_line.items) { |
1475 | 0 | CSSPixels half_line_size = flex_line.cross_size / 2; |
1476 | 0 | switch (alignment_for_item(item.box)) { |
1477 | 0 | case CSS::AlignItems::Normal: |
1478 | | // https://drafts.csswg.org/css-flexbox/#flex-wrap-property |
1479 | | // When flex-wrap is wrap-reverse, the cross-start and cross-end directions are swapped. |
1480 | 0 | if (flex_container().computed_values().flex_wrap() == CSS::FlexWrap::WrapReverse) { |
1481 | 0 | item.cross_offset = half_line_size - item.cross_size.value() - item.margins.cross_after - item.borders.cross_after - item.padding.cross_after; |
1482 | 0 | } else { |
1483 | 0 | item.cross_offset = -half_line_size + item.margins.cross_before + item.borders.cross_before + item.padding.cross_before; |
1484 | 0 | } |
1485 | 0 | break; |
1486 | 0 | case CSS::AlignItems::Baseline: |
1487 | | // FIXME: Implement this |
1488 | | // Fallthrough |
1489 | 0 | case CSS::AlignItems::Start: |
1490 | 0 | case CSS::AlignItems::FlexStart: |
1491 | 0 | case CSS::AlignItems::SelfStart: |
1492 | 0 | case CSS::AlignItems::Stretch: |
1493 | | // FIXME: 'start', 'flex-start' and 'self-start' have subtly different behavior. |
1494 | | // The same goes for the end values. |
1495 | 0 | item.cross_offset = -half_line_size + item.margins.cross_before + item.borders.cross_before + item.padding.cross_before; |
1496 | 0 | break; |
1497 | 0 | case CSS::AlignItems::End: |
1498 | 0 | case CSS::AlignItems::FlexEnd: |
1499 | 0 | case CSS::AlignItems::SelfEnd: |
1500 | 0 | item.cross_offset = half_line_size - item.cross_size.value() - item.margins.cross_after - item.borders.cross_after - item.padding.cross_after; |
1501 | 0 | break; |
1502 | 0 | case CSS::AlignItems::Center: |
1503 | 0 | item.cross_offset = -(item.cross_size.value() / 2); |
1504 | 0 | break; |
1505 | 0 | default: |
1506 | 0 | break; |
1507 | 0 | } |
1508 | 0 | } |
1509 | 0 | } |
1510 | 0 | } |
1511 | | |
1512 | | // https://www.w3.org/TR/css-flexbox-1/#algo-line-align |
1513 | | void FlexFormattingContext::align_all_flex_lines() |
1514 | 0 | { |
1515 | 0 | if (m_flex_lines.is_empty()) |
1516 | 0 | return; |
1517 | | |
1518 | | // FIXME: Support reverse |
1519 | | |
1520 | 0 | CSSPixels cross_size_of_flex_container = inner_cross_size(m_flex_container_state); |
1521 | |
|
1522 | 0 | if (is_single_line()) { |
1523 | | // For single-line flex containers, we only need to center the line along the cross axis. |
1524 | 0 | auto& flex_line = m_flex_lines[0]; |
1525 | 0 | CSSPixels center_of_line = cross_size_of_flex_container / 2; |
1526 | 0 | for (auto& item : flex_line.items) { |
1527 | 0 | item.cross_offset += center_of_line; |
1528 | 0 | } |
1529 | 0 | } else { |
1530 | |
|
1531 | 0 | CSSPixels sum_of_flex_line_cross_sizes = 0; |
1532 | 0 | for (auto& line : m_flex_lines) |
1533 | 0 | sum_of_flex_line_cross_sizes += line.cross_size; |
1534 | | |
1535 | | // CSS-FLEXBOX-2: Account for gap between flex lines. |
1536 | 0 | sum_of_flex_line_cross_sizes += cross_gap() * (m_flex_lines.size() - 1); |
1537 | |
|
1538 | 0 | CSSPixels start_of_current_line = 0; |
1539 | 0 | CSSPixels gap_size = 0; |
1540 | 0 | switch (flex_container().computed_values().align_content()) { |
1541 | 0 | case CSS::AlignContent::FlexStart: |
1542 | 0 | case CSS::AlignContent::Start: |
1543 | 0 | start_of_current_line = 0; |
1544 | 0 | break; |
1545 | 0 | case CSS::AlignContent::FlexEnd: |
1546 | 0 | case CSS::AlignContent::End: |
1547 | 0 | start_of_current_line = cross_size_of_flex_container - sum_of_flex_line_cross_sizes; |
1548 | 0 | break; |
1549 | 0 | case CSS::AlignContent::Center: |
1550 | 0 | start_of_current_line = (cross_size_of_flex_container / 2) - (sum_of_flex_line_cross_sizes / 2); |
1551 | 0 | break; |
1552 | 0 | case CSS::AlignContent::SpaceBetween: { |
1553 | 0 | start_of_current_line = 0; |
1554 | |
|
1555 | 0 | auto leftover_free_space = cross_size_of_flex_container - sum_of_flex_line_cross_sizes; |
1556 | 0 | auto leftover_flex_lines_size = m_flex_lines.size(); |
1557 | 0 | if (leftover_free_space >= 0 && leftover_flex_lines_size > 1) { |
1558 | 0 | int gap_count = leftover_flex_lines_size - 1; |
1559 | 0 | gap_size = leftover_free_space / gap_count; |
1560 | 0 | } |
1561 | 0 | break; |
1562 | 0 | } |
1563 | 0 | case CSS::AlignContent::SpaceAround: { |
1564 | 0 | auto leftover_free_space = cross_size_of_flex_container - sum_of_flex_line_cross_sizes; |
1565 | 0 | if (leftover_free_space < 0) { |
1566 | | // If the leftover free-space is negative this value is identical to center. |
1567 | 0 | start_of_current_line = (cross_size_of_flex_container / 2) - (sum_of_flex_line_cross_sizes / 2); |
1568 | 0 | break; |
1569 | 0 | } |
1570 | | |
1571 | 0 | gap_size = leftover_free_space / m_flex_lines.size(); |
1572 | | |
1573 | | // The spacing between the first/last lines and the flex container edges is half the size of the spacing between flex lines. |
1574 | 0 | start_of_current_line = gap_size / 2; |
1575 | 0 | break; |
1576 | 0 | } |
1577 | 0 | case CSS::AlignContent::SpaceEvenly: { |
1578 | 0 | auto leftover_free_space = cross_size_of_flex_container - sum_of_flex_line_cross_sizes; |
1579 | 0 | if (leftover_free_space < 0) { |
1580 | | // If the leftover free-space is negative this value is identical to center. |
1581 | 0 | start_of_current_line = (cross_size_of_flex_container / 2) - (sum_of_flex_line_cross_sizes / 2); |
1582 | 0 | break; |
1583 | 0 | } |
1584 | | |
1585 | 0 | gap_size = leftover_free_space / (m_flex_lines.size() + 1); |
1586 | | |
1587 | | // The spacing between the first/last lines and the flex container edges is the size of the spacing between flex lines. |
1588 | 0 | start_of_current_line = gap_size; |
1589 | 0 | break; |
1590 | 0 | } |
1591 | | |
1592 | 0 | case CSS::AlignContent::Normal: |
1593 | 0 | case CSS::AlignContent::Stretch: |
1594 | 0 | start_of_current_line = 0; |
1595 | 0 | break; |
1596 | 0 | } |
1597 | | |
1598 | 0 | for (auto& flex_line : m_flex_lines) { |
1599 | 0 | CSSPixels center_of_current_line = start_of_current_line + (flex_line.cross_size / 2); |
1600 | 0 | for (auto& item : flex_line.items) { |
1601 | 0 | item.cross_offset += center_of_current_line; |
1602 | 0 | } |
1603 | 0 | start_of_current_line += flex_line.cross_size + gap_size; |
1604 | | // CSS-FLEXBOX-2: Account for gap between flex lines. |
1605 | 0 | start_of_current_line += cross_gap(); |
1606 | 0 | } |
1607 | 0 | } |
1608 | 0 | } |
1609 | | |
1610 | | void FlexFormattingContext::copy_dimensions_from_flex_items_to_boxes() |
1611 | 0 | { |
1612 | 0 | for (auto& item : m_flex_items) { |
1613 | 0 | auto const& box = item.box; |
1614 | |
|
1615 | 0 | item.used_values.padding_left = box->computed_values().padding().left().to_px(box, m_flex_container_state.content_width()); |
1616 | 0 | item.used_values.padding_right = box->computed_values().padding().right().to_px(box, m_flex_container_state.content_width()); |
1617 | 0 | item.used_values.padding_top = box->computed_values().padding().top().to_px(box, m_flex_container_state.content_width()); |
1618 | 0 | item.used_values.padding_bottom = box->computed_values().padding().bottom().to_px(box, m_flex_container_state.content_width()); |
1619 | |
|
1620 | 0 | item.used_values.margin_left = box->computed_values().margin().left().to_px(box, m_flex_container_state.content_width()); |
1621 | 0 | item.used_values.margin_right = box->computed_values().margin().right().to_px(box, m_flex_container_state.content_width()); |
1622 | 0 | item.used_values.margin_top = box->computed_values().margin().top().to_px(box, m_flex_container_state.content_width()); |
1623 | 0 | item.used_values.margin_bottom = box->computed_values().margin().bottom().to_px(box, m_flex_container_state.content_width()); |
1624 | |
|
1625 | 0 | item.used_values.border_left = box->computed_values().border_left().width; |
1626 | 0 | item.used_values.border_right = box->computed_values().border_right().width; |
1627 | 0 | item.used_values.border_top = box->computed_values().border_top().width; |
1628 | 0 | item.used_values.border_bottom = box->computed_values().border_bottom().width; |
1629 | |
|
1630 | 0 | set_main_size(box, item.main_size.value()); |
1631 | 0 | set_cross_size(box, item.cross_size.value()); |
1632 | 0 | set_offset(box, item.main_offset, item.cross_offset); |
1633 | 0 | } |
1634 | 0 | } |
1635 | | |
1636 | | // https://drafts.csswg.org/css-flexbox-1/#intrinsic-sizes |
1637 | | void FlexFormattingContext::determine_intrinsic_size_of_flex_container() |
1638 | 0 | { |
1639 | 0 | if (m_available_space_for_items->main.is_intrinsic_sizing_constraint()) { |
1640 | 0 | CSSPixels main_size = calculate_intrinsic_main_size_of_flex_container(); |
1641 | 0 | set_main_size(flex_container(), main_size); |
1642 | 0 | } |
1643 | 0 | if (m_available_space_for_items->cross.is_intrinsic_sizing_constraint()) { |
1644 | 0 | CSSPixels cross_size = calculate_intrinsic_cross_size_of_flex_container(); |
1645 | 0 | set_cross_size(flex_container(), cross_size); |
1646 | 0 | } |
1647 | 0 | } |
1648 | | |
1649 | | // https://drafts.csswg.org/css-flexbox-1/#intrinsic-main-sizes |
1650 | | CSSPixels FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container() |
1651 | 0 | { |
1652 | | // The min-content main size of a single-line flex container is calculated identically to the max-content main size, |
1653 | | // except that the flex items’ min-content contributions are used instead of their max-content contributions. |
1654 | | // However, for a multi-line container, it is simply the largest min-content contribution of all the non-collapsed flex items in the flex container. |
1655 | 0 | if (!is_single_line() && m_available_space_for_items->main.is_min_content()) { |
1656 | 0 | CSSPixels largest_contribution = 0; |
1657 | 0 | for (auto const& item : m_flex_items) { |
1658 | | // FIXME: Skip collapsed flex items. |
1659 | 0 | largest_contribution = max(largest_contribution, calculate_main_min_content_contribution(item)); |
1660 | 0 | } |
1661 | 0 | return largest_contribution; |
1662 | 0 | } |
1663 | | |
1664 | | // The max-content main size of a flex container is, fundamentally, the smallest size the flex container |
1665 | | // can take such that when flex layout is run with that container size, each flex item ends up at least |
1666 | | // as large as its max-content contribution, to the extent allowed by the items’ flexibility. |
1667 | | // It is calculated, considering only non-collapsed flex items, by: |
1668 | | |
1669 | | // 1. For each flex item, subtract its outer flex base size from its max-content contribution size. |
1670 | | // If that result is positive, divide it by the item’s flex grow factor if the flex grow factor is ≥ 1, |
1671 | | // or multiply it by the flex grow factor if the flex grow factor is < 1; if the result is negative, |
1672 | | // divide it by the item’s scaled flex shrink factor (if dividing by zero, treat the result as negative infinity). |
1673 | | // This is the item’s desired flex fraction. |
1674 | | |
1675 | 0 | for (auto& item : m_flex_items) { |
1676 | 0 | CSSPixels contribution = 0; |
1677 | 0 | if (m_available_space_for_items->main.is_min_content()) |
1678 | 0 | contribution = calculate_main_min_content_contribution(item); |
1679 | 0 | else if (m_available_space_for_items->main.is_max_content()) |
1680 | 0 | contribution = calculate_main_max_content_contribution(item); |
1681 | |
|
1682 | 0 | CSSPixels outer_flex_base_size = item.flex_base_size + item.margins.main_before + item.margins.main_after + item.borders.main_before + item.borders.main_after + item.padding.main_before + item.padding.main_after; |
1683 | |
|
1684 | 0 | CSSPixels result = contribution - outer_flex_base_size; |
1685 | 0 | if (result > 0) { |
1686 | 0 | if (item.box->computed_values().flex_grow() >= 1) { |
1687 | 0 | result.scale_by(1 / item.box->computed_values().flex_grow()); |
1688 | 0 | } else { |
1689 | 0 | result.scale_by(item.box->computed_values().flex_grow()); |
1690 | 0 | } |
1691 | 0 | } else if (result < 0) { |
1692 | 0 | if (item.scaled_flex_shrink_factor == 0) |
1693 | 0 | result = CSSPixels::min(); |
1694 | 0 | else |
1695 | 0 | result.scale_by(1 / item.scaled_flex_shrink_factor); |
1696 | 0 | } |
1697 | |
|
1698 | 0 | item.desired_flex_fraction = result.to_double(); |
1699 | 0 | } |
1700 | | |
1701 | | // 2. Place all flex items into lines of infinite length. |
1702 | 0 | m_flex_lines.clear(); |
1703 | 0 | if (!m_flex_items.is_empty()) |
1704 | 0 | m_flex_lines.append(FlexLine {}); |
1705 | 0 | for (auto& item : m_flex_items) { |
1706 | | // FIXME: Honor breaking requests. |
1707 | 0 | m_flex_lines.last().items.append(item); |
1708 | 0 | } |
1709 | | |
1710 | | // Within each line, find the greatest (most positive) desired flex fraction among all the flex items. |
1711 | | // This is the line’s chosen flex fraction. |
1712 | 0 | for (auto& flex_line : m_flex_lines) { |
1713 | 0 | float greatest_desired_flex_fraction = 0; |
1714 | 0 | float sum_of_flex_grow_factors = 0; |
1715 | 0 | float sum_of_flex_shrink_factors = 0; |
1716 | 0 | for (auto& item : flex_line.items) { |
1717 | 0 | greatest_desired_flex_fraction = max(greatest_desired_flex_fraction, item.desired_flex_fraction); |
1718 | 0 | sum_of_flex_grow_factors += item.box->computed_values().flex_grow(); |
1719 | 0 | sum_of_flex_shrink_factors += item.box->computed_values().flex_shrink(); |
1720 | 0 | } |
1721 | 0 | float chosen_flex_fraction = greatest_desired_flex_fraction; |
1722 | | |
1723 | | // 3. If the chosen flex fraction is positive, and the sum of the line’s flex grow factors is less than 1, |
1724 | | // divide the chosen flex fraction by that sum. |
1725 | 0 | if (chosen_flex_fraction > 0 && sum_of_flex_grow_factors < 1) |
1726 | 0 | chosen_flex_fraction /= sum_of_flex_grow_factors; |
1727 | | |
1728 | | // If the chosen flex fraction is negative, and the sum of the line’s flex shrink factors is less than 1, |
1729 | | // multiply the chosen flex fraction by that sum. |
1730 | 0 | if (chosen_flex_fraction < 0 && sum_of_flex_shrink_factors < 1) |
1731 | 0 | chosen_flex_fraction *= sum_of_flex_shrink_factors; |
1732 | |
|
1733 | 0 | flex_line.chosen_flex_fraction = chosen_flex_fraction; |
1734 | 0 | } |
1735 | |
|
1736 | 0 | auto determine_main_size = [&]() -> CSSPixels { |
1737 | 0 | CSSPixels largest_sum = 0; |
1738 | 0 | for (auto& flex_line : m_flex_lines) { |
1739 | | // 4. Add each item’s flex base size to the product of its flex grow factor (scaled flex shrink factor, if shrinking) |
1740 | | // and the chosen flex fraction, then clamp that result by the max main size floored by the min main size. |
1741 | 0 | CSSPixels sum = 0; |
1742 | 0 | for (auto& item : flex_line.items) { |
1743 | 0 | double product = 0; |
1744 | 0 | if (item.desired_flex_fraction > 0) |
1745 | 0 | product = flex_line.chosen_flex_fraction * static_cast<double>(item.box->computed_values().flex_grow()); |
1746 | 0 | else if (item.desired_flex_fraction < 0) |
1747 | 0 | product = flex_line.chosen_flex_fraction * item.scaled_flex_shrink_factor; |
1748 | 0 | auto result = item.flex_base_size + CSSPixels::nearest_value_for(product); |
1749 | |
|
1750 | 0 | auto const& computed_min_size = this->computed_main_min_size(item.box); |
1751 | 0 | auto const& computed_max_size = this->computed_main_max_size(item.box); |
1752 | |
|
1753 | 0 | auto clamp_min = (!computed_min_size.is_auto() && !computed_min_size.contains_percentage()) ? specified_main_min_size(item.box) : automatic_minimum_size(item); |
1754 | 0 | auto clamp_max = (!should_treat_main_max_size_as_none(item.box) && !computed_max_size.contains_percentage()) ? specified_main_max_size(item.box) : CSSPixels::max(); |
1755 | |
|
1756 | 0 | result = css_clamp(result, clamp_min, clamp_max); |
1757 | | |
1758 | | // NOTE: The spec doesn't mention anything about the *outer* size here, but if we don't add the margin box, |
1759 | | // flex items with non-zero padding/border/margin in the main axis end up overflowing the container. |
1760 | 0 | result = item.add_main_margin_box_sizes(result); |
1761 | |
|
1762 | 0 | sum += result; |
1763 | 0 | } |
1764 | | // CSS-FLEXBOX-2: Account for gap between flex items. |
1765 | 0 | sum += main_gap() * (flex_line.items.size() - 1); |
1766 | 0 | largest_sum = max(largest_sum, sum); |
1767 | 0 | } |
1768 | | // 5. The flex container’s max-content size is the largest sum (among all the lines) of the afore-calculated sizes of all items within a single line. |
1769 | 0 | return largest_sum; |
1770 | 0 | }; |
1771 | |
|
1772 | 0 | auto main_size = determine_main_size(); |
1773 | 0 | set_main_size(flex_container(), main_size); |
1774 | 0 | return main_size; |
1775 | 0 | } |
1776 | | |
1777 | | // https://drafts.csswg.org/css-flexbox-1/#intrinsic-cross-sizes |
1778 | | CSSPixels FlexFormattingContext::calculate_intrinsic_cross_size_of_flex_container() |
1779 | 0 | { |
1780 | | // The min-content/max-content cross size of a single-line flex container |
1781 | | // is the largest min-content contribution/max-content contribution (respectively) of its flex items. |
1782 | 0 | if (is_single_line()) { |
1783 | 0 | auto calculate_largest_contribution = [&](bool resolve_percentage_min_max_sizes) { |
1784 | 0 | CSSPixels largest_contribution = 0; |
1785 | 0 | for (auto& item : m_flex_items) { |
1786 | 0 | CSSPixels contribution = 0; |
1787 | 0 | if (m_available_space_for_items->cross.is_min_content()) |
1788 | 0 | contribution = calculate_cross_min_content_contribution(item, resolve_percentage_min_max_sizes); |
1789 | 0 | else if (m_available_space_for_items->cross.is_max_content()) |
1790 | 0 | contribution = calculate_cross_max_content_contribution(item, resolve_percentage_min_max_sizes); |
1791 | 0 | largest_contribution = max(largest_contribution, contribution); |
1792 | 0 | } |
1793 | 0 | return largest_contribution; |
1794 | 0 | }; |
1795 | |
|
1796 | 0 | auto first_pass_largest_contribution = calculate_largest_contribution(false); |
1797 | 0 | set_cross_size(flex_container(), first_pass_largest_contribution); |
1798 | 0 | auto second_pass_largest_contribution = calculate_largest_contribution(true); |
1799 | 0 | return second_pass_largest_contribution; |
1800 | 0 | } |
1801 | | |
1802 | 0 | if (is_row_layout()) { |
1803 | | // row multi-line flex container cross-size |
1804 | | |
1805 | | // The min-content/max-content cross size is the sum of the flex line cross sizes resulting from |
1806 | | // sizing the flex container under a cross-axis min-content constraint/max-content constraint (respectively). |
1807 | | |
1808 | | // NOTE: We fall through to the ad-hoc section below. |
1809 | 0 | } else { |
1810 | | // column multi-line flex container cross-size |
1811 | | |
1812 | | // The min-content cross size is the largest min-content contribution among all of its flex items. |
1813 | 0 | if (m_available_space_for_items->cross.is_min_content()) { |
1814 | 0 | auto calculate_largest_contribution = [&](bool resolve_percentage_min_max_sizes) { |
1815 | 0 | CSSPixels largest_contribution = 0; |
1816 | 0 | for (auto& item : m_flex_items) { |
1817 | 0 | CSSPixels contribution = calculate_cross_min_content_contribution(item, resolve_percentage_min_max_sizes); |
1818 | 0 | largest_contribution = max(largest_contribution, contribution); |
1819 | 0 | } |
1820 | 0 | return largest_contribution; |
1821 | 0 | }; |
1822 | |
|
1823 | 0 | auto first_pass_largest_contribution = calculate_largest_contribution(false); |
1824 | 0 | set_cross_size(flex_container(), first_pass_largest_contribution); |
1825 | 0 | auto second_pass_largest_contribution = calculate_largest_contribution(true); |
1826 | 0 | return second_pass_largest_contribution; |
1827 | 0 | } |
1828 | | |
1829 | | // The max-content cross size is the sum of the flex line cross sizes resulting from |
1830 | | // sizing the flex container under a cross-axis max-content constraint, |
1831 | | // using the largest max-content cross-size contribution among the flex items |
1832 | | // as the available space in the cross axis for each of the flex items during layout. |
1833 | | |
1834 | | // NOTE: We fall through to the ad-hoc section below. |
1835 | 0 | } |
1836 | | |
1837 | 0 | CSSPixels sum_of_flex_line_cross_sizes = 0; |
1838 | 0 | for (auto& flex_line : m_flex_lines) { |
1839 | 0 | sum_of_flex_line_cross_sizes += flex_line.cross_size; |
1840 | 0 | } |
1841 | | // CSS-FLEXBOX-2: Account for gap between flex lines. |
1842 | 0 | sum_of_flex_line_cross_sizes += cross_gap() * (m_flex_lines.size() - 1); |
1843 | 0 | return sum_of_flex_line_cross_sizes; |
1844 | 0 | } |
1845 | | |
1846 | | // https://drafts.csswg.org/css-flexbox-1/#intrinsic-item-contributions |
1847 | | CSSPixels FlexFormattingContext::calculate_main_min_content_contribution(FlexItem const& item) const |
1848 | 0 | { |
1849 | | // The main-size min-content contribution of a flex item is |
1850 | | // the larger of its outer min-content size and outer preferred size if that is not auto, |
1851 | | // clamped by its min/max main size. |
1852 | 0 | auto larger_size = [&] { |
1853 | 0 | auto inner_min_content_size = calculate_min_content_main_size(item); |
1854 | 0 | if (computed_main_size(item.box).is_auto()) |
1855 | 0 | return inner_min_content_size; |
1856 | 0 | auto inner_preferred_size = is_row_layout() ? get_pixel_width(item.box, computed_main_size(item.box)) : get_pixel_height(item.box, computed_main_size(item.box)); |
1857 | 0 | return max(inner_min_content_size, inner_preferred_size); |
1858 | 0 | }(); |
1859 | |
|
1860 | 0 | auto clamp_min = has_main_min_size(item.box) ? specified_main_min_size(item.box) : automatic_minimum_size(item); |
1861 | 0 | auto clamp_max = has_main_max_size(item.box) ? specified_main_max_size(item.box) : CSSPixels::max(); |
1862 | 0 | auto clamped_inner_size = css_clamp(larger_size, clamp_min, clamp_max); |
1863 | |
|
1864 | 0 | return item.add_main_margin_box_sizes(clamped_inner_size); |
1865 | 0 | } |
1866 | | |
1867 | | // https://drafts.csswg.org/css-flexbox-1/#intrinsic-item-contributions |
1868 | | CSSPixels FlexFormattingContext::calculate_main_max_content_contribution(FlexItem const& item) const |
1869 | 0 | { |
1870 | | // The main-size max-content contribution of a flex item is |
1871 | | // the larger of its outer max-content size and outer preferred size if that is not auto, |
1872 | | // clamped by its min/max main size. |
1873 | 0 | auto larger_size = [&] { |
1874 | 0 | auto inner_max_content_size = calculate_max_content_main_size(item); |
1875 | 0 | if (computed_main_size(item.box).is_auto()) |
1876 | 0 | return inner_max_content_size; |
1877 | 0 | auto inner_preferred_size = is_row_layout() ? get_pixel_width(item.box, computed_main_size(item.box)) : get_pixel_height(item.box, computed_main_size(item.box)); |
1878 | 0 | return max(inner_max_content_size, inner_preferred_size); |
1879 | 0 | }(); |
1880 | |
|
1881 | 0 | auto clamp_min = has_main_min_size(item.box) ? specified_main_min_size(item.box) : automatic_minimum_size(item); |
1882 | 0 | auto clamp_max = has_main_max_size(item.box) ? specified_main_max_size(item.box) : CSSPixels::max(); |
1883 | 0 | auto clamped_inner_size = css_clamp(larger_size, clamp_min, clamp_max); |
1884 | |
|
1885 | 0 | return item.add_main_margin_box_sizes(clamped_inner_size); |
1886 | 0 | } |
1887 | | |
1888 | | bool FlexFormattingContext::should_treat_main_size_as_auto(Box const& box) const |
1889 | 0 | { |
1890 | 0 | if (is_row_layout()) |
1891 | 0 | return should_treat_width_as_auto(box, m_available_space_for_items->space); |
1892 | 0 | return should_treat_height_as_auto(box, m_available_space_for_items->space); |
1893 | 0 | } |
1894 | | |
1895 | | bool FlexFormattingContext::should_treat_cross_size_as_auto(Box const& box) const |
1896 | 0 | { |
1897 | 0 | if (is_row_layout()) |
1898 | 0 | return should_treat_height_as_auto(box, m_available_space_for_items->space); |
1899 | 0 | return should_treat_width_as_auto(box, m_available_space_for_items->space); |
1900 | 0 | } |
1901 | | |
1902 | | bool FlexFormattingContext::should_treat_main_max_size_as_none(Box const& box) const |
1903 | 0 | { |
1904 | 0 | if (is_row_layout()) |
1905 | 0 | return should_treat_max_width_as_none(box, m_available_space_for_items->space.width); |
1906 | 0 | return should_treat_max_height_as_none(box, m_available_space_for_items->space.height); |
1907 | 0 | } |
1908 | | |
1909 | | bool FlexFormattingContext::should_treat_cross_max_size_as_none(Box const& box) const |
1910 | 0 | { |
1911 | 0 | if (is_row_layout()) |
1912 | 0 | return should_treat_max_height_as_none(box, m_available_space_for_items->space.height); |
1913 | 0 | return should_treat_max_width_as_none(box, m_available_space_for_items->space.width); |
1914 | 0 | } |
1915 | | |
1916 | | CSSPixels FlexFormattingContext::calculate_cross_min_content_contribution(FlexItem const& item, bool resolve_percentage_min_max_sizes) const |
1917 | 0 | { |
1918 | 0 | auto size = [&] { |
1919 | 0 | if (should_treat_cross_size_as_auto(item.box)) |
1920 | 0 | return calculate_min_content_cross_size(item); |
1921 | 0 | return !is_row_layout() ? get_pixel_width(item.box, computed_cross_size(item.box)) : get_pixel_height(item.box, computed_cross_size(item.box)); |
1922 | 0 | }(); |
1923 | |
|
1924 | 0 | if (item.box->has_preferred_aspect_ratio()) |
1925 | 0 | size = adjust_cross_size_through_aspect_ratio_for_main_size_min_max_constraints(item.box, size, computed_main_min_size(item.box), computed_main_max_size(item.box)); |
1926 | |
|
1927 | 0 | auto const& computed_min_size = this->computed_cross_min_size(item.box); |
1928 | 0 | auto const& computed_max_size = this->computed_cross_max_size(item.box); |
1929 | |
|
1930 | 0 | auto clamp_min = (!computed_min_size.is_auto() && (resolve_percentage_min_max_sizes || !computed_min_size.contains_percentage())) ? specified_cross_min_size(item.box) : 0; |
1931 | 0 | auto clamp_max = (!should_treat_cross_max_size_as_none(item.box) && (resolve_percentage_min_max_sizes || !computed_max_size.contains_percentage())) ? specified_cross_max_size(item.box) : CSSPixels::max(); |
1932 | |
|
1933 | 0 | auto clamped_inner_size = css_clamp(size, clamp_min, clamp_max); |
1934 | |
|
1935 | 0 | return item.add_cross_margin_box_sizes(clamped_inner_size); |
1936 | 0 | } |
1937 | | |
1938 | | CSSPixels FlexFormattingContext::calculate_cross_max_content_contribution(FlexItem const& item, bool resolve_percentage_min_max_sizes) const |
1939 | 0 | { |
1940 | 0 | auto size = [&] { |
1941 | 0 | if (should_treat_cross_size_as_auto(item.box)) |
1942 | 0 | return calculate_max_content_cross_size(item); |
1943 | 0 | return !is_row_layout() ? get_pixel_width(item.box, computed_cross_size(item.box)) : get_pixel_height(item.box, computed_cross_size(item.box)); |
1944 | 0 | }(); |
1945 | |
|
1946 | 0 | if (item.box->has_preferred_aspect_ratio()) |
1947 | 0 | size = adjust_cross_size_through_aspect_ratio_for_main_size_min_max_constraints(item.box, size, computed_main_min_size(item.box), computed_main_max_size(item.box)); |
1948 | |
|
1949 | 0 | auto const& computed_min_size = this->computed_cross_min_size(item.box); |
1950 | 0 | auto const& computed_max_size = this->computed_cross_max_size(item.box); |
1951 | |
|
1952 | 0 | auto clamp_min = (!computed_min_size.is_auto() && (resolve_percentage_min_max_sizes || !computed_min_size.contains_percentage())) ? specified_cross_min_size(item.box) : 0; |
1953 | 0 | auto clamp_max = (!should_treat_cross_max_size_as_none(item.box) && (resolve_percentage_min_max_sizes || !computed_max_size.contains_percentage())) ? specified_cross_max_size(item.box) : CSSPixels::max(); |
1954 | |
|
1955 | 0 | auto clamped_inner_size = css_clamp(size, clamp_min, clamp_max); |
1956 | |
|
1957 | 0 | return item.add_cross_margin_box_sizes(clamped_inner_size); |
1958 | 0 | } |
1959 | | |
1960 | | CSSPixels FlexFormattingContext::calculate_width_to_use_when_determining_intrinsic_height_of_item(FlexItem const& item) const |
1961 | 0 | { |
1962 | 0 | auto const& box = *item.box; |
1963 | 0 | auto computed_width = box.computed_values().width(); |
1964 | 0 | auto const& computed_min_width = box.computed_values().min_width(); |
1965 | 0 | auto const& computed_max_width = box.computed_values().max_width(); |
1966 | 0 | auto clamp_min = (!computed_min_width.is_auto() && (!computed_min_width.contains_percentage())) ? get_pixel_width(box, computed_min_width) : 0; |
1967 | 0 | auto clamp_max = (!should_treat_max_width_as_none(box, m_available_space_for_items->space.width) && (!computed_max_width.contains_percentage())) ? get_pixel_width(box, computed_max_width) : CSSPixels::max(); |
1968 | |
|
1969 | 0 | CSSPixels width; |
1970 | 0 | if (should_treat_width_as_auto(box, m_available_space_for_items->space) || computed_width.is_fit_content()) |
1971 | 0 | width = calculate_fit_content_width(box, m_available_space_for_items->space); |
1972 | 0 | else if (computed_width.is_min_content()) |
1973 | 0 | width = calculate_min_content_width(box); |
1974 | 0 | else if (computed_width.is_max_content()) |
1975 | 0 | width = calculate_max_content_width(box); |
1976 | |
|
1977 | 0 | return css_clamp(width, clamp_min, clamp_max); |
1978 | 0 | } |
1979 | | |
1980 | | CSSPixels FlexFormattingContext::calculate_min_content_main_size(FlexItem const& item) const |
1981 | 0 | { |
1982 | 0 | if (is_row_layout()) { |
1983 | 0 | return calculate_min_content_width(item.box); |
1984 | 0 | } |
1985 | 0 | auto available_space = item.used_values.available_inner_space_or_constraints_from(m_available_space_for_items->space); |
1986 | 0 | if (available_space.width.is_indefinite()) { |
1987 | 0 | available_space.width = AvailableSize::make_definite(calculate_width_to_use_when_determining_intrinsic_height_of_item(item)); |
1988 | 0 | } |
1989 | 0 | return calculate_min_content_height(item.box, available_space.width.to_px_or_zero()); |
1990 | 0 | } |
1991 | | |
1992 | | CSSPixels FlexFormattingContext::calculate_max_content_main_size(FlexItem const& item) const |
1993 | 0 | { |
1994 | 0 | if (is_row_layout()) { |
1995 | 0 | return calculate_max_content_width(item.box); |
1996 | 0 | } |
1997 | 0 | auto available_space = item.used_values.available_inner_space_or_constraints_from(m_available_space_for_items->space); |
1998 | 0 | if (available_space.width.is_indefinite()) { |
1999 | 0 | available_space.width = AvailableSize::make_definite(calculate_width_to_use_when_determining_intrinsic_height_of_item(item)); |
2000 | 0 | } |
2001 | 0 | return calculate_max_content_height(item.box, available_space.width.to_px_or_zero()); |
2002 | 0 | } |
2003 | | |
2004 | | CSSPixels FlexFormattingContext::calculate_fit_content_main_size(FlexItem const& item) const |
2005 | 0 | { |
2006 | 0 | if (is_row_layout()) |
2007 | 0 | return calculate_fit_content_width(item.box, m_available_space_for_items->space); |
2008 | 0 | return calculate_fit_content_height(item.box, m_available_space_for_items->space); |
2009 | 0 | } |
2010 | | |
2011 | | CSSPixels FlexFormattingContext::calculate_fit_content_cross_size(FlexItem const& item) const |
2012 | 0 | { |
2013 | 0 | if (!is_row_layout()) |
2014 | 0 | return calculate_fit_content_width(item.box, m_available_space_for_items->space); |
2015 | 0 | return calculate_fit_content_height(item.box, m_available_space_for_items->space); |
2016 | 0 | } |
2017 | | |
2018 | | CSSPixels FlexFormattingContext::calculate_min_content_cross_size(FlexItem const& item) const |
2019 | 0 | { |
2020 | 0 | if (is_row_layout()) { |
2021 | 0 | auto available_space = item.used_values.available_inner_space_or_constraints_from(m_available_space_for_items->space); |
2022 | 0 | if (available_space.width.is_indefinite()) { |
2023 | 0 | available_space.width = AvailableSize::make_definite(calculate_width_to_use_when_determining_intrinsic_height_of_item(item)); |
2024 | 0 | } |
2025 | 0 | return calculate_min_content_height(item.box, available_space.width.to_px_or_zero()); |
2026 | 0 | } |
2027 | 0 | return calculate_min_content_width(item.box); |
2028 | 0 | } |
2029 | | |
2030 | | CSSPixels FlexFormattingContext::calculate_max_content_cross_size(FlexItem const& item) const |
2031 | 0 | { |
2032 | 0 | if (is_row_layout()) { |
2033 | 0 | auto available_space = item.used_values.available_inner_space_or_constraints_from(m_available_space_for_items->space); |
2034 | 0 | if (available_space.width.is_indefinite()) { |
2035 | 0 | available_space.width = AvailableSize::make_definite(calculate_width_to_use_when_determining_intrinsic_height_of_item(item)); |
2036 | 0 | } |
2037 | 0 | return calculate_max_content_height(item.box, available_space.width.to_px_or_zero()); |
2038 | 0 | } |
2039 | 0 | return calculate_max_content_width(item.box); |
2040 | 0 | } |
2041 | | |
2042 | | // https://drafts.csswg.org/css-flexbox-1/#stretched |
2043 | | bool FlexFormattingContext::flex_item_is_stretched(FlexItem const& item) const |
2044 | 0 | { |
2045 | 0 | auto alignment = alignment_for_item(item.box); |
2046 | 0 | if (alignment != CSS::AlignItems::Stretch && alignment != CSS::AlignItems::Normal) |
2047 | 0 | return false; |
2048 | | // If the cross size property of the flex item computes to auto, and neither of the cross-axis margins are auto, the flex item is stretched. |
2049 | 0 | auto const& computed_cross_size = is_row_layout() ? item.box->computed_values().height() : item.box->computed_values().width(); |
2050 | 0 | return computed_cross_size.is_auto() && !item.margins.cross_before_is_auto && !item.margins.cross_after_is_auto; |
2051 | 0 | } |
2052 | | |
2053 | | CSS::Size const& FlexFormattingContext::computed_main_size(Box const& box) const |
2054 | 0 | { |
2055 | 0 | return is_row_layout() ? box.computed_values().width() : box.computed_values().height(); |
2056 | 0 | } |
2057 | | |
2058 | | CSS::Size const& FlexFormattingContext::computed_main_min_size(Box const& box) const |
2059 | 0 | { |
2060 | 0 | return is_row_layout() ? box.computed_values().min_width() : box.computed_values().min_height(); |
2061 | 0 | } |
2062 | | |
2063 | | CSS::Size const& FlexFormattingContext::computed_main_max_size(Box const& box) const |
2064 | 0 | { |
2065 | 0 | return is_row_layout() ? box.computed_values().max_width() : box.computed_values().max_height(); |
2066 | 0 | } |
2067 | | |
2068 | | CSS::Size const& FlexFormattingContext::computed_cross_size(Box const& box) const |
2069 | 0 | { |
2070 | 0 | return !is_row_layout() ? box.computed_values().width() : box.computed_values().height(); |
2071 | 0 | } |
2072 | | |
2073 | | CSS::Size const& FlexFormattingContext::computed_cross_min_size(Box const& box) const |
2074 | 0 | { |
2075 | 0 | return !is_row_layout() ? box.computed_values().min_width() : box.computed_values().min_height(); |
2076 | 0 | } |
2077 | | |
2078 | | CSS::Size const& FlexFormattingContext::computed_cross_max_size(Box const& box) const |
2079 | 0 | { |
2080 | 0 | return !is_row_layout() ? box.computed_values().max_width() : box.computed_values().max_height(); |
2081 | 0 | } |
2082 | | |
2083 | | // https://drafts.csswg.org/css-flexbox-1/#algo-cross-margins |
2084 | | void FlexFormattingContext::resolve_cross_axis_auto_margins() |
2085 | 0 | { |
2086 | 0 | for (auto& line : m_flex_lines) { |
2087 | 0 | for (auto& item : line.items) { |
2088 | | // If a flex item has auto cross-axis margins: |
2089 | 0 | if (!item.margins.cross_before_is_auto && !item.margins.cross_after_is_auto) |
2090 | 0 | continue; |
2091 | | |
2092 | | // If its outer cross size (treating those auto margins as zero) is less than the cross size of its flex line, |
2093 | | // distribute the difference in those sizes equally to the auto margins. |
2094 | 0 | auto outer_cross_size = item.cross_size.value() + item.padding.cross_before + item.padding.cross_after + item.borders.cross_before + item.borders.cross_after; |
2095 | 0 | if (outer_cross_size < line.cross_size) { |
2096 | 0 | CSSPixels remainder = line.cross_size - outer_cross_size; |
2097 | 0 | if (item.margins.cross_before_is_auto && item.margins.cross_after_is_auto) { |
2098 | 0 | item.margins.cross_before = remainder / 2; |
2099 | 0 | item.margins.cross_after = remainder / 2; |
2100 | 0 | } else if (item.margins.cross_before_is_auto) { |
2101 | 0 | item.margins.cross_before = remainder; |
2102 | 0 | } else { |
2103 | 0 | item.margins.cross_after = remainder; |
2104 | 0 | } |
2105 | 0 | } else { |
2106 | | // FIXME: Otherwise, if the block-start or inline-start margin (whichever is in the cross axis) is auto, set it to zero. |
2107 | | // Set the opposite margin so that the outer cross size of the item equals the cross size of its flex line. |
2108 | 0 | } |
2109 | 0 | } |
2110 | 0 | } |
2111 | 0 | } |
2112 | | |
2113 | | // https://drafts.csswg.org/css-flexbox-1/#algo-line-stretch |
2114 | | void FlexFormattingContext::handle_align_content_stretch() |
2115 | 0 | { |
2116 | | // If the flex container has a definite cross size, |
2117 | 0 | if (!has_definite_cross_size(m_flex_container_state)) |
2118 | 0 | return; |
2119 | | |
2120 | | // align-content is stretch, |
2121 | 0 | if (flex_container().computed_values().align_content() != CSS::AlignContent::Stretch && flex_container().computed_values().align_content() != CSS::AlignContent::Normal) |
2122 | 0 | return; |
2123 | | |
2124 | | // and the sum of the flex lines' cross sizes is less than the flex container’s inner cross size, |
2125 | 0 | CSSPixels sum_of_flex_line_cross_sizes = 0; |
2126 | 0 | for (auto& line : m_flex_lines) |
2127 | 0 | sum_of_flex_line_cross_sizes += line.cross_size; |
2128 | | |
2129 | | // CSS-FLEXBOX-2: Account for gap between flex lines. |
2130 | 0 | sum_of_flex_line_cross_sizes += cross_gap() * (m_flex_lines.size() - 1); |
2131 | |
|
2132 | 0 | if (sum_of_flex_line_cross_sizes >= inner_cross_size(m_flex_container_state)) |
2133 | 0 | return; |
2134 | | |
2135 | | // increase the cross size of each flex line by equal amounts |
2136 | | // such that the sum of their cross sizes exactly equals the flex container’s inner cross size. |
2137 | 0 | CSSPixels remainder = inner_cross_size(m_flex_container_state) - sum_of_flex_line_cross_sizes; |
2138 | 0 | CSSPixels extra_per_line = remainder / m_flex_lines.size(); |
2139 | |
|
2140 | 0 | for (auto& line : m_flex_lines) |
2141 | 0 | line.cross_size += extra_per_line; |
2142 | 0 | } |
2143 | | |
2144 | | // https://drafts.csswg.org/css-flexbox-1/#abspos-items |
2145 | | CSSPixelPoint FlexFormattingContext::calculate_static_position(Box const& box) const |
2146 | 0 | { |
2147 | | // The cross-axis edges of the static-position rectangle of an absolutely-positioned child |
2148 | | // of a flex container are the content edges of the flex container. |
2149 | 0 | CSSPixels cross_offset = 0; |
2150 | 0 | CSSPixels half_line_size = inner_cross_size(m_flex_container_state) / 2; |
2151 | |
|
2152 | 0 | auto cross_to_px = [&](CSS::LengthPercentage const& length_percentage) -> CSSPixels { |
2153 | 0 | return length_percentage.to_px(box, m_flex_container_state.content_width()); |
2154 | 0 | }; |
2155 | |
|
2156 | 0 | auto main_to_px = [&](CSS::LengthPercentage const& length_percentage) -> CSSPixels { |
2157 | 0 | return length_percentage.to_px(box, m_flex_container_state.content_width()); |
2158 | 0 | }; |
2159 | |
|
2160 | 0 | auto const& box_state = m_state.get(box); |
2161 | 0 | CSSPixels cross_margin_before = is_row_layout() ? cross_to_px(box.computed_values().margin().top()) : cross_to_px(box.computed_values().margin().left()); |
2162 | 0 | CSSPixels cross_margin_after = is_row_layout() ? cross_to_px(box.computed_values().margin().bottom()) : cross_to_px(box.computed_values().margin().right()); |
2163 | 0 | CSSPixels cross_border_before = is_row_layout() ? box.computed_values().border_top().width : box.computed_values().border_left().width; |
2164 | 0 | CSSPixels cross_border_after = is_row_layout() ? box.computed_values().border_bottom().width : box.computed_values().border_right().width; |
2165 | 0 | CSSPixels cross_padding_before = is_row_layout() ? cross_to_px(box.computed_values().padding().top()) : cross_to_px(box.computed_values().padding().left()); |
2166 | 0 | CSSPixels cross_padding_after = is_row_layout() ? cross_to_px(box.computed_values().padding().bottom()) : cross_to_px(box.computed_values().padding().right()); |
2167 | 0 | CSSPixels main_margin_before = is_row_layout() ? main_to_px(box.computed_values().margin().left()) : main_to_px(box.computed_values().margin().top()); |
2168 | 0 | CSSPixels main_margin_after = is_row_layout() ? main_to_px(box.computed_values().margin().right()) : main_to_px(box.computed_values().margin().bottom()); |
2169 | 0 | CSSPixels main_border_before = is_row_layout() ? box.computed_values().border_left().width : box.computed_values().border_top().width; |
2170 | 0 | CSSPixels main_border_after = is_row_layout() ? box.computed_values().border_right().width : box.computed_values().border_bottom().width; |
2171 | 0 | CSSPixels main_padding_before = is_row_layout() ? main_to_px(box.computed_values().padding().left()) : main_to_px(box.computed_values().padding().top()); |
2172 | 0 | CSSPixels main_padding_after = is_row_layout() ? main_to_px(box.computed_values().padding().right()) : main_to_px(box.computed_values().padding().bottom()); |
2173 | |
|
2174 | 0 | switch (alignment_for_item(box)) { |
2175 | 0 | case CSS::AlignItems::Baseline: |
2176 | | // FIXME: Implement this |
2177 | | // Fallthrough |
2178 | 0 | case CSS::AlignItems::Start: |
2179 | 0 | case CSS::AlignItems::FlexStart: |
2180 | 0 | case CSS::AlignItems::SelfStart: |
2181 | 0 | case CSS::AlignItems::Stretch: |
2182 | 0 | case CSS::AlignItems::Normal: |
2183 | 0 | cross_offset = -half_line_size; |
2184 | 0 | break; |
2185 | 0 | case CSS::AlignItems::End: |
2186 | 0 | case CSS::AlignItems::SelfEnd: |
2187 | 0 | case CSS::AlignItems::FlexEnd: |
2188 | 0 | cross_offset = half_line_size - inner_cross_size(box_state) - (cross_margin_before + cross_margin_after) - (cross_border_before + cross_border_after) - (cross_padding_before + cross_padding_after); |
2189 | 0 | break; |
2190 | 0 | case CSS::AlignItems::Center: |
2191 | 0 | cross_offset = -((inner_cross_size(box_state) + cross_margin_after + cross_margin_before + cross_border_before + cross_border_after + cross_padding_before + cross_padding_after) / 2); |
2192 | 0 | break; |
2193 | 0 | default: |
2194 | 0 | break; |
2195 | 0 | } |
2196 | | |
2197 | 0 | cross_offset += inner_cross_size(m_flex_container_state) / 2; |
2198 | | |
2199 | | // The main-axis edges of the static-position rectangle are where the margin edges of the child |
2200 | | // would be positioned if it were the sole flex item in the flex container, |
2201 | | // assuming both the child and the flex container were fixed-size boxes of their used size. |
2202 | | // (For this purpose, auto margins are treated as zero. |
2203 | |
|
2204 | 0 | bool pack_from_end = true; |
2205 | 0 | CSSPixels main_offset = 0; |
2206 | 0 | switch (flex_container().computed_values().justify_content()) { |
2207 | 0 | case CSS::JustifyContent::Start: |
2208 | 0 | case CSS::JustifyContent::Left: |
2209 | 0 | pack_from_end = false; |
2210 | 0 | break; |
2211 | 0 | case CSS::JustifyContent::Stretch: |
2212 | 0 | case CSS::JustifyContent::Normal: |
2213 | 0 | case CSS::JustifyContent::FlexStart: |
2214 | 0 | case CSS::JustifyContent::SpaceBetween: |
2215 | 0 | pack_from_end = is_direction_reverse(); |
2216 | 0 | break; |
2217 | 0 | case CSS::JustifyContent::End: |
2218 | 0 | pack_from_end = true; |
2219 | 0 | break; |
2220 | 0 | case CSS::JustifyContent::Right: |
2221 | 0 | pack_from_end = is_row_layout(); |
2222 | 0 | break; |
2223 | 0 | case CSS::JustifyContent::FlexEnd: |
2224 | 0 | pack_from_end = !is_direction_reverse(); |
2225 | 0 | break; |
2226 | 0 | case CSS::JustifyContent::Center: |
2227 | 0 | case CSS::JustifyContent::SpaceAround: |
2228 | 0 | case CSS::JustifyContent::SpaceEvenly: |
2229 | 0 | pack_from_end = false; |
2230 | 0 | main_offset = (inner_main_size(m_flex_container_state) - inner_main_size(box_state) - main_margin_before - main_margin_after - main_border_before - main_border_after - main_padding_before - main_padding_after) / 2; |
2231 | 0 | break; |
2232 | 0 | } |
2233 | | |
2234 | 0 | if (pack_from_end) |
2235 | 0 | main_offset += inner_main_size(m_flex_container_state) - inner_main_size(box_state) - main_margin_before - main_margin_after - main_border_before - main_border_after - main_padding_before - main_padding_after; |
2236 | |
|
2237 | 0 | auto static_position_offset = is_row_layout() ? CSSPixelPoint { main_offset, cross_offset } : CSSPixelPoint { cross_offset, main_offset }; |
2238 | |
|
2239 | 0 | auto absolute_position_of_flex_container = absolute_content_rect(flex_container()).location(); |
2240 | 0 | auto absolute_position_of_abspos_containing_block = absolute_content_rect(*box.containing_block()).location(); |
2241 | 0 | auto diff = absolute_position_of_flex_container - absolute_position_of_abspos_containing_block; |
2242 | |
|
2243 | 0 | return static_position_offset + diff; |
2244 | 0 | } |
2245 | | |
2246 | | double FlexFormattingContext::FlexLine::sum_of_flex_factor_of_unfrozen_items() const |
2247 | 0 | { |
2248 | 0 | double sum = 0; |
2249 | 0 | for (auto const& item : items) { |
2250 | 0 | if (!item.frozen) |
2251 | 0 | sum += item.flex_factor.value(); |
2252 | 0 | } |
2253 | 0 | return sum; |
2254 | 0 | } |
2255 | | |
2256 | | double FlexFormattingContext::FlexLine::sum_of_scaled_flex_shrink_factor_of_unfrozen_items() const |
2257 | 0 | { |
2258 | 0 | double sum = 0; |
2259 | 0 | for (auto const& item : items) { |
2260 | 0 | if (!item.frozen) |
2261 | 0 | sum += item.scaled_flex_shrink_factor; |
2262 | 0 | } |
2263 | 0 | return sum; |
2264 | 0 | } |
2265 | | |
2266 | | CSSPixels FlexFormattingContext::main_gap() const |
2267 | 0 | { |
2268 | 0 | auto const& computed_values = flex_container().computed_values(); |
2269 | 0 | auto gap = is_row_layout() ? computed_values.column_gap() : computed_values.row_gap(); |
2270 | 0 | return gap.to_px(flex_container(), inner_main_size(m_flex_container_state)); |
2271 | 0 | } |
2272 | | |
2273 | | CSSPixels FlexFormattingContext::cross_gap() const |
2274 | 0 | { |
2275 | 0 | auto const& computed_values = flex_container().computed_values(); |
2276 | 0 | auto gap = is_row_layout() ? computed_values.row_gap() : computed_values.column_gap(); |
2277 | 0 | return gap.to_px(flex_container(), inner_cross_size(m_flex_container_state)); |
2278 | 0 | } |
2279 | | |
2280 | | } |