diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index 06cef0bc2f..7c89218795 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -27,6 +27,7 @@ #pragma once namespace Web::CSS { +class Length; class Selector; class StyleDeclaration; class StyleProperties; diff --git a/Libraries/LibWeb/Layout/FormattingContext.cpp b/Libraries/LibWeb/Layout/FormattingContext.cpp index 40e4b1bc29..8256ceccc3 100644 --- a/Libraries/LibWeb/Layout/FormattingContext.cpp +++ b/Libraries/LibWeb/Layout/FormattingContext.cpp @@ -120,6 +120,78 @@ FormattingContext::ShrinkToFitResult FormattingContext::calculate_shrink_to_fit_ return { preferred_width, preferred_minimum_width }; } +static Gfx::FloatSize solve_replaced_size_constraint(float w, float h, const ReplacedBox& box) +{ + // 10.4 Minimum and maximum widths: 'min-width' and 'max-width' + + auto& containing_block = *box.containing_block(); + auto specified_min_width = box.style().min_width().resolved_or_zero(box, containing_block.width()).to_px(box); + auto specified_max_width = box.style().max_width().resolved(CSS::Length::make_px(w), box, containing_block.width()).to_px(box); + auto specified_min_height = box.style().min_height().resolved_or_auto(box, containing_block.height()).to_px(box); + auto specified_max_height = box.style().max_height().resolved(CSS::Length::make_px(h), box, containing_block.height()).to_px(box); + + auto min_width = min(specified_min_width, specified_max_width); + auto max_width = max(specified_min_width, specified_max_width); + auto min_height = min(specified_min_height, specified_max_height); + auto max_height = max(specified_min_height, specified_max_height); + + if (w > max_width) + return { w, max(max_width * h / w, min_height) }; + if (w < min_width) + return { max_width, min(min_width * h / w, max_height) }; + if (h > max_height) + return { max(max_height * w / h, min_width), max_height }; + if (h < min_height) + return { min(min_height * w / h, max_width), min_height }; + if ((w > max_width && h > max_height) && (max_width / w < max_height / h)) + return { max_width, max(min_height, max_width * h / w) }; + if ((w > max_width && h > max_height) && (max_width / w > max_height / h)) + return { max(min_width, max_height * w / h), max_height }; + if ((w < min_width && h < min_height) && (min_width / w < min_height / h)) + return { min(max_width, min_height * w / h), min_height }; + if ((w < min_width && h < min_height) && (min_width / w > min_height / h)) + return { min_width, min(max_height, min_width * h / w) }; + if (w < min_width && h > max_height) + return { min_width, max_height }; + if (w > max_width && h < min_height) + return { max_width, min_height }; + return { w, h }; +} + +float FormattingContext::tentative_width_for_replaced_element(const ReplacedBox& box, const CSS::Length& width) +{ + auto& containing_block = *box.containing_block(); + auto specified_height = box.style().height().resolved_or_auto(box, containing_block.height()); + + float used_width = width.to_px(box); + + // If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic width, + // then that intrinsic width is the used value of 'width'. + if (specified_height.is_auto() && width.is_auto() && box.has_intrinsic_width()) { + used_width = box.intrinsic_width(); + } + + // If 'height' and 'width' both have computed values of 'auto' and the element has no intrinsic width, + // but does have an intrinsic height and intrinsic ratio; + // or if 'width' has a computed value of 'auto', + // '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) + else if ((specified_height.is_auto() && width.is_auto() && !box.has_intrinsic_width() && box.has_intrinsic_height() && box.has_intrinsic_ratio()) || (width.is_auto() && box.has_intrinsic_ratio())) { + used_width = compute_height_for_replaced_element(box) * box.intrinsic_ratio(); + } + + else if (width.is_auto() && box.has_intrinsic_width()) { + used_width = box.intrinsic_width(); + } + + else if (width.is_auto()) { + used_width = 300; + } + + return used_width; +} + float FormattingContext::compute_width_for_replaced_element(const ReplacedBox& box) { // 10.3.4 Block-level, replaced elements in normal flow... @@ -138,61 +210,68 @@ float FormattingContext::compute_width_for_replaced_element(const ReplacedBox& b margin_right = zero_value; auto specified_width = box.style().width().resolved_or_auto(box, containing_block.width()); - auto specified_height = box.style().height().resolved_or_auto(box, containing_block.height()); - // FIXME: Actually compute 'width' - auto computed_width = specified_width; + // 1. The tentative used width is calculated (without 'min-width' and 'max-width') + auto used_width = tentative_width_for_replaced_element(box, specified_width); - float used_width = specified_width.to_px(box); - - // If 'height' and 'width' both have computed values of 'auto' and the element also has an intrinsic width, - // then that intrinsic width is the used value of 'width'. - if (specified_height.is_auto() && specified_width.is_auto() && box.has_intrinsic_width()) { - used_width = box.intrinsic_width(); + // 2. The tentative used width is greater than 'max-width', the rules above are applied again, + // but this time using the computed value of 'max-width' as the computed value for 'width'. + auto specified_max_width = box.style().max_width().resolved_or_auto(box, containing_block.width()); + if (!specified_max_width.is_auto()) { + if (used_width > specified_max_width.to_px(box)) { + used_width = tentative_width_for_replaced_element(box, specified_max_width); + } } - // If 'height' and 'width' both have computed values of 'auto' and the element has no intrinsic width, - // but does have an intrinsic height and intrinsic ratio; - // or if 'width' has a computed value of 'auto', - // '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) - else if ((specified_height.is_auto() && specified_width.is_auto() && !box.has_intrinsic_width() && box.has_intrinsic_height() && box.has_intrinsic_ratio()) || (computed_width.is_auto() && box.has_intrinsic_ratio())) { - used_width = compute_height_for_replaced_element(box) * box.intrinsic_ratio(); - } - - else if (computed_width.is_auto() && box.has_intrinsic_width()) { - used_width = box.intrinsic_width(); - } - - else if (computed_width.is_auto()) { - used_width = 300; + // 3. If the resulting width is smaller than 'min-width', the rules above are applied again, + // but this time using the value of 'min-width' as the computed value for 'width'. + auto specified_min_width = box.style().min_width().resolved_or_auto(box, containing_block.width()); + if (!specified_min_width.is_auto()) { + if (used_width < specified_min_width.to_px(box)) { + used_width = tentative_width_for_replaced_element(box, specified_min_width); + } } return used_width; } +float FormattingContext::tentative_height_for_replaced_element(const ReplacedBox& box, const CSS::Length& height) +{ + auto& containing_block = *box.containing_block(); + auto specified_width = box.style().width().resolved_or_auto(box, containing_block.width()); + + float used_height = height.to_px(box); + + // 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'. + if (specified_width.is_auto() && height.is_auto() && box.has_intrinsic_height()) + used_height = box.intrinsic_height(); + else if (height.is_auto() && box.has_intrinsic_ratio()) + used_height = compute_width_for_replaced_element(box) / box.intrinsic_ratio(); + else if (height.is_auto() && box.has_intrinsic_height()) + used_height = box.intrinsic_height(); + else if (height.is_auto()) + used_height = 150; + + return used_height; +} + float FormattingContext::compute_height_for_replaced_element(const ReplacedBox& box) { // 10.6.2 Inline replaced elements, block-level replaced elements in normal flow, // 'inline-block' replaced elements in normal flow and floating replaced elements - auto& containing_block = *box.containing_block(); + auto& containing_block = *box.containing_block(); auto specified_width = box.style().width().resolved_or_auto(box, containing_block.width()); auto specified_height = box.style().height().resolved_or_auto(box, containing_block.height()); - float used_height = specified_height.to_px(box); + float used_height = tentative_height_for_replaced_element(box, specified_height); - // 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'. - if (specified_width.is_auto() && specified_height.is_auto() && box.has_intrinsic_height()) - used_height = box.intrinsic_height(); - else if (specified_height.is_auto() && box.has_intrinsic_ratio()) - used_height = compute_width_for_replaced_element(box) / box.intrinsic_ratio(); - else if (specified_height.is_auto() && box.has_intrinsic_height()) - used_height = box.intrinsic_height(); - else if (specified_height.is_auto()) - used_height = 150; + if (specified_width.is_auto() && specified_height.is_auto() && box.has_intrinsic_ratio()) { + float w = tentative_width_for_replaced_element(box, specified_width); + float h = used_height; + used_height = solve_replaced_size_constraint(w, h, box).height(); + } return used_height; } diff --git a/Libraries/LibWeb/Layout/FormattingContext.h b/Libraries/LibWeb/Layout/FormattingContext.h index 34f39c1c39..ca2b609c31 100644 --- a/Libraries/LibWeb/Layout/FormattingContext.h +++ b/Libraries/LibWeb/Layout/FormattingContext.h @@ -58,6 +58,9 @@ protected: float preferred_minimum_width { 0 }; }; + static float tentative_width_for_replaced_element(const ReplacedBox&, const CSS::Length& width); + static float tentative_height_for_replaced_element(const ReplacedBox&, const CSS::Length& width); + ShrinkToFitResult calculate_shrink_to_fit_widths(Box&); FormattingContext* m_parent { nullptr };