From 3f2b17f602801314520ab56b733faf1879afe32b Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Sat, 12 Mar 2022 14:36:59 +0100 Subject: [PATCH] LibWeb: Add functions for calculating intrinsic sizes of a Layout::Box FormattingContext can now calculate the intrinsic sizes (min-content and max-content in both axes) for a given Layout::Box. This is a rather expensive operation, as it necessitates performing two throwaway layouts of the subtree rooted at the box. Fortunately, we can cache the results of these calculations, as intrinsic sizes don't change based on other context around the box. They are intrinsic after all. :^) --- .../LibWeb/Layout/FormattingContext.cpp | 78 +++++++++++++++++++ .../LibWeb/Layout/FormattingContext.h | 14 ++++ .../Libraries/LibWeb/Layout/FormattingState.h | 8 ++ 3 files changed, 100 insertions(+) diff --git a/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp index 2f8ea7f2db..3baf0a60b0 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp @@ -811,4 +811,82 @@ void FormattingContext::compute_position(Box const& box) } } +FormattingState::IntrinsicSizes FormattingContext::calculate_intrinsic_sizes(Layout::Box const& box) const +{ + // If we have cached intrinsic sizes for this box, use them. + auto it = m_state.intrinsic_sizes.find(&box); + if (it != m_state.intrinsic_sizes.end()) + return it->value; + + // Nothing cached, perform two throwaway layouts to determine the intrinsic sizes. + // FIXME: This should handle replaced elements with "native" intrinsic size properly! + + auto& cached_box_sizes = m_state.intrinsic_sizes.ensure(&box); + auto const& containing_block = *box.containing_block(); + { + auto throwaway_state = m_state; + throwaway_state.get_mutable(containing_block).content_width = INFINITY; + throwaway_state.get_mutable(containing_block).content_height = INFINITY; + auto independent_formatting_context = const_cast(this)->create_independent_formatting_context_if_needed(throwaway_state, box); + VERIFY(independent_formatting_context); + + independent_formatting_context->run(box, LayoutMode::OnlyRequiredLineBreaks); + cached_box_sizes.max_content_size.set_width(greatest_child_width(throwaway_state, box)); + cached_box_sizes.max_content_size.set_height(BlockFormattingContext::compute_theoretical_height(throwaway_state, box)); + } + + { + auto throwaway_state = m_state; + throwaway_state.get_mutable(containing_block).content_width = 0; + throwaway_state.get_mutable(containing_block).content_height = 0; + auto independent_formatting_context = const_cast(this)->create_independent_formatting_context_if_needed(throwaway_state, box); + VERIFY(independent_formatting_context); + independent_formatting_context->run(box, LayoutMode::AllPossibleLineBreaks); + cached_box_sizes.min_content_size.set_width(greatest_child_width(throwaway_state, box)); + cached_box_sizes.min_content_size.set_height(BlockFormattingContext::compute_theoretical_height(throwaway_state, box)); + } + + return cached_box_sizes; +} + +FormattingContext::MinAndMaxContentSize FormattingContext::calculate_min_and_max_content_width(Layout::Box const& box) const +{ + auto const& sizes = calculate_intrinsic_sizes(box); + return { sizes.min_content_size.width(), sizes.max_content_size.width() }; +} + +FormattingContext::MinAndMaxContentSize FormattingContext::calculate_min_and_max_content_height(Layout::Box const& box) const +{ + auto const& sizes = calculate_intrinsic_sizes(box); + return { sizes.min_content_size.height(), sizes.max_content_size.height() }; +} + +float FormattingContext::calculate_fit_content_size(float min_content_size, float max_content_size, Optional available_space) 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()) { + // FIXME: Compute the real stretch-fit size. + auto stretch_fit_size = *available_space; + auto s = max(min_content_size, min(max_content_size, stretch_fit_size)); + return s; + } + + // FIXME: When sizing under a min-content constraint, equal to the 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, Optional available_space) const +{ + auto [min_content_size, max_content_size] = calculate_min_and_max_content_width(box); + return calculate_fit_content_size(min_content_size, max_content_size, available_space); +} + +float FormattingContext::calculate_fit_content_height(Layout::Box const& box, Optional available_space) const +{ + auto [min_content_size, max_content_size] = calculate_min_and_max_content_height(box); + return calculate_fit_content_size(min_content_size, max_content_size, available_space); +} } diff --git a/Userland/Libraries/LibWeb/Layout/FormattingContext.h b/Userland/Libraries/LibWeb/Layout/FormattingContext.h index c322ad6179..f8f3d7bf58 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.h @@ -45,9 +45,23 @@ public: virtual void parent_context_did_dimension_child_root_box() { } + struct MinAndMaxContentSize { + float min_content_size { 0 }; + float max_content_size { 0 }; + }; + + MinAndMaxContentSize calculate_min_and_max_content_width(Layout::Box const&) const; + MinAndMaxContentSize calculate_min_and_max_content_height(Layout::Box const&) const; + + float calculate_fit_content_height(Layout::Box const&, Optional available_height) const; + float calculate_fit_content_width(Layout::Box const&, Optional available_width) const; + protected: FormattingContext(Type, FormattingState&, Box const&, FormattingContext* parent = nullptr); + float calculate_fit_content_size(float min_content_size, float max_content_size, Optional available_space) const; + FormattingState::IntrinsicSizes calculate_intrinsic_sizes(Layout::Box const&) const; + OwnPtr layout_inside(Box const&, LayoutMode); void compute_position(Box const&); diff --git a/Userland/Libraries/LibWeb/Layout/FormattingState.h b/Userland/Libraries/LibWeb/Layout/FormattingState.h index 5e55509b71..2983e2f89c 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingState.h +++ b/Userland/Libraries/LibWeb/Layout/FormattingState.h @@ -90,6 +90,14 @@ struct FormattingState { NodeState const& get(NodeWithStyleAndBoxModelMetrics const&) const; HashMap> nodes; + + // We cache intrinsic sizes once determined, as they will not change over the course of a full layout. + // This avoids computing them several times while performing flex layout. + struct IntrinsicSizes { + Gfx::FloatSize min_content_size; + Gfx::FloatSize max_content_size; + }; + HashMap mutable intrinsic_sizes; }; Gfx::FloatRect absolute_content_rect(Box const&, FormattingState const&);