diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index df64ff4877c..a023d90572e 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -804,16 +804,15 @@ void Document::update_layout() auto& icb = static_cast(*m_layout_root); auto& icb_state = layout_state.get_mutable(icb); - icb_state.set_has_definite_width(true); - icb_state.set_has_definite_height(true); icb_state.set_content_width(viewport_rect.width()); icb_state.set_content_height(viewport_rect.height()); root_formatting_context.run( *m_layout_root, Layout::LayoutMode::Normal, - Layout::AvailableSpace::make_definite(viewport_rect.width()), - Layout::AvailableSpace::make_definite(viewport_rect.height())); + Layout::AvailableSpace( + Layout::AvailableSize::make_definite(viewport_rect.width()), + Layout::AvailableSize::make_definite(viewport_rect.height()))); } layout_state.commit(); diff --git a/Userland/Libraries/LibWeb/Layout/AvailableSpace.cpp b/Userland/Libraries/LibWeb/Layout/AvailableSpace.cpp index 026fe903115..605da0780c0 100644 --- a/Userland/Libraries/LibWeb/Layout/AvailableSpace.cpp +++ b/Userland/Libraries/LibWeb/Layout/AvailableSpace.cpp @@ -9,27 +9,27 @@ namespace Web::Layout { -AvailableSpace AvailableSpace::make_definite(float value) +AvailableSize AvailableSize::make_definite(float value) { - return AvailableSpace { Type::Definite, value }; + return AvailableSize { Type::Definite, value }; } -AvailableSpace AvailableSpace::make_indefinite() +AvailableSize AvailableSize::make_indefinite() { - return AvailableSpace { Type::Indefinite, INFINITY }; + return AvailableSize { Type::Indefinite, INFINITY }; } -AvailableSpace AvailableSpace::make_min_content() +AvailableSize AvailableSize::make_min_content() { - return AvailableSpace { Type::MinContent, 0 }; + return AvailableSize { Type::MinContent, 0 }; } -AvailableSpace AvailableSpace::make_max_content() +AvailableSize AvailableSize::make_max_content() { - return AvailableSpace { Type::MaxContent, INFINITY }; + return AvailableSize { Type::MaxContent, INFINITY }; } -String AvailableSpace::to_string() const +String AvailableSize::to_string() const { switch (m_type) { case Type::Definite: @@ -44,7 +44,12 @@ String AvailableSpace::to_string() const VERIFY_NOT_REACHED(); } -AvailableSpace::AvailableSpace(Type type, float value) +String AvailableSpace::to_string() const +{ + return String::formatted("{} x {}", width, height); +} + +AvailableSize::AvailableSize(Type type, float value) : m_type(type) , m_value(value) { diff --git a/Userland/Libraries/LibWeb/Layout/AvailableSpace.h b/Userland/Libraries/LibWeb/Layout/AvailableSpace.h index 7f319ab9cb0..7d2b1e7fb89 100644 --- a/Userland/Libraries/LibWeb/Layout/AvailableSpace.h +++ b/Userland/Libraries/LibWeb/Layout/AvailableSpace.h @@ -12,7 +12,7 @@ namespace Web::Layout { -class AvailableSpace { +class AvailableSize { public: enum class Type { Definite, @@ -21,10 +21,10 @@ public: MaxContent, }; - static AvailableSpace make_definite(float); - static AvailableSpace make_indefinite(); - static AvailableSpace make_min_content(); - static AvailableSpace make_max_content(); + static AvailableSize make_definite(float); + static AvailableSize make_indefinite(); + static AvailableSize make_min_content(); + static AvailableSize make_max_content(); bool is_definite() const { return m_type == Type::Definite; } bool is_indefinite() const { return m_type == Type::Indefinite; } @@ -32,28 +32,51 @@ public: bool is_max_content() const { return m_type == Type::MaxContent; } bool is_intrinsic_sizing_constraint() const { return is_min_content() || is_max_content(); } - float definite_value() const + float to_px() const { - VERIFY(is_definite()); return m_value; } - float to_px() const + float to_px_or_zero() const { + if (!is_definite()) + return 0.0f; return m_value; } String to_string() const; private: - AvailableSpace(Type type, float); + AvailableSize(Type type, float); Type m_type {}; float m_value {}; }; +class AvailableSpace { +public: + AvailableSpace(AvailableSize w, AvailableSize h) + : width(move(w)) + , height(move(h)) + { + } + + AvailableSize width; + AvailableSize height; + + String to_string() const; +}; + } +template<> +struct AK::Formatter : Formatter { + ErrorOr format(FormatBuilder& builder, Web::Layout::AvailableSize const& available_size) + { + return Formatter::format(builder, available_size.to_string()); + } +}; + template<> struct AK::Formatter : Formatter { ErrorOr format(FormatBuilder& builder, Web::Layout::AvailableSpace const& available_space) diff --git a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp index 5c540403ab6..0416ffeac3f 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -41,20 +42,20 @@ bool BlockFormattingContext::is_initial() const float BlockFormattingContext::automatic_content_height() const { - return compute_auto_height_for_block_formatting_context_root(m_state, root()); + return compute_auto_height_for_block_formatting_context_root(root()); } -void BlockFormattingContext::run(Box const&, LayoutMode layout_mode, [[maybe_unused]] AvailableSpace const& available_width, [[maybe_unused]] AvailableSpace const& available_height) +void BlockFormattingContext::run(Box const&, LayoutMode layout_mode, AvailableSpace const& available_space) { if (is_initial()) { - layout_initial_containing_block(layout_mode); + layout_initial_containing_block(layout_mode, available_space); return; } if (root().children_are_inline()) - layout_inline_children(root(), layout_mode); + layout_inline_children(root(), layout_mode, available_space); else - layout_block_level_children(root(), layout_mode); + layout_block_level_children(root(), layout_mode, available_space); } void BlockFormattingContext::parent_context_did_dimension_child_root_box() @@ -64,25 +65,29 @@ void BlockFormattingContext::parent_context_did_dimension_child_root_box() // Left-side floats: offset_from_edge is from left edge (0) to left content edge of floating_box. for (auto& floating_box : m_left_floats.all_boxes) { auto& box_state = m_state.get_mutable(floating_box->box); - box_state.offset.set_x(floating_box->offset_from_edge); + box_state.set_content_x(floating_box->offset_from_edge); } // Right-side floats: offset_from_edge is from right edge (float_containing_block_width) to the left content edge of floating_box. for (auto& floating_box : m_right_floats.all_boxes) { auto float_containing_block_width = containing_block_width_for(floating_box->box); auto& box_state = m_state.get_mutable(floating_box->box); - box_state.offset.set_x(float_containing_block_width - floating_box->offset_from_edge); + box_state.set_content_x(float_containing_block_width - floating_box->offset_from_edge); } // We can also layout absolutely positioned boxes within this BFC. - for (auto& box : m_absolutely_positioned_boxes) - layout_absolutely_positioned_element(box); + for (auto& box : m_absolutely_positioned_boxes) { + auto& cb_state = m_state.get(*box.containing_block()); + auto available_width = AvailableSize::make_definite(cb_state.content_width() + cb_state.padding_left + cb_state.padding_right); + auto available_height = AvailableSize::make_definite(cb_state.content_height() + cb_state.padding_top + cb_state.padding_bottom); + layout_absolutely_positioned_element(box, AvailableSpace(available_width, available_height)); + } } -void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mode) +void BlockFormattingContext::compute_width(Box const& box, AvailableSpace const& available_space, LayoutMode) { if (box.is_absolutely_positioned()) { - compute_width_for_absolutely_positioned_element(box); + compute_width_for_absolutely_positioned_element(box, available_space); return; } @@ -91,30 +96,34 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod auto& replaced = verify_cast(box); // FIXME: This const_cast is gross. const_cast(replaced).prepare_for_replaced_layout(); - compute_width_for_block_level_replaced_element_in_normal_flow(replaced); + compute_width_for_block_level_replaced_element_in_normal_flow(replaced, available_space); // NOTE: We don't return here. } if (box.is_floating()) { - compute_width_for_floating_box(box, layout_mode); + compute_width_for_floating_box(box, available_space); return; } auto const& computed_values = box.computed_values(); - float width_of_containing_block = containing_block_width_for(box); - auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); + + if (should_treat_width_as_auto(box, available_space) && available_space.width.is_intrinsic_sizing_constraint()) + return; + + float width_of_containing_block = available_space.width.to_px(); + auto width_of_containing_block_as_length_for_resolve = available_space.width.is_definite() ? CSS::Length::make_px(width_of_containing_block) : CSS::Length::make_px(0); auto zero_value = CSS::Length::make_px(0); auto margin_left = CSS::Length::make_auto(); auto margin_right = CSS::Length::make_auto(); - auto padding_left = computed_values.padding().left().resolved(box, width_of_containing_block_as_length).resolved(box); - auto padding_right = computed_values.padding().right().resolved(box, width_of_containing_block_as_length).resolved(box); + auto padding_left = computed_values.padding().left().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); + auto padding_right = computed_values.padding().right().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); auto try_compute_width = [&](auto const& a_width) { CSS::Length width = a_width; - margin_left = computed_values.margin().left().resolved(box, width_of_containing_block_as_length).resolved(box); - margin_right = computed_values.margin().right().resolved(box, width_of_containing_block_as_length).resolved(box); + margin_left = computed_values.margin().left().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); + margin_right = computed_values.margin().right().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); float total_px = computed_values.border_left().width + computed_values.border_right().width; for (auto& value : { margin_left, padding_left, width, padding_right, margin_right }) { total_px += value.to_px(box); @@ -132,6 +141,8 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod // 10.3.3 cont'd. auto underflow_px = width_of_containing_block - total_px; + if (!isfinite(underflow_px)) + underflow_px = 0; if (width.is_auto()) { if (margin_left.is_auto()) @@ -139,19 +150,7 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod if (margin_right.is_auto()) margin_right = zero_value; - if (width_of_containing_block == INFINITY) { - // If width of containing block is infinity - // then we might as well behave like we don't have a containing block - // and remove it from the calculation. In that case, our width - // will end up being the sum of margin_*, padding_*, border_* - - float sum_of_all = computed_values.border_left().width + computed_values.border_right().width; - for (const auto& value : { margin_left, padding_left, width, padding_right, margin_right }) { - sum_of_all += value.to_px(box); - } - - width = CSS::Length(sum_of_all, CSS::Length::Type::Px); - } else { + if (available_space.width.is_definite()) { if (underflow_px >= 0) { width = CSS::Length(underflow_px, CSS::Length::Type::Px); } else { @@ -178,9 +177,9 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod }; auto input_width = [&] { - if (should_treat_width_as_auto(box)) + if (should_treat_width_as_auto(box, available_space)) return CSS::Length::make_auto(); - return computed_values.width().resolved(box, width_of_containing_block_as_length).resolved(box); + return computed_values.width().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); }(); // 1. The tentative used width is calculated (without 'min-width' and 'max-width') @@ -189,7 +188,7 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod // 2. The tentative used width is greater than 'max-width', the rules above are applied again, // but this time using the computed value of 'max-width' as the computed value for 'width'. if (!computed_values.max_width().is_none()) { - auto max_width = computed_values.max_width().resolved(box, width_of_containing_block_as_length).resolved(box); + auto max_width = computed_values.max_width().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); if (used_width.to_px(box) > max_width.to_px(box)) { used_width = try_compute_width(max_width); } @@ -198,7 +197,7 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod // 3. If the resulting width is smaller than 'min-width', the rules above are applied again, // but this time using the value of 'min-width' as the computed value for 'width'. if (!computed_values.min_width().is_auto()) { - auto min_width = computed_values.min_width().resolved(box, width_of_containing_block_as_length).resolved(box); + auto min_width = computed_values.min_width().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); if (used_width.to_px(box) < min_width.to_px(box)) { used_width = try_compute_width(min_width); } @@ -206,7 +205,7 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod auto& box_state = m_state.get_mutable(box); - if (!is(box)) + if (!is(box) && !used_width.is_auto()) box_state.set_content_width(used_width.to_px(box)); box_state.margin_left = margin_left.to_px(box); @@ -217,19 +216,21 @@ void BlockFormattingContext::compute_width(Box const& box, LayoutMode layout_mod box_state.padding_right = padding_right.to_px(box); } -void BlockFormattingContext::compute_width_for_floating_box(Box const& box, LayoutMode) +void BlockFormattingContext::compute_width_for_floating_box(Box const& box, AvailableSpace const& available_space) { // 10.3.5 Floating, non-replaced elements auto& computed_values = box.computed_values(); - float width_of_containing_block = containing_block_width_for(box); - auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); auto zero_value = CSS::Length::make_px(0); + float width_of_containing_block = available_space.width.to_px(); + auto width_of_containing_block_as_length_for_resolve = CSS::Length::make_px(width_of_containing_block); + if (!available_space.width.is_definite()) + width_of_containing_block_as_length_for_resolve = CSS::Length::make_px(0); - auto margin_left = computed_values.margin().left().resolved(box, width_of_containing_block_as_length).resolved(box); - auto margin_right = computed_values.margin().right().resolved(box, width_of_containing_block_as_length).resolved(box); - auto const padding_left = computed_values.padding().left().resolved(box, width_of_containing_block_as_length).resolved(box); - auto const padding_right = computed_values.padding().right().resolved(box, width_of_containing_block_as_length).resolved(box); + auto margin_left = computed_values.margin().left().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); + auto margin_right = computed_values.margin().right().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); + auto const padding_left = computed_values.padding().left().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); + auto const padding_right = computed_values.padding().right().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); // If 'margin-left', or 'margin-right' are computed as 'auto', their used value is '0'. if (margin_left.is_auto()) @@ -258,9 +259,9 @@ void BlockFormattingContext::compute_width_for_floating_box(Box const& box, Layo }; auto input_width = [&] { - if (should_treat_width_as_auto(box)) + if (should_treat_width_as_auto(box, available_space)) return CSS::Length::make_auto(); - return computed_values.width().resolved(box, width_of_containing_block_as_length).resolved(box); + return computed_values.width().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); }(); // 1. The tentative used width is calculated (without 'min-width' and 'max-width') @@ -269,7 +270,7 @@ void BlockFormattingContext::compute_width_for_floating_box(Box const& box, Layo // 2. The tentative used width is greater than 'max-width', the rules above are applied again, // but this time using the computed value of 'max-width' as the computed value for 'width'. if (!computed_values.max_width().is_none()) { - auto max_width = computed_values.max_width().resolved(box, width_of_containing_block_as_length).resolved(box); + auto max_width = computed_values.max_width().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); if (width.to_px(box) > max_width.to_px(box)) width = compute_width(max_width); } @@ -277,7 +278,7 @@ void BlockFormattingContext::compute_width_for_floating_box(Box const& box, Layo // 3. If the resulting width is smaller than 'min-width', the rules above are applied again, // but this time using the value of 'min-width' as the computed value for 'width'. if (!computed_values.min_width().is_auto()) { - auto min_width = computed_values.min_width().resolved(box, width_of_containing_block_as_length).resolved(box); + auto min_width = computed_values.min_width().resolved(box, width_of_containing_block_as_length_for_resolve).resolved(box); if (width.to_px(box) < min_width.to_px(box)) width = compute_width(min_width); } @@ -292,25 +293,25 @@ void BlockFormattingContext::compute_width_for_floating_box(Box const& box, Layo box_state.padding_right = padding_right.to_px(box); } -void BlockFormattingContext::compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox const& box) +void BlockFormattingContext::compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox const& box, AvailableSpace const& available_space) { - m_state.get_mutable(box).set_content_width(compute_width_for_replaced_element(m_state, box)); + m_state.get_mutable(box).set_content_width(compute_width_for_replaced_element(m_state, box, available_space)); } -void BlockFormattingContext::compute_height(Box const& box, LayoutState& state) +void BlockFormattingContext::compute_height(Box const& box, AvailableSpace const& available_space) { - resolve_vertical_box_model_metrics(box, *box.containing_block(), state); + resolve_vertical_box_model_metrics(box, available_space, m_state); auto const& computed_values = box.computed_values(); - auto containing_block_height = CSS::Length::make_px(containing_block_height_for(box, state)); + auto containing_block_height = CSS::Length::make_px(available_space.height.to_px()); // Then work out what the height is, based on box type and CSS properties. float height = 0; if (is(box)) { - height = compute_height_for_replaced_element(state, verify_cast(box)); + height = compute_height_for_replaced_element(m_state, verify_cast(box), available_space); } else { - if (should_treat_height_as_auto(box, state)) { - height = compute_auto_height_for_block_level_element(state, box); + if (should_treat_height_as_auto(box, available_space)) { + height = compute_auto_height_for_block_level_element(box); } else { height = computed_values.height().resolved(box, containing_block_height).to_px(box); } @@ -321,51 +322,33 @@ void BlockFormattingContext::compute_height(Box const& box, LayoutState& state) if (!max_height.is_auto()) height = min(height, max_height.to_px(box)); } - auto specified_min_height = computed_values.min_height().resolved(box, containing_block_height).resolved(box); - if (!specified_min_height.is_auto()) - height = max(height, specified_min_height.to_px(box)); + if (!computed_values.min_height().is_auto()) { + auto min_height = computed_values.min_height().resolved(box, containing_block_height).resolved(box); + height = max(height, min_height.to_px(box)); + } - state.get_mutable(box).set_content_height(height); + m_state.get_mutable(box).set_content_height(height); } -void BlockFormattingContext::layout_inline_children(BlockContainer const& block_container, LayoutMode layout_mode) +void BlockFormattingContext::layout_inline_children(BlockContainer const& block_container, LayoutMode layout_mode, AvailableSpace const& available_space) { VERIFY(block_container.children_are_inline()); auto& block_container_state = m_state.get_mutable(block_container); - if (layout_mode == LayoutMode::IntrinsicSizing) { - if (should_treat_width_as_auto(block_container) || block_container_state.width_constraint != SizeConstraint::None) - block_container_state.set_content_width(containing_block_width_for(block_container)); - if (should_treat_height_as_auto(block_container) || block_container_state.height_constraint != SizeConstraint::None) - block_container_state.set_content_height(containing_block_height_for(block_container)); - } - InlineFormattingContext context(m_state, block_container, *this); context.run( block_container, layout_mode, - AvailableSpace::make_definite(containing_block_width_for(block_container)), - AvailableSpace::make_definite(containing_block_height_for(block_container))); + available_space); - float max_line_width = 0; - float content_height = 0; - - for (auto& line_box : block_container_state.line_boxes) { - max_line_width = max(max_line_width, line_box.width()); - content_height += line_box.height(); - } - - if (layout_mode == LayoutMode::IntrinsicSizing) { - if (should_treat_width_as_auto(block_container) || block_container_state.width_constraint != SizeConstraint::None) - block_container_state.set_content_width(max_line_width); - } - - // FIXME: This is weird. Figure out a way to make callers responsible for setting the content height. - block_container_state.set_content_height(content_height); + if (!block_container_state.has_definite_width()) + block_container_state.set_content_width(context.automatic_content_width()); + if (!block_container_state.has_definite_height()) + block_container_state.set_content_height(context.automatic_content_height()); } -void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContainer const& block_container, LayoutMode layout_mode, float& bottom_of_lowest_margin_box) +void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContainer const& block_container, LayoutMode layout_mode, float& bottom_of_lowest_margin_box, AvailableSpace const& available_space) { auto& box_state = m_state.get_mutable(box); @@ -379,38 +362,39 @@ void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContain return; if (box.is_floating()) { - layout_floating_box(box, block_container, layout_mode); + layout_floating_box(box, block_container, layout_mode, available_space); bottom_of_lowest_margin_box = max(bottom_of_lowest_margin_box, box_state.offset.y() + box_state.content_height() + box_state.margin_box_bottom()); return; } - compute_width(box, layout_mode); + compute_width(box, available_space, layout_mode); + if (is(box) || is(box)) - place_block_level_element_in_normal_flow_vertically(box, block_container); + place_block_level_element_in_normal_flow_vertically(box, available_space); if (box_state.has_definite_height()) { - compute_height(box, m_state); + compute_height(box, available_space); } OwnPtr independent_formatting_context; if (box.can_have_children()) { if (box.children_are_inline()) { - layout_inline_children(verify_cast(box), layout_mode); + layout_inline_children(verify_cast(box), layout_mode, box_state.available_inner_space_or_constraints_from(available_space)); } else { independent_formatting_context = create_independent_formatting_context_if_needed(m_state, box); if (independent_formatting_context) - independent_formatting_context->run(box, layout_mode, AvailableSpace::make_indefinite(), AvailableSpace::make_indefinite()); + independent_formatting_context->run(box, layout_mode, box_state.available_inner_space_or_constraints_from(available_space)); else - layout_block_level_children(verify_cast(box), layout_mode); + layout_block_level_children(verify_cast(box), layout_mode, box_state.available_inner_space_or_constraints_from(available_space)); } } - compute_height(box, m_state); + compute_height(box, available_space); compute_inset(box); if (is(box) || is(box)) - place_block_level_element_in_normal_flow_horizontally(box, block_container); + place_block_level_element_in_normal_flow_horizontally(box, available_space); if (is(box)) { layout_list_item_marker(static_cast(box)); @@ -422,54 +406,46 @@ void BlockFormattingContext::layout_block_level_box(Box const& box, BlockContain independent_formatting_context->parent_context_did_dimension_child_root_box(); } -void BlockFormattingContext::layout_block_level_children(BlockContainer const& block_container, LayoutMode layout_mode) +void BlockFormattingContext::layout_block_level_children(BlockContainer const& block_container, LayoutMode layout_mode, AvailableSpace const& available_space) { VERIFY(!block_container.children_are_inline()); float bottom_of_lowest_margin_box = 0; - if (layout_mode == LayoutMode::IntrinsicSizing) { - auto& block_container_state = m_state.get_mutable(block_container); - if (should_treat_width_as_auto(block_container) || block_container_state.width_constraint != SizeConstraint::None) - block_container_state.set_content_width(containing_block_width_for(block_container)); - if (should_treat_height_as_auto(block_container) || block_container_state.height_constraint != SizeConstraint::None) - block_container_state.set_content_height(containing_block_height_for(block_container)); - } - block_container.for_each_child_of_type([&](Box& box) { - layout_block_level_box(box, block_container, layout_mode, bottom_of_lowest_margin_box); + layout_block_level_box(box, block_container, layout_mode, bottom_of_lowest_margin_box, available_space); return IterationDecision::Continue; }); if (layout_mode == LayoutMode::IntrinsicSizing) { auto& block_container_state = m_state.get_mutable(block_container); - if (should_treat_width_as_auto(block_container) || block_container_state.width_constraint != SizeConstraint::None) + if (!block_container_state.has_definite_width()) block_container_state.set_content_width(greatest_child_width(block_container)); - if (should_treat_height_as_auto(block_container) || block_container_state.height_constraint != SizeConstraint::None) + if (!block_container_state.has_definite_height()) block_container_state.set_content_height(bottom_of_lowest_margin_box); } } -void BlockFormattingContext::resolve_vertical_box_model_metrics(Box const& box, BlockContainer const& containing_block, LayoutState& state) +void BlockFormattingContext::resolve_vertical_box_model_metrics(Box const& box, AvailableSpace const& available_space, LayoutState& state) { auto& box_state = state.get_mutable(box); auto const& computed_values = box.computed_values(); - auto width_of_containing_block = CSS::Length::make_px(containing_block_width_for(box, state)); + auto width_of_containing_block = CSS::Length::make_px(available_space.width.to_px_or_zero()); - box_state.margin_top = computed_values.margin().top().resolved(box, width_of_containing_block).resolved(containing_block).to_px(box); - box_state.margin_bottom = computed_values.margin().bottom().resolved(box, width_of_containing_block).resolved(containing_block).to_px(box); + box_state.margin_top = computed_values.margin().top().resolved(box, width_of_containing_block).to_px(box); + box_state.margin_bottom = computed_values.margin().bottom().resolved(box, width_of_containing_block).to_px(box); box_state.border_top = computed_values.border_top().width; box_state.border_bottom = computed_values.border_bottom().width; - box_state.padding_top = computed_values.padding().top().resolved(box, width_of_containing_block).resolved(containing_block).to_px(box); - box_state.padding_bottom = computed_values.padding().bottom().resolved(box, width_of_containing_block).resolved(containing_block).to_px(box); + box_state.padding_top = computed_values.padding().top().resolved(box, width_of_containing_block).to_px(box); + box_state.padding_bottom = computed_values.padding().bottom().resolved(box, width_of_containing_block).to_px(box); } -void BlockFormattingContext::place_block_level_element_in_normal_flow_vertically(Box const& child_box, BlockContainer const& containing_block) +void BlockFormattingContext::place_block_level_element_in_normal_flow_vertically(Box const& child_box, AvailableSpace const& available_space) { auto& box_state = m_state.get_mutable(child_box); auto const& computed_values = child_box.computed_values(); - resolve_vertical_box_model_metrics(child_box, containing_block, m_state); + resolve_vertical_box_model_metrics(child_box, available_space, m_state); auto y = FormattingContext::compute_box_y_position_with_respect_to_siblings(child_box, box_state); @@ -504,15 +480,15 @@ void BlockFormattingContext::place_block_level_element_in_normal_flow_vertically if ((computed_values.clear() == CSS::Clear::Right || computed_values.clear() == CSS::Clear::Both) && !child_box.is_flex_item()) clear_floating_boxes(m_right_floats); - box_state.offset = Gfx::FloatPoint { box_state.offset.x(), y }; + box_state.set_content_offset(Gfx::FloatPoint { box_state.offset.x(), y }); } -void BlockFormattingContext::place_block_level_element_in_normal_flow_horizontally(Box const& child_box, BlockContainer const& containing_block) +void BlockFormattingContext::place_block_level_element_in_normal_flow_horizontally(Box const& child_box, AvailableSpace const& available_space) { auto& box_state = m_state.get_mutable(child_box); float x = 0; - float available_width_within_containing_block = containing_block_width_for(child_box); + float available_width_within_containing_block = available_space.width.to_px(); if ((!m_left_floats.current_boxes.is_empty() || !m_right_floats.current_boxes.is_empty()) && creates_block_formatting_context(child_box)) { @@ -520,13 +496,13 @@ void BlockFormattingContext::place_block_level_element_in_normal_flow_horizontal x += m_left_floats.current_width; } - if (containing_block.computed_values().text_align() == CSS::TextAlign::LibwebCenter) { + if (child_box.containing_block()->computed_values().text_align() == CSS::TextAlign::LibwebCenter) { x += (available_width_within_containing_block / 2) - box_state.content_width() / 2; } else { x += box_state.margin_box_left(); } - box_state.offset = Gfx::FloatPoint { x, box_state.offset.y() }; + box_state.set_content_offset(Gfx::FloatPoint { x, box_state.offset.y() }); } static void measure_scrollable_overflow(LayoutState const& state, Box const& box, float& bottom_edge, float& right_edge) @@ -547,7 +523,7 @@ static void measure_scrollable_overflow(LayoutState const& state, Box const& box }); } -void BlockFormattingContext::layout_initial_containing_block(LayoutMode layout_mode) +void BlockFormattingContext::layout_initial_containing_block(LayoutMode layout_mode, AvailableSpace const& available_space) { auto viewport_rect = root().browsing_context().viewport_rect(); @@ -555,9 +531,9 @@ void BlockFormattingContext::layout_initial_containing_block(LayoutMode layout_m auto& icb_state = m_state.get_mutable(icb); if (root().children_are_inline()) - layout_inline_children(root(), layout_mode); + layout_inline_children(root(), layout_mode, available_space); else - layout_block_level_children(root(), layout_mode); + layout_block_level_children(root(), layout_mode, available_space); float bottom_edge = 0; float right_edge = 0; @@ -572,25 +548,26 @@ void BlockFormattingContext::layout_initial_containing_block(LayoutMode layout_m } } -void BlockFormattingContext::layout_floating_box(Box const& box, BlockContainer const& containing_block, LayoutMode layout_mode, LineBuilder* line_builder) +void BlockFormattingContext::layout_floating_box(Box const& box, BlockContainer const&, LayoutMode layout_mode, AvailableSpace const& available_space, LineBuilder* line_builder) { VERIFY(box.is_floating()); auto& box_state = m_state.get_mutable(box); - float width_of_containing_block = containing_block_width_for(box); + float width_of_containing_block = available_space.width.to_px(); - compute_width(box, layout_mode); - (void)layout_inside(box, layout_mode); - compute_height(box, m_state); + compute_width(box, available_space, layout_mode); + if (auto independent_formatting_context = layout_inside(box, layout_mode, box_state.available_inner_space_or_constraints_from(available_space))) + independent_formatting_context->parent_context_did_dimension_child_root_box(); + compute_height(box, available_space); // First we place the box normally (to get the right y coordinate.) // If we have a LineBuilder, we're in the middle of inline layout, otherwise this is block layout. if (line_builder) { auto y = line_builder->y_for_float_to_be_inserted_here(box); - box_state.offset.set_y(y + box_state.margin_box_top()); + box_state.set_content_y(y + box_state.margin_box_top()); } else { - place_block_level_element_in_normal_flow_vertically(box, containing_block); - place_block_level_element_in_normal_flow_horizontally(box, containing_block); + place_block_level_element_in_normal_flow_vertically(box, available_space); + place_block_level_element_in_normal_flow_horizontally(box, available_space); } // Then we float it to the left or right. @@ -678,7 +655,7 @@ void BlockFormattingContext::layout_floating_box(Box const& box, BlockContainer // NOTE: We don't set the X position here, that happens later, once we know the root block width. // See parent_context_did_dimension_child_root_box() for that logic. - box_state.offset.set_y(y); + box_state.set_content_y(y); // If the new box was inserted below the bottom of the opposite side, // we reset the other side back to its edge. @@ -726,10 +703,8 @@ void BlockFormattingContext::layout_list_item_marker(ListItemBox const& list_ite marker_state.set_content_height(max(image_height, marker.font().glyph_height() + 1)); - marker_state.offset = { - -(marker_state.content_width() + default_marker_width), - max(0.f, (marker.line_height() - marker_state.content_height()) / 2.f) - }; + marker_state.set_content_offset({ -(marker_state.content_width() + default_marker_width), + max(0.f, (marker.line_height() - marker_state.content_height()) / 2.f) }); if (marker_state.content_height() > list_item_state.content_height()) list_item_state.set_content_height(marker_state.content_height()); @@ -801,16 +776,26 @@ float BlockFormattingContext::greatest_child_width(Box const& box) return max_width; } -bool BlockFormattingContext::should_treat_width_as_auto(Box const& box, LayoutState const& state) +bool BlockFormattingContext::should_treat_width_as_auto(Box const& box, AvailableSpace const& available_space) { return box.computed_values().width().is_auto() - || (box.computed_values().width().contains_percentage() && !state.get(*box.containing_block()).has_definite_width()); + || (box.computed_values().width().contains_percentage() && !available_space.width.is_definite()); } -bool BlockFormattingContext::should_treat_height_as_auto(Box const& box, LayoutState const& state) +bool BlockFormattingContext::should_treat_height_as_auto(Box const& box, AvailableSpace const& available_space) { return box.computed_values().height().is_auto() - || (box.computed_values().height().contains_percentage() && !state.get(*box.containing_block()).has_definite_height()); + || (box.computed_values().height().contains_percentage() && !available_space.height.is_definite()); +} + +void BlockFormattingContext::determine_width_of_child(Box const& box, AvailableSpace const& available_space) +{ + compute_width(box, available_space); +} + +void BlockFormattingContext::determine_height_of_child(Box const& box, AvailableSpace const& available_space) +{ + compute_height(box, available_space); } } diff --git a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h index 03b1e5ef638..55f35c7156d 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h @@ -21,7 +21,7 @@ public: explicit BlockFormattingContext(LayoutState&, BlockContainer const&, FormattingContext* parent); ~BlockFormattingContext(); - virtual void run(Box const&, LayoutMode, AvailableSpace const& available_width, AvailableSpace const& available_height) override; + virtual void run(Box const&, LayoutMode, AvailableSpace const&) override; virtual float automatic_content_height() const override; bool is_initial() const; @@ -29,14 +29,14 @@ public: auto const& left_side_floats() const { return m_left_floats; } auto const& right_side_floats() const { return m_right_floats; } - void compute_width(Box const&, LayoutMode = LayoutMode::Normal); + void compute_width(Box const&, AvailableSpace const&, LayoutMode = LayoutMode::Normal); // https://www.w3.org/TR/css-display/#block-formatting-context-root BlockContainer const& root() const { return static_cast(context_box()); } virtual void parent_context_did_dimension_child_root_box() override; - static void compute_height(Box const&, LayoutState&); + void compute_height(Box const&, AvailableSpace const&); void add_absolutely_positioned_box(Box const& box) { m_absolutely_positioned_boxes.append(box); } @@ -44,38 +44,32 @@ public: virtual float greatest_child_width(Box const&) override; - void layout_floating_box(Box const& child, BlockContainer const& containing_block, LayoutMode, LineBuilder* = nullptr); + void layout_floating_box(Box const& child, BlockContainer const& containing_block, LayoutMode, AvailableSpace const&, LineBuilder* = nullptr); - void layout_block_level_box(Box const&, BlockContainer const&, LayoutMode, float& bottom_of_lowest_margin_box); + void layout_block_level_box(Box const&, BlockContainer const&, LayoutMode, float& bottom_of_lowest_margin_box, AvailableSpace const&); - static bool should_treat_width_as_auto(Box const&, LayoutState const&); - static bool should_treat_height_as_auto(Box const&, LayoutState const&); + static bool should_treat_width_as_auto(Box const&, AvailableSpace const&); + static bool should_treat_height_as_auto(Box const&, AvailableSpace const&); - bool should_treat_width_as_auto(Box const& box) const - { - return should_treat_width_as_auto(box, m_state); - } - - bool should_treat_height_as_auto(Box const& box) const - { - return should_treat_height_as_auto(box, m_state); - } + virtual bool can_determine_size_of_child() const override { return true; } + virtual void determine_width_of_child(Box const&, AvailableSpace const&) override; + virtual void determine_height_of_child(Box const&, AvailableSpace const&) override; private: virtual bool is_block_formatting_context() const final { return true; } - void compute_width_for_floating_box(Box const&, LayoutMode); + void compute_width_for_floating_box(Box const&, AvailableSpace const&); - void compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox const&); + void compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox const&, AvailableSpace const&); - void layout_initial_containing_block(LayoutMode); + void layout_initial_containing_block(LayoutMode, AvailableSpace const&); - void layout_block_level_children(BlockContainer const&, LayoutMode); - void layout_inline_children(BlockContainer const&, LayoutMode); + void layout_block_level_children(BlockContainer const&, LayoutMode, AvailableSpace const&); + void layout_inline_children(BlockContainer const&, LayoutMode, AvailableSpace const&); - static void resolve_vertical_box_model_metrics(Box const& box, BlockContainer const& containing_block, LayoutState&); - void place_block_level_element_in_normal_flow_horizontally(Box const& child_box, BlockContainer const&); - void place_block_level_element_in_normal_flow_vertically(Box const& child_box, BlockContainer const&); + static void resolve_vertical_box_model_metrics(Box const& box, AvailableSpace const&, LayoutState&); + void place_block_level_element_in_normal_flow_horizontally(Box const& child_box, AvailableSpace const&); + void place_block_level_element_in_normal_flow_vertically(Box const& child_box, AvailableSpace const&); void layout_list_item_marker(ListItemBox const&); diff --git a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp index e8e81f9e38c..eb13e6966f6 100644 --- a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp @@ -67,10 +67,29 @@ float FlexFormattingContext::automatic_content_height() const return m_state.get(flex_container()).content_height(); } -void FlexFormattingContext::run(Box const& run_box, LayoutMode layout_mode, [[maybe_unused]] AvailableSpace const& available_width, [[maybe_unused]] AvailableSpace const& available_height) +void FlexFormattingContext::run(Box const& run_box, LayoutMode, AvailableSpace const& available_content_space) { VERIFY(&run_box == &flex_container()); + // NOTE: The available space provided by the parent context is basically our *content box*. + // FFC is currently written in a way that expects that to include padding and border as well, + // so we pad out the available space here to accommodate that. + // FIXME: Refactor the necessary parts of FFC so we don't need this hack! + + auto available_width = available_content_space.width; + if (available_width.is_definite()) + available_width = AvailableSize::make_definite(available_width.to_px() + m_flex_container_state.border_box_left() + m_flex_container_state.border_box_right()); + auto available_height = available_content_space.height; + if (available_height.is_definite()) + available_height = AvailableSize::make_definite(available_height.to_px() + m_flex_container_state.border_box_top() + m_flex_container_state.border_box_bottom()); + + m_available_space_for_flex_container = AxisAgnosticAvailableSpace { + .main = is_row_layout() ? available_width : available_height, + .cross = !is_row_layout() ? available_width : available_height, + .width = available_width, + .height = available_height, + }; + // This implements https://www.w3.org/TR/css-flexbox-1/#layout-algorithm // 1. Generate anonymous flex items @@ -91,34 +110,14 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode layout_mode, [[ma auto item_preferred_outer_cross_size = css_clamp(flex_container_inner_cross_size, item_min_cross_size, item_max_cross_size); 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; set_cross_size(item.box, item_inner_cross_size); - set_has_definite_cross_size(item.box, true); - item.has_assigned_definite_cross_size = true; } } } // 2. Determine the available main and cross space for the flex items - float main_max_size = NumericLimits::max(); - float main_min_size = 0; - float cross_max_size = NumericLimits::max(); - float cross_min_size = 0; - bool main_is_constrained = false; - bool cross_is_constrained = false; - determine_available_main_and_cross_space(main_is_constrained, cross_is_constrained, main_min_size, main_max_size, cross_min_size, cross_max_size); - - if (m_flex_container_state.width_constraint == SizeConstraint::MaxContent || m_flex_container_state.height_constraint == SizeConstraint::MaxContent) { - if (is_row_layout()) - m_available_space->main = INFINITY; - else - m_available_space->cross = INFINITY; - } - - if (m_flex_container_state.width_constraint == SizeConstraint::MinContent || m_flex_container_state.height_constraint == SizeConstraint::MinContent) { - if (is_row_layout()) - m_available_space->main = 0; - else - m_available_space->cross = 0; - } + float cross_min_size = has_cross_min_size(flex_container()) ? specified_cross_min_size(flex_container()) : 0; + float cross_max_size = has_cross_max_size(flex_container()) ? specified_cross_max_size(flex_container()) : INFINITY; + determine_available_space_for_items(AvailableSpace(available_width, available_height)); // 3. Determine the flex base size and hypothetical main size of each item for (auto& flex_item : m_flex_items) { @@ -129,9 +128,9 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode layout_mode, [[ma determine_flex_base_size_and_hypothetical_main_size(flex_item); } - if (m_flex_container_state.width_constraint != SizeConstraint::None || m_flex_container_state.height_constraint != SizeConstraint::None) { + if (available_width.is_intrinsic_sizing_constraint() || available_height.is_intrinsic_sizing_constraint()) { // We're computing intrinsic size for the flex container. - determine_intrinsic_size_of_flex_container(layout_mode); + determine_intrinsic_size_of_flex_container(); // Our caller is only interested in the content-width and content-height results, // which have now been set on m_flex_container_state, so there's no need to continue @@ -140,7 +139,7 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode layout_mode, [[ma } // 4. Determine the main size of the flex container - determine_main_size_of_flex_container(main_is_constrained, main_min_size, main_max_size); + determine_main_size_of_flex_container(); // 5. Collect flex items into flex lines: // After this step no additional items are to be added to flex_lines or any of its items! @@ -187,8 +186,6 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode layout_mode, [[ma if (flex_container_computed_cross_size.is_auto()) { for (auto& item : m_flex_items) { set_cross_size(item.box, item.cross_size); - set_has_definite_cross_size(item.box, true); - item.has_assigned_definite_cross_size = true; } } } @@ -211,7 +208,8 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode layout_mode, [[ma // AD-HOC: Layout the inside of all flex items. copy_dimensions_from_flex_items_to_boxes(); for (auto& flex_item : m_flex_items) { - if (auto independent_formatting_context = layout_inside(flex_item.box, LayoutMode::Normal)) + auto& box_state = m_state.get(flex_item.box); + if (auto independent_formatting_context = layout_inside(flex_item.box, LayoutMode::Normal, box_state.available_inner_space_or_constraints_from(AvailableSpace(m_available_space_for_flex_container->width, m_available_space_for_flex_container->height)))) independent_formatting_context->parent_context_did_dimension_child_root_box(); } @@ -225,8 +223,12 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode layout_mode, [[ma void FlexFormattingContext::parent_context_did_dimension_child_root_box() { flex_container().for_each_child_of_type([&](Layout::Box& box) { - if (box.is_absolutely_positioned()) - layout_absolutely_positioned_element(box); + if (box.is_absolutely_positioned()) { + auto& cb_state = m_state.get(*box.containing_block()); + auto available_width = AvailableSize::make_definite(cb_state.content_width() + cb_state.padding_left + cb_state.padding_right); + auto available_height = AvailableSize::make_definite(cb_state.content_height() + cb_state.padding_top + cb_state.padding_bottom); + layout_absolutely_positioned_element(box, AvailableSpace(available_width, available_height)); + } }); } @@ -363,15 +365,11 @@ float FlexFormattingContext::specified_cross_size(Box const& box) const float FlexFormattingContext::resolved_definite_cross_size(FlexItem const& item) const { - if (item.has_assigned_definite_cross_size) - return specified_cross_size(item.box); return !is_row_layout() ? m_state.resolved_definite_width(item.box) : m_state.resolved_definite_height(item.box); } float FlexFormattingContext::resolved_definite_main_size(FlexItem const& item) const { - if (item.has_assigned_definite_main_size) - return specified_main_size(item.box); return is_row_layout() ? m_state.resolved_definite_width(item.box) : m_state.resolved_definite_height(item.box); } @@ -393,13 +391,6 @@ bool FlexFormattingContext::has_definite_cross_size(Box const& box) const return is_row_layout() ? used_values.has_definite_height() : used_values.has_definite_width(); } -float FlexFormattingContext::specified_main_size_of_child_box(Box const& child_box) const -{ - auto main_size_of_parent = specified_main_size(flex_container()); - auto& value = is_row_layout() ? child_box.computed_values().width() : child_box.computed_values().height(); - return value.resolved(child_box, CSS::Length::make_px(main_size_of_parent)).to_px(child_box); -} - float FlexFormattingContext::specified_main_min_size(Box const& box) const { return is_row_layout() @@ -440,12 +431,6 @@ float FlexFormattingContext::specified_cross_max_size(Box const& box) const : get_pixel_width(box, box.computed_values().max_width()); } -float FlexFormattingContext::calculated_main_size(Box const& box) const -{ - auto const& box_state = m_state.get(box); - return is_row_layout() ? box_state.content_width() : box_state.content_height(); -} - bool FlexFormattingContext::is_cross_auto(Box const& box) const { auto& cross_length = is_row_layout() ? box.computed_values().height() : box.computed_values().width(); @@ -468,24 +453,6 @@ void FlexFormattingContext::set_cross_size(Box const& box, float size) m_state.get_mutable(box).set_content_width(size); } -void FlexFormattingContext::set_has_definite_main_size(Box const& box, bool definite) -{ - auto& used_values = m_state.get_mutable(box); - if (is_row_layout()) - used_values.set_has_definite_width(definite); - else - used_values.set_has_definite_height(definite); -} - -void FlexFormattingContext::set_has_definite_cross_size(Box const& box, bool definite) -{ - auto& used_values = m_state.get_mutable(box); - if (!is_row_layout()) - used_values.set_has_definite_width(definite); - else - used_values.set_has_definite_height(definite); -} - void FlexFormattingContext::set_offset(Box const& box, float main_offset, float cross_offset) { if (is_row_layout()) @@ -512,95 +479,75 @@ void FlexFormattingContext::set_main_axis_second_margin(FlexItem& item, float ma m_state.get_mutable(item.box).margin_bottom = margin; } -float FlexFormattingContext::sum_of_margin_padding_border_in_main_axis(Box const& box) const +// https://drafts.csswg.org/css-flexbox-1/#algo-available +void FlexFormattingContext::determine_available_space_for_items(AvailableSpace const& available_space) { - auto const& box_state = m_state.get(box); + // For each dimension, if that dimension of the flex container’s content box is a definite size, use that; + // 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; + // 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. + // This might result in an infinite value. + + Optional available_width_for_items; + if (m_flex_container_state.has_definite_width()) { + available_width_for_items = AvailableSize::make_definite(m_state.resolved_definite_width(flex_container())); + } else { + if (available_space.width.is_intrinsic_sizing_constraint()) { + available_width_for_items = available_space.width; + } else { + if (available_space.width.is_definite()) { + auto remaining = available_space.width.to_px() + - m_flex_container_state.margin_left + - m_flex_container_state.margin_right + - m_flex_container_state.border_left + - m_flex_container_state.padding_right + - m_flex_container_state.padding_left + - m_flex_container_state.padding_right; + available_width_for_items = AvailableSize::make_definite(remaining); + } else { + available_width_for_items = AvailableSize::make_indefinite(); + } + } + } + + Optional available_height_for_items; + if (m_flex_container_state.has_definite_height()) { + available_height_for_items = AvailableSize::make_definite(m_state.resolved_definite_height(flex_container())); + } else { + if (available_space.height.is_intrinsic_sizing_constraint()) { + available_height_for_items = available_space.height; + } else { + if (available_space.height.is_definite()) { + auto remaining = available_space.height.to_px() + - m_flex_container_state.margin_top + - m_flex_container_state.margin_bottom + - m_flex_container_state.border_top + - m_flex_container_state.padding_bottom + - m_flex_container_state.padding_top + - m_flex_container_state.padding_bottom; + available_height_for_items = AvailableSize::make_definite(remaining); + } else { + available_height_for_items = AvailableSize::make_indefinite(); + } + } + } if (is_row_layout()) { - return box_state.margin_left + box_state.margin_right - + box_state.padding_left + box_state.padding_right - + box_state.border_left + box_state.border_right; + m_available_space_for_items = AxisAgnosticAvailableSpace { + .main = *available_width_for_items, + .cross = *available_height_for_items, + .width = *available_width_for_items, + .height = *available_height_for_items, + }; } else { - return box_state.margin_top + box_state.margin_bottom - + box_state.padding_top + box_state.padding_bottom - + box_state.border_top + box_state.border_bottom; + m_available_space_for_items = AxisAgnosticAvailableSpace { + .main = *available_height_for_items, + .cross = *available_width_for_items, + .width = *available_width_for_items, + .height = *available_height_for_items, + }; } } -// https://www.w3.org/TR/css-flexbox-1/#algo-available -void FlexFormattingContext::determine_available_main_and_cross_space(bool& main_is_constrained, bool& cross_is_constrained, float& main_min_size, float& main_max_size, float& cross_min_size, float& cross_max_size) -{ - auto containing_block_effective_main_size = [&](Box const& box) -> Optional { - auto& containing_block = *box.containing_block(); - if (has_definite_main_size(containing_block)) - return is_row_layout() ? m_state.resolved_definite_width(box) : m_state.resolved_definite_height(box); - return {}; - }; - - Optional main_available_space; - main_is_constrained = false; - - // For each dimension, - // if that dimension of the flex container’s content box is a definite size, use that; - // 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; - // 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. (This might result in an infinite value.) - - if (has_definite_main_size(flex_container())) { - main_is_constrained = true; - main_available_space = specified_main_size(flex_container()); - } else { - if (has_main_max_size(flex_container())) { - bool main_max_size_behaves_like_auto = false; - if (computed_main_max_size(flex_container()).contains_percentage()) - main_max_size_behaves_like_auto = !has_definite_main_size(*flex_container().containing_block()); - - if (!main_max_size_behaves_like_auto) { - main_max_size = specified_main_max_size(flex_container()); - main_available_space = main_max_size; - main_is_constrained = true; - } - } - if (has_main_min_size(flex_container())) { - main_min_size = specified_main_min_size(flex_container()); - main_is_constrained = true; - } - - if (!main_is_constrained) { - auto available_main_size = containing_block_effective_main_size(flex_container()); - main_available_space = available_main_size.value_or(NumericLimits::max()) - sum_of_margin_padding_border_in_main_axis(flex_container()); - } - } - - Optional cross_available_space; - cross_is_constrained = false; - - if (has_definite_cross_size(flex_container())) { - cross_available_space = specified_cross_size(flex_container()); - } else { - if (has_cross_max_size(flex_container())) { - - bool cross_max_size_behaves_like_auto = false; - if (computed_cross_max_size(flex_container()).contains_percentage()) - cross_max_size_behaves_like_auto = !has_definite_cross_size(*flex_container().containing_block()); - - if (!cross_max_size_behaves_like_auto) { - cross_max_size = specified_cross_max_size(flex_container()); - cross_is_constrained = true; - } - } - if (has_cross_min_size(flex_container())) { - cross_min_size = specified_cross_min_size(flex_container()); - cross_is_constrained = true; - } - - // FIXME: Is this right? Probably not. - if (!cross_is_constrained) - cross_available_space = cross_max_size; - } - - m_available_space = AvailableSpaceForItems { .main = main_available_space, .cross = cross_available_space }; -} - float FlexFormattingContext::calculate_indefinite_main_size(FlexItem const& item) { VERIFY(!has_definite_main_size(item.box)); @@ -635,7 +582,7 @@ float FlexFormattingContext::calculate_indefinite_main_size(FlexItem const& item VERIFY(independent_formatting_context); box_state.set_content_width(fit_content_cross_size); - independent_formatting_context->run(item.box, LayoutMode::Normal, AvailableSpace::make_indefinite(), AvailableSpace::make_indefinite()); + independent_formatting_context->run(item.box, LayoutMode::Normal, AvailableSpace(m_available_space_for_items->width, m_available_space_for_items->height)); return independent_formatting_context->automatic_content_height(); } @@ -719,9 +666,8 @@ void FlexFormattingContext::determine_flex_base_size_and_hypothetical_main_size( // and the flex container is being sized under a min-content or max-content constraint // (e.g. when performing automatic table layout [CSS21]), size the item under that constraint. // The flex base size is the item’s resulting main size. - auto flex_container_main_size_constraint = is_row_layout() ? m_flex_container_state.width_constraint : m_flex_container_state.height_constraint; - if (flex_item.used_flex_basis.type == CSS::FlexBasis::Content && flex_container_main_size_constraint != SizeConstraint::None) { - if (flex_container_main_size_constraint == SizeConstraint::MinContent) + if (flex_item.used_flex_basis.type == CSS::FlexBasis::Content && m_available_space_for_items->main.is_intrinsic_sizing_constraint()) { + if (m_available_space_for_items->main.is_min_content()) return calculate_min_content_main_size(flex_item); return calculate_max_content_main_size(flex_item); } @@ -825,15 +771,39 @@ float FlexFormattingContext::content_based_minimum_size(FlexItem const& item) co return unclamped_size; } -// https://www.w3.org/TR/css-flexbox-1/#algo-main-container -void FlexFormattingContext::determine_main_size_of_flex_container(bool const main_is_constrained, float const main_min_size, float const main_max_size) +// https://drafts.csswg.org/css-flexbox-1/#algo-main-container +void FlexFormattingContext::determine_main_size_of_flex_container() { - // FIXME: This needs to be reworked. - if (!main_is_constrained || !m_available_space->main.has_value()) { - auto result = is_row_layout() ? calculate_max_content_width(flex_container()) : calculate_max_content_height(flex_container()); - m_available_space->main = css_clamp(result, main_min_size, main_max_size); + // Determine the main size of the flex container using the rules of the formatting context in which it participates. + // NOTE: The automatic block size of a block-level flex container is its max-content size. + + // FIXME: Once all parent contexts now how to size a given child, we can remove + // `can_determine_size_of_child()`. + if (parent()->can_determine_size_of_child()) { + AvailableSpace available_space(m_available_space_for_flex_container->width, m_available_space_for_flex_container->height); + if (is_row_layout()) { + parent()->determine_width_of_child(flex_container(), available_space); + } else { + parent()->determine_height_of_child(flex_container(), available_space); + } + return; + } + + // HACK: The hack below doesn't know how to size absolutely positioned flex containers at all. + // We just leave it alone for now and let the parent context deal with it. + if (flex_container().is_absolutely_positioned()) + return; + + if (is_row_layout()) { + if (!flex_container().is_out_of_flow(*parent()) && m_state.get(*flex_container().containing_block()).has_definite_width()) { + set_main_size(flex_container(), calculate_stretch_fit_width(flex_container(), m_available_space_for_flex_container->main)); + } else { + set_main_size(flex_container(), calculate_max_content_width(flex_container())); + } + } else { + if (!has_definite_main_size(flex_container())) + set_main_size(flex_container(), calculate_max_content_height(flex_container())); } - set_main_size(flex_container(), m_available_space->main.value_or(NumericLimits::max())); } // https://www.w3.org/TR/css-flexbox-1/#algo-line-break @@ -864,7 +834,7 @@ void FlexFormattingContext::collect_flex_items_into_flex_lines() float line_main_size = 0; for (auto& flex_item : m_flex_items) { auto outer_hypothetical_main_size = flex_item.hypothetical_main_size + flex_item.margins.main_before + flex_item.margins.main_after + flex_item.borders.main_before + flex_item.borders.main_after + flex_item.padding.main_before + flex_item.padding.main_after; - if ((line_main_size + outer_hypothetical_main_size) > m_available_space->main.value_or(NumericLimits::max())) { + if (!line.items.is_empty() && (line_main_size + outer_hypothetical_main_size) > specified_main_size(flex_container())) { m_flex_lines.append(move(line)); line = {}; line_main_size = 0; @@ -892,7 +862,7 @@ void FlexFormattingContext::resolve_flexible_lengths() for (auto& flex_item : flex_line.items) { sum_of_hypothetical_main_sizes += (flex_item->hypothetical_main_size + flex_item->margins.main_before + flex_item->margins.main_after + flex_item->borders.main_before + flex_item->borders.main_after + flex_item->padding.main_before + flex_item->padding.main_after); } - if (sum_of_hypothetical_main_sizes < m_available_space->main.value_or(NumericLimits::max())) + if (sum_of_hypothetical_main_sizes < specified_main_size(flex_container())) used_flex_factor = FlexFactor::FlexGrowFactor; else used_flex_factor = FlexFactor::FlexShrinkFactor; @@ -1042,14 +1012,6 @@ void FlexFormattingContext::resolve_flexible_lengths() for (auto& flex_item : flex_line.items) { flex_item->main_size = flex_item->target_main_size; set_main_size(flex_item->box, flex_item->main_size); - - // https://drafts.csswg.org/css-flexbox-1/#definite-sizes - // 1. If the flex container has a definite main size, then the post-flexing main sizes of its flex items are treated as definite. - // 2. If a flex-item’s flex basis is definite, then its post-flexing main size is also definite. - if (has_definite_main_size(flex_container()) || flex_item->used_flex_basis_is_definite) { - set_has_definite_main_size(flex_item->box, true); - flex_item->has_assigned_definite_main_size = true; - } } flex_line.remaining_free_space = calculate_free_space(); @@ -1087,10 +1049,8 @@ void FlexFormattingContext::determine_hypothetical_cross_size_of_item(FlexItem& auto& containing_block_state = throwaway_state.get_mutable(flex_container()); if (is_row_layout()) { containing_block_state.set_content_width(item.main_size); - containing_block_state.set_has_definite_width(true); } else { containing_block_state.set_content_height(item.main_size); - containing_block_state.set_has_definite_height(true); } auto& box_state = throwaway_state.get_mutable(item.box); @@ -1100,7 +1060,7 @@ void FlexFormattingContext::determine_hypothetical_cross_size_of_item(FlexItem& // NOTE: Flex items should always create an independent formatting context! VERIFY(independent_formatting_context); - independent_formatting_context->run(item.box, LayoutMode::Normal, AvailableSpace::make_indefinite(), AvailableSpace::make_indefinite()); + independent_formatting_context->run(item.box, LayoutMode::Normal, AvailableSpace(m_available_space_for_items->width, m_available_space_for_items->height)); auto automatic_cross_size = is_row_layout() ? independent_formatting_context->automatic_content_height() : box_state.content_width(); @@ -1124,12 +1084,6 @@ void FlexFormattingContext::calculate_cross_size_of_each_flex_line(float const c // and its hypothetical outer cross-start edge, and the largest of the distances between each item’s baseline // and its hypothetical outer cross-end edge, and sum these two values. - // FIXME: This isn't spec but makes sense here - if (has_definite_cross_size(flex_container()) && flex_container().computed_values().align_items() == CSS::AlignItems::Stretch) { - flex_line.cross_size = specified_cross_size(flex_container()) / m_flex_lines.size(); - continue; - } - // 2. Among all the items not collected by the previous step, find the largest outer hypothetical cross size. float largest_hypothetical_cross_size = 0; for (auto& flex_item : flex_line.items) { @@ -1227,10 +1181,10 @@ void FlexFormattingContext::distribute_any_remaining_free_space() break; case CSS::JustifyContent::FlexEnd: flex_region_render_cursor = FlexRegionRenderCursor::Right; - initial_offset = m_available_space->main.value_or(NumericLimits::max()); + initial_offset = specified_main_size(flex_container()); break; case CSS::JustifyContent::Center: - initial_offset = (m_available_space->main.value_or(NumericLimits::max()) - used_main_space) / 2.0f; + initial_offset = (specified_main_size(flex_container()) - used_main_space) / 2.0f; break; case CSS::JustifyContent::SpaceBetween: space_between_items = flex_line.remaining_free_space / (number_of_items - 1); @@ -1435,12 +1389,11 @@ void FlexFormattingContext::copy_dimensions_from_flex_items_to_boxes() } // https://drafts.csswg.org/css-flexbox-1/#intrinsic-sizes -void FlexFormattingContext::determine_intrinsic_size_of_flex_container(LayoutMode layout_mode) +void FlexFormattingContext::determine_intrinsic_size_of_flex_container() { - VERIFY(layout_mode != LayoutMode::Normal); + float main_size = calculate_intrinsic_main_size_of_flex_container(); + float cross_size = calculate_intrinsic_cross_size_of_flex_container(); - float main_size = calculate_intrinsic_main_size_of_flex_container(layout_mode); - float cross_size = calculate_intrinsic_cross_size_of_flex_container(layout_mode); if (is_row_layout()) { m_flex_container_state.set_content_width(main_size); m_flex_container_state.set_content_height(cross_size); @@ -1451,14 +1404,12 @@ void FlexFormattingContext::determine_intrinsic_size_of_flex_container(LayoutMod } // https://drafts.csswg.org/css-flexbox-1/#intrinsic-main-sizes -float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(LayoutMode layout_mode) +float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container() { - VERIFY(layout_mode != LayoutMode::Normal); - // The min-content main size of a single-line flex container is calculated identically to the max-content main size, // except that the flex items’ min-content contributions are used instead of their max-content contributions. // 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. - if (!is_single_line() && flex_container_main_constraint() == SizeConstraint::MinContent) { + if (!is_single_line() && m_available_space_for_items->main.is_min_content()) { float largest_contribution = 0; for (auto const& flex_item : m_flex_items) { // FIXME: Skip collapsed flex items. @@ -1479,10 +1430,10 @@ float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(Lay // This is the item’s desired flex fraction. for (auto& flex_item : m_flex_items) { - float contribution; - if (m_flex_container_state.width_constraint == SizeConstraint::MinContent || m_flex_container_state.height_constraint == SizeConstraint::MinContent) + float contribution = 0; + if (m_available_space_for_items->main.is_min_content()) contribution = calculate_main_min_content_contribution(flex_item); - else + else if (m_available_space_for_items->main.is_max_content()) contribution = calculate_main_max_content_contribution(flex_item); float outer_flex_base_size = flex_item.flex_base_size + flex_item.margins.main_before + flex_item.margins.main_after + flex_item.borders.main_before + flex_item.borders.main_after + flex_item.padding.main_before + flex_item.padding.main_after; @@ -1539,7 +1490,7 @@ float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(Lay flex_line.chosen_flex_fraction = chosen_flex_fraction; } - auto determine_main_size = [&](bool resolve_percentage_min_max_sizes) -> float { + auto determine_main_size = [&]() -> float { float largest_sum = 0; for (auto& flex_line : m_flex_lines) { // 4. Add each item’s flex base size to the product of its flex grow factor (scaled flex shrink factor, if shrinking) @@ -1556,8 +1507,8 @@ float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(Lay auto const& computed_min_size = this->computed_main_min_size(flex_item->box); auto const& computed_max_size = this->computed_main_max_size(flex_item->box); - auto clamp_min = (!computed_min_size.is_auto() && (resolve_percentage_min_max_sizes || !computed_min_size.contains_percentage())) ? specified_main_min_size(flex_item->box) : automatic_minimum_size(*flex_item); - auto clamp_max = (!computed_max_size.is_none() && (resolve_percentage_min_max_sizes || !computed_max_size.contains_percentage())) ? specified_main_max_size(flex_item->box) : NumericLimits::max(); + auto clamp_min = (!computed_min_size.is_auto() && !computed_min_size.contains_percentage()) ? specified_main_min_size(flex_item->box) : automatic_minimum_size(*flex_item); + auto clamp_max = (!computed_max_size.is_none() && !computed_max_size.contains_percentage()) ? specified_main_max_size(flex_item->box) : NumericLimits::max(); result = css_clamp(result, clamp_min, clamp_max); @@ -1573,27 +1524,24 @@ float FlexFormattingContext::calculate_intrinsic_main_size_of_flex_container(Lay return largest_sum; }; - auto first_pass_main_size = determine_main_size(false); - set_main_size(flex_container(), first_pass_main_size); - auto second_pass_main_size = determine_main_size(true); - return second_pass_main_size; + auto main_size = determine_main_size(); + set_main_size(flex_container(), main_size); + return main_size; } // https://drafts.csswg.org/css-flexbox-1/#intrinsic-cross-sizes -float FlexFormattingContext::calculate_intrinsic_cross_size_of_flex_container(LayoutMode layout_mode) +float FlexFormattingContext::calculate_intrinsic_cross_size_of_flex_container() { - VERIFY(layout_mode != LayoutMode::Normal); - // The min-content/max-content cross size of a single-line flex container // is the largest min-content contribution/max-content contribution (respectively) of its flex items. if (is_single_line()) { auto calculate_largest_contribution = [&](bool resolve_percentage_min_max_sizes) { float largest_contribution = 0; for (auto& flex_item : m_flex_items) { - float contribution; - if (m_flex_container_state.width_constraint == SizeConstraint::MinContent || m_flex_container_state.height_constraint == SizeConstraint::MinContent) + float contribution = 0; + if (m_available_space_for_items->cross.is_min_content()) contribution = calculate_cross_min_content_contribution(flex_item, resolve_percentage_min_max_sizes); - else if (m_flex_container_state.width_constraint == SizeConstraint::MaxContent || m_flex_container_state.height_constraint == SizeConstraint::MaxContent) + else if (m_available_space_for_items->cross.is_max_content()) contribution = calculate_cross_max_content_contribution(flex_item, resolve_percentage_min_max_sizes); largest_contribution = max(largest_contribution, contribution); } @@ -1606,11 +1554,19 @@ float FlexFormattingContext::calculate_intrinsic_cross_size_of_flex_container(La return second_pass_largest_contribution; } - // For a multi-line flex container, the min-content/max-content cross size is the sum of the flex line cross sizes - // resulting from sizing the flex container under a cross-axis min-content constraint/max-content constraint (respectively). + // FIXME: For a multi-line flex container, the min-content/max-content cross size is the sum of the flex line cross sizes + // resulting from sizing the flex container under a cross-axis min-content constraint/max-content constraint (respectively). // FIXME: However, if the flex container is flex-flow: column wrap;, then it’s sized by first finding the largest // min-content/max-content cross-size contribution among the flex items (respectively), then using that size // as the available space in the cross axis for each of the flex items during layout. + + // HACK: We run steps 7, 9 and 11 from the main algorithm. This gives us *some* cross size information to work with. + for (auto& flex_item : m_flex_items) { + determine_hypothetical_cross_size_of_item(flex_item, false); + } + calculate_cross_size_of_each_flex_line(0, INFINITY); + determine_used_cross_size_of_each_flex_item(); + float sum_of_flex_line_cross_sizes = 0; for (auto& flex_line : m_flex_lines) { sum_of_flex_line_cross_sizes += flex_line.cross_size; @@ -1709,14 +1665,14 @@ float FlexFormattingContext::calculate_min_content_main_size(FlexItem const& ite float FlexFormattingContext::calculate_fit_content_main_size(FlexItem const& item) const { - return is_row_layout() ? calculate_fit_content_width(item.box, m_state.get(item.box).width_constraint, m_available_space->main) - : calculate_fit_content_height(item.box, m_state.get(item.box).height_constraint, m_available_space->main); + return is_row_layout() ? calculate_fit_content_width(item.box, m_available_space_for_items->main) + : calculate_fit_content_height(item.box, m_available_space_for_items->main); } float FlexFormattingContext::calculate_fit_content_cross_size(FlexItem const& item) const { - return !is_row_layout() ? calculate_fit_content_width(item.box, m_state.get(item.box).width_constraint, m_available_space->cross) - : calculate_fit_content_height(item.box, m_state.get(item.box).height_constraint, m_available_space->cross); + return !is_row_layout() ? calculate_fit_content_width(item.box, m_available_space_for_items->cross) + : calculate_fit_content_height(item.box, m_available_space_for_items->cross); } float FlexFormattingContext::calculate_max_content_main_size(FlexItem const& item) const @@ -1734,16 +1690,6 @@ float FlexFormattingContext::calculate_max_content_cross_size(FlexItem const& it return is_row_layout() ? calculate_max_content_height(item.box) : calculate_max_content_width(item.box); } -SizeConstraint FlexFormattingContext::flex_container_main_constraint() const -{ - return is_row_layout() ? m_flex_container_state.width_constraint : m_flex_container_state.height_constraint; -} - -SizeConstraint FlexFormattingContext::flex_container_cross_constraint() const -{ - return is_row_layout() ? m_flex_container_state.height_constraint : m_flex_container_state.width_constraint; -} - // https://drafts.csswg.org/css-flexbox-1/#stretched bool FlexFormattingContext::flex_item_is_stretched(FlexItem const& item) const { diff --git a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h index ea810da2b9e..d3031e23d85 100644 --- a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h @@ -18,15 +18,12 @@ public: virtual bool inhibits_floating() const override { return true; } - virtual void run(Box const&, LayoutMode, AvailableSpace const& available_width, AvailableSpace const& available_height) override; + virtual void run(Box const&, LayoutMode, AvailableSpace const&) override; virtual float automatic_content_height() const override; Box const& flex_container() const { return context_box(); } private: - SizeConstraint flex_container_main_constraint() const; - SizeConstraint flex_container_cross_constraint() const; - void dump_items() const; struct DirectionAgnosticMargins { @@ -63,8 +60,6 @@ private: DirectionAgnosticMargins padding {}; bool is_min_violation { false }; bool is_max_violation { false }; - bool has_assigned_definite_main_size { false }; - bool has_assigned_definite_cross_size { false }; float add_main_margin_box_sizes(float content_size) const { @@ -94,14 +89,11 @@ private: bool has_cross_min_size(Box const&) const; float specified_main_max_size(Box const&) const; float specified_cross_max_size(Box const&) const; - float calculated_main_size(Box const&) const; bool is_cross_auto(Box const&) const; - float specified_main_size_of_child_box(Box const& child_box) const; float specified_main_min_size(Box const&) const; float specified_cross_min_size(Box const&) const; bool has_main_max_size(Box const&) const; bool has_cross_max_size(Box const&) const; - float sum_of_margin_padding_border_in_main_axis(Box const&) const; float automatic_minimum_size(FlexItem const&) const; float content_based_minimum_size(FlexItem const&) const; Optional specified_size_suggestion(FlexItem const&) const; @@ -121,8 +113,6 @@ private: void set_main_size(Box const&, float size); void set_cross_size(Box const&, float size); - void set_has_definite_main_size(Box const&, bool); - void set_has_definite_cross_size(Box const&, bool); void set_offset(Box const&, float main_offset, float cross_offset); void set_main_axis_first_margin(FlexItem&, float margin); void set_main_axis_second_margin(FlexItem&, float margin); @@ -131,12 +121,12 @@ private: void generate_anonymous_flex_items(); - void determine_available_main_and_cross_space(bool& main_is_constrained, bool& cross_is_constrained, float& main_min_size, float& main_max_size, float& cross_min_size, float& cross_max_size); + void determine_available_space_for_items(AvailableSpace const&); float calculate_indefinite_main_size(FlexItem const&); void determine_flex_base_size_and_hypothetical_main_size(FlexItem&); - void determine_main_size_of_flex_container(bool main_is_constrained, float main_min_size, float main_max_size); + void determine_main_size_of_flex_container(); void collect_flex_items_into_flex_lines(); @@ -165,9 +155,9 @@ private: bool is_direction_reverse() const { return m_flex_direction == CSS::FlexDirection::ColumnReverse || m_flex_direction == CSS::FlexDirection::RowReverse; } void populate_specified_margins(FlexItem&, CSS::FlexDirection) const; - void determine_intrinsic_size_of_flex_container(LayoutMode); - [[nodiscard]] float calculate_intrinsic_main_size_of_flex_container(LayoutMode); - [[nodiscard]] float calculate_intrinsic_cross_size_of_flex_container(LayoutMode); + void determine_intrinsic_size_of_flex_container(); + [[nodiscard]] float calculate_intrinsic_main_size_of_flex_container(); + [[nodiscard]] float calculate_intrinsic_cross_size_of_flex_container(); [[nodiscard]] float calculate_cross_min_content_contribution(FlexItem const&, bool resolve_percentage_min_max_sizes) const; [[nodiscard]] float calculate_cross_max_content_contribution(FlexItem const&, bool resolve_percentage_min_max_sizes) const; @@ -192,11 +182,14 @@ private: Vector m_flex_items; CSS::FlexDirection m_flex_direction {}; - struct AvailableSpaceForItems { - Optional main; - Optional cross; + struct AxisAgnosticAvailableSpace { + AvailableSize main; + AvailableSize cross; + AvailableSize width; + AvailableSize height; }; - Optional m_available_space; + Optional m_available_space_for_items; + Optional m_available_space_for_flex_container; }; } diff --git a/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp index 1d255d5e717..3db024b7170 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp @@ -33,24 +33,18 @@ void FormattingContext::run_intrinsic_sizing(Box const& box) { auto& box_state = m_state.get_mutable(box); - if (box_state.has_definite_width()) - box_state.set_content_width(box.computed_values().width().resolved(box, CSS::Length::make_px(containing_block_width_for(box))).to_px(box)); - - if (box_state.has_definite_height()) - box_state.set_content_height(box.computed_values().height().resolved(box, CSS::Length::make_px(containing_block_height_for(box))).to_px(box)); - - auto to_available_space = [&](SizeConstraint constraint) { + auto to_available_size = [&](SizeConstraint constraint) { if (constraint == SizeConstraint::MinContent) - return AvailableSpace::make_min_content(); + return AvailableSize::make_min_content(); if (constraint == SizeConstraint::MaxContent) - return AvailableSpace::make_max_content(); - return AvailableSpace::make_indefinite(); + return AvailableSize::make_max_content(); + return AvailableSize::make_indefinite(); }; - auto available_width = to_available_space(box_state.width_constraint); - auto available_height = to_available_space(box_state.height_constraint); + auto available_width = to_available_size(box_state.width_constraint); + auto available_height = to_available_size(box_state.height_constraint); - run(box, LayoutMode::IntrinsicSizing, available_width, available_height); + run(box, LayoutMode::IntrinsicSizing, AvailableSpace(available_width, available_height)); } bool FormattingContext::creates_block_formatting_context(Box const& box) @@ -117,7 +111,7 @@ OwnPtr FormattingContext::create_independent_formatting_conte { } virtual float automatic_content_height() const override { return 0; }; - virtual void run(Box const&, LayoutMode, AvailableSpace const&, AvailableSpace const&) override { } + virtual void run(Box const&, LayoutMode, AvailableSpace const&) override { } }; return make(state, child_box); } @@ -159,7 +153,7 @@ OwnPtr FormattingContext::create_independent_formatting_conte { } virtual float automatic_content_height() const override { return 0; }; - virtual void run(Box const&, LayoutMode, AvailableSpace const&, AvailableSpace const&) override { } + virtual void run(Box const&, LayoutMode, AvailableSpace const&) override { } }; return make(state, child_box); } @@ -168,7 +162,7 @@ OwnPtr FormattingContext::create_independent_formatting_conte return {}; } -OwnPtr FormattingContext::layout_inside(Box const& child_box, LayoutMode layout_mode) +OwnPtr FormattingContext::layout_inside(Box const& child_box, LayoutMode layout_mode, AvailableSpace const& available_space) { { // OPTIMIZATION: If we're doing intrinsic sizing and `child_box` has definite size in both axes, @@ -189,9 +183,9 @@ OwnPtr FormattingContext::layout_inside(Box const& child_box, auto independent_formatting_context = create_independent_formatting_context_if_needed(m_state, child_box); if (independent_formatting_context) - independent_formatting_context->run(child_box, layout_mode, AvailableSpace::make_indefinite(), AvailableSpace::make_indefinite()); + independent_formatting_context->run(child_box, layout_mode, available_space); else - run(child_box, layout_mode, AvailableSpace::make_indefinite(), AvailableSpace::make_indefinite()); + run(child_box, layout_mode, available_space); return independent_formatting_context; } @@ -262,16 +256,19 @@ static Gfx::FloatSize solve_replaced_size_constraint(LayoutState const& state, f return { w, h }; } -float FormattingContext::compute_auto_height_for_block_level_element(LayoutState const& state, Box const& box) +float FormattingContext::compute_auto_height_for_block_level_element(Box const& box) const { if (creates_block_formatting_context(box)) - return compute_auto_height_for_block_formatting_context_root(state, verify_cast(box)); + return compute_auto_height_for_block_formatting_context_root(verify_cast(box)); - auto const& box_state = state.get(box); + auto const& box_state = m_state.get(box); auto display = box.computed_values().display(); - if (display.is_flex_inside()) - return box_state.content_height(); + if (display.is_flex_inside()) { + // https://drafts.csswg.org/css-flexbox-1/#algo-main-container + // NOTE: The automatic block size of a block-level flex container is its max-content size. + return calculate_max_content_height(box); + } // https://www.w3.org/TR/CSS22/visudet.html#normal-block // 10.6.3 Block-level non-replaced elements in normal flow when 'overflow' computes to 'visible' @@ -295,7 +292,7 @@ float FormattingContext::compute_auto_height_for_block_level_element(LayoutState if (child_box->is_list_item_marker_box()) continue; - auto const& child_box_state = state.get(*child_box); + auto const& child_box_state = m_state.get(*child_box); // Ignore anonymous block containers with no lines. These don't count as in-flow block boxes. if (child_box->is_anonymous() && child_box->is_block_container() && child_box_state.line_boxes.is_empty()) @@ -311,7 +308,7 @@ float FormattingContext::compute_auto_height_for_block_level_element(LayoutState } // https://www.w3.org/TR/CSS22/visudet.html#root-height -float FormattingContext::compute_auto_height_for_block_formatting_context_root(LayoutState const& state, BlockContainer const& root) +float FormattingContext::compute_auto_height_for_block_formatting_context_root(BlockContainer const& root) const { // 10.6.7 'Auto' heights for block formatting context roots Optional top; @@ -320,7 +317,7 @@ float FormattingContext::compute_auto_height_for_block_formatting_context_root(L if (root.children_are_inline()) { // If it only has inline-level children, the height is the distance between // the top content edge and the bottom of the bottommost line box. - auto const& line_boxes = state.get(root).line_boxes; + auto const& line_boxes = m_state.get(root).line_boxes; top = 0; if (!line_boxes.is_empty()) bottom = line_boxes.last().bottom(); @@ -339,7 +336,7 @@ float FormattingContext::compute_auto_height_for_block_formatting_context_root(L if ((root.computed_values().overflow_y() == CSS::Overflow::Visible) && child_box.is_floating()) return IterationDecision::Continue; - auto const& child_box_state = state.get(child_box); + auto const& child_box_state = m_state.get(child_box); float child_box_top = child_box_state.offset.y() - child_box_state.margin_box_top(); float child_box_bottom = child_box_state.offset.y() + child_box_state.content_height() + child_box_state.margin_box_bottom(); @@ -357,10 +354,10 @@ float FormattingContext::compute_auto_height_for_block_formatting_context_root(L // In addition, if the element has any floating descendants // whose bottom margin edge is below the element's bottom content edge, // then the height is increased to include those edges. - for (auto* floating_box : state.get(root).floating_descendants()) { + for (auto* floating_box : m_state.get(root).floating_descendants()) { // NOTE: Floating box coordinates are relative to their own containing block, // which may or may not be the BFC root. - auto margin_box = margin_box_rect_in_ancestor_coordinate_space(*floating_box, root, state); + auto margin_box = margin_box_rect_in_ancestor_coordinate_space(*floating_box, root, m_state); float floating_box_bottom_margin_edge = margin_box.bottom() + 1; if (!bottom.has_value() || floating_box_bottom_margin_edge > bottom.value()) bottom = floating_box_bottom_margin_edge; @@ -370,7 +367,7 @@ float FormattingContext::compute_auto_height_for_block_formatting_context_root(L } // 10.3.2 Inline, replaced elements, https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-width -float FormattingContext::tentative_width_for_replaced_element(LayoutState const& state, ReplacedBox const& box, CSS::Size const& computed_width) +float FormattingContext::tentative_width_for_replaced_element(LayoutState const& state, ReplacedBox const& box, CSS::Size const& computed_width, AvailableSpace const& available_space) { // Treat percentages of indefinite containing block widths as 0 (the initial width). if (computed_width.is_percentage() && !state.get(*box.containing_block()).has_definite_width()) @@ -379,7 +376,7 @@ float FormattingContext::tentative_width_for_replaced_element(LayoutState const& auto height_of_containing_block = CSS::Length::make_px(containing_block_height_for(box, state)); auto const& computed_height = box.computed_values().height(); - float used_width = computed_width.resolved(box, CSS::Length::make_px(containing_block_width_for(box, state))).to_px(box); + float used_width = computed_width.resolved(box, CSS::Length::make_px(available_space.width.to_px())).to_px(box); // If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic width, // then that intrinsic width is the used value of 'width'. @@ -394,7 +391,7 @@ float FormattingContext::tentative_width_for_replaced_element(LayoutState const& // (used height) * (intrinsic ratio) if ((computed_height.is_auto() && computed_width.is_auto() && !box.has_intrinsic_width() && box.has_intrinsic_height() && box.has_intrinsic_aspect_ratio()) || (computed_width.is_auto() && !computed_height.is_auto() && box.has_intrinsic_aspect_ratio())) { - return compute_height_for_replaced_element(state, box) * box.intrinsic_aspect_ratio().value(); + return compute_height_for_replaced_element(state, box, available_space) * box.intrinsic_aspect_ratio().value(); } // If 'height' and 'width' both have computed values of 'auto' and the element has an intrinsic ratio but no intrinsic height or width, @@ -414,29 +411,29 @@ float FormattingContext::tentative_width_for_replaced_element(LayoutState const& return used_width; } -void FormattingContext::compute_width_for_absolutely_positioned_element(Box const& box) +void FormattingContext::compute_width_for_absolutely_positioned_element(Box const& box, AvailableSpace const& available_space) { if (is(box)) - compute_width_for_absolutely_positioned_replaced_element(verify_cast(box)); + compute_width_for_absolutely_positioned_replaced_element(verify_cast(box), available_space); else - compute_width_for_absolutely_positioned_non_replaced_element(box); + compute_width_for_absolutely_positioned_non_replaced_element(box, available_space); } -void FormattingContext::compute_height_for_absolutely_positioned_element(Box const& box) +void FormattingContext::compute_height_for_absolutely_positioned_element(Box const& box, AvailableSpace const& available_space) { if (is(box)) - compute_height_for_absolutely_positioned_replaced_element(verify_cast(box)); + compute_height_for_absolutely_positioned_replaced_element(static_cast(box), available_space); else - compute_height_for_absolutely_positioned_non_replaced_element(box); + compute_height_for_absolutely_positioned_non_replaced_element(box, available_space); } -float FormattingContext::compute_width_for_replaced_element(LayoutState const& state, ReplacedBox const& box) +float FormattingContext::compute_width_for_replaced_element(LayoutState const& state, ReplacedBox const& box, AvailableSpace const& available_space) { // 10.3.4 Block-level, replaced elements in normal flow... // 10.3.2 Inline, replaced elements auto zero_value = CSS::Length::make_px(0); - auto width_of_containing_block_as_length = CSS::Length::make_px(containing_block_width_for(box, state)); + auto width_of_containing_block_as_length = CSS::Length::make_px(available_space.width.to_px()); auto margin_left = box.computed_values().margin().left().resolved(box, width_of_containing_block_as_length).resolved(box); auto margin_right = box.computed_values().margin().right().resolved(box, width_of_containing_block_as_length).resolved(box); @@ -450,14 +447,14 @@ float FormattingContext::compute_width_for_replaced_element(LayoutState const& s auto computed_width = box.computed_values().width(); // 1. The tentative used width is calculated (without 'min-width' and 'max-width') - auto used_width = tentative_width_for_replaced_element(state, box, computed_width); + auto used_width = tentative_width_for_replaced_element(state, box, computed_width, available_space); // 2. The tentative used width is greater than 'max-width', the rules above are applied again, // but this time using the computed value of 'max-width' as the computed value for 'width'. auto computed_max_width = box.computed_values().max_width(); if (!computed_max_width.is_none()) { if (used_width > computed_max_width.resolved(box, width_of_containing_block_as_length).to_px(box)) { - used_width = tentative_width_for_replaced_element(state, box, computed_max_width); + used_width = tentative_width_for_replaced_element(state, box, computed_max_width, available_space); } } @@ -466,7 +463,7 @@ float FormattingContext::compute_width_for_replaced_element(LayoutState const& s auto computed_min_width = box.computed_values().min_width(); if (!computed_min_width.is_auto()) { if (used_width < computed_min_width.resolved(box, width_of_containing_block_as_length).to_px(box)) { - used_width = tentative_width_for_replaced_element(state, box, computed_min_width); + used_width = tentative_width_for_replaced_element(state, box, computed_min_width, available_space); } } @@ -475,7 +472,7 @@ float FormattingContext::compute_width_for_replaced_element(LayoutState const& s // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, 'inline-block' replaced elements in normal flow and floating replaced elements // https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-height -float FormattingContext::tentative_height_for_replaced_element(LayoutState const& state, ReplacedBox const& box, CSS::Size const& computed_height) +float FormattingContext::tentative_height_for_replaced_element(LayoutState const& state, ReplacedBox const& box, CSS::Size const& computed_height, AvailableSpace const& available_space) { // Treat percentages of indefinite containing block heights as 0 (the initial height). if (computed_height.is_percentage() && !state.get(*box.containing_block()).has_definite_height()) @@ -492,7 +489,7 @@ float FormattingContext::tentative_height_for_replaced_element(LayoutState const // // (used width) / (intrinsic ratio) if (computed_height.is_auto() && box.has_intrinsic_aspect_ratio()) - return compute_width_for_replaced_element(state, box) / box.intrinsic_aspect_ratio().value(); + return compute_width_for_replaced_element(state, box, available_space) / box.intrinsic_aspect_ratio().value(); // Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic height, then that intrinsic height is the used value of 'height'. if (computed_height.is_auto() && box.has_intrinsic_height()) @@ -504,23 +501,23 @@ float FormattingContext::tentative_height_for_replaced_element(LayoutState const if (computed_height.is_auto()) return 150; - return computed_height.resolved(box, CSS::Length::make_px(containing_block_height_for(box, state))).to_px(box); + return computed_height.resolved(box, CSS::Length::make_px(available_space.height.to_px())).to_px(box); } -float FormattingContext::compute_height_for_replaced_element(LayoutState const& state, ReplacedBox const& box) +float FormattingContext::compute_height_for_replaced_element(LayoutState const& state, ReplacedBox const& box, AvailableSpace const& available_space) { // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, // 'inline-block' replaced elements in normal flow and floating replaced elements - auto width_of_containing_block_as_length = CSS::Length::make_px(containing_block_width_for(box, state)); - auto height_of_containing_block_as_length = CSS::Length::make_px(containing_block_height_for(box, state)); + auto width_of_containing_block_as_length = CSS::Length::make_px(available_space.width.to_px()); + auto height_of_containing_block_as_length = CSS::Length::make_px(available_space.height.to_px()); auto computed_width = box.computed_values().width(); auto computed_height = box.computed_values().height(); - float used_height = tentative_height_for_replaced_element(state, box, computed_height); + float used_height = tentative_height_for_replaced_element(state, box, computed_height, available_space); if (computed_width.is_auto() && computed_height.is_auto() && box.has_intrinsic_aspect_ratio()) { - float w = tentative_width_for_replaced_element(state, box, computed_width); + float w = tentative_width_for_replaced_element(state, box, computed_width, available_space); float h = used_height; used_height = solve_replaced_size_constraint(state, w, h, box).height(); } @@ -528,9 +525,9 @@ float FormattingContext::compute_height_for_replaced_element(LayoutState const& return used_height; } -void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_element(Box const& box) +void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_element(Box const& box, AvailableSpace const& available_space) { - auto width_of_containing_block = containing_block_width_for(box); + auto width_of_containing_block = available_space.width.to_px(); auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); auto& computed_values = box.computed_values(); auto zero_value = CSS::Length::make_px(0); @@ -670,17 +667,17 @@ void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_ele box_state.padding_right = padding_right; } -void FormattingContext::compute_width_for_absolutely_positioned_replaced_element(ReplacedBox const& box) +void FormattingContext::compute_width_for_absolutely_positioned_replaced_element(ReplacedBox const& box, AvailableSpace const& available_space) { // 10.3.8 Absolutely positioned, replaced elements // The used value of 'width' is determined as for inline replaced elements. // FIXME: This const_cast is gross. const_cast(box).prepare_for_replaced_layout(); - m_state.get_mutable(box).set_content_width(compute_width_for_replaced_element(m_state, box)); + m_state.get_mutable(box).set_content_width(compute_width_for_replaced_element(m_state, box, available_space)); } // https://www.w3.org/TR/CSS22/visudet.html#abs-non-replaced-height -void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_element(Box const& box) +void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_element(Box const& box, AvailableSpace const& available_space) { // 10.6.4 Absolutely positioned, non-replaced elements @@ -688,7 +685,7 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el auto& computed_values = box.computed_values(); auto width_of_containing_block = containing_block_width_for(box); - auto height_of_containing_block = containing_block_height_for(box); + auto height_of_containing_block = available_space.height.to_px(); auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); auto height_of_containing_block_as_length = CSS::Length::make_px(height_of_containing_block); @@ -714,11 +711,11 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el box_state.padding_bottom = computed_values.padding().bottom().resolved(box, width_of_containing_block_as_length).to_px(box); if (computed_height.is_auto() && computed_top.is_auto() && computed_bottom.is_auto()) { - tentative_height = CSS::Length(compute_auto_height_for_block_level_element(m_state, box), CSS::Length::Type::Px); + tentative_height = CSS::Length(compute_auto_height_for_block_level_element(box), CSS::Length::Type::Px); } else if (computed_height.is_auto() && !computed_top.is_auto() && computed_bottom.is_auto()) { - tentative_height = CSS::Length(compute_auto_height_for_block_level_element(m_state, box), CSS::Length::Type::Px); + tentative_height = CSS::Length(compute_auto_height_for_block_level_element(box), CSS::Length::Type::Px); box_state.inset_bottom = height_of_containing_block - tentative_height.to_px(box) - used_top - box_state.margin_top - box_state.padding_top - box_state.border_top - box_state.margin_bottom - box_state.padding_bottom - box_state.border_bottom; } @@ -735,29 +732,22 @@ void FormattingContext::compute_height_for_absolutely_positioned_non_replaced_el box_state.set_content_height(used_height); } -void FormattingContext::layout_absolutely_positioned_element(Box const& box) +void FormattingContext::layout_absolutely_positioned_element(Box const& box, AvailableSpace const& available_space) { - // https://drafts.csswg.org/css-sizing-3/#definite - // Additionally, the size of the containing block of an absolutely positioned element is always definite with respect to that element. auto& containing_block_state = m_state.get_mutable(*box.containing_block()); - auto containing_block_had_definite_width = containing_block_state.has_definite_width(); - containing_block_state.set_has_definite_width(true); - auto containing_block_definite_width_guard = ScopeGuard([&] { - containing_block_state.set_has_definite_width(containing_block_had_definite_width); - }); + auto& box_state = m_state.get_mutable(box); - auto width_of_containing_block = containing_block_width_for(box); - auto height_of_containing_block = containing_block_height_for(box); + auto width_of_containing_block = available_space.width.to_px(); + auto height_of_containing_block = available_space.height.to_px(); auto width_of_containing_block_as_length = CSS::Length::make_px(width_of_containing_block); auto height_of_containing_block_as_length = CSS::Length::make_px(height_of_containing_block); auto specified_width = box.computed_values().width().resolved(box, width_of_containing_block_as_length).resolved(box); - compute_width_for_absolutely_positioned_element(box); - auto independent_formatting_context = layout_inside(box, LayoutMode::Normal); - compute_height_for_absolutely_positioned_element(box); + compute_width_for_absolutely_positioned_element(box, available_space); + auto independent_formatting_context = layout_inside(box, LayoutMode::Normal, box_state.available_inner_space_or_constraints_from(available_space)); + compute_height_for_absolutely_positioned_element(box, available_space); - auto& box_state = m_state.get_mutable(box); box_state.margin_left = box.computed_values().margin().left().resolved(box, width_of_containing_block_as_length).to_px(box); box_state.margin_top = box.computed_values().margin().top().resolved(box, height_of_containing_block_as_length).to_px(box); box_state.margin_right = box.computed_values().margin().right().resolved(box, width_of_containing_block_as_length).to_px(box); @@ -773,7 +763,7 @@ void FormattingContext::layout_absolutely_positioned_element(Box const& box) box_state.inset_right = box.computed_values().inset().right().resolved(box, width_of_containing_block_as_length).to_px(box); box_state.inset_bottom = box.computed_values().inset().bottom().resolved(box, height_of_containing_block_as_length).to_px(box); - if (box.computed_values().inset().left().is_auto() && specified_width.is_auto() && box.computed_values().inset().right().is_auto()) { + if (box.computed_values().inset().left().is_auto() && box.computed_values().width().is_auto() && box.computed_values().inset().right().is_auto()) { if (box.computed_values().margin().left().is_auto()) box_state.margin_left = 0; if (box.computed_values().margin().right().is_auto()) @@ -826,17 +816,17 @@ void FormattingContext::layout_absolutely_positioned_element(Box const& box) // NOTE: Absolutely positioned boxes are relative to the *padding edge* of the containing block. used_offset.translate_by(-containing_block_state.padding_left, -containing_block_state.padding_top); - box_state.offset = used_offset; + box_state.set_content_offset(used_offset); if (independent_formatting_context) independent_formatting_context->parent_context_did_dimension_child_root_box(); } -void FormattingContext::compute_height_for_absolutely_positioned_replaced_element(ReplacedBox const& box) +void FormattingContext::compute_height_for_absolutely_positioned_replaced_element(ReplacedBox const& box, AvailableSpace const& available_space) { // 10.6.5 Absolutely positioned, replaced elements // The used value of 'height' is determined as for inline replaced elements. - m_state.get_mutable(box).set_content_height(compute_height_for_replaced_element(m_state, box)); + m_state.get_mutable(box).set_content_height(compute_height_for_replaced_element(m_state, box, available_space)); } // https://www.w3.org/TR/css-position-3/#relpos-insets @@ -879,49 +869,49 @@ void FormattingContext::compute_inset(Box const& box) resolve_two_opposing_insets(computed_values.inset().top(), computed_values.inset().bottom(), box_state.inset_top, box_state.inset_bottom, containing_block_height_for(box)); } -float FormattingContext::calculate_fit_content_size(float min_content_size, float max_content_size, SizeConstraint constraint, Optional available_space) const +float FormattingContext::calculate_fit_content_size(float min_content_size, float max_content_size, AvailableSize const& available_size) const { // If the available space in a given axis is definite, equal to clamp(min-content size, stretch-fit size, max-content size) // (i.e. max(min-content size, min(max-content size, stretch-fit size))). - if (available_space.has_value()) { + if (available_size.is_definite()) { // FIXME: Compute the real stretch-fit size. - auto stretch_fit_size = *available_space; + auto stretch_fit_size = available_size.to_px(); auto s = max(min_content_size, min(max_content_size, stretch_fit_size)); return s; } // When sizing under a min-content constraint, equal to the min-content size. - if (constraint == SizeConstraint::MinContent) + if (available_size.is_min_content()) return min_content_size; // Otherwise, equal to the max-content size in that axis. return max_content_size; } -float FormattingContext::calculate_fit_content_width(Layout::Box const& box, SizeConstraint constraint, Optional available_space) const +float FormattingContext::calculate_fit_content_width(Layout::Box const& box, AvailableSize const& available_size) const { // When sizing under a min-content constraint, equal to the min-content size. // NOTE: We check this first, to avoid needlessly calculating the max-content size. - if (constraint == SizeConstraint::MinContent) + if (available_size.is_min_content()) return calculate_min_content_width(box); - if (constraint == SizeConstraint::MaxContent) + if (available_size.is_max_content()) return calculate_max_content_width(box); - return calculate_fit_content_size(calculate_min_content_width(box), calculate_max_content_width(box), constraint, available_space); + return calculate_fit_content_size(calculate_min_content_width(box), calculate_max_content_width(box), available_size); } -float FormattingContext::calculate_fit_content_height(Layout::Box const& box, SizeConstraint constraint, Optional available_space) const +float FormattingContext::calculate_fit_content_height(Layout::Box const& box, AvailableSize const& available_size) const { // When sizing under a min-content constraint, equal to the min-content size. // NOTE: We check this first, to avoid needlessly calculating the max-content size. - if (constraint == SizeConstraint::MinContent) + if (available_size.is_min_content()) return calculate_min_content_height(box); - if (constraint == SizeConstraint::MaxContent) + if (available_size.is_max_content()) return calculate_max_content_height(box); - return calculate_fit_content_size(calculate_min_content_height(box), calculate_max_content_height(box), constraint, available_space); + return calculate_fit_content_size(calculate_min_content_height(box), calculate_max_content_height(box), available_size); } float FormattingContext::calculate_min_content_width(Layout::Box const& box) const @@ -936,14 +926,6 @@ float FormattingContext::calculate_min_content_width(Layout::Box const& box) con return *cache.min_content_width; LayoutState throwaway_state(&m_state); - auto const& containing_block = *box.containing_block(); - auto& containing_block_state = throwaway_state.get_mutable(containing_block); - containing_block_state.set_content_width(0); - - if (!containing_block_state.has_definite_height()) - containing_block_state.set_content_height(INFINITY); - else if (containing_block.computed_values().height().is_auto()) - containing_block_state.set_content_height(containing_block_height_for(containing_block)); auto& box_state = throwaway_state.get_mutable(box); box_state.width_constraint = SizeConstraint::MinContent; @@ -978,14 +960,6 @@ float FormattingContext::calculate_max_content_width(Layout::Box const& box) con return *cache.max_content_width; LayoutState throwaway_state(&m_state); - auto const& containing_block = *box.containing_block(); - auto& containing_block_state = throwaway_state.get_mutable(containing_block); - containing_block_state.set_content_width(INFINITY); - - if (!containing_block_state.has_definite_height()) - containing_block_state.set_content_height(INFINITY); - else if (containing_block.computed_values().height().is_auto()) - containing_block_state.set_content_height(containing_block_height_for(containing_block)); auto& box_state = throwaway_state.get_mutable(box); box_state.width_constraint = SizeConstraint::MaxContent; @@ -1020,14 +994,6 @@ float FormattingContext::calculate_min_content_height(Layout::Box const& box) co return *cache.min_content_height; LayoutState throwaway_state(&m_state); - auto const& containing_block = *box.containing_block(); - auto& containing_block_state = throwaway_state.get_mutable(containing_block); - containing_block_state.set_content_height(0); - - if (!containing_block_state.has_definite_width()) - containing_block_state.set_content_width(INFINITY); - else if (containing_block.computed_values().width().is_auto()) - containing_block_state.set_content_width(containing_block_width_for(containing_block)); auto& box_state = throwaway_state.get_mutable(box); box_state.height_constraint = SizeConstraint::MinContent; @@ -1058,14 +1024,6 @@ float FormattingContext::calculate_max_content_height(Layout::Box const& box) co return *cache.max_content_height; LayoutState throwaway_state(&m_state); - auto const& containing_block = *box.containing_block(); - auto& containing_block_state = throwaway_state.get_mutable(containing_block); - containing_block_state.set_content_height(INFINITY); - - if (!containing_block_state.has_definite_width()) - containing_block_state.set_content_width(INFINITY); - else if (containing_block.computed_values().width().is_auto()) - containing_block_state.set_content_width(containing_block_width_for(containing_block)); auto& box_state = throwaway_state.get_mutable(box); box_state.height_constraint = SizeConstraint::MaxContent; @@ -1181,7 +1139,7 @@ float FormattingContext::compute_box_y_position_with_respect_to_siblings(Box con } // https://drafts.csswg.org/css-sizing-3/#stretch-fit-size -float FormattingContext::calculate_stretch_fit_width(Box const& box, AvailableSpace const& available_width) const +float FormattingContext::calculate_stretch_fit_width(Box const& box, AvailableSize const& available_width) const { // The size a box would take if its outer size filled the available space in the given axis; // in other words, the stretch fit into the available space, if that is definite. diff --git a/Userland/Libraries/LibWeb/Layout/FormattingContext.h b/Userland/Libraries/LibWeb/Layout/FormattingContext.h index 18001637632..bb0d8e519a8 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.h @@ -25,7 +25,10 @@ public: SVG, }; - virtual void run(Box const&, LayoutMode, AvailableSpace const& available_width, AvailableSpace const& available_height) = 0; + virtual void run(Box const&, LayoutMode, AvailableSpace const&) = 0; + + // This function returns the automatic content height of the context's root box. + virtual float automatic_content_width() const { return 0; } // This function returns the automatic content height of the context's root box. virtual float automatic_content_height() const = 0; @@ -42,8 +45,8 @@ public: static bool creates_block_formatting_context(Box const&); - static float compute_width_for_replaced_element(LayoutState const&, ReplacedBox const&); - static float compute_height_for_replaced_element(LayoutState const&, ReplacedBox const&); + static float compute_width_for_replaced_element(LayoutState const&, ReplacedBox const&, AvailableSpace const&); + static float compute_height_for_replaced_element(LayoutState const&, ReplacedBox const&, AvailableSpace const&); OwnPtr create_independent_formatting_context_if_needed(LayoutState&, Box const& child_box); @@ -54,8 +57,8 @@ public: float calculate_min_content_height(Layout::Box const&) const; float calculate_max_content_height(Layout::Box const&) const; - float calculate_fit_content_height(Layout::Box const&, SizeConstraint, Optional available_height) const; - float calculate_fit_content_width(Layout::Box const&, SizeConstraint, Optional available_width) const; + float calculate_fit_content_height(Layout::Box const&, AvailableSize const&) const; + float calculate_fit_content_width(Layout::Box const&, AvailableSize const&) const; virtual float greatest_child_width(Box const&); @@ -69,14 +72,18 @@ public: float compute_box_y_position_with_respect_to_siblings(Box const&, LayoutState::UsedValues const&); - float calculate_stretch_fit_width(Box const&, AvailableSpace const& available_width) const; + float calculate_stretch_fit_width(Box const&, AvailableSize const&) const; + + virtual bool can_determine_size_of_child() const { return false; } + virtual void determine_width_of_child(Box const&, AvailableSpace const&) { } + virtual void determine_height_of_child(Box const&, AvailableSpace const&) { } protected: FormattingContext(Type, LayoutState&, Box const&, FormattingContext* parent = nullptr); - float calculate_fit_content_size(float min_content_size, float max_content_size, SizeConstraint, Optional available_space) const; + float calculate_fit_content_size(float min_content_size, float max_content_size, AvailableSize const&) const; - OwnPtr layout_inside(Box const&, LayoutMode); + OwnPtr layout_inside(Box const&, LayoutMode, AvailableSpace const&); void compute_inset(Box const& box); struct SpaceUsedByFloats { @@ -89,20 +96,20 @@ protected: float preferred_minimum_width { 0 }; }; - static float tentative_width_for_replaced_element(LayoutState const&, ReplacedBox const&, CSS::Size const& computed_width); - static float tentative_height_for_replaced_element(LayoutState const&, ReplacedBox const&, CSS::Size const& computed_height); - static float compute_auto_height_for_block_formatting_context_root(LayoutState const&, BlockContainer const&); - static float compute_auto_height_for_block_level_element(LayoutState const&, Box const&); + static float tentative_width_for_replaced_element(LayoutState const&, ReplacedBox const&, CSS::Size const& computed_width, AvailableSpace const&); + static float tentative_height_for_replaced_element(LayoutState const&, ReplacedBox const&, CSS::Size const& computed_height, AvailableSpace const&); + float compute_auto_height_for_block_formatting_context_root(BlockContainer const&) const; + float compute_auto_height_for_block_level_element(Box const&) const; ShrinkToFitResult calculate_shrink_to_fit_widths(Box const&); - void layout_absolutely_positioned_element(Box const&); - void compute_width_for_absolutely_positioned_element(Box const&); - void compute_width_for_absolutely_positioned_non_replaced_element(Box const&); - void compute_width_for_absolutely_positioned_replaced_element(ReplacedBox const&); - void compute_height_for_absolutely_positioned_element(Box const&); - void compute_height_for_absolutely_positioned_non_replaced_element(Box const&); - void compute_height_for_absolutely_positioned_replaced_element(ReplacedBox const&); + void layout_absolutely_positioned_element(Box const&, AvailableSpace const&); + void compute_width_for_absolutely_positioned_element(Box const&, AvailableSpace const&); + void compute_width_for_absolutely_positioned_non_replaced_element(Box const&, AvailableSpace const&); + void compute_width_for_absolutely_positioned_replaced_element(ReplacedBox const&, AvailableSpace const&); + void compute_height_for_absolutely_positioned_element(Box const&, AvailableSpace const&); + void compute_height_for_absolutely_positioned_non_replaced_element(Box const&, AvailableSpace const&); + void compute_height_for_absolutely_positioned_replaced_element(ReplacedBox const&, AvailableSpace const&); Type m_type {}; diff --git a/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp index 1ac3ee8941d..8bf6a686ebd 100644 --- a/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/GridFormattingContext.cpp @@ -17,7 +17,7 @@ GridFormattingContext::GridFormattingContext(LayoutState& state, BlockContainer GridFormattingContext::~GridFormattingContext() = default; -void GridFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] AvailableSpace const& available_width, [[maybe_unused]] AvailableSpace const& available_height) +void GridFormattingContext::run(Box const& box, LayoutMode, AvailableSpace const& available_space) { auto should_skip_is_anonymous_text_run = [&](Box& child_box) -> bool { if (child_box.is_anonymous() && !child_box.first_child_of_type()) { @@ -374,7 +374,8 @@ void GridFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] Ava auto& child_box_state = m_state.get_mutable(positioned_box.box); if (child_box_state.content_height() > positioned_box.computed_height) positioned_box.computed_height = child_box_state.content_height(); - (void)layout_inside(positioned_box.box, LayoutMode::Normal); + if (auto independent_formatting_context = layout_inside(positioned_box.box, LayoutMode::Normal, available_space)) + independent_formatting_context->parent_context_did_dimension_child_root_box(); if (child_box_state.content_height() > positioned_box.computed_height) positioned_box.computed_height = child_box_state.content_height(); } diff --git a/Userland/Libraries/LibWeb/Layout/GridFormattingContext.h b/Userland/Libraries/LibWeb/Layout/GridFormattingContext.h index 09a5450ed36..0e9f48cc146 100644 --- a/Userland/Libraries/LibWeb/Layout/GridFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/GridFormattingContext.h @@ -17,7 +17,7 @@ public: explicit GridFormattingContext(LayoutState&, BlockContainer const&, FormattingContext* parent); ~GridFormattingContext(); - virtual void run(Box const&, LayoutMode, AvailableSpace const& available_width, AvailableSpace const& available_height) override; + virtual void run(Box const&, LayoutMode, AvailableSpace const& available_space) override; virtual float automatic_content_height() const override; private: diff --git a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp index 351288f67ec..e174d2fdbea 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp @@ -24,17 +24,6 @@ InlineFormattingContext::InlineFormattingContext(LayoutState& state, BlockContai : FormattingContext(Type::Inline, state, containing_block, &parent) , m_containing_block_state(state.get(containing_block)) { - switch (m_containing_block_state.width_constraint) { - case SizeConstraint::MinContent: - m_effective_containing_block_width = 0; - break; - case SizeConstraint::MaxContent: - m_effective_containing_block_width = INFINITY; - break; - default: - m_effective_containing_block_width = m_containing_block_state.content_width(); - break; - } } InlineFormattingContext::~InlineFormattingContext() = default; @@ -55,43 +44,54 @@ float InlineFormattingContext::leftmost_x_offset_at(float y) const auto box_in_root_rect = content_box_rect_in_ancestor_coordinate_space(containing_block(), parent().root(), m_state); float y_in_root = box_in_root_rect.y() + y; auto space = parent().space_used_by_floats(y_in_root); - float containing_block_x = m_containing_block_state.offset.x(); - return max(space.left, containing_block_x) - containing_block_x; + return space.left; } float InlineFormattingContext::available_space_for_line(float y) const { - if (m_effective_containing_block_width == 0) - return 0; - if (!isfinite(m_effective_containing_block_width)) - return INFINITY; - // NOTE: Floats are relative to the BFC root box, not necessarily the containing block of this IFC. auto& root_block = parent().root(); auto box_in_root_rect = content_box_rect_in_ancestor_coordinate_space(containing_block(), root_block, m_state); float y_in_root = box_in_root_rect.y() + y; auto space = parent().space_used_by_floats(y_in_root); - space.left = max(space.left, m_containing_block_state.offset.x()) - m_containing_block_state.offset.x(); - space.right = min(m_containing_block_state.content_width() - space.right, m_containing_block_state.offset.x() + m_effective_containing_block_width); + space.left = space.left; + space.right = min(m_available_space->width.to_px() - space.right, m_available_space->width.to_px()); return space.right - space.left; } -float InlineFormattingContext::automatic_content_height() const +float InlineFormattingContext::automatic_content_width() const { - return compute_auto_height_for_block_formatting_context_root(m_state, containing_block()); + return m_automatic_content_width; } -void InlineFormattingContext::run(Box const&, LayoutMode layout_mode, [[maybe_unused]] AvailableSpace const& available_width, [[maybe_unused]] AvailableSpace const& available_height) +float InlineFormattingContext::automatic_content_height() const +{ + return m_automatic_content_height; +} + +void InlineFormattingContext::run(Box const&, LayoutMode layout_mode, AvailableSpace const& available_space) { VERIFY(containing_block().children_are_inline()); + m_available_space = available_space; generate_line_boxes(layout_mode); + + float max_line_width = 0; + float content_height = 0; + + for (auto& line_box : m_containing_block_state.line_boxes) { + max_line_width = max(max_line_width, line_box.width()); + content_height += line_box.height(); + } + + m_automatic_content_width = max_line_width; + m_automatic_content_height = content_height; } void InlineFormattingContext::dimension_box_on_line(Box const& box, LayoutMode layout_mode) { - auto width_of_containing_block = CSS::Length::make_px(m_effective_containing_block_width); + auto width_of_containing_block = CSS::Length::make_px(m_available_space->width.to_px()); auto& box_state = m_state.get_mutable(box); auto const& computed_values = box.computed_values(); @@ -115,10 +115,10 @@ void InlineFormattingContext::dimension_box_on_line(Box const& box, LayoutMode l auto& replaced = verify_cast(box); if (is(box)) - (void)layout_inside(replaced, layout_mode); + (void)layout_inside(replaced, layout_mode, *m_available_space); - box_state.set_content_width(compute_width_for_replaced_element(m_state, replaced)); - box_state.set_content_height(compute_height_for_replaced_element(m_state, replaced)); + box_state.set_content_width(compute_width_for_replaced_element(m_state, replaced, *m_available_space)); + box_state.set_content_height(compute_height_for_replaced_element(m_state, replaced, *m_available_space)); return; } @@ -129,7 +129,7 @@ void InlineFormattingContext::dimension_box_on_line(Box const& box, LayoutMode l if (width_value.is_auto()) { auto result = calculate_shrink_to_fit_widths(inline_block); - auto available_width = m_containing_block_state.content_width() + auto available_width = m_available_space->width.to_px() - box_state.margin_left - box_state.border_left - box_state.padding_left @@ -140,15 +140,19 @@ void InlineFormattingContext::dimension_box_on_line(Box const& box, LayoutMode l auto width = min(max(result.preferred_minimum_width, available_width), result.preferred_width); box_state.set_content_width(width); } else { - auto container_width = CSS::Length::make_px(m_effective_containing_block_width); - box_state.set_content_width(width_value.resolved(box, container_width).to_px(inline_block)); + if (width_value.contains_percentage() && !m_available_space->width.is_definite()) { + // NOTE: We can't resolve percentages yet. We'll have to wait until after inner layout. + } else { + auto container_width = CSS::Length::make_px(m_available_space->width.to_px()); + box_state.set_content_width(width_value.resolved(box, container_width).to_px(inline_block)); + } } - auto independent_formatting_context = layout_inside(inline_block, layout_mode); + auto independent_formatting_context = layout_inside(inline_block, layout_mode, box_state.available_inner_space_or_constraints_from(*m_available_space)); auto& height_value = inline_block.computed_values().height(); if (height_value.is_auto()) { // FIXME: (10.6.6) If 'height' is 'auto', the height depends on the element's descendants per 10.6.7. - BlockFormattingContext::compute_height(inline_block, m_state); + parent().compute_height(inline_block, AvailableSpace(AvailableSize::make_indefinite(), AvailableSize::make_indefinite())); } else { auto container_height = CSS::Length::make_px(m_containing_block_state.content_height()); box_state.set_content_height(height_value.resolved(box, container_height).to_px(inline_block)); @@ -177,11 +181,11 @@ void InlineFormattingContext::apply_justification_to_fragments(CSS::TextJustify break; } - float excess_horizontal_space = m_effective_containing_block_width - line_box.width(); + float excess_horizontal_space = m_available_space->width.to_px() - line_box.width(); // Only justify the text if the excess horizontal space is less than or // equal to 10%, or if we are not looking at the last line box. - if (is_last_line && excess_horizontal_space / m_effective_containing_block_width > text_justification_threshold) + if (is_last_line && excess_horizontal_space / m_available_space->width.to_px() > text_justification_threshold) return; float excess_horizontal_space_including_whitespace = excess_horizontal_space; @@ -250,7 +254,7 @@ void InlineFormattingContext::generate_line_boxes(LayoutMode layout_mode) case InlineLevelIterator::Item::Type::FloatingElement: if (is(*item.node)) - parent().layout_floating_box(static_cast(*item.node), containing_block(), layout_mode, &line_builder); + parent().layout_floating_box(static_cast(*item.node), containing_block(), layout_mode, *m_available_space, &line_builder); break; case InlineLevelIterator::Item::Type::Text: { @@ -318,9 +322,9 @@ bool InlineFormattingContext::can_fit_new_line_at_y(float y) const auto space_bottom = parent().space_used_by_floats(y_in_root + containing_block().line_height() - 1); [[maybe_unused]] auto top_left_edge = space_top.left; - [[maybe_unused]] auto top_right_edge = m_effective_containing_block_width - space_top.right; + [[maybe_unused]] auto top_right_edge = m_available_space->width.to_px() - space_top.right; [[maybe_unused]] auto bottom_left_edge = space_bottom.left; - [[maybe_unused]] auto bottom_right_edge = m_effective_containing_block_width - space_bottom.right; + [[maybe_unused]] auto bottom_right_edge = m_available_space->width.to_px() - space_bottom.right; if (top_left_edge > bottom_right_edge) return false; diff --git a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.h b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.h index 8bb801f6582..03de64809ae 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.h @@ -23,8 +23,9 @@ public: BlockContainer const& containing_block() const { return static_cast(context_box()); } - virtual void run(Box const&, LayoutMode, AvailableSpace const& available_width, AvailableSpace const& available_height) override; + virtual void run(Box const&, LayoutMode, AvailableSpace const&) override; virtual float automatic_content_height() const override; + virtual float automatic_content_width() const override; void dimension_box_on_line(Box const&, LayoutMode); @@ -33,14 +34,16 @@ public: bool any_floats_intrude_at_y(float y) const; bool can_fit_new_line_at_y(float y) const; - float effective_containing_block_width() const { return m_effective_containing_block_width; } - private: void generate_line_boxes(LayoutMode); void apply_justification_to_fragments(CSS::TextJustify, LineBox&, bool is_last_line); LayoutState::UsedValues const& m_containing_block_state; - float m_effective_containing_block_width { 0 }; + + Optional m_available_space; + + float m_automatic_content_width { 0 }; + float m_automatic_content_height { 0 }; }; } diff --git a/Userland/Libraries/LibWeb/Layout/LayoutState.cpp b/Userland/Libraries/LibWeb/Layout/LayoutState.cpp index 940059baf78..ed110d252b8 100644 --- a/Userland/Libraries/LibWeb/Layout/LayoutState.cpp +++ b/Userland/Libraries/LibWeb/Layout/LayoutState.cpp @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-2-Clause */ +#include #include #include #include @@ -195,9 +196,21 @@ void LayoutState::UsedValues::set_node(NodeWithStyleAndBoxModelMetrics& node, Us if (size.is_auto()) { // NOTE: The width of a non-flex-item block is considered definite if it's auto and the containing block has definite width. - if (width && node.parent() && !node.parent()->computed_values().display().is_flex_inside()) { + if (width + && node.computed_values().display().is_block_outside() + && node.parent() + && !node.parent()->is_floating() + && (node.parent()->computed_values().display().is_flow_root_inside() + || node.parent()->computed_values().display().is_flow_inside())) { if (containing_block_has_definite_size) { - resolved_definite_size = width ? containing_block_used_values->content_width() : containing_block_used_values->content_height(); + float available_width = containing_block_used_values->content_width(); + resolved_definite_size = available_width + - margin_left + - margin_right + - padding_left + - padding_right + - border_left + - border_right; return true; } return false; @@ -205,9 +218,22 @@ void LayoutState::UsedValues::set_node(NodeWithStyleAndBoxModelMetrics& node, Us return false; } - if (size.is_length()) { - if (size.length().is_calculated()) + if (size.is_length() && size.length().is_calculated()) { + if (width && size.length().calculated_style_value()->contains_percentage() && containing_block_has_definite_size) { + auto& calc_value = *size.length().calculated_style_value(); + auto containing_block_width_as_length = CSS::Length::make_px(containing_block_used_values->content_width()); + resolved_definite_size = calc_value.resolve_length_percentage(node, containing_block_width_as_length).value_or(CSS::Length::make_auto()).to_px(node); return false; + } + if (size.length().calculated_style_value()->contains_percentage()) + return false; + resolved_definite_size = size.length().to_px(node); + return true; + } + + if (size.is_length()) { + VERIFY(!size.is_auto()); // This should have been covered by the Size::is_auto() branch above. + VERIFY(!size.length().is_calculated()); // Covered above. resolved_definite_size = size.length().to_px(node); return true; } @@ -230,33 +256,73 @@ void LayoutState::UsedValues::set_node(NodeWithStyleAndBoxModelMetrics& node, Us void LayoutState::UsedValues::set_content_width(float width) { m_content_width = width; + m_has_definite_width = true; } void LayoutState::UsedValues::set_content_height(float height) { m_content_height = height; + m_has_definite_height = true; } float LayoutState::resolved_definite_width(Box const& box) const { - auto const& computed_value = box.computed_values().width(); - if (computed_value.is_auto()) - return get(*box.containing_block()).content_width(); - if (computed_value.is_length()) - return get(box).content_width(); - auto containing_block_size = get(*box.containing_block()).content_width(); - return computed_value.resolved(box, CSS::Length::make_px(containing_block_size)).to_px(box); + return get(box).content_width(); } float LayoutState::resolved_definite_height(Box const& box) const { - auto const& computed_value = box.computed_values().height(); - if (computed_value.is_auto()) - return get(*box.containing_block()).content_height(); - if (computed_value.is_length()) - return get(box).content_height(); - auto containing_block_size = get(*box.containing_block()).content_height(); - return computed_value.resolved(box, CSS::Length::make_px(containing_block_size)).to_px(box); + return get(box).content_height(); +} + +AvailableSize LayoutState::UsedValues::available_width_inside() const +{ + if (width_constraint == SizeConstraint::MinContent) + return AvailableSize::make_min_content(); + if (width_constraint == SizeConstraint::MaxContent) + return AvailableSize::make_max_content(); + if (has_definite_width()) + return AvailableSize::make_definite(m_content_width); + return AvailableSize::make_indefinite(); +} + +AvailableSize LayoutState::UsedValues::available_height_inside() const +{ + if (height_constraint == SizeConstraint::MinContent) + return AvailableSize::make_min_content(); + if (height_constraint == SizeConstraint::MaxContent) + return AvailableSize::make_max_content(); + if (has_definite_height()) + return AvailableSize::make_definite(m_content_height); + return AvailableSize::make_indefinite(); +} + +AvailableSpace LayoutState::UsedValues::available_inner_space_or_constraints_from(AvailableSpace const& outer_space) const +{ + auto inner_width = available_width_inside(); + auto inner_height = available_height_inside(); + + if (inner_width.is_indefinite() && outer_space.width.is_intrinsic_sizing_constraint()) + inner_width = outer_space.width; + if (inner_height.is_indefinite() && outer_space.height.is_intrinsic_sizing_constraint()) + inner_height = outer_space.height; + return AvailableSpace(inner_width, inner_height); +} + +void LayoutState::UsedValues::set_content_offset(Gfx::FloatPoint offset) +{ + set_content_x(offset.x()); + set_content_y(offset.y()); +} + +void LayoutState::UsedValues::set_content_x(float x) +{ + offset.set_x(x); +} + +void LayoutState::UsedValues::set_content_y(float y) +{ + offset.set_y(y); } } diff --git a/Userland/Libraries/LibWeb/Layout/LayoutState.h b/Userland/Libraries/LibWeb/Layout/LayoutState.h index 091e79dee24..b82bb775ae0 100644 --- a/Userland/Libraries/LibWeb/Layout/LayoutState.h +++ b/Userland/Libraries/LibWeb/Layout/LayoutState.h @@ -20,6 +20,9 @@ enum class SizeConstraint { MaxContent, }; +class AvailableSize; +class AvailableSpace; + struct LayoutState { LayoutState() : m_root(*this) @@ -52,8 +55,15 @@ struct LayoutState { bool has_definite_width() const { return m_has_definite_width && width_constraint == SizeConstraint::None; } bool has_definite_height() const { return m_has_definite_height && height_constraint == SizeConstraint::None; } - void set_has_definite_width(bool value) { m_has_definite_width = value; } - void set_has_definite_height(bool value) { m_has_definite_height = value; } + + // Returns the available space for content inside this layout box. + // If the space in an axis is indefinite, and the outer space is an intrinsic sizing constraint, + // the constraint is used in that axis instead. + AvailableSpace available_inner_space_or_constraints_from(AvailableSpace const& outer_space) const; + + void set_content_offset(Gfx::FloatPoint); + void set_content_x(float); + void set_content_y(float); Gfx::FloatPoint offset; @@ -113,6 +123,9 @@ struct LayoutState { auto const& floating_descendants() const { return m_floating_descendants; } private: + AvailableSize available_width_inside() const; + AvailableSize available_height_inside() const; + Layout::NodeWithStyleAndBoxModelMetrics* m_node { nullptr }; float m_content_width { 0 }; diff --git a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp index e7b0f5f1548..aacfa2be84c 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.cpp @@ -25,7 +25,7 @@ float SVGFormattingContext::automatic_content_height() const return 0; } -void SVGFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] AvailableSpace const& available_width, [[maybe_unused]] AvailableSpace const& available_height) +void SVGFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] AvailableSpace const& available_space) { auto& svg_svg_element = verify_cast(*box.dom_node()); @@ -38,7 +38,7 @@ void SVGFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] Avai auto& dom_node = const_cast(geometry_box).dom_node(); if (svg_svg_element.has_attribute(HTML::AttributeNames::width) && svg_svg_element.has_attribute(HTML::AttributeNames::height)) { - geometry_box_state.offset = { 0, 0 }; + geometry_box_state.set_content_offset({ 0, 0 }); auto& layout_node = *svg_svg_element.layout_node(); // FIXME: Allow for relative lengths here @@ -67,7 +67,7 @@ void SVGFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] Avai if (maybe_view_box.has_value()) { auto view_box = maybe_view_box.value(); Gfx::FloatPoint viewbox_offset = { view_box.min_x, view_box.min_y }; - geometry_box_state.offset = path_bounding_box.top_left() + viewbox_offset; + geometry_box_state.set_content_offset(path_bounding_box.top_left() + viewbox_offset); geometry_box_state.set_content_width(view_box.width); geometry_box_state.set_content_height(view_box.height); @@ -75,7 +75,7 @@ void SVGFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] Avai return IterationDecision::Continue; } - geometry_box_state.offset = path_bounding_box.top_left(); + geometry_box_state.set_content_offset(path_bounding_box.top_left()); geometry_box_state.set_content_width(path_bounding_box.width()); geometry_box_state.set_content_height(path_bounding_box.height()); } diff --git a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.h b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.h index 6c6d011f1bf..0d403d99a4a 100644 --- a/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/SVGFormattingContext.h @@ -16,7 +16,7 @@ public: explicit SVGFormattingContext(LayoutState&, Box const&, FormattingContext* parent); ~SVGFormattingContext(); - virtual void run(Box const&, LayoutMode, AvailableSpace const& available_width, AvailableSpace const& available_height) override; + virtual void run(Box const&, LayoutMode, AvailableSpace const&) override; virtual float automatic_content_height() const override; }; diff --git a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp index f391be5efb4..5e2a95dd1b0 100644 --- a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.cpp @@ -23,11 +23,11 @@ TableFormattingContext::TableFormattingContext(LayoutState& state, BlockContaine TableFormattingContext::~TableFormattingContext() = default; -void TableFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] AvailableSpace const& available_width, [[maybe_unused]] AvailableSpace const& available_height) +void TableFormattingContext::run(Box const& box, LayoutMode, AvailableSpace const& available_space) { auto& box_state = m_state.get_mutable(box); - compute_width(box); + compute_width(box, available_space); auto table_width = CSS::Length::make_px(box_state.content_width()); auto table_width_is_auto = box.computed_values().width().is_auto(); @@ -36,12 +36,12 @@ void TableFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] Av Vector column_widths; box.for_each_child_of_type([&](auto& row_group_box) { - compute_width(row_group_box); + compute_width(row_group_box, available_space); auto column_count = row_group_box.column_count(); column_widths.resize(max(column_count, column_widths.size())); row_group_box.template for_each_child_of_type([&](auto& row) { - calculate_column_widths(row, table_width, column_widths); + calculate_column_widths(row, table_width, column_widths, available_space); }); }); box.for_each_child_of_type([&](auto& row_group_box) { @@ -89,7 +89,7 @@ void TableFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] Av row_group_box.template for_each_child_of_type([&](auto& row) { auto& row_state = m_state.get_mutable(row); row_state.offset = { 0, content_height }; - layout_row(row, column_widths); + layout_row(row, column_widths, available_space); content_width = max(content_width, row_state.content_width()); content_height += row_state.content_height(); }); @@ -110,7 +110,7 @@ void TableFormattingContext::run(Box const& box, LayoutMode, [[maybe_unused]] Av m_automatic_content_height = total_content_height; } -void TableFormattingContext::calculate_column_widths(Box const& row, CSS::Length const& table_width, Vector& column_widths) +void TableFormattingContext::calculate_column_widths(Box const& row, CSS::Length const& table_width, Vector& column_widths, AvailableSpace const& available_space) { m_state.get_mutable(row); size_t column_index = 0; @@ -123,10 +123,11 @@ void TableFormattingContext::calculate_column_widths(Box const& row, CSS::Length auto width = calculate_max_content_width(cell); cell_state.set_content_width(width); } else { - compute_width(cell, LayoutMode::Normal); + compute_width(cell, AvailableSpace(AvailableSize::make_indefinite(), AvailableSize::make_indefinite()), LayoutMode::Normal); } - (void)layout_inside(cell, LayoutMode::Normal); + if (auto independent_formatting_context = layout_inside(cell, LayoutMode::Normal, cell_state.available_inner_space_or_constraints_from(available_space))) + independent_formatting_context->parent_context_did_dimension_child_root_box(); if (cell.colspan() == 1) { auto min_width = calculate_min_content_width(cell); @@ -167,7 +168,7 @@ void TableFormattingContext::calculate_column_widths(Box const& row, CSS::Length }); } -void TableFormattingContext::layout_row(Box const& row, Vector& column_widths) +void TableFormattingContext::layout_row(Box const& row, Vector& column_widths, AvailableSpace const& available_space) { auto& row_state = m_state.get_mutable(row); size_t column_index = 0; @@ -184,11 +185,12 @@ void TableFormattingContext::layout_row(Box const& row, Vector& col span_width += column_widths[column_index++].used; cell_state.set_content_width(span_width - cell_state.border_box_left() - cell_state.border_box_right()); - BlockFormattingContext::compute_height(cell, m_state); + BlockFormattingContext::compute_height(cell, AvailableSpace(AvailableSize::make_indefinite(), AvailableSize::make_indefinite())); cell_state.offset = row_state.offset.translated(cell_state.border_box_left() + content_width, cell_state.border_box_top()); // Layout the cell contents a second time, now that we know its final width. - (void)layout_inside(cell, LayoutMode::Normal); + if (auto independent_formatting_context = layout_inside(cell, LayoutMode::Normal, cell_state.available_inner_space_or_constraints_from(available_space))) + independent_formatting_context->parent_context_did_dimension_child_root_box(); content_width += span_width; tallest_cell_height = max(tallest_cell_height, cell_state.border_box_height()); diff --git a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h index afef496e96c..9f4d56d5ba2 100644 --- a/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/TableFormattingContext.h @@ -23,12 +23,12 @@ public: explicit TableFormattingContext(LayoutState&, BlockContainer const&, FormattingContext* parent); ~TableFormattingContext(); - virtual void run(Box const&, LayoutMode, AvailableSpace const& available_width, AvailableSpace const& available_height) override; + virtual void run(Box const&, LayoutMode, AvailableSpace const&) override; virtual float automatic_content_height() const override; private: - void calculate_column_widths(Box const& row, CSS::Length const& table_width, Vector& column_widths); - void layout_row(Box const& row, Vector& column_widths); + void calculate_column_widths(Box const& row, CSS::Length const& table_width, Vector& column_widths, AvailableSpace const&); + void layout_row(Box const& row, Vector& column_widths, AvailableSpace const&); float m_automatic_content_height { 0 }; };