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