From 10516240845026321201724f28be9f32683d6a87 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Thu, 8 Jun 2023 16:47:50 +0100 Subject: [PATCH] LibWeb: Obey CSS aspect-ratio property during layout Calculate a "preferred aspect ratio" based on the value of `aspect-ratio` and the presence of a natural aspect ratio, and use that in layout. This is by no means complete or perfect, but we do now apply the given aspect-ratio to things. The spec is a bit vague, just saying to calculate sizes for aspect-ratio'ed boxes the same as you would for replaced elements. My naive solution here is to find everywhere we were checking for a ReplacedBox, and then also accept a regular Box with a preferred aspect ratio. This gets us pretty far. :^) https://www.w3.org/TR/css-sizing-4/#aspect-ratio-minimum is not at all implemented. --- .../expected/aspect-ratio-auto-and-ratio.txt | 9 +++ .../Layout/expected/aspect-ratio-auto.txt | 9 +++ .../Layout/expected/aspect-ratio-ratio.txt | 9 +++ .../input/aspect-ratio-auto-and-ratio.html | 7 +++ .../Layout/input/aspect-ratio-auto.html | 7 +++ .../Layout/input/aspect-ratio-ratio.html | 7 +++ .../LibWeb/Layout/BlockFormattingContext.cpp | 25 ++++---- .../LibWeb/Layout/BlockFormattingContext.h | 2 +- Userland/Libraries/LibWeb/Layout/Box.cpp | 8 +++ Userland/Libraries/LibWeb/Layout/Box.h | 4 ++ .../LibWeb/Layout/FlexFormattingContext.cpp | 16 +++--- .../LibWeb/Layout/FormattingContext.cpp | 57 +++++++++++-------- .../LibWeb/Layout/FormattingContext.h | 16 +++--- .../LibWeb/Layout/InlineFormattingContext.cpp | 10 ++-- 14 files changed, 131 insertions(+), 55 deletions(-) create mode 100644 Tests/LibWeb/Layout/expected/aspect-ratio-auto-and-ratio.txt create mode 100644 Tests/LibWeb/Layout/expected/aspect-ratio-auto.txt create mode 100644 Tests/LibWeb/Layout/expected/aspect-ratio-ratio.txt create mode 100644 Tests/LibWeb/Layout/input/aspect-ratio-auto-and-ratio.html create mode 100644 Tests/LibWeb/Layout/input/aspect-ratio-auto.html create mode 100644 Tests/LibWeb/Layout/input/aspect-ratio-ratio.html diff --git a/Tests/LibWeb/Layout/expected/aspect-ratio-auto-and-ratio.txt b/Tests/LibWeb/Layout/expected/aspect-ratio-auto-and-ratio.txt new file mode 100644 index 0000000000..9ba915dd16 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/aspect-ratio-auto-and-ratio.txt @@ -0,0 +1,9 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x320 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x304 children: not-inline + BlockContainer
at (9,9) content-size 200x100 children: not-inline + BlockContainer <(anonymous)> at (8,110) content-size 784x202 children: inline + line 0 width: 202, height: 202, bottom: 202, baseline: 202 + frag 0 from ImageBox start: 0, length: 0, rect: [9,111 200x200] + ImageBox at (9,111) content-size 200x200 children: not-inline + TextNode <#text> diff --git a/Tests/LibWeb/Layout/expected/aspect-ratio-auto.txt b/Tests/LibWeb/Layout/expected/aspect-ratio-auto.txt new file mode 100644 index 0000000000..1639b0fa46 --- /dev/null +++ b/Tests/LibWeb/Layout/expected/aspect-ratio-auto.txt @@ -0,0 +1,9 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x220 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x204 children: not-inline + BlockContainer
at (9,9) content-size 200x0 children: not-inline + BlockContainer <(anonymous)> at (8,10) content-size 784x202 children: inline + line 0 width: 202, height: 202, bottom: 202, baseline: 202 + frag 0 from ImageBox start: 0, length: 0, rect: [9,11 200x200] + ImageBox at (9,11) content-size 200x200 children: not-inline + TextNode <#text> diff --git a/Tests/LibWeb/Layout/expected/aspect-ratio-ratio.txt b/Tests/LibWeb/Layout/expected/aspect-ratio-ratio.txt new file mode 100644 index 0000000000..e820211fef --- /dev/null +++ b/Tests/LibWeb/Layout/expected/aspect-ratio-ratio.txt @@ -0,0 +1,9 @@ +Viewport <#document> at (0,0) content-size 800x600 children: not-inline + BlockContainer at (0,0) content-size 800x220 [BFC] children: not-inline + BlockContainer at (8,8) content-size 784x204 children: not-inline + BlockContainer
at (9,9) content-size 200x100 children: not-inline + BlockContainer <(anonymous)> at (8,110) content-size 784x102 children: inline + line 0 width: 202, height: 102, bottom: 102, baseline: 102 + frag 0 from ImageBox start: 0, length: 0, rect: [9,111 200x100] + ImageBox at (9,111) content-size 200x100 children: not-inline + TextNode <#text> diff --git a/Tests/LibWeb/Layout/input/aspect-ratio-auto-and-ratio.html b/Tests/LibWeb/Layout/input/aspect-ratio-auto-and-ratio.html new file mode 100644 index 0000000000..803806662c --- /dev/null +++ b/Tests/LibWeb/Layout/input/aspect-ratio-auto-and-ratio.html @@ -0,0 +1,7 @@ +
diff --git a/Tests/LibWeb/Layout/input/aspect-ratio-auto.html b/Tests/LibWeb/Layout/input/aspect-ratio-auto.html new file mode 100644 index 0000000000..7de1e453a0 --- /dev/null +++ b/Tests/LibWeb/Layout/input/aspect-ratio-auto.html @@ -0,0 +1,7 @@ +
diff --git a/Tests/LibWeb/Layout/input/aspect-ratio-ratio.html b/Tests/LibWeb/Layout/input/aspect-ratio-ratio.html new file mode 100644 index 0000000000..9b946e4116 --- /dev/null +++ b/Tests/LibWeb/Layout/input/aspect-ratio-ratio.html @@ -0,0 +1,7 @@ +
diff --git a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp index e362008da1..321457917f 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.cpp @@ -54,6 +54,9 @@ static bool margins_collapse_through(Box const& box, LayoutState& state) // nor top or bottom padding, and it has a 'height' of either 0 or 'auto', and it does not contain a line box, and // all of its in-flow children's margins (if any) collapse. // https://www.w3.org/TR/CSS22/box.html#collapsing-margins + // FIXME: For the purpose of margin collapsing (CSS 2 §8.3.1 Collapsing margins), if the block axis is the + // ratio-dependent axis, it is not considered to have a computed block-size of auto. + // https://www.w3.org/TR/css-sizing-4/#aspect-ratio-margin-collapse return state.get(box).border_box_height() == 0; } @@ -130,12 +133,14 @@ void BlockFormattingContext::compute_width(Box const& box, AvailableSpace const& remaining_available_space.width = AvailableSize::make_definite(remaining_width); } - if (is(box)) { + if (box_is_sized_as_replaced_element(box)) { // FIXME: This should not be done *by* ReplacedBox - 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, remaining_available_space); + if (is(box)) { + 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(box, remaining_available_space); if (box.is_floating()) { // 10.3.6 Floating, replaced elements: // https://www.w3.org/TR/CSS22/visudet.html#float-replaced-width @@ -231,7 +236,7 @@ void BlockFormattingContext::compute_width(Box const& box, AvailableSpace const& }; auto input_width = [&] { - if (is(box)) { + if (box_is_sized_as_replaced_element(box)) { // NOTE: Replaced elements had their width calculated independently above. // We use that width as the input here to ensure that margins get resolved. return CSS::Length::make_px(box_state.content_width()); @@ -266,7 +271,7 @@ void BlockFormattingContext::compute_width(Box const& box, AvailableSpace const& } } - if (!is(box) && !used_width.is_auto()) + if (!box_is_sized_as_replaced_element(box) && !used_width.is_auto()) box_state.set_content_width(used_width.to_px(box)); box_state.margin_left = margin_left.to_px(box); @@ -350,7 +355,7 @@ void BlockFormattingContext::compute_width_for_floating_box(Box const& box, Avai box_state.padding_right = padding_right; } -void BlockFormattingContext::compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox const& box, AvailableSpace const& available_space) +void BlockFormattingContext::compute_width_for_block_level_replaced_element_in_normal_flow(Box const& box, AvailableSpace const& available_space) { // 10.3.6 Floating, replaced elements auto& computed_values = box.computed_values(); @@ -436,8 +441,8 @@ void BlockFormattingContext::compute_height(Box const& box, AvailableSpace const // Then work out what the height is, based on box type and CSS properties. CSSPixels height = 0; - if (is(box)) { - height = compute_height_for_replaced_element(verify_cast(box), available_space); + if (box_is_sized_as_replaced_element(box)) { + height = compute_height_for_replaced_element(box, available_space); } else { if (should_treat_height_as_auto(box, available_space)) { height = compute_auto_height_for_block_level_element(box, m_state.get(box).available_inner_space_or_constraints_from(available_space)); diff --git a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h index 972772faec..f74f6cb1b1 100644 --- a/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/BlockFormattingContext.h @@ -57,7 +57,7 @@ private: void compute_width_for_floating_box(Box const&, AvailableSpace const&); - void compute_width_for_block_level_replaced_element_in_normal_flow(ReplacedBox const&, AvailableSpace const&); + void compute_width_for_block_level_replaced_element_in_normal_flow(Box const&, AvailableSpace const&); CSSPixels compute_table_box_width_inside_table_wrapper(Box const&, AvailableSpace const&); diff --git a/Userland/Libraries/LibWeb/Layout/Box.cpp b/Userland/Libraries/LibWeb/Layout/Box.cpp index 1842224c85..31a7d6e8e9 100644 --- a/Userland/Libraries/LibWeb/Layout/Box.cpp +++ b/Userland/Libraries/LibWeb/Layout/Box.cpp @@ -91,4 +91,12 @@ Painting::PaintableBox const* Box::paintable_box() const return static_cast(Node::paintable()); } +Optional Box::preferred_aspect_ratio() const +{ + auto computed_aspect_ratio = computed_values().aspect_ratio(); + if (computed_aspect_ratio.use_natural_aspect_ratio_if_available && natural_aspect_ratio().has_value()) + return natural_aspect_ratio(); + return computed_aspect_ratio.preferred_ratio.map([](CSS::Ratio const& ratio) { return ratio.value(); }); +} + } diff --git a/Userland/Libraries/LibWeb/Layout/Box.h b/Userland/Libraries/LibWeb/Layout/Box.h index 76eb956303..13bc82443e 100644 --- a/Userland/Libraries/LibWeb/Layout/Box.h +++ b/Userland/Libraries/LibWeb/Layout/Box.h @@ -41,6 +41,10 @@ public: void set_natural_height(Optional height) { m_natural_height = height; } void set_natural_aspect_ratio(Optional ratio) { m_natural_aspect_ratio = ratio; } + // https://www.w3.org/TR/css-sizing-4/#preferred-aspect-ratio + Optional preferred_aspect_ratio() const; + bool has_preferred_aspect_ratio() const { return preferred_aspect_ratio().has_value(); } + virtual ~Box() override; virtual void did_set_content_size() { } diff --git a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp index 96f93fdf26..e8a982b439 100644 --- a/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/FlexFormattingContext.cpp @@ -590,12 +590,12 @@ CSSPixels FlexFormattingContext::adjust_main_size_through_aspect_ratio_for_cross { 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.natural_aspect_ratio().value())); + main_size = min(main_size, calculate_main_size_from_cross_size_and_aspect_ratio(max_cross_size_px, box.preferred_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.natural_aspect_ratio().value())); + main_size = max(main_size, calculate_main_size_from_cross_size_and_aspect_ratio(min_cross_size_px, box.preferred_aspect_ratio().value())); } return main_size; @@ -647,13 +647,13 @@ void FlexFormattingContext::determine_flex_base_size_and_hypothetical_main_size( // - an intrinsic aspect ratio, // - a used flex basis of content, and // - a definite cross size, - if (item.box->has_natural_aspect_ratio() + if (item.box->has_preferred_aspect_ratio() && 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 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->natural_aspect_ratio().value()), + calculate_main_size_from_cross_size_and_aspect_ratio(inner_cross_size(item.box), item.box->preferred_aspect_ratio().value()), computed_cross_min_size(item.box), computed_cross_max_size(item.box)); } @@ -713,7 +713,7 @@ void FlexFormattingContext::determine_flex_base_size_and_hypothetical_main_size( // 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_natural_aspect_ratio()) { + if (item.box->has_preferred_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)); } @@ -762,7 +762,7 @@ CSSPixels FlexFormattingContext::content_size_suggestion(FlexItem const& item) c { auto suggestion = calculate_min_content_main_size(item); - if (item.box->has_natural_aspect_ratio()) { + if (item.box->has_preferred_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)); } @@ -775,8 +775,8 @@ Optional FlexFormattingContext::transferred_size_suggestion(FlexItem // If the item has a preferred aspect ratio and its preferred cross size is definite, // then the transferred size suggestion is that size // (clamped by its minimum and maximum cross sizes if they are definite), converted through the aspect ratio. - if (item.box->has_natural_aspect_ratio() && has_definite_cross_size(item.box)) { - auto aspect_ratio = item.box->natural_aspect_ratio().value(); + if (item.box->has_preferred_aspect_ratio() && has_definite_cross_size(item.box)) { + auto aspect_ratio = item.box->preferred_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), aspect_ratio), diff --git a/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp index b91ce39bc0..6751016ac5 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp @@ -256,7 +256,7 @@ FormattingContext::ShrinkToFitResult FormattingContext::calculate_shrink_to_fit_ }; } -CSSPixelSize FormattingContext::solve_replaced_size_constraint(CSSPixels input_width, CSSPixels input_height, ReplacedBox const& box) const +CSSPixelSize FormattingContext::solve_replaced_size_constraint(CSSPixels input_width, CSSPixels input_height, Box const& box) const { // 10.4 Minimum and maximum widths: 'min-width' and 'max-width' @@ -365,7 +365,7 @@ CSSPixels FormattingContext::compute_auto_height_for_block_formatting_context_ro } // 10.3.2 Inline, replaced elements, https://www.w3.org/TR/CSS22/visudet.html#inline-replaced-width -CSSPixels FormattingContext::tentative_width_for_replaced_element(ReplacedBox const& box, CSS::Size const& computed_width, AvailableSpace const& available_space) const +CSSPixels FormattingContext::tentative_width_for_replaced_element(Box const& box, CSS::Size const& computed_width, AvailableSpace const& available_space) const { // Treat percentages of indefinite containing block widths as 0 (the initial width). if (computed_width.is_percentage() && !m_state.get(*box.containing_block()).has_definite_width()) @@ -387,16 +387,16 @@ CSSPixels FormattingContext::tentative_width_for_replaced_element(ReplacedBox co // 'height' has some other computed value, and the element does have an intrinsic ratio; then the used value of 'width' is: // // (used height) * (intrinsic ratio) - if ((computed_height.is_auto() && computed_width.is_auto() && !box.has_natural_width() && box.has_natural_height() && box.has_natural_aspect_ratio()) - || (computed_width.is_auto() && !computed_height.is_auto() && box.has_natural_aspect_ratio())) { - return compute_height_for_replaced_element(box, available_space) * static_cast(box.natural_aspect_ratio().value()); + if ((computed_height.is_auto() && computed_width.is_auto() && !box.has_natural_width() && box.has_natural_height() && box.has_preferred_aspect_ratio()) + || (computed_width.is_auto() && !computed_height.is_auto() && box.has_preferred_aspect_ratio())) { + return compute_height_for_replaced_element(box, available_space) * static_cast(box.preferred_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, // then the used value of 'width' is undefined in CSS 2.2. However, it is suggested that, if the containing block's width does not itself // depend on the replaced element's width, then the used value of 'width' is calculated from the constraint equation used for block-level, // non-replaced elements in normal flow. - if (computed_height.is_auto() && computed_width.is_auto() && !box.has_natural_width() && !box.has_natural_height() && box.has_natural_aspect_ratio()) { + if (computed_height.is_auto() && computed_width.is_auto() && !box.has_natural_width() && !box.has_natural_height() && box.has_preferred_aspect_ratio()) { return calculate_stretch_fit_width(box, available_space.width); } @@ -414,21 +414,21 @@ CSSPixels FormattingContext::tentative_width_for_replaced_element(ReplacedBox co 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), available_space); + if (box_is_sized_as_replaced_element(box)) + compute_width_for_absolutely_positioned_replaced_element(box, available_space); else compute_width_for_absolutely_positioned_non_replaced_element(box, available_space); } void FormattingContext::compute_height_for_absolutely_positioned_element(Box const& box, AvailableSpace const& available_space, BeforeOrAfterInsideLayout before_or_after_inside_layout) { - if (is(box)) - compute_height_for_absolutely_positioned_replaced_element(static_cast(box), available_space, before_or_after_inside_layout); + if (box_is_sized_as_replaced_element(box)) + compute_height_for_absolutely_positioned_replaced_element(box, available_space, before_or_after_inside_layout); else compute_height_for_absolutely_positioned_non_replaced_element(box, available_space, before_or_after_inside_layout); } -CSSPixels FormattingContext::compute_width_for_replaced_element(ReplacedBox const& box, AvailableSpace const& available_space) const +CSSPixels FormattingContext::compute_width_for_replaced_element(Box const& box, AvailableSpace const& available_space) const { // 10.3.4 Block-level, replaced elements in normal flow... // 10.3.2 Inline, replaced elements @@ -443,7 +443,7 @@ CSSPixels FormattingContext::compute_width_for_replaced_element(ReplacedBox cons // 1. The tentative used width is calculated (without 'min-width' and 'max-width') auto used_width = tentative_width_for_replaced_element(box, computed_width, available_space); - if (computed_width.is_auto() && computed_height.is_auto() && box.has_natural_aspect_ratio()) { + if (computed_width.is_auto() && computed_height.is_auto() && box.has_preferred_aspect_ratio()) { CSSPixels w = used_width; CSSPixels h = tentative_height_for_replaced_element(box, computed_height, available_space); used_width = solve_replaced_size_constraint(w, h, box).width(); @@ -473,7 +473,7 @@ CSSPixels FormattingContext::compute_width_for_replaced_element(ReplacedBox cons // 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 -CSSPixels FormattingContext::tentative_height_for_replaced_element(ReplacedBox const& box, CSS::Size const& computed_height, AvailableSpace const& available_space) const +CSSPixels FormattingContext::tentative_height_for_replaced_element(Box const& box, CSS::Size const& computed_height, AvailableSpace const& available_space) const { // If 'height' and 'width' both have computed values of 'auto' and the element also has // an intrinsic height, then that intrinsic height is the used value of 'height'. @@ -483,8 +483,8 @@ CSSPixels FormattingContext::tentative_height_for_replaced_element(ReplacedBox c // Otherwise, if 'height' has a computed value of 'auto', and the element has an intrinsic ratio then the used value of 'height' is: // // (used width) / (intrinsic ratio) - if (computed_height.is_auto() && box.has_natural_aspect_ratio()) - return m_state.get(box).content_width() / static_cast(box.natural_aspect_ratio().value()); + if (computed_height.is_auto() && box.has_preferred_aspect_ratio()) + return m_state.get(box).content_width() / static_cast(box.preferred_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_natural_height()) @@ -500,7 +500,7 @@ CSSPixels FormattingContext::tentative_height_for_replaced_element(ReplacedBox c return calculate_inner_height(box, available_space.height, computed_height).to_px(box); } -CSSPixels FormattingContext::compute_height_for_replaced_element(ReplacedBox const& box, AvailableSpace const& available_space) const +CSSPixels FormattingContext::compute_height_for_replaced_element(Box const& box, AvailableSpace const& available_space) const { // 10.6.2 Inline replaced elements // 10.6.4 Block-level replaced elements in normal flow @@ -518,7 +518,7 @@ CSSPixels FormattingContext::compute_height_for_replaced_element(ReplacedBox con // use the algorithm under 'Minimum and maximum widths' // https://www.w3.org/TR/CSS22/visudet.html#min-max-widths // to find the used width and height. - if (computed_width.is_auto() && computed_height.is_auto() && box.has_natural_aspect_ratio()) { + if (computed_width.is_auto() && computed_height.is_auto() && box.has_preferred_aspect_ratio()) { CSSPixels w = tentative_width_for_replaced_element(box, computed_width, available_space); CSSPixels h = used_height; used_height = solve_replaced_size_constraint(w, h, box).height(); @@ -689,12 +689,14 @@ 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, AvailableSpace const& available_space) +void FormattingContext::compute_width_for_absolutely_positioned_replaced_element(Box 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(); + if (is(box)) { + // FIXME: This const_cast is gross. + static_cast(const_cast(box)).prepare_for_replaced_layout(); + } m_state.get_mutable(box).set_content_width(compute_width_for_replaced_element(box, available_space)); } @@ -1061,7 +1063,7 @@ void FormattingContext::layout_absolutely_positioned_element(Box const& box, Ava independent_formatting_context->parent_context_did_dimension_child_root_box(); } -void FormattingContext::compute_height_for_absolutely_positioned_replaced_element(ReplacedBox const& box, AvailableSpace const& available_space, BeforeOrAfterInsideLayout) +void FormattingContext::compute_height_for_absolutely_positioned_replaced_element(Box const& box, AvailableSpace const& available_space, BeforeOrAfterInsideLayout) { // 10.6.5 Absolutely positioned, replaced elements // The used value of 'height' is determined as for inline replaced elements. @@ -1283,8 +1285,8 @@ CSSPixels FormattingContext::calculate_min_content_height(Layout::Box const& box CSSPixels FormattingContext::calculate_max_content_height(Layout::Box const& box, AvailableSize const& available_width) const { - if (box.has_natural_aspect_ratio() && available_width.is_definite()) - return available_width.to_px() / static_cast(*box.natural_aspect_ratio()); + if (box.has_preferred_aspect_ratio() && available_width.is_definite()) + return available_width.to_px() / static_cast(*box.preferred_aspect_ratio()); if (box.has_natural_height()) return *box.natural_height(); @@ -1623,4 +1625,13 @@ CSSPixelRect FormattingContext::margin_box_rect_in_ancestor_coordinate_space(Box VERIFY_NOT_REACHED(); } +bool box_is_sized_as_replaced_element(Box const& box) +{ + // When a box has a preferred aspect ratio, its automatic sizes are calculated the same as for a + // replaced element with a natural aspect ratio and no natural size in that axis, see e.g. CSS2 §10 + // and CSS Flexible Box Model Level 1 §9.2. + // https://www.w3.org/TR/css-sizing-4/#aspect-ratio-automatic + return is(box) || box.has_preferred_aspect_ratio(); +} + } diff --git a/Userland/Libraries/LibWeb/Layout/FormattingContext.h b/Userland/Libraries/LibWeb/Layout/FormattingContext.h index 412c9842fc..6c5cceb0e1 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingContext.h +++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.h @@ -50,8 +50,8 @@ public: static bool creates_block_formatting_context(Box const&); - CSSPixels compute_width_for_replaced_element(ReplacedBox const&, AvailableSpace const&) const; - CSSPixels compute_height_for_replaced_element(ReplacedBox const&, AvailableSpace const&) const; + CSSPixels compute_width_for_replaced_element(Box const&, AvailableSpace const&) const; + CSSPixels compute_height_for_replaced_element(Box const&, AvailableSpace const&) const; OwnPtr create_independent_formatting_context_if_needed(LayoutState&, Box const& child_box); @@ -112,18 +112,18 @@ protected: CSSPixels preferred_minimum_width { 0 }; }; - CSSPixels tentative_width_for_replaced_element(ReplacedBox const&, CSS::Size const& computed_width, AvailableSpace const&) const; - CSSPixels tentative_height_for_replaced_element(ReplacedBox const&, CSS::Size const& computed_height, AvailableSpace const&) const; + CSSPixels tentative_width_for_replaced_element(Box const&, CSS::Size const& computed_width, AvailableSpace const&) const; + CSSPixels tentative_height_for_replaced_element(Box const&, CSS::Size const& computed_height, AvailableSpace const&) const; CSSPixels compute_auto_height_for_block_formatting_context_root(Box const&) const; - [[nodiscard]] CSSPixelSize solve_replaced_size_constraint(CSSPixels input_width, CSSPixels input_height, ReplacedBox const&) const; + [[nodiscard]] CSSPixelSize solve_replaced_size_constraint(CSSPixels input_width, CSSPixels input_height, Box const&) const; ShrinkToFitResult calculate_shrink_to_fit_widths(Box 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_width_for_absolutely_positioned_replaced_element(Box const&, AvailableSpace const&); enum class BeforeOrAfterInsideLayout { Before, @@ -131,7 +131,7 @@ protected: }; void compute_height_for_absolutely_positioned_element(Box const&, AvailableSpace const&, BeforeOrAfterInsideLayout); void compute_height_for_absolutely_positioned_non_replaced_element(Box const&, AvailableSpace const&, BeforeOrAfterInsideLayout); - void compute_height_for_absolutely_positioned_replaced_element(ReplacedBox const&, AvailableSpace const&, BeforeOrAfterInsideLayout); + void compute_height_for_absolutely_positioned_replaced_element(Box const&, AvailableSpace const&, BeforeOrAfterInsideLayout); Type m_type {}; @@ -141,4 +141,6 @@ protected: LayoutState& m_state; }; +bool box_is_sized_as_replaced_element(Box const&); + } diff --git a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp index 3dd30d58f6..66e0c42f72 100644 --- a/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/InlineFormattingContext.cpp @@ -110,14 +110,12 @@ void InlineFormattingContext::dimension_box_on_line(Box const& box, LayoutMode l box_state.border_bottom = computed_values.border_bottom().width; box_state.margin_bottom = computed_values.margin().bottom().to_px(box, width_of_containing_block); - if (is(box)) { - auto& replaced = verify_cast(box); - - box_state.set_content_width(compute_width_for_replaced_element(replaced, *m_available_space)); - box_state.set_content_height(compute_height_for_replaced_element(replaced, *m_available_space)); + if (box_is_sized_as_replaced_element(box)) { + box_state.set_content_width(compute_width_for_replaced_element(box, *m_available_space)); + box_state.set_content_height(compute_height_for_replaced_element(box, *m_available_space)); if (is(box)) - (void)layout_inside(replaced, layout_mode, box_state.available_inner_space_or_constraints_from(*m_available_space)); + (void)layout_inside(box, layout_mode, box_state.available_inner_space_or_constraints_from(*m_available_space)); return; }