From b13a8706e143368d769a408b2a992c991b94abf4 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 3 Oct 2022 23:37:38 +0200 Subject: [PATCH] LibWeb: Make intrinsic heights dependent on available width After speaking with fantasai at CSSWG about this, it turns out I had misunderstood intrinsic heights. I originally believed all intrinsic sizes had to be computed with no influence from the surrounding context. As it turns out, intrinsic heights *are* influenced by the available width, since it's needed to determine where lines break. The APIs for calculating min-content and max-content heights now take the available width as inputs. This instantly improves layout in many cases where we'd previously make things way too wide. --- .../LibWeb/Layout/BlockFormattingContext.cpp | 2 +- .../LibWeb/Layout/FlexFormattingContext.cpp | 50 +++++++++---------- .../LibWeb/Layout/FlexFormattingContext.h | 3 +- .../LibWeb/Layout/FormattingContext.cpp | 40 +++++++-------- .../LibWeb/Layout/FormattingContext.h | 10 ++-- 5 files changed, 48 insertions(+), 57 deletions(-) diff --git a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp index 4f5145d4340..90080345aaf 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp @@ -311,7 +311,7 @@ void BlockFormattingContext::compute_height(Box const& box, AvailableSpace const height = compute_height_for_replaced_element(m_state, verify_cast(box), available_space); } else { if (should_treat_height_as_auto(box, available_space)) { - height = compute_auto_height_for_block_level_element(box); + height = compute_auto_height_for_block_level_element(box, available_space); } else { height = computed_values.height().resolved(box, containing_block_height).to_px(box); } diff --git a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp index eb13e6966f6..ad9cd016527 100644 --- a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp @@ -86,8 +86,7 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode, AvailableSpace c 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, + .space = { available_width, available_height }, }; // This implements https://www.w3.org/TR/css-flexbox-1/#layout-algorithm @@ -95,6 +94,11 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode, AvailableSpace c // 1. Generate anonymous flex items generate_anonymous_flex_items(); + // 2. Determine the available main and cross space for the flex items + 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)); + { // https://drafts.csswg.org/css-flexbox-1/#definite-sizes // 3. If a single-line flex container has a definite cross size, @@ -114,11 +118,6 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode, AvailableSpace c } } - // 2. Determine the available main and cross space for the flex items - 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) { if (flex_item.box.is_replaced_box()) { @@ -209,7 +208,7 @@ void FlexFormattingContext::run(Box const& run_box, LayoutMode, AvailableSpace c copy_dimensions_from_flex_items_to_boxes(); for (auto& flex_item : m_flex_items) { 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)))) + if (auto independent_formatting_context = layout_inside(flex_item.box, LayoutMode::Normal, box_state.available_inner_space_or_constraints_from(m_available_space_for_flex_container->space))) independent_formatting_context->parent_context_did_dimension_child_root_box(); } @@ -535,15 +534,13 @@ void FlexFormattingContext::determine_available_space_for_items(AvailableSpace c 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, + .space = { *available_width_for_items, *available_height_for_items }, }; } else { 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, + .space = { *available_width_for_items, *available_height_for_items }, }; } } @@ -582,7 +579,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(m_available_space_for_items->width, m_available_space_for_items->height)); + independent_formatting_context->run(item.box, LayoutMode::Normal, m_available_space_for_items->space); return independent_formatting_context->automatic_content_height(); } @@ -780,11 +777,10 @@ void FlexFormattingContext::determine_main_size_of_flex_container() // 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); + parent()->determine_width_of_child(flex_container(), m_available_space_for_flex_container->space); } else { - parent()->determine_height_of_child(flex_container(), available_space); + parent()->determine_height_of_child(flex_container(), m_available_space_for_flex_container->space); } return; } @@ -796,13 +792,13 @@ void FlexFormattingContext::determine_main_size_of_flex_container() 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)); + set_main_size(flex_container(), calculate_stretch_fit_width(flex_container(), m_available_space_for_flex_container->space.width)); } 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(), calculate_max_content_height(flex_container(), m_available_space_for_flex_container->space.width)); } } @@ -1060,7 +1056,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(m_available_space_for_items->width, m_available_space_for_items->height)); + independent_formatting_context->run(item.box, LayoutMode::Normal, m_available_space_for_items->space); auto automatic_cross_size = is_row_layout() ? independent_formatting_context->automatic_content_height() : box_state.content_width(); @@ -1660,34 +1656,34 @@ float FlexFormattingContext::calculate_cross_max_content_contribution(FlexItem c float FlexFormattingContext::calculate_min_content_main_size(FlexItem const& item) const { - return is_row_layout() ? calculate_min_content_width(item.box) : calculate_min_content_height(item.box); + return is_row_layout() ? calculate_min_content_width(item.box) : calculate_min_content_height(item.box, m_available_space_for_items->space.width); } float FlexFormattingContext::calculate_fit_content_main_size(FlexItem const& item) const { - 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); + return is_row_layout() ? calculate_fit_content_width(item.box, m_state.get(item.box).available_inner_space_or_constraints_from(m_available_space_for_items->space)) + : calculate_fit_content_height(item.box, m_state.get(item.box).available_inner_space_or_constraints_from(m_available_space_for_items->space)); } float FlexFormattingContext::calculate_fit_content_cross_size(FlexItem const& item) const { - 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); + return !is_row_layout() ? calculate_fit_content_width(item.box, m_state.get(item.box).available_inner_space_or_constraints_from(m_available_space_for_items->space)) + : calculate_fit_content_height(item.box, m_state.get(item.box).available_inner_space_or_constraints_from(m_available_space_for_items->space)); } float FlexFormattingContext::calculate_max_content_main_size(FlexItem const& item) const { - return is_row_layout() ? calculate_max_content_width(item.box) : calculate_max_content_height(item.box); + return is_row_layout() ? calculate_max_content_width(item.box) : calculate_max_content_height(item.box, m_available_space_for_items->space.width); } float FlexFormattingContext::calculate_min_content_cross_size(FlexItem const& item) const { - return is_row_layout() ? calculate_min_content_height(item.box) : calculate_min_content_width(item.box); + return is_row_layout() ? calculate_min_content_height(item.box, m_available_space_for_items->space.width) : calculate_min_content_width(item.box); } float FlexFormattingContext::calculate_max_content_cross_size(FlexItem const& item) const { - return is_row_layout() ? calculate_max_content_height(item.box) : calculate_max_content_width(item.box); + return is_row_layout() ? calculate_max_content_height(item.box, m_available_space_for_items->space.width) : calculate_max_content_width(item.box); } // https://drafts.csswg.org/css-flexbox-1/#stretched diff --git a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h index d3031e23d85..bedafc54fef 100644 --- a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h @@ -185,8 +185,7 @@ private: struct AxisAgnosticAvailableSpace { AvailableSize main; AvailableSize cross; - AvailableSize width; - AvailableSize height; + AvailableSpace 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 4b30852f0c2..25377d52e61 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp @@ -239,7 +239,7 @@ static Gfx::FloatSize solve_replaced_size_constraint(LayoutState const& state, f return { w, h }; } -float FormattingContext::compute_auto_height_for_block_level_element(Box const& box) const +float FormattingContext::compute_auto_height_for_block_level_element(Box const& box, AvailableSpace const& available_space) const { if (creates_block_formatting_context(box)) return compute_auto_height_for_block_formatting_context_root(verify_cast(box)); @@ -250,7 +250,7 @@ float FormattingContext::compute_auto_height_for_block_level_element(Box const& 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); + return calculate_max_content_height(box, available_space.width); } // https://www.w3.org/TR/CSS22/visudet.html#normal-block @@ -694,11 +694,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(box), CSS::Length::Type::Px); + tentative_height = CSS::Length(compute_auto_height_for_block_level_element(box, available_space), 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(box), CSS::Length::Type::Px); + tentative_height = CSS::Length(compute_auto_height_for_block_level_element(box, available_space), 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; } @@ -926,30 +926,30 @@ float FormattingContext::calculate_fit_content_size(float min_content_size, floa return max_content_size; } -float FormattingContext::calculate_fit_content_width(Layout::Box const& box, AvailableSize const& available_size) const +float FormattingContext::calculate_fit_content_width(Layout::Box const& box, AvailableSpace const& available_space) 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 (available_size.is_min_content()) + if (available_space.width.is_min_content()) return calculate_min_content_width(box); - if (available_size.is_max_content()) + if (available_space.width.is_max_content()) return calculate_max_content_width(box); - return calculate_fit_content_size(calculate_min_content_width(box), calculate_max_content_width(box), available_size); + return calculate_fit_content_size(calculate_min_content_width(box), calculate_max_content_width(box), available_space.width); } -float FormattingContext::calculate_fit_content_height(Layout::Box const& box, AvailableSize const& available_size) const +float FormattingContext::calculate_fit_content_height(Layout::Box const& box, AvailableSpace const& available_space) 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 (available_size.is_min_content()) - return calculate_min_content_height(box); + if (available_space.height.is_min_content()) + return calculate_min_content_height(box, available_space.width); - if (available_size.is_max_content()) - return calculate_max_content_height(box); + if (available_space.height.is_max_content()) + return calculate_max_content_height(box, available_space.width); - return calculate_fit_content_size(calculate_min_content_height(box), calculate_max_content_height(box), available_size); + return calculate_fit_content_size(calculate_min_content_height(box, available_space.width), calculate_max_content_height(box, available_space.width), available_space.height); } float FormattingContext::calculate_min_content_width(Layout::Box const& box) const @@ -1028,7 +1028,7 @@ float FormattingContext::calculate_max_content_width(Layout::Box const& box) con return *cache.max_content_width; } -float FormattingContext::calculate_min_content_height(Layout::Box const& box) const +float FormattingContext::calculate_min_content_height(Layout::Box const& box, AvailableSize const& available_width) const { if (box.has_intrinsic_height()) return *box.intrinsic_height(); @@ -1047,9 +1047,7 @@ float FormattingContext::calculate_min_content_height(Layout::Box const& box) co auto context = const_cast(this)->create_independent_formatting_context_if_needed(throwaway_state, box); VERIFY(context); - auto available_width = AvailableSize::make_indefinite(); - auto available_height = AvailableSize::make_min_content(); - context->run(box, LayoutMode::IntrinsicSizing, AvailableSpace(available_width, available_height)); + context->run(box, LayoutMode::IntrinsicSizing, AvailableSpace(available_width, AvailableSize::make_min_content())); cache.min_content_height = context->automatic_content_height(); @@ -1062,7 +1060,7 @@ float FormattingContext::calculate_min_content_height(Layout::Box const& box) co return *cache.min_content_height; } -float FormattingContext::calculate_max_content_height(Layout::Box const& box) const +float FormattingContext::calculate_max_content_height(Layout::Box const& box, AvailableSize const& available_width) const { if (box.has_intrinsic_height()) return *box.intrinsic_height(); @@ -1081,9 +1079,7 @@ float FormattingContext::calculate_max_content_height(Layout::Box const& box) co auto context = const_cast(this)->create_independent_formatting_context_if_needed(throwaway_state, box); VERIFY(context); - auto available_width = AvailableSize::make_indefinite(); - auto available_height = AvailableSize::make_max_content(); - context->run(box, LayoutMode::IntrinsicSizing, AvailableSpace(available_width, available_height)); + context->run(box, LayoutMode::IntrinsicSizing, AvailableSpace(available_width, AvailableSize::make_max_content())); cache.max_content_height = context->automatic_content_height(); diff --git a/Userland/Libraries/LibWeb/Layout/FormattingContext.h b/Userland/Libraries/LibWeb/Layout/FormattingContext.h index 7560fc44c21..35f62659efb 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.h @@ -54,11 +54,11 @@ public: float calculate_min_content_width(Layout::Box const&) const; float calculate_max_content_width(Layout::Box const&) const; - float calculate_min_content_height(Layout::Box const&) const; - float calculate_max_content_height(Layout::Box const&) const; + float calculate_min_content_height(Layout::Box const&, AvailableSize const& available_width) const; + float calculate_max_content_height(Layout::Box const&, AvailableSize const& 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; + float calculate_fit_content_height(Layout::Box const&, AvailableSpace const&) const; + float calculate_fit_content_width(Layout::Box const&, AvailableSpace const&) const; virtual float greatest_child_width(Box const&); @@ -99,7 +99,7 @@ protected: 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; + float compute_auto_height_for_block_level_element(Box const&, AvailableSpace const&) const; ShrinkToFitResult calculate_shrink_to_fit_widths(Box const&);