diff --git a/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp index 9a1b8b277f7..cdc1d3a8c64 100644 --- a/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp +++ b/Userland/Libraries/LibWeb/Layout/FormattingContext.cpp @@ -734,12 +734,84 @@ void FormattingContext::compute_width_for_absolutely_positioned_non_replaced_ele 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. + // In this case, section 10.3.7 applies up through and including the constraint equation, + // but the rest of section 10.3.7 is replaced by the following rules: + + // 1. The used value of 'width' is determined as for inline replaced elements. 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)); + + auto width = compute_width_for_replaced_element(box, available_space); + auto width_of_containing_block = available_space.width.to_px(); + auto available = width_of_containing_block - width; + auto const& computed_values = box.computed_values(); + auto left = computed_values.inset().left(); + auto margin_left = computed_values.margin().left(); + auto right = computed_values.inset().right(); + auto margin_right = computed_values.margin().right(); + auto static_position = calculate_static_position(box); + + auto to_px = [&](const CSS::LengthPercentage& l) { + return l.to_px(box, width_of_containing_block); + }; + + // If 'margin-left' or 'margin-right' is specified as 'auto' its used value is determined by the rules below. + // 2. If both 'left' and 'right' have the value 'auto', then if the 'direction' property of the + // element establishing the static-position containing block is 'ltr', set 'left' to the static + // position; else if 'direction' is 'rtl', set 'right' to the static position. + if (left.is_auto() && right.is_auto()) { + left = CSS::Length::make_px(static_position.x()); + } + + // 3. If 'left' or 'right' are 'auto', replace any 'auto' on 'margin-left' or 'margin-right' with '0'. + if (left.is_auto() || right.is_auto()) { + if (margin_left.is_auto()) + margin_left = CSS::Length::make_px(0); + if (margin_right.is_auto()) + margin_right = CSS::Length::make_px(0); + } + + // 4. If at this point both 'margin-left' and 'margin-right' are still 'auto', solve the equation + // under the extra constraint that the two margins must get equal values, unless this would make + // them negative, in which case when the direction of the containing block is 'ltr' ('rtl'), + // set 'margin-left' ('margin-right') to zero and solve for 'margin-right' ('margin-left'). + if (margin_left.is_auto() && margin_right.is_auto()) { + auto remainder = available - to_px(left) - to_px(right); + if (remainder < 0) { + margin_left = CSS::Length::make_px(0); + margin_right = CSS::Length::make_px(0); + } else { + margin_left = CSS::Length::make_px(remainder / 2); + margin_right = CSS::Length::make_px(remainder / 2); + } + } + + // 5. If at this point there is an 'auto' left, solve the equation for that value. + if (left.is_auto()) { + left = CSS::Length::make_px(available - to_px(right) - to_px(margin_left) - to_px(margin_right)); + } else if (right.is_auto()) { + right = CSS::Length::make_px(available - to_px(left) - to_px(margin_left) - to_px(margin_right)); + } else if (margin_left.is_auto()) { + margin_left = CSS::Length::make_px(available - to_px(left) - to_px(right) - to_px(margin_right)); + } else if (margin_right.is_auto()) { + margin_right = CSS::Length::make_px(available - to_px(left) - to_px(margin_left) - to_px(right)); + } + + // 6. If at this point the values are over-constrained, ignore the value for either 'left' + // (in case the 'direction' property of the containing block is 'rtl') or 'right' + // (in case 'direction' is 'ltr') and solve for that value. + if (0 != available - to_px(left) - to_px(right) - to_px(margin_left) - to_px(margin_right)) { + right = CSS::Length::make_px(available - to_px(left) - to_px(margin_left) - to_px(margin_right)); + } + + auto& box_state = m_state.get_mutable(box); + box_state.inset_left = to_px(left); + box_state.inset_right = to_px(right); + box_state.margin_left = to_px(margin_left); + box_state.margin_right = to_px(margin_right); + box_state.set_content_width(width); } // https://drafts.csswg.org/css-position-3/#abs-non-replaced-height @@ -1093,11 +1165,75 @@ 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(Box const& box, AvailableSpace const& available_space, BeforeOrAfterInsideLayout) +void FormattingContext::compute_height_for_absolutely_positioned_replaced_element(Box const& box, AvailableSpace const& available_space, BeforeOrAfterInsideLayout before_or_after_inside_layout) { // 10.6.5 Absolutely positioned, replaced elements + // This situation is similar to 10.6.4, except that the element has an intrinsic height. + // The used value of 'height' is determined as for inline replaced elements. - m_state.get_mutable(box).set_content_height(compute_height_for_replaced_element(box, available_space)); + auto height = compute_height_for_replaced_element(box, available_space); + + auto height_of_containing_block = available_space.height.to_px(); + auto available = height_of_containing_block - height; + auto const& computed_values = box.computed_values(); + auto top = computed_values.inset().top(); + auto margin_top = computed_values.margin().top(); + auto bottom = computed_values.inset().bottom(); + auto margin_bottom = computed_values.margin().bottom(); + auto static_position = calculate_static_position(box); + + auto to_px = [&](const CSS::LengthPercentage& l) { + return l.to_px(box, height_of_containing_block); + }; + + // If 'margin-top' or 'margin-bottom' is specified as 'auto' its used value is determined by the rules below. + // 2. If both 'top' and 'bottom' have the value 'auto', replace 'top' with the element's static position. + if (top.is_auto() && bottom.is_auto()) { + top = CSS::Length::make_px(static_position.x()); + } + + // 3. If 'bottom' is 'auto', replace any 'auto' on 'margin-top' or 'margin-bottom' with '0'. + if (bottom.is_auto()) { + if (margin_top.is_auto()) + margin_top = CSS::Length::make_px(0); + if (margin_bottom.is_auto()) + margin_bottom = CSS::Length::make_px(0); + } + + // 4. If at this point both 'margin-top' and 'margin-bottom' are still 'auto', + // solve the equation under the extra constraint that the two margins must get equal values. + if (margin_top.is_auto() && margin_bottom.is_auto()) { + auto remainder = available - to_px(top) - to_px(bottom); + margin_top = CSS::Length::make_px(remainder / 2); + margin_bottom = CSS::Length::make_px(remainder / 2); + } + + // 5. If at this point there is an 'auto' left, solve the equation for that value. + if (top.is_auto()) { + top = CSS::Length::make_px(available - to_px(bottom) - to_px(margin_top) - to_px(margin_bottom)); + } else if (bottom.is_auto()) { + bottom = CSS::Length::make_px(available - to_px(top) - to_px(margin_top) - to_px(margin_bottom)); + } else if (margin_top.is_auto()) { + margin_top = CSS::Length::make_px(available - to_px(top) - to_px(bottom) - to_px(margin_bottom)); + } else if (margin_bottom.is_auto()) { + margin_bottom = CSS::Length::make_px(available - to_px(top) - to_px(margin_top) - to_px(bottom)); + } + + // 6. If at this point the values are over-constrained, ignore the value for 'bottom' and solve for that value. + if (0 != available - to_px(top) - to_px(bottom) - to_px(margin_top) - to_px(margin_bottom)) { + bottom = CSS::Length::make_px(available - to_px(top) - to_px(margin_top) - to_px(margin_bottom)); + } + + auto& box_state = m_state.get_mutable(box); + box_state.set_content_height(height); + + // do not set calculated insets or margins on the first pass, there will be a second pass + if (before_or_after_inside_layout == BeforeOrAfterInsideLayout::Before) + return; + box_state.inset_top = to_px(top); + box_state.inset_bottom = to_px(bottom); + box_state.margin_top = to_px(margin_top); + box_state.margin_bottom = to_px(margin_bottom); } // https://www.w3.org/TR/css-position-3/#relpos-insets