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.
This commit is contained in:
Andreas Kling 2023-05-10 10:09:29 +02:00
parent f248f566b8
commit 0c26717ba3
Notes: sideshowbarker 2024-07-17 10:05:47 +09:00
4 changed files with 66 additions and 9 deletions

View file

@ -0,0 +1,6 @@
Viewport <#document> at (0,0) content-size 800x600 children: not-inline
BlockContainer <html> at (1,1) content-size 798x70 [BFC] children: not-inline
Box <body> at (10,10) content-size 780x52 flex-container(row) [FFC] children: not-inline
ImageBox <img> 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>

View file

@ -0,0 +1,10 @@
<!doctype html><style>
* { border: 1px solid black; }
body {
background: #444;
display: flex;
}
img {
max-height: 50px;
}
</style><img src="data:image/gif;base64,R0lGODdhgALgAYAAAP///////ywAAAAAgALgAQAC/oSPqcvtD6OctNqLs968+w+G4kiW5omm6sq27gvH8kzX9o3n+s73/g8MCofEovGITCqXzKbzCY1Kp9Sq9YrNarfcrvcLDovH5LL5jE6r1+y2+w2Py+f0uv2Oz+v3/L7/DxgoOEhYaHiImKi4yNjo+AgZKTlJWWl5iZmpucnZ6fkJGio6SlpqeoqaqrrK2ur6ChsrO0tba3uLm6u7y9vr+wscLDxMXGx8jJysvMzc7PwMHS09TV1tfY2drb3N3e39DR4uPk5ebn6Onq6+zt7u/g4fLz9PX29/j5+vv8/f7/8PMKDAgQQLGjyIMKHChQwbOnwIMaLEiRQrWryIMaPG/o0cO3r8CDKkyJEkS5o8iTKlypUsW7p8CTOmzJk0a9q8iTOnzp08e/r8CTSo0KFEixo9ijSp0qVMmzp9CjWq1KlUq1q9ijWr1q1cu3r9Cjas2LFky5o9izat2rVs27p9Czeu3Ll069q9izev3r18+/r9Cziw4MGECxs+jDix4sWMGzt+DDmy5MmUK1u+jDmz5s2cO3v+DDq06NGkS5s+jTq16tWsW7t+DTu27Nm0a9u+jTu37t28e/v+DTy48OHEixs/jjy58uXMmzt/Dj269OnUq1u/jj279u3cu3v/Dj68+PHky5s/jz69+vXs27t/Dz++/Pn069u/jz+//v38/vv7/w9ggAIOSGCBBh6IYIIKLshggw4+CGGEEk5IYYUWXohhhhpuyGGHHn4IYogijkhiiSaeiGKKKq7IYosuvghjjDLOSGONNt6IY4467shjjz7+CGSQQg5JZJFGHolkkkouyWSTTj4JZZRSTklllVZeiWWWWm7JZZdefglmmGKOSWaZZp6JZppqrslmm26+CWeccs5JZ5123olnnnruyWeffv4JaKCCDkpooYYeimiiii7KaKOOPgpppJJOSmmlll6Kaaaabsppp55+Cmqooo5Kaqmmnopqqqquymqrrr4Ka6yyzkprrbbeimuuuu7Ka6++/gpssMIOS2yxxh6LYWyyyi7LbLPOPgtttNJOS2211l6Lbbbabsttt95+C2644o5Lbrnmnotuuuquy2677r4Lb7zyzktvvfbei2+++u7Lb7/+/gtwwAIPTHDBBh+McMIKL8xwww4/DHHEEk/MUAEAOw==" />

View file

@ -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 items 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<float>::max();
@ -713,8 +745,13 @@ Optional<CSSPixels> 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<CSSPixels> 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.

View file

@ -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 {