blob: 7508de5cab48725216cdcfcf81bd7e58998537dc [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 "cobalt/layout/block_formatting_context.h"
#include <algorithm>
#include "cobalt/cssom/keyword_value.h"
#include "cobalt/layout/box.h"
namespace cobalt {
namespace layout {
BlockFormattingContext::BlockFormattingContext(
const LayoutParams& layout_params, const bool is_margin_collapsible)
: layout_params_(layout_params),
margin_collapsing_params_(MarginCollapsingParams(is_margin_collapsible)) {
}
BlockFormattingContext::~BlockFormattingContext() {}
void BlockFormattingContext::UpdateRect(Box* child_box) {
DCHECK(!child_box->IsAbsolutelyPositioned());
child_box->UpdateSize(layout_params_);
// In a block formatting context, each box's left outer edge touches
// the left edge of the containing block.
// https://www.w3.org/TR/CSS21/visuren.html#block-formatting
child_box->set_left(LayoutUnit());
UpdatePosition(child_box);
// Shrink-to-fit width cannot be less than the width of the widest child.
// https://www.w3.org/TR/CSS21/visudet.html#float-width
set_shrink_to_fit_width(
std::max(shrink_to_fit_width(),
child_box->GetMarginBoxRightEdgeOffsetFromContainingBlock()));
// If "height" is "auto", the used value is the distance from box's top
// content edge to the bottom edge of the bottom margin of its last in-flow
// child.
// https://www.w3.org/TR/CSS21/visudet.html#normal-block
set_auto_height(child_box->GetMarginBoxBottomEdgeOffsetFromContainingBlock());
// The baseline of an "inline-block" is the baseline of its last line box
// in the normal flow, unless it has no in-flow line boxes.
// https://www.w3.org/TR/CSS21/visudet.html#line-height
if (child_box->AffectsBaselineInBlockFormattingContext()) {
set_baseline_offset_from_top_content_edge(
child_box->top() + child_box->GetBaselineOffsetFromTopMarginEdge());
}
}
void BlockFormattingContext::UpdatePosition(Box* child_box) {
DCHECK_EQ(Box::kBlockLevel, child_box->GetLevel());
switch (child_box->GetMarginCollapsingStatus()) {
case Box::kIgnore:
child_box->set_top(auto_height());
break;
case Box::kSeparateAdjoiningMargins:
child_box->set_top(auto_height());
margin_collapsing_params_.collapsing_margin = LayoutUnit();
margin_collapsing_params_.should_collapse_margin_bottom = false;
if (!margin_collapsing_params_.context_margin_top) {
margin_collapsing_params_.should_collapse_margin_top = false;
}
margin_collapsing_params_.should_collapse_own_margins_together = false;
break;
case Box::kCollapseMargins:
margin_collapsing_params_.should_collapse_margin_bottom = true;
// For first child, if top margin will collapse with parent's top margin,
// parent will handle margin positioning for both itself and the child.
if (!margin_collapsing_params_.context_margin_top &&
margin_collapsing_params_.should_collapse_margin_top) {
child_box->set_top(auto_height() - child_box->margin_top());
if (child_box->collapsed_empty_margin_) {
margin_collapsing_params_.should_collapse_margin_bottom = false;
}
} else {
// Collapse top margin with previous sibling's bottom margin.
LayoutUnit collapsed_margin =
CollapseMargins(child_box->margin_top(),
margin_collapsing_params_.collapsing_margin);
LayoutUnit combined_margin =
margin_collapsing_params_.collapsing_margin +
child_box->margin_top();
LayoutUnit position_difference = combined_margin - collapsed_margin;
child_box->set_top(auto_height() - position_difference);
}
// Collapse margins for in-flow siblings.
margin_collapsing_params_.collapsing_margin =
child_box->collapsed_empty_margin_.value_or(
child_box->margin_bottom());
if (!margin_collapsing_params_.context_margin_top) {
margin_collapsing_params_.context_margin_top = child_box->margin_top();
}
break;
}
}
void BlockFormattingContext::CollapseContainingMargins(Box* containing_box) {
bool has_padding_top = containing_box->padding_top() != LayoutUnit();
bool has_border_top = containing_box->border_top_width() != LayoutUnit();
bool has_padding_bottom = containing_box->padding_bottom() != LayoutUnit();
bool has_border_bottom =
containing_box->border_bottom_width() != LayoutUnit();
LayoutUnit margin_top =
layout_params_.maybe_margin_top.value_or(LayoutUnit());
LayoutUnit margin_bottom =
layout_params_.maybe_margin_bottom.value_or(LayoutUnit());
// If no in-flow children, do not collapse margins with children.
if (!margin_collapsing_params_.context_margin_top) {
// Empty boxes with auto or 0 height collapse top/bottom margins together.
// https://www.w3.org/TR/CSS22/box.html#collapsing-margins
if (!has_padding_top && !has_border_top && !has_padding_bottom &&
!has_border_bottom &&
layout_params_.containing_block_size.height() == LayoutUnit() &&
margin_collapsing_params_.should_collapse_own_margins_together) {
containing_box->collapsed_empty_margin_ =
CollapseMargins(margin_top, margin_bottom);
return;
}
// Reset in case min-height iteration reverses 0/auto height criteria.
containing_box->collapsed_empty_margin_.reset();
return;
}
// Collapse top margin with top margin of first in-flow child.
if (!has_padding_top && !has_border_top &&
margin_collapsing_params_.should_collapse_margin_top) {
LayoutUnit collapsed_margin_top = CollapseMargins(
margin_top,
margin_collapsing_params_.context_margin_top.value_or(LayoutUnit()));
containing_box->collapsed_margin_top_ = collapsed_margin_top;
}
// If height is auto, collapse bottom margin with bottom margin of last
// in-flow child.
if (!layout_params_.maybe_height && !has_padding_bottom &&
!has_border_bottom &&
margin_collapsing_params_.should_collapse_margin_bottom) {
LayoutUnit collapsed_margin_bottom = CollapseMargins(
margin_bottom, margin_collapsing_params_.collapsing_margin);
containing_box->collapsed_margin_bottom_ = collapsed_margin_bottom;
set_auto_height(auto_height() -
margin_collapsing_params_.collapsing_margin);
}
}
LayoutUnit BlockFormattingContext::CollapseMargins(
const LayoutUnit box_margin, const LayoutUnit adjoining_margin) {
// In a block formatting context, boxes are laid out one after the other,
// vertically, beginning at the top of a containing block. The vertical
// distance between two sibling boxes is determined by the "margin"
// properties. Vertical margins between adjacent block-level boxes in a block
// formatting context collapse.
// https://www.w3.org/TR/CSS21/visuren.html#block-formatting
// When two or more margins collapse, the resulting margin width is the
// maximum of the collapsing margins' widths.
// https://www.w3.org/TR/CSS21/box.html#collapsing-margins
LayoutUnit collapsed_margin;
if ((box_margin >= LayoutUnit()) && (adjoining_margin >= LayoutUnit())) {
collapsed_margin = std::max(box_margin, adjoining_margin);
} else if ((box_margin < LayoutUnit()) && (adjoining_margin < LayoutUnit())) {
// If there are no positive margins, the maximum of the absolute values of
// the adjoining margins is deducted from zero.
collapsed_margin = LayoutUnit() + std::min(box_margin, adjoining_margin);
} else {
// In the case of negative margins, the maximum of the absolute values of
// the negative adjoining margins is deducted from the maximum of the
// positive adjoining margins.
// When there is only one negative and one positive margin, that translates
// to: The margins are summed.
DCHECK(adjoining_margin.GreaterEqualOrNaN(LayoutUnit()) ||
box_margin.GreaterEqualOrNaN(LayoutUnit()));
collapsed_margin = adjoining_margin + box_margin;
}
return collapsed_margin;
}
} // namespace layout
} // namespace cobalt