diff options
author | Andreas Kling <kling@serenityos.org> | 2020-06-18 18:28:32 +0200 |
---|---|---|
committer | Andreas Kling <kling@serenityos.org> | 2020-06-18 18:28:32 +0200 |
commit | abe811104f0666309dfbd834f7bc9f74988888a3 (patch) | |
tree | 52588e190f257222ff8b718c531fed3ae28a9e17 /Libraries/LibWeb | |
parent | 3f87c98f445838e905559fd51ad8f5768110d716 (diff) | |
download | serenity-abe811104f0666309dfbd834f7bc9f74988888a3.zip |
LibWeb: Better width computation for position:absolute blocks
This patch basically translates the CSS2.2 spec language into C++ for
computing the width of absolutely positioned non-replaced elements.
Diffstat (limited to 'Libraries/LibWeb')
-rw-r--r-- | Libraries/LibWeb/Layout/LayoutBlock.cpp | 157 | ||||
-rw-r--r-- | Libraries/LibWeb/Layout/LayoutBlock.h | 4 |
2 files changed, 122 insertions, 39 deletions
diff --git a/Libraries/LibWeb/Layout/LayoutBlock.cpp b/Libraries/LibWeb/Layout/LayoutBlock.cpp index b139a0e759..5b18cf2c79 100644 --- a/Libraries/LibWeb/Layout/LayoutBlock.cpp +++ b/Libraries/LibWeb/Layout/LayoutBlock.cpp @@ -264,10 +264,85 @@ void LayoutBlock::layout_inline_children(LayoutMode layout_mode) set_height(content_height); } -void LayoutBlock::compute_width() +void LayoutBlock::compute_width_for_absolutely_positioned_block() { auto& style = this->style(); + auto& containing_block = *this->containing_block(); + auto zero_value = Length(0, Length::Type::Px); + + auto margin_left = style.length_or_fallback(CSS::PropertyID::MarginLeft, zero_value, containing_block.width()); + auto margin_right = style.length_or_fallback(CSS::PropertyID::MarginRight, zero_value, containing_block.width()); + auto border_left = style.length_or_fallback(CSS::PropertyID::BorderLeftWidth, zero_value); + auto border_right = style.length_or_fallback(CSS::PropertyID::BorderRightWidth, zero_value); + auto padding_left = style.length_or_fallback(CSS::PropertyID::PaddingLeft, zero_value, containing_block.width()); + auto padding_right = style.length_or_fallback(CSS::PropertyID::PaddingRight, zero_value, containing_block.width()); + + auto left = style.length_or_fallback(CSS::PropertyID::Left, {}, containing_block.width()); + auto right = style.length_or_fallback(CSS::PropertyID::Right, {}, containing_block.width()); + auto width = style.length_or_fallback(CSS::PropertyID::Width, {}, containing_block.width()); + + auto solve_for_left = [&] { + left = Length(containing_block.width() - margin_left.to_px(*this) - border_left.to_px(*this) - padding_left.to_px(*this) - width.to_px(*this) - padding_right.to_px(*this) - border_right.to_px(*this) - margin_right.to_px(*this) - right.to_px(*this), Length::Type::Px); + }; + + auto solve_for_width = [&] { + width = Length(containing_block.width() - left.to_px(*this) - margin_left.to_px(*this) - border_left.to_px(*this) - padding_left.to_px(*this) - padding_right.to_px(*this) - border_right.to_px(*this) - margin_right.to_px(*this) - right.to_px(*this), Length::Type::Px); + }; + + auto solve_for_right = [&] { + left = Length(containing_block.width() - left.to_px(*this) - margin_left.to_px(*this) - border_left.to_px(*this) - padding_left.to_px(*this) - width.to_px(*this) - padding_right.to_px(*this) - border_right.to_px(*this) - margin_right.to_px(*this), Length::Type::Px); + }; + + // 1. 'left' and 'width' are 'auto' and 'right' is not 'auto', + // then the width is shrink-to-fit. Then solve for 'left' + if (left.is_auto() && width.is_auto() && !right.is_auto()) { + width = Length(calculate_shrink_to_fit_width(margin_left, border_left, padding_left, padding_right, border_right, margin_right), Length::Type::Px); + solve_for_left(); + } + // 2. 'left' and 'right' are 'auto' and 'width' is not 'auto', + // then if the 'direction' property of the element establishing + // the static-position containing block is 'ltr' set 'left' + // to the static position, otherwise set 'right' to the static position. + // Then solve for 'left' (if 'direction is 'rtl') or 'right' (if 'direction' is 'ltr'). + else if (left.is_auto() && right.is_auto() && !width.is_auto()) { + // FIXME: Check direction + // FIXME: Use the static-position containing block + left = zero_value; + solve_for_right(); + } + + // 3. 'width' and 'right' are 'auto' and 'left' is not 'auto', + // then the width is shrink-to-fit. Then solve for 'right' + else if (width.is_auto() && right.is_auto() && !left.is_auto()) { + width = Length(calculate_shrink_to_fit_width(margin_left, border_left, padding_left, padding_right, border_right, margin_right), Length::Type::Px); + solve_for_right(); + } + + // 4. 'left' is 'auto', 'width' and 'right' are not 'auto', then solve for 'left' + else if (left.is_auto() && !width.is_auto() && !right.is_auto()) { + solve_for_left(); + } + + // 5. 'width' is 'auto', 'left' and 'right' are not 'auto', then solve for 'width' + else if (width.is_auto() && !left.is_auto() && !right.is_auto()) { + solve_for_width(); + } + + // 6. 'right' is 'auto', 'left' and 'width' are not 'auto', then solve for 'right' + else if (right.is_auto() && !left.is_auto() && !width.is_auto()) { + solve_for_right(); + } + + set_width(width.to_px(*this)); +} + +void LayoutBlock::compute_width() +{ + if (is_absolutely_positioned()) + return compute_width_for_absolutely_positioned_block(); + + auto& style = this->style(); auto auto_value = Length(); auto zero_value = Length(0, Length::Type::Px); @@ -350,44 +425,8 @@ void LayoutBlock::compute_width() margin_right = zero_value; // If 'width' is 'auto', the used value is the shrink-to-fit width as for floating elements. - if (width.is_auto()) { - auto greatest_child_width = [&] { - float max_width = 0; - if (children_are_inline()) { - for (auto& box : line_boxes()) { - max_width = max(max_width, box.width()); - } - } else { - for_each_child([&](auto& child) { - if (child.is_box()) - max_width = max(max_width, to<LayoutBox>(child).width()); - }); - } - return max_width; - }; - - // Find the available width: in this case, this is the width of the containing - // block minus the used values of 'margin-left', 'border-left-width', 'padding-left', - // 'padding-right', 'border-right-width', 'margin-right', and the widths of any relevant scroll bars. - - float available_width = containing_block.width() - - margin_left.to_px(*this) - border_left.to_px(*this) - padding_left.to_px(*this) - - padding_right.to_px(*this) - border_right.to_px(*this) - margin_right.to_px(*this); - - // Calculate the preferred width by formatting the content without breaking lines - // other than where explicit line breaks occur. - layout_inside(LayoutMode::OnlyRequiredLineBreaks); - float preferred_width = greatest_child_width(); - - // Also calculate the preferred minimum width, e.g., by trying all possible line breaks. - // CSS 2.2 does not define the exact algorithm. - - layout_inside(LayoutMode::AllPossibleLineBreaks); - float preferred_minimum_width = greatest_child_width(); - - // Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width). - width = Length(min(max(preferred_minimum_width, available_width), preferred_width), Length::Type::Px); - } + if (width.is_auto()) + width = Length(calculate_shrink_to_fit_width(margin_left, border_left, padding_left, padding_right, border_right, margin_right), Length::Type::Px); } return width; @@ -450,6 +489,46 @@ void LayoutBlock::place_block_level_replaced_element_in_normal_flow(LayoutReplac box.set_offset(x, y); } +float LayoutBlock::calculate_shrink_to_fit_width(const Length& margin_left, const Length& border_left, const Length& padding_left, const Length& padding_right, const Length& border_right, const Length& margin_right) +{ + auto greatest_child_width = [&] { + float max_width = 0; + if (children_are_inline()) { + for (auto& box : line_boxes()) { + max_width = max(max_width, box.width()); + } + } else { + for_each_child([&](auto& child) { + if (child.is_box()) + max_width = max(max_width, to<LayoutBox>(child).width()); + }); + } + return max_width; + }; + + // Find the available width: in this case, this is the width of the containing + // block minus the used values of 'margin-left', 'border-left-width', 'padding-left', + // 'padding-right', 'border-right-width', 'margin-right', and the widths of any relevant scroll bars. + + float available_width = containing_block()->width() + - margin_left.to_px(*this) - border_left.to_px(*this) - padding_left.to_px(*this) + - padding_right.to_px(*this) - border_right.to_px(*this) - margin_right.to_px(*this); + + // Calculate the preferred width by formatting the content without breaking lines + // other than where explicit line breaks occur. + layout_inside(LayoutMode::OnlyRequiredLineBreaks); + float preferred_width = greatest_child_width(); + + // Also calculate the preferred minimum width, e.g., by trying all possible line breaks. + // CSS 2.2 does not define the exact algorithm. + + layout_inside(LayoutMode::AllPossibleLineBreaks); + float preferred_minimum_width = greatest_child_width(); + + // Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width). + return min(max(preferred_minimum_width, available_width), preferred_width); +} + void LayoutBlock::place_block_level_non_replaced_element_in_normal_flow(LayoutBlock& block) { auto& style = block.style(); diff --git a/Libraries/LibWeb/Layout/LayoutBlock.h b/Libraries/LibWeb/Layout/LayoutBlock.h index da29fca128..f3fcc91f45 100644 --- a/Libraries/LibWeb/Layout/LayoutBlock.h +++ b/Libraries/LibWeb/Layout/LayoutBlock.h @@ -73,6 +73,10 @@ protected: private: virtual bool is_block() const override { return true; } + float calculate_shrink_to_fit_width(const Length& margin_left, const Length& border_left, const Length& padding_left, const Length& padding_right, const Length& border_right, const Length& margin_right); + + void compute_width_for_absolutely_positioned_block(); + void place_block_level_non_replaced_element_in_normal_flow(LayoutBlock&); void place_block_level_replaced_element_in_normal_flow(LayoutReplaced&); void layout_absolutely_positioned_descendant(LayoutBox&); |