summaryrefslogtreecommitdiff
path: root/Libraries/LibWeb
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2020-06-18 18:28:32 +0200
committerAndreas Kling <kling@serenityos.org>2020-06-18 18:28:32 +0200
commitabe811104f0666309dfbd834f7bc9f74988888a3 (patch)
tree52588e190f257222ff8b718c531fed3ae28a9e17 /Libraries/LibWeb
parent3f87c98f445838e905559fd51ad8f5768110d716 (diff)
downloadserenity-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.cpp157
-rw-r--r--Libraries/LibWeb/Layout/LayoutBlock.h4
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&);