From 0c26717ba3317e769f64c605668603a79e909518 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Wed, 10 May 2023 10:09:29 +0200 Subject: [PATCH] LibWeb: Adjust flex item main size through aspect ratio if needed If there are min or max size constraints in the cross axis for a flex item that has a desired aspect ratio, we may need to adjust the main size *after* applying the cross size constraints. All the steps to achieving this aren't mentioned in the spec, but it seems that all other browsers behave this way, so we should too. --- ...-intrinsic-aspect-ratio-and-max-height.txt | 6 ++ ...intrinsic-aspect-ratio-and-max-height.html | 10 ++++ .../LibWeb/Layout/FlexFormattingContext.cpp | 56 ++++++++++++++++--- .../LibWeb/Layout/FlexFormattingContext.h | 3 + 4 files changed, 66 insertions(+), 9 deletions(-) create mode 100644 Tests/LibWeb/Layout/expected/flex/flex-item-with-intrinsic-aspect-ratio-and-max-height.txt create mode 100644 Tests/LibWeb/Layout/input/flex/flex-item-with-intrinsic-aspect-ratio-and-max-height.html diff --git a/Tests/LibWeb/Layout/expected/flex/flex-item-with-intrinsic-aspect-ratio-and-max-height.txt b/Tests/LibWeb/Layout/expected/flex/flex-item-with-intrinsic-aspect-ratio-and-max-height.txt new file mode 100644 index 0000000000..84a112a78b --- /dev/null +++ b/Tests/LibWeb/Layout/expected/flex/flex-item-with-intrinsic-aspect-ratio-and-max-height.txt @@ -0,0 +1,6 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (1,1) content-size 798x70 [BFC] children: not-inline + Box at (10,10) content-size 780x52 flex-container(row) [FFC] children: not-inline + ImageBox at (11,11) content-size 66.666671x50 flex-item children: not-inline + BlockContainer <(anonymous)> at (10,10) content-size 0x0 [BFC] children: inline + TextNode <#text> diff --git a/Tests/LibWeb/Layout/input/flex/flex-item-with-intrinsic-aspect-ratio-and-max-height.html b/Tests/LibWeb/Layout/input/flex/flex-item-with-intrinsic-aspect-ratio-and-max-height.html new file mode 100644 index 0000000000..870721e317 --- /dev/null +++ b/Tests/LibWeb/Layout/input/flex/flex-item-with-intrinsic-aspect-ratio-and-max-height.html @@ -0,0 +1,10 @@ + diff --git a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp index 96c64f2d1f..655b177caa 100644 --- a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp @@ -573,6 +573,30 @@ CSS::FlexBasisData FlexFormattingContext::used_flex_basis_for_item(FlexItem cons return flex_basis; } +CSSPixels FlexFormattingContext::calculate_main_size_from_cross_size_and_aspect_ratio(CSSPixels cross_size, float aspect_ratio) const +{ + if (is_row_layout()) + return cross_size * aspect_ratio; + return cross_size / aspect_ratio; +} + +// This function takes a size in the main axis and adjusts it according to the aspect ratio of the box +// if the min/max constraints in the cross axis forces us to come up with a new main axis size. +CSSPixels FlexFormattingContext::adjust_main_size_through_aspect_ratio_for_cross_size_min_max_constraints(Box const& box, CSSPixels main_size, CSS::Size const& min_cross_size, CSS::Size const& max_cross_size) const +{ + if (!max_cross_size.is_none()) { + auto max_cross_size_px = max_cross_size.to_px(box, is_row_layout() ? m_flex_container_state.content_width() : m_flex_container_state.content_height()); + main_size = min(main_size, calculate_main_size_from_cross_size_and_aspect_ratio(max_cross_size_px, box.intrinsic_aspect_ratio().value())); + } + + if (!min_cross_size.is_auto()) { + auto min_cross_size_px = min_cross_size.to_px(box, is_row_layout() ? m_flex_container_state.content_width() : m_flex_container_state.content_height()); + main_size = max(main_size, calculate_main_size_from_cross_size_and_aspect_ratio(min_cross_size_px, box.intrinsic_aspect_ratio().value())); + } + + return main_size; +} + // https://www.w3.org/TR/css-flexbox-1/#algo-main-item void FlexFormattingContext::determine_flex_base_size_and_hypothetical_main_size(FlexItem& item) { @@ -612,9 +636,11 @@ void FlexFormattingContext::determine_flex_base_size_and_hypothetical_main_size( && item.used_flex_basis.type == CSS::FlexBasis::Content && has_definite_cross_size(item.box)) { // flex_base_size is calculated from definite cross size and intrinsic aspect ratio - if (is_row_layout()) - return inner_cross_size(item.box) * item.box->intrinsic_aspect_ratio().value(); - return inner_cross_size(item.box) / item.box->intrinsic_aspect_ratio().value(); + return adjust_main_size_through_aspect_ratio_for_cross_size_min_max_constraints( + item.box, + calculate_main_size_from_cross_size_and_aspect_ratio(inner_cross_size(item.box), item.box->intrinsic_aspect_ratio().value()), + computed_cross_min_size(item.box), + computed_cross_max_size(item.box)); } // C. If the used flex basis is content or depends on its available space, @@ -670,6 +696,12 @@ void FlexFormattingContext::determine_flex_base_size_and_hypothetical_main_size( return calculate_fit_content_main_size(item); }(); + // AD-HOC: This is not mentioned in the spec, but if the item has an aspect ratio, + // we may need to adjust the main size in response to cross size min/max constraints. + if (item.box->has_intrinsic_aspect_ratio()) { + item.flex_base_size = adjust_main_size_through_aspect_ratio_for_cross_size_min_max_constraints(child_box, item.flex_base_size, computed_cross_min_size(child_box), computed_cross_max_size(child_box)); + } + // The hypothetical main size is the item’s flex base size clamped according to its used min and max main sizes (and flooring the content box size at zero). auto clamp_min = has_main_min_size(child_box) ? specified_main_min_size(child_box) : automatic_minimum_size(item); auto clamp_max = has_main_max_size(child_box) ? specified_main_max_size(child_box) : NumericLimits::max(); @@ -713,8 +745,13 @@ Optional FlexFormattingContext::specified_size_suggestion(FlexItem co // https://drafts.csswg.org/css-flexbox-1/#content-size-suggestion CSSPixels FlexFormattingContext::content_size_suggestion(FlexItem const& item) const { - // FIXME: Apply clamps - return calculate_min_content_main_size(item); + auto suggestion = calculate_min_content_main_size(item); + + if (item.box->has_intrinsic_aspect_ratio()) { + suggestion = adjust_main_size_through_aspect_ratio_for_cross_size_min_max_constraints(item.box, suggestion, computed_cross_min_size(item.box), computed_cross_max_size(item.box)); + } + + return suggestion; } // https://drafts.csswg.org/css-flexbox-1/#transferred-size-suggestion @@ -725,10 +762,11 @@ Optional FlexFormattingContext::transferred_size_suggestion(FlexItem // (clamped by its minimum and maximum cross sizes if they are definite), converted through the aspect ratio. if (item.box->has_intrinsic_aspect_ratio() && has_definite_cross_size(item.box)) { auto aspect_ratio = item.box->intrinsic_aspect_ratio().value(); - // FIXME: Clamp cross size to min/max cross size before this conversion. - if (is_row_layout()) - return inner_cross_size(item.box) * aspect_ratio; - return inner_cross_size(item.box) / aspect_ratio; + return adjust_main_size_through_aspect_ratio_for_cross_size_min_max_constraints( + item.box, + calculate_main_size_from_cross_size_and_aspect_ratio(inner_cross_size(item.box), aspect_ratio), + computed_cross_min_size(item.box), + computed_cross_max_size(item.box)); } // It is otherwise undefined. diff --git a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h index 5495395ded..7b78703495 100644 --- a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.h @@ -34,6 +34,9 @@ private: [[nodiscard]] bool should_treat_main_size_as_auto(Box const&) const; [[nodiscard]] bool should_treat_cross_size_as_auto(Box const&) const; + [[nodiscard]] CSSPixels adjust_main_size_through_aspect_ratio_for_cross_size_min_max_constraints(Box const&, CSSPixels main_size, CSS::Size const& min_cross_size, CSS::Size const& max_cross_size) const; + [[nodiscard]] CSSPixels calculate_main_size_from_cross_size_and_aspect_ratio(CSSPixels cross_size, float aspect_ratio) const; + void dump_items() const; struct DirectionAgnosticMargins {