From 9c44634ca58fc940d0661f8c9427043fa5ff3194 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Tue, 27 Sep 2022 15:29:17 +0200 Subject: [PATCH] LibWeb: Reorganize layout algorithms around available space This is a big and messy change, and here's the gist: - AvaliableSpace is now 2x AvailableSize (width and height) - Layout algorithms are redesigned around the idea of available space - When doing layout across nested formatting contexts, the parent context tells the child context how much space is available for the child's root box in both axes. - "Available space" replaces "containing block width" in most places. - The width and height in a box's UsedValues are considered to be definite after they're assigned to. Marking something as having definite size is no longer a separate step, This probably introduces various regressions, but the big win here is that our layout system now works with available space, just like the specs are written. Fixing issues will be much easier going forward, since you don't need to do nearly as much conversion from "spec logic" to "LibWeb logic" as you previously did. --- Userland/Libraries/LibWeb/DOM/Document.cpp | 7 +- .../LibWeb/Layout/AvailableSpace.cpp | 25 +- .../Libraries/LibWeb/Layout/AvailableSpace.h | 41 +- .../LibWeb/Layout/BlockFormattingContext.cpp | 265 ++++++------ .../LibWeb/Layout/BlockFormattingContext.h | 42 +- .../LibWeb/Layout/FlexFormattingContext.cpp | 388 ++++++++---------- .../LibWeb/Layout/FlexFormattingContext.h | 33 +- .../LibWeb/Layout/FormattingContext.cpp | 202 ++++----- .../LibWeb/Layout/FormattingContext.h | 45 +- .../LibWeb/Layout/GridFormattingContext.cpp | 5 +- .../LibWeb/Layout/GridFormattingContext.h | 2 +- .../LibWeb/Layout/InlineFormattingContext.cpp | 78 ++-- .../LibWeb/Layout/InlineFormattingContext.h | 11 +- .../Libraries/LibWeb/Layout/LayoutState.cpp | 102 ++++- .../Libraries/LibWeb/Layout/LayoutState.h | 17 +- .../LibWeb/Layout/SVGFormattingContext.cpp | 8 +- .../LibWeb/Layout/SVGFormattingContext.h | 2 +- .../LibWeb/Layout/TableFormattingContext.cpp | 24 +- .../LibWeb/Layout/TableFormattingContext.h | 6 +- 19 files changed, 651 insertions(+), 652 deletions(-) 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 }; };