| // Copyright 2015 The Cobalt Authors. All Rights Reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| #include <memory> |
| |
| #include "cobalt/layout/block_container_box.h" |
| |
| #include "cobalt/layout/formatting_context.h" |
| #include "cobalt/layout/used_style.h" |
| |
| namespace cobalt { |
| namespace layout { |
| |
| BlockContainerBox::BlockContainerBox( |
| const scoped_refptr<cssom::CSSComputedStyleDeclaration>& |
| css_computed_style_declaration, |
| BaseDirection base_direction, UsedStyleProvider* used_style_provider, |
| LayoutStatTracker* layout_stat_tracker) |
| : ContainerBox(css_computed_style_declaration, used_style_provider, |
| layout_stat_tracker), |
| base_direction_(base_direction) {} |
| |
| BlockContainerBox::~BlockContainerBox() {} |
| |
| // Updates used values of "width" and "margin" properties based on |
| // https://www.w3.org/TR/CSS21/visudet.html#Computing_widths_and_margins. |
| void BlockContainerBox::UpdateContentWidthAndMargins( |
| BaseDirection containing_block_direction, |
| LayoutUnit containing_block_width, bool shrink_to_fit_width_forced, |
| bool width_depends_on_containing_block, |
| const base::Optional<LayoutUnit>& maybe_left, |
| const base::Optional<LayoutUnit>& maybe_right, |
| const base::Optional<LayoutUnit>& maybe_margin_left, |
| const base::Optional<LayoutUnit>& maybe_margin_right, |
| const base::Optional<LayoutUnit>& maybe_width, |
| const base::Optional<LayoutUnit>& maybe_height) { |
| if (IsAbsolutelyPositioned()) { |
| UpdateWidthAssumingAbsolutelyPositionedBox( |
| containing_block_direction, containing_block_width, |
| maybe_left, maybe_right, maybe_width, |
| maybe_margin_left, maybe_margin_right, maybe_height); |
| } else { |
| base::Optional<LayoutUnit> maybe_nulled_width = maybe_width; |
| Level forced_level = GetLevel(); |
| if (shrink_to_fit_width_forced) { |
| forced_level = kInlineLevel; |
| // Break circular dependency if needed. |
| if (width_depends_on_containing_block) { |
| maybe_nulled_width = base::nullopt; |
| } |
| } |
| |
| switch (forced_level) { |
| case kBlockLevel: |
| UpdateWidthAssumingBlockLevelInFlowBox( |
| containing_block_direction, containing_block_width, |
| maybe_nulled_width, maybe_margin_left, maybe_margin_right); |
| break; |
| case kInlineLevel: |
| UpdateWidthAssumingInlineLevelInFlowBox( |
| containing_block_width, maybe_nulled_width, maybe_margin_left, |
| maybe_margin_right, maybe_height); |
| break; |
| } |
| } |
| } |
| |
| // Updates used values of "height" and "margin" properties based on |
| // https://www.w3.org/TR/CSS21/visudet.html#Computing_heights_and_margins. |
| void BlockContainerBox::UpdateContentHeightAndMargins( |
| const SizeLayoutUnit& containing_block_size, |
| const base::Optional<LayoutUnit>& maybe_top, |
| const base::Optional<LayoutUnit>& maybe_bottom, |
| const base::Optional<LayoutUnit>& maybe_margin_top, |
| const base::Optional<LayoutUnit>& maybe_margin_bottom, |
| const base::Optional<LayoutUnit>& maybe_height) { |
| LayoutParams child_layout_params; |
| LayoutParams absolute_child_layout_params; |
| child_layout_params.containing_block_direction = base_direction_; |
| absolute_child_layout_params.containing_block_direction = base_direction_; |
| if (AsAnonymousBlockBox()) { |
| // Anonymous block boxes are ignored when resolving percentage values |
| // that would refer to it: the closest non-anonymous ancestor box is used |
| // instead. |
| // https://www.w3.org/TR/CSS21/visuren.html#anonymous-block-level |
| child_layout_params.containing_block_size = containing_block_size; |
| } else { |
| // If the element's position is "relative" or "static", the containing block |
| // is formed by the content edge of the nearest block container ancestor |
| // box. |
| // https://www.w3.org/TR/CSS21/visudet.html#containing-block-details |
| child_layout_params.containing_block_size.set_width(width()); |
| // If the element has 'position: absolute', ... |
| // the containing block is formed by the padding edge of the ancestor. |
| // http://www.w3.org/TR/CSS21/visudet.html#containing-block-details |
| absolute_child_layout_params.containing_block_size.set_width( |
| GetPaddingBoxWidth()); |
| // The "auto" height is not known yet but it shouldn't matter for in-flow |
| // children, as per: |
| // |
| // If the height of the containing block is not specified explicitly (i.e., |
| // it depends on content height), and this element is not absolutely |
| // positioned, the value [of "height"] computes to "auto". |
| // https://www.w3.org/TR/CSS21/visudet.html#the-height-property |
| if (maybe_height) { |
| child_layout_params.containing_block_size.set_height(*maybe_height); |
| } else if (maybe_top && maybe_bottom) { |
| child_layout_params.containing_block_size.set_height( |
| containing_block_size.height() - *maybe_top - *maybe_bottom); |
| } else { |
| child_layout_params.containing_block_size.set_height(LayoutUnit()); |
| } |
| } |
| child_layout_params.maybe_margin_top = maybe_margin_top; |
| child_layout_params.maybe_margin_bottom = maybe_margin_bottom; |
| child_layout_params.maybe_height = maybe_height; |
| |
| std::unique_ptr<FormattingContext> formatting_context = |
| UpdateRectOfInFlowChildBoxes(child_layout_params); |
| |
| if (IsAbsolutelyPositioned()) { |
| UpdateHeightAssumingAbsolutelyPositionedBox( |
| containing_block_size.height(), maybe_top, maybe_bottom, maybe_height, |
| maybe_margin_top, maybe_margin_bottom, *formatting_context); |
| } else { |
| UpdateHeightAssumingInFlowBox(maybe_height, maybe_margin_top, |
| maybe_margin_bottom, *formatting_context); |
| } |
| |
| // Positioned children are laid out at the end as their position and size |
| // depends on the size of the containing block as well as possibly their |
| // previously calculated in-flow position. |
| child_layout_params.containing_block_size.set_height(height()); |
| absolute_child_layout_params.containing_block_size.set_height( |
| GetPaddingBoxHeight()); |
| UpdateRectOfPositionedChildBoxes(child_layout_params, |
| absolute_child_layout_params); |
| |
| if (formatting_context->maybe_baseline_offset_from_top_content_edge()) { |
| maybe_baseline_offset_from_top_margin_edge_ = |
| margin_top() + border_top_width() + padding_top() + |
| *formatting_context->maybe_baseline_offset_from_top_content_edge(); |
| } else { |
| maybe_baseline_offset_from_top_margin_edge_ = base::nullopt; |
| } |
| } |
| |
| void BlockContainerBox::UpdateContentSizeAndMargins( |
| const LayoutParams& layout_params) { |
| base::Optional<LayoutUnit> maybe_height = GetUsedHeightIfNotAuto( |
| computed_style(), layout_params.containing_block_size, NULL); |
| bool width_depends_on_containing_block; |
| |
| base::Optional<LayoutUnit> maybe_top = GetUsedTopIfNotAuto( |
| computed_style(), layout_params.containing_block_size); |
| base::Optional<LayoutUnit> maybe_bottom = GetUsedBottomIfNotAuto( |
| computed_style(), layout_params.containing_block_size); |
| base::Optional<LayoutUnit> maybe_margin_top = GetUsedMarginTopIfNotAuto( |
| computed_style(), layout_params.containing_block_size); |
| base::Optional<LayoutUnit> maybe_margin_bottom = GetUsedMarginBottomIfNotAuto( |
| computed_style(), layout_params.containing_block_size); |
| |
| if (layout_params.freeze_height) { |
| maybe_height = height(); |
| } |
| |
| if (!layout_params.freeze_width) { |
| base::Optional<LayoutUnit> maybe_width = GetUsedWidthIfNotAuto( |
| computed_style(), layout_params.containing_block_size, |
| &width_depends_on_containing_block); |
| base::Optional<LayoutUnit> maybe_margin_left = GetUsedMarginLeftIfNotAuto( |
| computed_style(), layout_params.containing_block_size); |
| base::Optional<LayoutUnit> maybe_margin_right = GetUsedMarginRightIfNotAuto( |
| computed_style(), layout_params.containing_block_size); |
| base::Optional<LayoutUnit> maybe_left = GetUsedLeftIfNotAuto( |
| computed_style(), layout_params.containing_block_size); |
| base::Optional<LayoutUnit> maybe_right = GetUsedRightIfNotAuto( |
| computed_style(), layout_params.containing_block_size); |
| |
| UpdateContentWidthAndMargins(layout_params.containing_block_direction, |
| layout_params.containing_block_size.width(), |
| layout_params.shrink_to_fit_width_forced, |
| width_depends_on_containing_block, maybe_left, |
| maybe_right, maybe_margin_left, |
| maybe_margin_right, maybe_width, maybe_height); |
| |
| // If 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'. |
| // https://www.w3.org/TR/CSS21/visudet.html#min-max-widths |
| bool max_width_depends_on_containing_block; |
| base::Optional<LayoutUnit> maybe_max_width = GetUsedMaxWidthIfNotNone( |
| computed_style(), layout_params.containing_block_size, |
| &max_width_depends_on_containing_block); |
| if (maybe_max_width && width() > maybe_max_width.value()) { |
| UpdateContentWidthAndMargins( |
| layout_params.containing_block_direction, |
| layout_params.containing_block_size.width(), |
| layout_params.shrink_to_fit_width_forced, |
| max_width_depends_on_containing_block, maybe_left, maybe_right, |
| maybe_margin_left, maybe_margin_right, maybe_max_width, maybe_height); |
| } |
| |
| // 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'. |
| // https://www.w3.org/TR/CSS21/visudet.html#min-max-widths |
| bool min_width_depends_on_containing_block; |
| base::Optional<LayoutUnit> maybe_min_width = GetUsedMinWidthIfNotAuto( |
| computed_style(), layout_params.containing_block_size, |
| &min_width_depends_on_containing_block); |
| if (maybe_min_width && (width() < maybe_min_width.value_or(LayoutUnit()))) { |
| UpdateContentWidthAndMargins( |
| layout_params.containing_block_direction, |
| layout_params.containing_block_size.width(), |
| layout_params.shrink_to_fit_width_forced, |
| min_width_depends_on_containing_block, maybe_left, maybe_right, |
| maybe_margin_left, maybe_margin_right, maybe_min_width, maybe_height); |
| } |
| } |
| |
| UpdateContentHeightAndMargins(layout_params.containing_block_size, maybe_top, |
| maybe_bottom, maybe_margin_top, |
| maybe_margin_bottom, maybe_height); |
| |
| // If the tentative height is greater than 'max-height', the rules above are |
| // applied again, but this time using the value of 'max-height' as the |
| // computed value for 'height'. |
| // https://www.w3.org/TR/CSS21/visudet.html#min-max-heights |
| base::Optional<LayoutUnit> maybe_max_height = GetUsedMaxHeightIfNotNone( |
| computed_style(), layout_params.containing_block_size); |
| if (maybe_max_height && height() > maybe_max_height.value()) { |
| UpdateContentHeightAndMargins(layout_params.containing_block_size, |
| maybe_top, maybe_bottom, maybe_margin_top, |
| maybe_margin_bottom, maybe_max_height); |
| } |
| |
| // If the resulting height is smaller than 'min-height', the rules above are |
| // applied again, but this time using the value of 'min-height' as the |
| // computed value for 'height'. |
| // https://www.w3.org/TR/CSS21/visudet.html#min-max-heights |
| base::Optional<LayoutUnit> min_height = GetUsedMinHeightIfNotAuto( |
| computed_style(), layout_params.containing_block_size); |
| if (min_height && (height() < min_height.value())) { |
| UpdateContentHeightAndMargins(layout_params.containing_block_size, |
| maybe_top, maybe_bottom, maybe_margin_top, |
| maybe_margin_bottom, min_height); |
| } |
| } |
| |
| WrapResult BlockContainerBox::TryWrapAt( |
| WrapAtPolicy wrap_at_policy, WrapOpportunityPolicy wrap_opportunity_policy, |
| bool is_line_existence_justified, LayoutUnit available_width, |
| bool should_collapse_trailing_white_space) { |
| DCHECK(!IsAbsolutelyPositioned()); |
| DCHECK_EQ(kInlineLevel, GetLevel()); |
| return kWrapResultNoWrap; |
| } |
| |
| bool BlockContainerBox::TrySplitAtSecondBidiLevelRun() { return false; } |
| |
| base::Optional<int> BlockContainerBox::GetBidiLevel() const { |
| return base::Optional<int>(); |
| } |
| |
| void BlockContainerBox::SetShouldCollapseLeadingWhiteSpace( |
| bool should_collapse_leading_white_space) { |
| DCHECK_EQ(kInlineLevel, GetLevel()); |
| // Do nothing. |
| } |
| |
| void BlockContainerBox::SetShouldCollapseTrailingWhiteSpace( |
| bool should_collapse_trailing_white_space) { |
| DCHECK_EQ(kInlineLevel, GetLevel()); |
| // Do nothing. |
| } |
| |
| bool BlockContainerBox::HasLeadingWhiteSpace() const { |
| DCHECK_EQ(kInlineLevel, GetLevel()); |
| return false; |
| } |
| |
| bool BlockContainerBox::HasTrailingWhiteSpace() const { |
| DCHECK_EQ(kInlineLevel, GetLevel()); |
| return false; |
| } |
| |
| bool BlockContainerBox::IsCollapsed() const { |
| DCHECK_EQ(kInlineLevel, GetLevel()); |
| return false; |
| } |
| |
| bool BlockContainerBox::JustifiesLineExistence() const { |
| DCHECK_EQ(kInlineLevel, GetLevel()); |
| return true; |
| } |
| |
| bool BlockContainerBox::AffectsBaselineInBlockFormattingContext() const { |
| return static_cast<bool>(maybe_baseline_offset_from_top_margin_edge_); |
| } |
| |
| LayoutUnit BlockContainerBox::GetBaselineOffsetFromTopMarginEdge() const { |
| return maybe_baseline_offset_from_top_margin_edge_.value_or( |
| GetMarginBoxHeight()); |
| } |
| |
| BlockContainerBox* BlockContainerBox::AsBlockContainerBox() { return this; } |
| const BlockContainerBox* BlockContainerBox::AsBlockContainerBox() const { |
| return this; |
| } |
| |
| scoped_refptr<ContainerBox> BlockContainerBox::TrySplitAtEnd() { |
| return scoped_refptr<ContainerBox>(); |
| } |
| |
| bool BlockContainerBox::IsTransformable() const { return true; } |
| |
| #ifdef COBALT_BOX_DUMP_ENABLED |
| |
| void BlockContainerBox::DumpProperties(std::ostream* stream) const { |
| ContainerBox::DumpProperties(stream); |
| |
| *stream << std::boolalpha << "affects_baseline_in_block_formatting_context=" |
| << AffectsBaselineInBlockFormattingContext() << " " |
| << std::noboolalpha; |
| } |
| |
| #endif // COBALT_BOX_DUMP_ENABLED |
| |
| // Based on https://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width. |
| // |
| // The constraint that determines the used values for these elements is: |
| // "left" + "margin-left" + "border-left-width" + "padding-left" |
| // + "width" |
| // + "padding-right" + "border-right-width" + "margin-right" + "right" |
| // = width of containing block |
| void BlockContainerBox::UpdateWidthAssumingAbsolutelyPositionedBox( |
| BaseDirection containing_block_direction, |
| LayoutUnit containing_block_width, |
| const base::Optional<LayoutUnit>& maybe_left, |
| const base::Optional<LayoutUnit>& maybe_right, |
| const base::Optional<LayoutUnit>& maybe_width, |
| const base::Optional<LayoutUnit>& maybe_margin_left, |
| const base::Optional<LayoutUnit>& maybe_margin_right, |
| const base::Optional<LayoutUnit>& maybe_height) { |
| // If all three of "left", "width", and "right" are "auto": |
| if (!maybe_left && !maybe_width && !maybe_right) { |
| // First set any "auto" values for "margin-left" and "margin-right" to 0. |
| set_margin_left(maybe_margin_left.value_or(LayoutUnit())); |
| set_margin_right(maybe_margin_right.value_or(LayoutUnit())); |
| |
| // Then, if the "direction" property of the element establishing the |
| // static-position containing block is "ltr"... |
| if (containing_block_direction == kLeftToRightBaseDirection) { |
| // ...set "left" to the static position... |
| set_left(GetStaticPositionLeft()); |
| |
| // ...and apply rule number three (the width is shrink-to-fit; solve for |
| // "right"). |
| set_width(GetShrinkToFitWidth(containing_block_width, maybe_height)); |
| } else { |
| // ...otherwise, set "right" to the static position... |
| // ...and apply rule number one (the width is shrink-to-fit; solve for |
| // "left"). |
| set_width(GetShrinkToFitWidth(containing_block_width, maybe_height)); |
| set_left(containing_block_width - GetStaticPositionRight() - |
| GetMarginBoxWidth()); |
| } |
| return; |
| } |
| |
| // If none of the three is "auto": |
| if (maybe_left && maybe_width && maybe_right) { |
| set_left(*maybe_left); |
| set_width(*maybe_width); |
| |
| LayoutUnit horizontal_margin_sum = containing_block_width - *maybe_left - |
| GetBorderBoxWidth() - *maybe_right; |
| |
| if (!maybe_margin_left && !maybe_margin_right) { |
| // If both "margin-left" and "margin-right" are "auto", solve the equation |
| // under the extra constraint that the two margins get equal values... |
| LayoutUnit horizontal_margin = horizontal_margin_sum / 2; |
| if (horizontal_margin < LayoutUnit()) { |
| // ...unless this would make them negative, in which case when direction |
| // of the containing block is "ltr" ("rtl"), set "margin-left" |
| // ("margin-right") to zero and solve for "margin-right" |
| // ("margin-left"). |
| if (containing_block_direction == kLeftToRightBaseDirection) { |
| set_margin_left(LayoutUnit()); |
| set_margin_right(horizontal_margin_sum); |
| } else { |
| set_margin_left(horizontal_margin_sum); |
| set_margin_right(LayoutUnit()); |
| } |
| } else { |
| set_margin_left(horizontal_margin); |
| set_margin_right(horizontal_margin); |
| } |
| } else if (!maybe_margin_left) { |
| // If one of "margin-left" or "margin-right" is "auto", solve the equation |
| // for that value. |
| set_margin_left(horizontal_margin_sum - *maybe_margin_right); |
| set_margin_right(*maybe_margin_right); |
| } else if (!maybe_margin_right) { |
| // If one of "margin-left" or "margin-right" is "auto", solve the equation |
| // for that value. |
| set_margin_left(*maybe_margin_left); |
| set_margin_right(horizontal_margin_sum - *maybe_margin_left); |
| } else { |
| // If the values are over-constrained, ignore the value for "left" (in |
| // case the "direction" property of the containing block is "rtl") or |
| // "right" (in case "direction" is "ltr") and solve for that value. |
| set_margin_left(*maybe_margin_left); |
| set_margin_right(*maybe_margin_right); |
| if (containing_block_direction == kRightToLeftBaseDirection) { |
| set_left(containing_block_width - GetMarginBoxWidth() - *maybe_right); |
| } |
| } |
| return; |
| } |
| |
| // Otherwise, set "auto" values for "margin-left" and "margin-right" to 0... |
| set_margin_left(maybe_margin_left.value_or(LayoutUnit())); |
| set_margin_right(maybe_margin_right.value_or(LayoutUnit())); |
| |
| // ...and pick the one of the following six rules that applies. |
| |
| // 1. "left" and "width" are "auto" and "right" is not "auto"... |
| if (!maybe_left && !maybe_width && maybe_right) { |
| // ...then the width is shrink-to-fit. |
| set_width(GetShrinkToFitWidth(containing_block_width, maybe_height)); |
| // Then solve for "left". |
| set_left(containing_block_width - GetMarginBoxWidth() - *maybe_right); |
| return; |
| } |
| |
| // 2. "left" and "right" are "auto" and "width" is not "auto"... |
| if (!maybe_left && !maybe_right && maybe_width) { |
| set_width(*maybe_width); |
| // ...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"). |
| if (containing_block_direction == kLeftToRightBaseDirection) { |
| set_left(GetStaticPositionLeft()); |
| } else { |
| set_left(containing_block_width - GetStaticPositionRight() - |
| GetMarginBoxWidth()); |
| } |
| DCHECK_EQ(left(), left()); // Check for NaN. |
| return; |
| } |
| |
| // 3. "width" and "right" are "auto" and "left" is not "auto"... |
| if (!maybe_width && !maybe_right && maybe_left) { |
| set_left(*maybe_left); |
| // ...then the width is shrink-to-fit. |
| set_width(GetShrinkToFitWidth(containing_block_width, maybe_height)); |
| return; |
| } |
| |
| // 4. "left" is "auto", "width" and "right" are not "auto"... |
| if (!maybe_left && maybe_width && maybe_right) { |
| set_width(*maybe_width); |
| // ...then solve for "left". |
| set_left(containing_block_width - GetMarginBoxWidth() - *maybe_right); |
| return; |
| } |
| |
| // 5. "width" is "auto", "left" and "right" are not "auto"... |
| if (!maybe_width && maybe_left && maybe_right) { |
| set_left(*maybe_left); |
| // ...then solve for "width". |
| set_width(containing_block_width - *maybe_left - margin_left() - |
| border_left_width() - padding_left() - padding_right() - |
| border_right_width() - margin_right() - *maybe_right); |
| return; |
| } |
| |
| // 6. "right" is "auto", "left" and "width" are not "auto". |
| if (!maybe_right && maybe_left && maybe_width) { |
| set_left(*maybe_left); |
| set_width(*maybe_width); |
| return; |
| } |
| } |
| |
| // Based on https://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-height. |
| // |
| // The constraint that determines the used values for these elements is: |
| // "top" + "margin-top" + "border-top-width" + "padding-top" |
| // + "height" |
| // + "padding-bottom" + "border-bottom-width" + "margin-bottom" + "bottom" |
| // = height of containing block |
| void BlockContainerBox::UpdateHeightAssumingAbsolutelyPositionedBox( |
| LayoutUnit containing_block_height, |
| const base::Optional<LayoutUnit>& maybe_top, |
| const base::Optional<LayoutUnit>& maybe_bottom, |
| const base::Optional<LayoutUnit>& maybe_height, |
| const base::Optional<LayoutUnit>& maybe_margin_top, |
| const base::Optional<LayoutUnit>& maybe_margin_bottom, |
| const FormattingContext& formatting_context) { |
| // If all three of "top", "height", and "bottom" are "auto": |
| if (!maybe_top && !maybe_height && !maybe_bottom) { |
| // First set any "auto" values for "margin-top" and "margin-bottom" to 0. |
| set_margin_top(maybe_margin_top.value_or(LayoutUnit())); |
| set_margin_bottom(maybe_margin_bottom.value_or(LayoutUnit())); |
| |
| // Then set "top" to the static position... |
| set_top(GetStaticPositionTop()); |
| DCHECK_EQ(top(), top()); // Check for NaN. |
| |
| // ...and apply rule number three (the height is based on the content). |
| set_height(formatting_context.auto_height()); |
| return; |
| } |
| |
| // If none of the three is "auto": |
| if (maybe_top && maybe_height && maybe_bottom) { |
| set_top(*maybe_top); |
| set_height(*maybe_height); |
| |
| LayoutUnit vertical_margin_sum = containing_block_height - *maybe_top - |
| GetBorderBoxHeight() - *maybe_bottom; |
| |
| if (!maybe_margin_top && !maybe_margin_bottom) { |
| // If both "margin-top" and "margin-bottom" are "auto", solve the equation |
| // under the extra constraint that the two margins get equal values... |
| LayoutUnit vertical_margin = vertical_margin_sum / 2; |
| set_margin_top(vertical_margin); |
| set_margin_bottom(vertical_margin); |
| } else if (!maybe_margin_top) { |
| // If one of "margin-top" or "margin-bottom" is "auto", solve the equation |
| // for that value. |
| set_margin_top(vertical_margin_sum - *maybe_margin_bottom); |
| set_margin_bottom(*maybe_margin_bottom); |
| } else if (!maybe_margin_bottom) { |
| // If one of "margin-top" or "margin-bottom" is "auto", solve the equation |
| // for that value. |
| set_margin_top(*maybe_margin_top); |
| set_margin_bottom(vertical_margin_sum - *maybe_margin_top); |
| } else { |
| // If the values are over-constrained, ignore the value for "bottom". |
| set_margin_top(*maybe_margin_top); |
| set_margin_bottom(*maybe_margin_bottom); |
| } |
| return; |
| } |
| |
| // Otherwise, set "auto" values for "margin-top" and "margin-bottom" to 0... |
| set_margin_top(maybe_margin_top.value_or(LayoutUnit())); |
| set_margin_bottom(maybe_margin_bottom.value_or(LayoutUnit())); |
| |
| // ...and pick the one of the following six rules that applies. |
| |
| // 1. "top" and "height" are "auto" and "bottom" is not "auto"... |
| if (!maybe_top && !maybe_height && maybe_bottom) { |
| // ...then the height is based on the content. |
| set_height(formatting_context.auto_height()); |
| // Then solve for "top". |
| set_top(containing_block_height - GetMarginBoxHeight() - *maybe_bottom); |
| return; |
| } |
| |
| // 2. "top" and "bottom" are "auto" and "height" is not "auto"... |
| if (!maybe_top && !maybe_bottom && maybe_height) { |
| set_height(*maybe_height); |
| // ...then set "top" to the static position. |
| set_top(GetStaticPositionTop()); |
| DCHECK_EQ(top(), top()); // Check for NaN. |
| return; |
| } |
| |
| // 3. "height" and "bottom" are "auto" and "top" is not "auto"... |
| if (!maybe_height && !maybe_bottom && maybe_top) { |
| set_top(*maybe_top); |
| // ...then the height is based on the content. |
| set_height(formatting_context.auto_height()); |
| return; |
| } |
| |
| // 4. "top" is "auto", "height" and "bottom" are not "auto"... |
| if (!maybe_top && maybe_height && maybe_bottom) { |
| set_height(*maybe_height); |
| // ...then solve for "top". |
| set_top(containing_block_height - GetMarginBoxHeight() - *maybe_bottom); |
| return; |
| } |
| |
| // 5. "height" is "auto", "top" and "bottom" are not "auto"... |
| if (!maybe_height && maybe_top && maybe_bottom) { |
| set_top(*maybe_top); |
| // ...then solve for "height". |
| set_height(containing_block_height - *maybe_top - margin_top() - |
| border_top_width() - padding_top() - padding_bottom() - |
| border_bottom_width() - margin_bottom() - *maybe_bottom); |
| return; |
| } |
| |
| // 6. "bottom" is "auto", "top" and "height" are not "auto". |
| if (!maybe_bottom && maybe_top && maybe_height) { |
| set_top(*maybe_top); |
| set_height(*maybe_height); |
| return; |
| } |
| } |
| |
| // Based on https://www.w3.org/TR/CSS21/visudet.html#blockwidth. |
| // |
| // The following constraints must hold among the used values of the other |
| // properties: |
| // "margin-left" + "border-left-width" + "padding-left" |
| // + "width" |
| // + "padding-right" + "border-right-width" + "margin-right" |
| // = width of containing block |
| void BlockContainerBox::UpdateWidthAssumingBlockLevelInFlowBox( |
| BaseDirection containing_block_direction, |
| LayoutUnit containing_block_width, |
| const base::Optional<LayoutUnit>& maybe_width, |
| const base::Optional<LayoutUnit>& possibly_overconstrained_margin_left, |
| const base::Optional<LayoutUnit>& possibly_overconstrained_margin_right) { |
| base::Optional<LayoutUnit> maybe_margin_left = |
| possibly_overconstrained_margin_left; |
| base::Optional<LayoutUnit> maybe_margin_right = |
| possibly_overconstrained_margin_right; |
| |
| if (maybe_width) { |
| set_width(*maybe_width); |
| UpdateHorizontalMarginsAssumingBlockLevelInFlowBox( |
| containing_block_direction, containing_block_width, GetBorderBoxWidth(), |
| maybe_margin_left, maybe_margin_right); |
| } else { |
| // If "width" is set to "auto", any other "auto" values become "0" and |
| // "width" follows from the resulting equality. |
| set_margin_left(maybe_margin_left.value_or(LayoutUnit())); |
| set_margin_right(maybe_margin_right.value_or(LayoutUnit())); |
| |
| set_width(containing_block_width - margin_left() - border_left_width() - |
| padding_left() - padding_right() - border_right_width() - |
| margin_right()); |
| } |
| } |
| |
| void BlockContainerBox::UpdateWidthAssumingInlineLevelInFlowBox( |
| LayoutUnit containing_block_width, |
| const base::Optional<LayoutUnit>& maybe_width, |
| const base::Optional<LayoutUnit>& maybe_margin_left, |
| const base::Optional<LayoutUnit>& maybe_margin_right, |
| const base::Optional<LayoutUnit>& maybe_height) { |
| // A computed value of "auto" for "margin-left" or "margin-right" becomes |
| // a used value of "0". |
| // https://www.w3.org/TR/CSS21/visudet.html#inlineblock-width |
| set_margin_left(maybe_margin_left.value_or(LayoutUnit())); |
| set_margin_right(maybe_margin_right.value_or(LayoutUnit())); |
| |
| if (!maybe_width) { |
| // If "width" is "auto", the used value is the shrink-to-fit width. |
| // https://www.w3.org/TR/CSS21/visudet.html#inlineblock-width |
| set_width(GetShrinkToFitWidth(containing_block_width, maybe_height)); |
| } else { |
| set_width(*maybe_width); |
| } |
| } |
| |
| LayoutUnit BlockContainerBox::GetShrinkToFitWidth( |
| LayoutUnit containing_block_width, |
| const base::Optional<LayoutUnit>& maybe_height) { |
| LayoutParams child_layout_params; |
| child_layout_params.containing_block_direction = base_direction_; |
| // The available width 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". |
| // https://www.w3.org/TR/CSS21/visudet.html#shrink-to-fit-float |
| child_layout_params.containing_block_size.set_width( |
| containing_block_width - margin_left() - border_left_width() - |
| padding_left() - padding_right() - border_right_width() - margin_right()); |
| // The "auto" height is not known yet but it shouldn't matter for in-flow |
| // children, as per: |
| // |
| // If the height of the containing block is not specified explicitly (i.e., |
| // it depends on content height), and this element is not absolutely |
| // positioned, the value [of "height"] computes to "auto". |
| // https://www.w3.org/TR/CSS21/visudet.html#the-height-property |
| child_layout_params.containing_block_size.set_height( |
| maybe_height.value_or(LayoutUnit())); |
| // Although the spec does not mention it explicitly, Chromium operates under |
| // the assumption that child block-level boxes must shrink instead of |
| // expanding when calculating shrink-to-fit width of the parent box. |
| child_layout_params.shrink_to_fit_width_forced = true; |
| |
| // Do a preliminary layout using the available width as a containing block |
| // width. See |InlineFormattingContext::EndUpdates()| for details. |
| // |
| // TODO: Laying out the children twice has an exponential worst-case |
| // complexity (because every child could lay out itself twice as |
| // well). Figure out if there is a better way. |
| std::unique_ptr<FormattingContext> formatting_context = |
| UpdateRectOfInFlowChildBoxes(child_layout_params); |
| |
| return formatting_context->shrink_to_fit_width(); |
| } |
| |
| // Based on https://www.w3.org/TR/CSS21/visudet.html#normal-block. |
| // |
| // TODO: Implement https://www.w3.org/TR/CSS21/visudet.html#block-root-margin |
| // when the margin collapsing is supported. |
| void BlockContainerBox::UpdateHeightAssumingInFlowBox( |
| const base::Optional<LayoutUnit>& maybe_height, |
| const base::Optional<LayoutUnit>& maybe_margin_top, |
| const base::Optional<LayoutUnit>& maybe_margin_bottom, |
| const FormattingContext& formatting_context) { |
| if (collapsed_empty_margin_) { |
| // If empty box has a collapsed margin, only set top margin. |
| // https://www.w3.org/TR/CSS22/box.html#collapsing-margins |
| set_margin_top(collapsed_empty_margin_.value()); |
| set_margin_bottom(LayoutUnit()); |
| } else { |
| // If "margin-top", or "margin-bottom" are "auto", their used value is 0. |
| LayoutUnit margin_top = |
| collapsed_margin_top_.value_or(maybe_margin_top.value_or(LayoutUnit())); |
| LayoutUnit margin_bottom = collapsed_margin_bottom_.value_or( |
| maybe_margin_bottom.value_or(LayoutUnit())); |
| set_margin_top(margin_top); |
| set_margin_bottom(margin_bottom); |
| } |
| |
| // If "height" is "auto", the used value is the distance from box's top |
| // content edge to the first applicable of the following: |
| // 1. the bottom edge of the last line box, if the box establishes |
| // an inline formatting context with one or more lines; |
| // 2. the bottom edge of the bottom margin of its last in-flow child. |
| set_height(maybe_height.value_or(formatting_context.auto_height())); |
| } |
| |
| } // namespace layout |
| } // namespace cobalt |