blob: 7608224ff2fa39e2ee0513bf8e7fe9ab25a1d03b [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "core/layout/ng/ng_block_layout_algorithm.h"
#include "core/layout/ng/ng_absolute_utils.h"
#include "core/layout/ng/ng_block_break_token.h"
#include "core/layout/ng/ng_block_child_iterator.h"
#include "core/layout/ng/ng_box_fragment.h"
#include "core/layout/ng/ng_constraint_space.h"
#include "core/layout/ng/ng_constraint_space_builder.h"
#include "core/layout/ng/ng_fragment.h"
#include "core/layout/ng/ng_fragment_builder.h"
#include "core/layout/ng/ng_inline_node.h"
#include "core/layout/ng/ng_layout_opportunity_iterator.h"
#include "core/layout/ng/ng_length_utils.h"
#include "core/layout/ng/ng_line_builder.h"
#include "core/layout/ng/ng_out_of_flow_layout_part.h"
#include "core/layout/ng/ng_units.h"
#include "core/style/ComputedStyle.h"
#include "platform/LengthFunctions.h"
#include "wtf/Optional.h"
namespace blink {
namespace {
// Whether child's constraint space should shrink to its intrinsic width.
// This is needed for buttons, select, input, floats and orthogonal children.
// See LayoutBox::sizesLogicalWidthToFitContent for the rationale behind this.
bool ShouldShrinkToFit(const NGConstraintSpace& parent_space,
const ComputedStyle& child_style) {
NGWritingMode child_writing_mode =
FromPlatformWritingMode(child_style.getWritingMode());
// Whether the child and the containing block are parallel to each other.
// Example: vertical-rl and vertical-lr
bool is_in_parallel_flow =
IsParallelWritingMode(parent_space.WritingMode(), child_writing_mode);
return child_style.display() == EDisplay::InlineBlock ||
child_style.isFloating() || !is_in_parallel_flow;
}
// Returns max of 2 {@code WTF::Optional} values.
template <typename T>
WTF::Optional<T> OptionalMax(const WTF::Optional<T>& value1,
const WTF::Optional<T>& value2) {
if (value1 && value2) {
return std::max(value1.value(), value2.value());
} else if (value1) {
return value1;
}
return value2;
}
WTF::Optional<LayoutUnit> GetClearanceOffset(
const std::shared_ptr<NGExclusions>& exclusions,
const ComputedStyle& style) {
const NGExclusion* right_exclusion = exclusions->last_right_float;
const NGExclusion* left_exclusion = exclusions->last_left_float;
WTF::Optional<LayoutUnit> left_offset;
if (left_exclusion) {
left_offset = left_exclusion->rect.BlockEndOffset();
}
WTF::Optional<LayoutUnit> right_offset;
if (right_exclusion) {
right_offset = right_exclusion->rect.BlockEndOffset();
}
switch (style.clear()) {
case EClear::kNone:
return WTF::nullopt; // nothing to do here.
case EClear::kLeft:
return left_offset;
case EClear::kRight:
return right_offset;
case EClear::kBoth:
return OptionalMax<LayoutUnit>(left_offset, right_offset);
default:
ASSERT_NOT_REACHED();
}
return WTF::nullopt;
}
// Creates an exclusion from the fragment that will be placed in the provided
// layout opportunity.
NGExclusion CreateExclusion(const NGFragment& fragment,
const NGLayoutOpportunity& opportunity,
const LayoutUnit float_offset,
const NGBoxStrut& margins,
NGExclusion::Type exclusion_type) {
NGExclusion exclusion;
exclusion.type = exclusion_type;
NGLogicalRect& rect = exclusion.rect;
rect.offset = opportunity.offset;
rect.offset.inline_offset += float_offset;
rect.size.inline_size = fragment.InlineSize() + margins.InlineSum();
rect.size.block_size = fragment.BlockSize() + margins.BlockSum();
return exclusion;
}
// Adjusts the provided offset to the top edge alignment rule.
// Top edge alignment rule: the outer top of a floating box may not be higher
// than the outer top of any block or floated box generated by an element
// earlier in the source document.
NGLogicalOffset AdjustToTopEdgeAlignmentRule(const NGConstraintSpace& space,
const NGLogicalOffset& offset) {
NGLogicalOffset adjusted_offset = offset;
LayoutUnit& adjusted_block_offset = adjusted_offset.block_offset;
if (space.Exclusions()->last_left_float)
adjusted_block_offset =
std::max(adjusted_block_offset,
space.Exclusions()->last_left_float->rect.BlockStartOffset());
if (space.Exclusions()->last_right_float)
adjusted_block_offset =
std::max(adjusted_block_offset,
space.Exclusions()->last_right_float->rect.BlockStartOffset());
return adjusted_offset;
}
// Finds a layout opportunity for the fragment.
// It iterates over all layout opportunities in the constraint space and returns
// the first layout opportunity that is wider than the fragment or returns the
// last one which is always the widest.
//
// @param space Constraint space that is used to find layout opportunity for
// the fragment.
// @param fragment Fragment that needs to be placed.
// @param origin_point {@code space}'s offset relative to the space that
// establishes a new formatting context that we're currently
// in and where all our exclusions reside.
// @param margins Margins of the fragment.
// @return Layout opportunity for the fragment.
const NGLayoutOpportunity FindLayoutOpportunityForFragment(
NGConstraintSpace* space,
const NGFragment& fragment,
const NGLogicalOffset& origin_point,
const NGBoxStrut& margins) {
NGLogicalOffset adjusted_origin_point =
AdjustToTopEdgeAlignmentRule(*space, origin_point);
NGLayoutOpportunityIterator opportunity_iter(space, adjusted_origin_point);
NGLayoutOpportunity opportunity;
NGLayoutOpportunity opportunity_candidate = opportunity_iter.Next();
while (!opportunity_candidate.IsEmpty()) {
opportunity = opportunity_candidate;
// Checking opportunity's block size is not necessary as a float cannot be
// positioned on top of another float inside of the same constraint space.
auto fragment_inline_size = fragment.InlineSize() + margins.InlineSum();
if (opportunity.size.inline_size >= fragment_inline_size)
break;
opportunity_candidate = opportunity_iter.Next();
}
return opportunity;
}
// Calculates the logical offset for opportunity.
NGLogicalOffset CalculateLogicalOffsetForOpportunity(
const NGLayoutOpportunity& opportunity,
const LayoutUnit float_offset,
const NGLogicalOffset& from_offset,
NGFloatingObject* floating_object) {
auto margins = floating_object->margins;
// Adjust to child's margin.
LayoutUnit inline_offset = margins.inline_start;
LayoutUnit block_offset = margins.block_start;
// Offset from the opportunity's block/inline start.
inline_offset += opportunity.offset.inline_offset;
block_offset += opportunity.offset.block_offset;
// Adjust to float: right offset if needed.
inline_offset += float_offset;
block_offset -= from_offset.block_offset;
inline_offset -= from_offset.inline_offset;
return NGLogicalOffset(inline_offset, block_offset);
}
// Calculates the relative position from {@code from_offset} of the
// floating object that is requested to be positioned from {@code origin_point}.
NGLogicalOffset PositionFloat(const NGLogicalOffset& origin_point,
const NGLogicalOffset& from_offset,
NGFloatingObject* floating_object) {
NGConstraintSpace* float_space = floating_object->space;
DCHECK(floating_object->fragment) << "Fragment cannot be null here";
// TODO(ikilpatrick): The writing mode switching here looks wrong.
NGBoxFragment float_fragment(
float_space->WritingMode(),
toNGPhysicalBoxFragment(floating_object->fragment.get()));
// Find a layout opportunity that will fit our float.
const NGLayoutOpportunity opportunity =
FindLayoutOpportunityForFragment(floating_object->space, float_fragment,
origin_point, floating_object->margins);
DCHECK(!opportunity.IsEmpty()) << "Opportunity is empty but it shouldn't be";
// Calculate the float offset if needed.
LayoutUnit float_offset;
if (floating_object->exclusion_type == NGExclusion::kFloatRight) {
LayoutUnit float_margin_box_inline_size =
float_fragment.InlineSize() + floating_object->margins.InlineSum();
float_offset = opportunity.size.inline_size - float_margin_box_inline_size;
}
// Add the float as an exclusion.
const NGExclusion exclusion = CreateExclusion(
float_fragment, opportunity, float_offset, floating_object->margins,
floating_object->exclusion_type);
float_space->AddExclusion(exclusion);
return CalculateLogicalOffsetForOpportunity(opportunity, float_offset,
from_offset, floating_object);
}
// Updates the Floating Object's left offset from the provided parent_space
// and {@code floating_object}'s space and margins.
void UpdateFloatingObjectLeftOffset(
const NGConstraintSpace& new_parent_space,
const Persistent<NGFloatingObject>& floating_object,
const NGLogicalOffset& float_logical_offset) {
// TODO(glebl): We should use physical offset here.
floating_object->left_offset =
floating_object->original_parent_space->BfcOffset().inline_offset -
new_parent_space.BfcOffset().inline_offset +
float_logical_offset.inline_offset;
}
// Positions pending floats stored on the fragment builder starting from
// {@code origin_point_block_offset}.
void PositionPendingFloats(const LayoutUnit origin_point_block_offset,
const NGConstraintSpace& new_parent_space,
NGFragmentBuilder* builder) {
DCHECK(builder->BfcOffset()) << "Parent BFC offset should be known here";
LayoutUnit bfc_block_offset = builder->BfcOffset().value().block_offset;
for (auto& floating_object : builder->UnpositionedFloats()) {
Member<NGConstraintSpace> float_space = floating_object->space;
Member<const NGConstraintSpace> original_parent_space =
floating_object->original_parent_space;
NGLogicalOffset origin_point = {float_space->BfcOffset().inline_offset,
origin_point_block_offset};
NGLogicalOffset from_offset = {
original_parent_space->BfcOffset().inline_offset, bfc_block_offset};
NGLogicalOffset float_fragment_offset =
PositionFloat(origin_point, from_offset, floating_object);
builder->AddFloatingObject(floating_object, float_fragment_offset);
UpdateFloatingObjectLeftOffset(new_parent_space, floating_object,
float_fragment_offset);
}
builder->MutableUnpositionedFloats().clear();
}
// Whether an in-flow block-level child creates a new formatting context.
//
// This will *NOT* check the following cases:
// - The child is out-of-flow, e.g. floating or abs-pos.
// - The child is a inline-level, e.g. "display: inline-block".
// - The child establishes a new formatting context, but should be a child of
// another layout algorithm, e.g. "display: table-caption" or flex-item.
bool IsNewFormattingContextForInFlowBlockLevelChild(
const NGConstraintSpace& space,
const ComputedStyle& style) {
// TODO(layout-dev): This doesn't capture a few cases which can't be computed
// directly from style yet:
// - The child is a <fieldset>.
// - "column-span: all" is set on the child (requires knowledge that we are
// in a multi-col formatting context).
// (https://drafts.csswg.org/css-multicol-1/#valdef-column-span-all)
if (style.specifiesColumns() || style.containsPaint() ||
style.containsLayout())
return true;
if (!style.isOverflowVisible())
return true;
EDisplay display = style.display();
if (display == EDisplay::Grid || display == EDisplay::Flex ||
display == EDisplay::WebkitBox)
return true;
if (space.WritingMode() != FromPlatformWritingMode(style.getWritingMode()))
return true;
return false;
}
// Whether we've run out of space in this flow. If so, there will be no work
// left to do for this block in this fragmentainer.
bool IsOutOfSpace(const NGConstraintSpace& space, LayoutUnit content_size) {
return space.HasBlockFragmentation() &&
content_size >= space.FragmentainerSpaceAvailable();
}
} // namespace
NGBlockLayoutAlgorithm::NGBlockLayoutAlgorithm(
NGBlockNode* node,
NGConstraintSpace* constraint_space,
NGBlockBreakToken* break_token)
: node_(node),
constraint_space_(constraint_space),
break_token_(break_token),
builder_(NGPhysicalFragment::kFragmentBox, node),
space_builder_(constraint_space_) {}
Optional<MinAndMaxContentSizes>
NGBlockLayoutAlgorithm::ComputeMinAndMaxContentSizes() const {
MinAndMaxContentSizes sizes;
// Size-contained elements don't consider their contents for intrinsic sizing.
if (Style().containsSize())
return sizes;
// TODO: handle floats & orthogonal children.
for (NGLayoutInputNode* node = node_->FirstChild(); node;
node = node->NextSibling()) {
MinAndMaxContentSizes child_sizes;
if (node->Type() == NGLayoutInputNode::kLegacyInline) {
// From |NGBlockLayoutAlgorithm| perspective, we can handle |NGInlineNode|
// almost the same as |NGBlockNode|, because an |NGInlineNode| includes
// all inline nodes following |node| and their descendants, and produces
// an anonymous box that contains all line boxes.
// |NextSibling| returns the next block sibling, or nullptr, skipping all
// following inline siblings and descendants.
child_sizes = toNGInlineNode(node)->ComputeMinAndMaxContentSizes();
} else {
Optional<MinAndMaxContentSizes> child_minmax;
NGBlockNode* block_child = toNGBlockNode(node);
if (NeedMinAndMaxContentSizesForContentContribution(
block_child->Style())) {
child_minmax = block_child->ComputeMinAndMaxContentSizes();
}
child_sizes = ComputeMinAndMaxContentContribution(block_child->Style(),
child_minmax);
}
sizes.min_content = std::max(sizes.min_content, child_sizes.min_content);
sizes.max_content = std::max(sizes.max_content, child_sizes.max_content);
}
sizes.max_content = std::max(sizes.min_content, sizes.max_content);
return sizes;
}
NGLogicalOffset NGBlockLayoutAlgorithm::CalculateLogicalOffset(
const WTF::Optional<NGLogicalOffset>& known_fragment_offset) {
LayoutUnit inline_offset =
border_and_padding_.inline_start + curr_child_margins_.inline_start;
LayoutUnit block_offset = content_size_;
if (known_fragment_offset) {
block_offset = known_fragment_offset.value().block_offset -
builder_.BfcOffset().value().block_offset;
}
return {inline_offset, block_offset};
}
void NGBlockLayoutAlgorithm::UpdateFragmentBfcOffset(
const NGLogicalOffset& offset) {
if (!builder_.BfcOffset()) {
NGLogicalOffset bfc_offset = offset;
if (ConstraintSpace().ClearanceOffset()) {
bfc_offset.block_offset = std::max(
ConstraintSpace().ClearanceOffset().value(), offset.block_offset);
}
builder_.SetBfcOffset(bfc_offset);
}
}
RefPtr<NGLayoutResult> NGBlockLayoutAlgorithm::Layout() {
WTF::Optional<MinAndMaxContentSizes> sizes;
if (NeedMinAndMaxContentSizes(ConstraintSpace(), Style()))
sizes = ComputeMinAndMaxContentSizes();
border_and_padding_ = ComputeBorders(ConstraintSpace(), Style()) +
ComputePadding(ConstraintSpace(), Style());
LayoutUnit inline_size =
ComputeInlineSizeForFragment(ConstraintSpace(), Style(), sizes);
LayoutUnit adjusted_inline_size =
inline_size - border_and_padding_.InlineSum();
// TODO(layout-ng): For quirks mode, should we pass blockSize instead of
// -1?
LayoutUnit block_size =
ComputeBlockSizeForFragment(ConstraintSpace(), Style(), NGSizeIndefinite);
LayoutUnit adjusted_block_size(block_size);
// Our calculated block-axis size may be indefinite at this point.
// If so, just leave the size as NGSizeIndefinite instead of subtracting
// borders and padding.
if (adjusted_block_size != NGSizeIndefinite)
adjusted_block_size -= border_and_padding_.BlockSum();
space_builder_
.SetAvailableSize(
NGLogicalSize(adjusted_inline_size, adjusted_block_size))
.SetPercentageResolutionSize(
NGLogicalSize(adjusted_inline_size, adjusted_block_size));
builder_.SetDirection(constraint_space_->Direction());
builder_.SetWritingMode(constraint_space_->WritingMode());
builder_.SetInlineSize(inline_size).SetBlockSize(block_size);
NGBlockChildIterator child_iterator(node_->FirstChild(), break_token_);
NGBlockChildIterator::Entry entry = child_iterator.NextChild();
NGLayoutInputNode* child = entry.node;
NGBreakToken* child_break_token = entry.token;
// If we are resuming from a break token our start border and padding is
// within a previous fragment.
content_size_ = break_token_ ? LayoutUnit() : border_and_padding_.block_start;
curr_margin_strut_ = ConstraintSpace().MarginStrut();
curr_bfc_offset_ = ConstraintSpace().BfcOffset();
// Margins collapsing:
// Do not collapse margins between parent and its child if there is
// border/padding between them.
if (border_and_padding_.block_start) {
curr_bfc_offset_.block_offset += curr_margin_strut_.Sum();
UpdateFragmentBfcOffset(curr_bfc_offset_);
curr_margin_strut_ = NGMarginStrut();
}
// Block that establishes a new BFC knows its BFC offset == {}
// If a new formatting context hits the if branch above then the BFC offset is
// still {} as the margin strut from the constraint space must also be empty.
if (ConstraintSpace().IsNewFormattingContext()) {
UpdateFragmentBfcOffset(curr_bfc_offset_);
DCHECK_EQ(builder_.BfcOffset().value(), NGLogicalOffset());
DCHECK_EQ(curr_margin_strut_, NGMarginStrut());
}
curr_bfc_offset_.block_offset += content_size_;
while (child) {
if (child->Type() == NGLayoutInputNode::kLegacyBlock) {
NGBlockNode* current_block_child = toNGBlockNode(child);
EPosition position = current_block_child->Style().position();
if (position == EPosition::kAbsolute || position == EPosition::kFixed) {
builder_.AddOutOfFlowChildCandidate(current_block_child,
GetChildSpaceOffset());
NGBlockChildIterator::Entry entry = child_iterator.NextChild();
child = entry.node;
child_break_token = entry.token;
continue;
}
}
NGConstraintSpace* child_space = CreateConstraintSpaceForChild(child);
if (child->Type() == NGLayoutInputNode::kLegacyInline) {
LayoutInlineChildren(toNGInlineNode(child), child_space);
break;
}
RefPtr<NGLayoutResult> layout_result =
child->Layout(child_space, child_break_token);
FinishChildLayout(child, child_space, layout_result);
entry = child_iterator.NextChild();
child = entry.node;
child_break_token = entry.token;
if (IsOutOfSpace(ConstraintSpace(), content_size_))
break;
}
// Margins collapsing:
// Bottom margins of an in-flow block box doesn't collapse with its last
// in-flow block-level child's bottom margin if the box has bottom
// border/padding.
content_size_ += border_and_padding_.block_end;
if (border_and_padding_.block_end ||
ConstraintSpace().IsNewFormattingContext()) {
content_size_ += curr_margin_strut_.Sum();
curr_margin_strut_ = NGMarginStrut();
}
// Recompute the block-axis size now that we know our content size.
block_size =
ComputeBlockSizeForFragment(ConstraintSpace(), Style(), content_size_);
builder_.SetBlockSize(block_size);
// Layout our absolute and fixed positioned children.
NGOutOfFlowLayoutPart(ConstraintSpace(), Style(), &builder_).Run();
// Non-empty blocks always know their position in space:
if (block_size) {
curr_bfc_offset_.block_offset += curr_margin_strut_.Sum();
UpdateFragmentBfcOffset(curr_bfc_offset_);
PositionPendingFloats(curr_bfc_offset_.block_offset, ConstraintSpace(),
&builder_);
}
// Margins collapsing:
// Do not collapse margins between the last in-flow child and bottom margin
// of its parent if the parent has height != auto()
if (!Style().logicalHeight().isAuto()) {
// TODO(glebl): handle minLogicalHeight, maxLogicalHeight.
curr_margin_strut_ = NGMarginStrut();
}
builder_.SetEndMarginStrut(curr_margin_strut_);
builder_.SetInlineOverflow(max_inline_size_).SetBlockOverflow(content_size_);
if (ConstraintSpace().HasBlockFragmentation())
FinalizeForFragmentation();
return builder_.ToBoxFragment();
}
void NGBlockLayoutAlgorithm::LayoutInlineChildren(
NGInlineNode* inline_child,
NGConstraintSpace* child_space) {
// TODO(kojii): This logic does not handle when children are mix of
// inline/block. We need to detect the case and setup appropriately; e.g.,
// constraint space, margin collapsing, next siblings, etc.
NGLineBuilder line_builder(inline_child, child_space);
// TODO(kojii): Need to determine when to invalidate PrepareLayout() more
// efficiently than "everytime".
inline_child->InvalidatePrepareLayout();
inline_child->LayoutInline(child_space, &line_builder);
// TODO(kojii): The wrapper fragment should not be needed.
NGFragmentBuilder wrapper_fragment_builder(NGPhysicalFragment::kFragmentBox,
inline_child);
line_builder.CreateFragments(&wrapper_fragment_builder);
RefPtr<NGLayoutResult> child_result =
wrapper_fragment_builder.ToBoxFragment();
line_builder.CopyFragmentDataToLayoutBlockFlow();
FinishChildLayout(inline_child, child_space, child_result);
}
void NGBlockLayoutAlgorithm::FinishChildLayout(
NGLayoutInputNode* child,
NGConstraintSpace* child_space,
RefPtr<NGLayoutResult> layout_result) {
NGBoxFragment fragment(
ConstraintSpace().WritingMode(),
toNGPhysicalBoxFragment(layout_result->PhysicalFragment().get()));
// Pull out unpositioned floats to the current fragment. This may needed if
// for example the child fragment could not position its floats because it's
// empty and therefore couldn't determine its position in space.
builder_.MutableUnpositionedFloats().appendVector(
layout_result->UnpositionedFloats());
if (child->Type() == NGLayoutInputNode::kLegacyBlock &&
toNGBlockNode(child)->Style().isFloating()) {
NGFloatingObject* floating_object = new NGFloatingObject(
layout_result->PhysicalFragment().get(), child_space, constraint_space_,
toNGBlockNode(child), toNGBlockNode(child)->Style(),
curr_child_margins_);
builder_.AddUnpositionedFloat(floating_object);
// No need to postpone the positioning if we know the correct offset.
if (builder_.BfcOffset()) {
NGLogicalOffset origin_point = curr_bfc_offset_;
// Adjust origin point to the margins of the last child.
// Example: <div style="margin-bottom: 20px"><float></div>
// <div style="margin-bottom: 30px"></div>
origin_point.block_offset += curr_margin_strut_.Sum();
PositionPendingFloats(origin_point.block_offset, ConstraintSpace(),
&builder_);
}
return;
}
// Determine the fragment's position in the parent space either by using
// content_size_ or known fragment's BFC offset.
WTF::Optional<NGLogicalOffset> bfc_offset;
if (child_space->IsNewFormattingContext()) {
// TODO(ikilpatrick): We may need to place ourself within the BFC
// before a new formatting context child is laid out. (Not after layout as
// is done here).
curr_bfc_offset_.block_offset += curr_margin_strut_.Sum();
bfc_offset = curr_bfc_offset_;
} else if (fragment.BfcOffset()) {
// Fragment that knows its offset can be used to set parent's BFC position.
curr_bfc_offset_.block_offset = fragment.BfcOffset().value().block_offset;
bfc_offset = curr_bfc_offset_;
} else if (builder_.BfcOffset()) {
// Fragment doesn't know its offset but we can still calculate its BFC
// position because the parent fragment's BFC is known.
// Example:
// BFC Offset is known here because of the padding.
// <div style="padding: 1px">
// <div id="empty-div" style="margins: 1px"></div>
bfc_offset = curr_bfc_offset_;
bfc_offset.value().block_offset += curr_margin_strut_.Sum();
}
if (bfc_offset) {
UpdateFragmentBfcOffset(curr_bfc_offset_);
PositionPendingFloats(curr_bfc_offset_.block_offset, ConstraintSpace(),
&builder_);
}
NGLogicalOffset logical_offset = CalculateLogicalOffset(bfc_offset);
// Update margin strut.
curr_margin_strut_ = fragment.EndMarginStrut();
curr_margin_strut_.Append(curr_child_margins_.block_end);
// Only modify content_size if BlockSize is not empty. It's needed to prevent
// the situation when logical_offset is included in content_size for empty
// blocks. Example:
// <div style="overflow:hidden">
// <div style="margin-top: 8px"></div>
// <div style="margin-top: 10px"></div>
// </div>
if (fragment.BlockSize())
content_size_ = fragment.BlockSize() + logical_offset.block_offset;
max_inline_size_ =
std::max(max_inline_size_, fragment.InlineSize() +
curr_child_margins_.InlineSum() +
border_and_padding_.InlineSum());
builder_.AddChild(layout_result, logical_offset);
}
void NGBlockLayoutAlgorithm::FinalizeForFragmentation() {
LayoutUnit used_block_size =
break_token_ ? break_token_->UsedBlockSize() : LayoutUnit();
LayoutUnit block_size = ComputeBlockSizeForFragment(
ConstraintSpace(), Style(), used_block_size + content_size_);
block_size -= used_block_size;
DCHECK_GE(block_size, LayoutUnit())
<< "Adding and subtracting the used_block_size shouldn't leave the "
"block_size for this fragment smaller than zero.";
DCHECK(builder_.BfcOffset()) << "We must have our BfcOffset by this point "
"to determine the space left in the flow.";
LayoutUnit space_left = ConstraintSpace().FragmentainerSpaceAvailable() -
builder_.BfcOffset().value().block_offset;
DCHECK_GE(space_left, LayoutUnit());
if (builder_.DidBreak()) {
// One of our children broke. Even if we fit within the remaining space we
// need to prepare a break token.
builder_.SetUsedBlockSize(std::min(space_left, block_size) +
used_block_size);
builder_.SetBlockSize(std::min(space_left, block_size));
builder_.SetBlockOverflow(space_left);
return;
}
if (block_size > space_left) {
// Need a break inside this block.
builder_.SetUsedBlockSize(space_left + used_block_size);
builder_.SetBlockSize(space_left);
builder_.SetBlockOverflow(space_left);
return;
}
// The end of the block fits in the current fragmentainer.
builder_.SetBlockSize(block_size);
builder_.SetBlockOverflow(content_size_);
}
NGBoxStrut NGBlockLayoutAlgorithm::CalculateMargins(
NGBlockNode* child,
const NGConstraintSpace& space) {
DCHECK(child);
const ComputedStyle& child_style = child->Style();
WTF::Optional<MinAndMaxContentSizes> sizes;
if (NeedMinAndMaxContentSizes(space, child_style))
sizes = child->ComputeMinAndMaxContentSizes();
LayoutUnit child_inline_size =
ComputeInlineSizeForFragment(space, child_style, sizes);
NGBoxStrut margins = ComputeMargins(space, child_style, space.WritingMode(),
space.Direction());
if (!child_style.isFloating()) {
ApplyAutoMargins(space, child_style, child_inline_size, &margins);
}
return margins;
}
NGConstraintSpace* NGBlockLayoutAlgorithm::CreateConstraintSpaceForChild(
NGLayoutInputNode* child) {
DCHECK(child);
if (child->Type() == NGLayoutInputNode::kLegacyInline) {
// TODO(kojii): Setup space_builder_ appropriately for inline child.
// Margins collapsing: Inline block.
curr_bfc_offset_.block_offset += curr_margin_strut_.Sum();
UpdateFragmentBfcOffset(curr_bfc_offset_);
PositionPendingFloats(curr_bfc_offset_.block_offset, ConstraintSpace(),
&builder_);
curr_margin_strut_ = {};
return space_builder_.ToConstraintSpace(
FromPlatformWritingMode(Style().getWritingMode()));
}
NGBlockNode* block_child = toNGBlockNode(child);
const ComputedStyle& child_style = block_child->Style();
// Calculate margins in parent's writing mode.
curr_child_margins_ = CalculateMargins(
block_child, *space_builder_.ToConstraintSpace(
FromPlatformWritingMode(Style().getWritingMode())));
bool is_new_bfc = IsNewFormattingContextForInFlowBlockLevelChild(
ConstraintSpace(), child_style);
space_builder_.SetIsNewFormattingContext(is_new_bfc)
.SetIsShrinkToFit(ShouldShrinkToFit(ConstraintSpace(), child_style))
.SetTextDirection(child_style.direction());
// Clearance :
// - *Always* collapse margins and update *container*'s BFC offset.
// - Position all pending floats since the fragment's BFC offset is known.
// - Set the clearance offset on the constraint space's builder.
if (child_style.clear() != EClear::kNone) {
curr_bfc_offset_.block_offset += curr_margin_strut_.Sum();
UpdateFragmentBfcOffset(curr_bfc_offset_);
// Only collapse margins if it's an adjoining block with clearance.
if (!content_size_) {
curr_margin_strut_ = NGMarginStrut();
curr_child_margins_.block_start = LayoutUnit();
}
PositionPendingFloats(curr_bfc_offset_.block_offset, ConstraintSpace(),
&builder_);
WTF::Optional<LayoutUnit> clearance_offset =
GetClearanceOffset(constraint_space_->Exclusions(), child_style);
space_builder_.SetClearanceOffset(clearance_offset);
}
// Set estimated BFC offset to the next child's constraint space.
curr_bfc_offset_ = builder_.BfcOffset() ? builder_.BfcOffset().value()
: ConstraintSpace().BfcOffset();
curr_bfc_offset_.block_offset += content_size_;
curr_bfc_offset_.inline_offset += border_and_padding_.inline_start;
// Floats margins are not included in child's CS because
// 1) Floats do not participate in margins collapsing
// 2) Floats margins are used separately to calculate floating exclusions.
if (!child_style.isFloating()) {
curr_bfc_offset_.inline_offset += curr_child_margins_.inline_start;
// Append the current margin strut with child's block start margin.
// Non empty border/padding use cases are handled inside of the child's
// layout.
curr_margin_strut_.Append(curr_child_margins_.block_start);
space_builder_.SetMarginStrut(curr_margin_strut_);
}
space_builder_.SetBfcOffset(curr_bfc_offset_);
LayoutUnit space_available;
if (constraint_space_->HasBlockFragmentation()) {
space_available = ConstraintSpace().FragmentainerSpaceAvailable();
// If a block establishes a new formatting context we must know our
// position in the formatting context, and are able to adjust the
// fragmentation line.
if (is_new_bfc) {
DCHECK(builder_.BfcOffset());
space_available -= curr_bfc_offset_.block_offset;
}
}
space_builder_.SetFragmentainerSpaceAvailable(space_available);
return space_builder_.ToConstraintSpace(
FromPlatformWritingMode(child_style.getWritingMode()));
}
} // namespace blink