blob: 0c04ea1c6afe9ab57aa8db32c0b8d3d12997abe6 [file] [log] [blame]
// 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