blob: 9e3a1933d3d272ce7b8d2349fab52f285402dbed [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_box_fragment.h"
#include "core/layout/ng/ng_column_mapper.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_layout_opportunity_iterator.h"
#include "core/layout/ng/ng_length_utils.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 {
// Updates the fragment's BFC offset if it's not already set.
void UpdateFragmentBfcOffset(const NGLogicalOffset& offset,
const NGConstraintSpace& space,
NGFragmentBuilder* builder) {
NGLogicalOffset fragment_offset =
space.IsNewFormattingContext() ? NGLogicalOffset() : offset;
if (!builder->BfcOffset())
builder->SetBfcOffset(fragment_offset);
}
// Adjusts content_size to respect the CSS "clear" property.
// Picks up the maximum between left/right exclusions and content_size depending
// on the value of style.clear() property.
void AdjustToClearance(const std::shared_ptr<NGExclusions>& exclusions,
const ComputedStyle& style,
const NGLogicalOffset& from_offset,
LayoutUnit* content_size) {
DCHECK(content_size) << "content_size cannot be null here";
const NGExclusion* right_exclusion = exclusions->last_right_float;
const NGExclusion* left_exclusion = exclusions->last_left_float;
LayoutUnit left_block_end_offset = *content_size;
if (left_exclusion) {
left_block_end_offset = std::max(
left_exclusion->rect.BlockEndOffset() - from_offset.block_offset,
*content_size);
}
LayoutUnit right_block_end_offset = *content_size;
if (right_exclusion) {
right_block_end_offset = std::max(
right_exclusion->rect.BlockEndOffset() - from_offset.block_offset,
*content_size);
}
switch (style.clear()) {
case EClear::kNone:
return; // nothing to do here.
case EClear::kLeft:
*content_size = left_block_end_offset;
break;
case EClear::kRight:
*content_size = right_block_end_offset;
break;
case EClear::kBoth:
*content_size = std::max(left_block_end_offset, right_block_end_offset);
break;
default:
ASSERT_NOT_REACHED();
}
}
// 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 NGBoxStrut& margins,
const NGLogicalOffset& space_offset) {
// 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;
inline_offset += float_offset;
block_offset -= space_offset.block_offset;
inline_offset -= space_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) {
float_offset = opportunity.size.inline_size - float_fragment.InlineSize();
}
// 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, floating_object->margins, from_offset);
}
// Positions pending floats stored on the fragment builder starting from
// {@code origin_point}.
void PositionPendingFloats(const NGLogicalOffset& origin_point,
NGFragmentBuilder* builder) {
DCHECK(builder->BfcOffset()) << "Parent BFC offset should be known here";
NGLogicalOffset from_offset = builder->BfcOffset().value();
for (auto& floating_object : builder->UnpositionedFloats()) {
NGLogicalOffset float_fragment_offset =
PositionFloat(origin_point, from_offset, floating_object);
builder->AddFloatingObject(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;
}
} // namespace
NGBlockLayoutAlgorithm::NGBlockLayoutAlgorithm(
LayoutObject* layout_object,
PassRefPtr<const ComputedStyle> style,
NGBlockNode* first_child,
NGConstraintSpace* constraint_space,
NGBreakToken* break_token)
: style_(style),
first_child_(first_child),
constraint_space_(constraint_space),
break_token_(break_token),
builder_(WTF::wrapUnique(
new NGFragmentBuilder(NGPhysicalFragment::kFragmentBox,
layout_object))) {
DCHECK(style_);
}
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 (NGBlockNode* node = first_child_; node; node = node->NextSibling()) {
Optional<MinAndMaxContentSizes> child_minmax;
if (NeedMinAndMaxContentSizesForContentContribution(node->Style())) {
child_minmax = node->ComputeMinAndMaxContentSizes();
}
MinAndMaxContentSizes child_sizes =
ComputeMinAndMaxContentContribution(node->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::CalculateRelativeOffset(
const NGBoxFragment& fragment) {
LayoutUnit inline_offset =
border_and_padding_.inline_start + curr_child_margins_.inline_start;
LayoutUnit block_offset = content_size_;
if (fragment.BfcOffset()) {
block_offset = fragment.BfcOffset().value().block_offset -
builder_->BfcOffset().value().block_offset;
}
return {inline_offset, block_offset};
}
RefPtr<NGPhysicalFragment> NGBlockLayoutAlgorithm::Layout() {
WTF::Optional<MinAndMaxContentSizes> sizes;
if (NeedMinAndMaxContentSizes(ConstraintSpace(), Style()))
sizes = ComputeMinAndMaxContentSizes();
border_and_padding_ =
ComputeBorders(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_ = new NGConstraintSpaceBuilder(constraint_space_);
if (Style().specifiesColumns()) {
space_builder_->SetFragmentationType(kFragmentColumn);
adjusted_inline_size =
ResolveUsedColumnInlineSize(adjusted_inline_size, Style());
LayoutUnit inline_progression =
adjusted_inline_size + ResolveUsedColumnGap(Style());
fragmentainer_mapper_ =
new NGColumnMapper(inline_progression, adjusted_block_size);
}
space_builder_->SetAvailableSize(
NGLogicalSize(adjusted_inline_size, adjusted_block_size));
space_builder_->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);
// TODO(glebl): fix multicol after the new margin collapsing/floats algorithm
// based on BFCOffset is checked in.
if (NGBlockBreakToken* token = CurrentBlockBreakToken()) {
// Resume after a previous break.
content_size_ = token->BreakOffset();
current_child_ = token->InputNode();
} else {
content_size_ = border_and_padding_.block_start;
current_child_ = first_child_;
}
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();
builder_->SetBfcOffset(curr_bfc_offset_);
curr_margin_strut_ = NGMarginStrut();
}
curr_bfc_offset_.block_offset += content_size_;
while (current_child_) {
EPosition position = current_child_->Style().position();
if (position == AbsolutePosition || position == FixedPosition) {
builder_->AddOutOfFlowChildCandidate(current_child_,
GetChildSpaceOffset());
current_child_ = current_child_->NextSibling();
continue;
}
DCHECK(!ConstraintSpace().HasBlockFragmentation() ||
SpaceAvailableForCurrentChild() > LayoutUnit());
space_for_current_child_ = CreateConstraintSpaceForCurrentChild();
RefPtr<NGPhysicalFragment> physical_fragment =
current_child_->Layout(space_for_current_child_);
FinishCurrentChildLayout(toNGPhysicalBoxFragment(physical_fragment.get()));
if (!ProceedToNextUnfinishedSibling(physical_fragment.get()))
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(Style(), builder_.get()).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_, ConstraintSpace(),
builder_.get());
PositionPendingFloats(curr_bfc_offset_, builder_.get());
}
// 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::FinishCurrentChildLayout(
RefPtr<NGPhysicalBoxFragment> physical_fragment) {
NGBoxFragment fragment(ConstraintSpace().WritingMode(),
physical_fragment.get());
if (!physical_fragment->UnpositionedFloats().isEmpty())
DCHECK(!builder_->BfcOffset()) << "Parent BFC offset shouldn't be set here";
// 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(
physical_fragment->UnpositionedFloats());
if (CurrentChildStyle().isFloating()) {
NGFloatingObject* floating_object = new NGFloatingObject(
physical_fragment.get(), space_for_current_child_, current_child_,
CurrentChildStyle(), 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, builder_.get());
}
return;
}
// Fragment that knows its offset can be used to set parent's BFC position.
if (fragment.BfcOffset()) {
curr_bfc_offset_.block_offset = fragment.BfcOffset().value().block_offset;
UpdateFragmentBfcOffset(curr_bfc_offset_, ConstraintSpace(),
builder_.get());
PositionPendingFloats(curr_bfc_offset_, builder_.get());
}
NGLogicalOffset fragment_offset = CalculateRelativeOffset(fragment);
if (fragmentainer_mapper_)
fragmentainer_mapper_->ToVisualOffset(fragment_offset);
else
fragment_offset.block_offset -= PreviousBreakOffset();
// Update margin strut.
curr_margin_strut_ = fragment.EndMarginStrut();
curr_margin_strut_.Append(curr_child_margins_.block_end);
content_size_ = fragment.BlockSize() + fragment_offset.block_offset;
max_inline_size_ =
std::max(max_inline_size_, fragment.InlineSize() +
curr_child_margins_.InlineSum() +
border_and_padding_.InlineSum());
builder_->AddChild(std::move(physical_fragment), fragment_offset);
}
bool NGBlockLayoutAlgorithm::ProceedToNextUnfinishedSibling(
NGPhysicalFragment* child_fragment) {
DCHECK(current_child_);
NGBlockNode* finished_child = current_child_;
current_child_ = current_child_->NextSibling();
if (!ConstraintSpace().HasBlockFragmentation() && !fragmentainer_mapper_)
return true;
// If we're resuming layout after a fragmentainer break, we need to skip
// siblings that we're done with. We may have been able to fully lay out some
// node(s) preceding a node that we had to break inside (and therefore were
// not able to fully lay out). This happens when we have parallel flows [1],
// which are caused by floats, overflow, etc.
//
// [1] https://drafts.csswg.org/css-break/#parallel-flows
if (CurrentBlockBreakToken()) {
// TODO(layout-ng): Figure out if we need a better way to determine if the
// node is finished. Maybe something to encode in a break token?
while (current_child_ && current_child_->IsLayoutFinished())
current_child_ = current_child_->NextSibling();
}
LayoutUnit break_offset = NextBreakOffset();
bool is_out_of_space = content_size_ - PreviousBreakOffset() >= break_offset;
if (!HasPendingBreakToken()) {
bool child_broke = child_fragment->BreakToken();
// This block needs to break if the child broke, or if we're out of space
// and there's more content waiting to be laid out. Otherwise, just bail
// now.
if (!child_broke && (!is_out_of_space || !current_child_))
return true;
// Prepare a break token for this block, so that we know where to resume
// when the time comes for that. We may not be able to abort layout of this
// block right away, due to the posibility of parallel flows. We can only
// abort when we're out of space, or when there are no siblings left to
// process.
NGBlockBreakToken* token;
if (child_broke) {
// The child we just laid out was the first one to break. So that is
// where we need to resume.
token = new NGBlockBreakToken(finished_child, break_offset);
} else {
// Resume layout at the next sibling that needs layout.
DCHECK(current_child_);
token = new NGBlockBreakToken(current_child_, break_offset);
}
SetPendingBreakToken(token);
}
if (!fragmentainer_mapper_) {
if (!is_out_of_space)
return true;
// We have run out of space in this flow, so there's no work left to do for
// this block in this fragmentainer. We should finalize the fragment and get
// back to the remaining content when laying out the next fragmentainer(s).
return false;
}
if (is_out_of_space || !current_child_) {
NGBlockBreakToken* token = fragmentainer_mapper_->Advance();
DCHECK(token || !is_out_of_space);
if (token) {
break_token_ = token;
content_size_ = token->BreakOffset();
current_child_ = token->InputNode();
}
}
return true;
}
void NGBlockLayoutAlgorithm::SetPendingBreakToken(NGBlockBreakToken* token) {
if (fragmentainer_mapper_)
fragmentainer_mapper_->SetBreakToken(token);
else
builder_->SetBreakToken(token);
}
bool NGBlockLayoutAlgorithm::HasPendingBreakToken() const {
if (fragmentainer_mapper_)
return fragmentainer_mapper_->HasBreakToken();
return builder_->HasBreakToken();
}
void NGBlockLayoutAlgorithm::FinalizeForFragmentation() {
LayoutUnit block_size =
ComputeBlockSizeForFragment(ConstraintSpace(), Style(), content_size_);
LayoutUnit previous_break_offset = PreviousBreakOffset();
block_size -= previous_break_offset;
block_size = std::max(LayoutUnit(), block_size);
LayoutUnit space_left = ConstraintSpace().FragmentainerSpaceAvailable();
DCHECK_GE(space_left, LayoutUnit());
if (builder_->HasBreakToken()) {
// A break token is ready, which means that we're going to break
// before or inside a block-level child.
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_->SetBreakToken(new NGBlockBreakToken(nullptr, NextBreakOffset()));
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_ - previous_break_offset);
}
NGBlockBreakToken* NGBlockLayoutAlgorithm::CurrentBlockBreakToken() const {
NGBreakToken* token = break_token_;
if (!token || token->Type() != NGBreakToken::kBlockBreakToken)
return nullptr;
return toNGBlockBreakToken(token);
}
LayoutUnit NGBlockLayoutAlgorithm::PreviousBreakOffset() const {
const NGBlockBreakToken* token = CurrentBlockBreakToken();
return token ? token->BreakOffset() : LayoutUnit();
}
LayoutUnit NGBlockLayoutAlgorithm::NextBreakOffset() const {
if (fragmentainer_mapper_)
return fragmentainer_mapper_->NextBreakOffset();
DCHECK(ConstraintSpace().HasBlockFragmentation());
return PreviousBreakOffset() +
ConstraintSpace().FragmentainerSpaceAvailable();
}
LayoutUnit NGBlockLayoutAlgorithm::SpaceAvailableForCurrentChild() const {
LayoutUnit space_left;
if (fragmentainer_mapper_)
space_left = fragmentainer_mapper_->BlockSize();
else if (ConstraintSpace().HasBlockFragmentation())
space_left = ConstraintSpace().FragmentainerSpaceAvailable();
else
return NGSizeIndefinite;
space_left -= BorderEdgeForCurrentChild() - PreviousBreakOffset();
return space_left;
}
NGBoxStrut NGBlockLayoutAlgorithm::CalculateMargins(
const NGConstraintSpace& space,
const ComputedStyle& style) {
WTF::Optional<MinAndMaxContentSizes> sizes;
if (NeedMinAndMaxContentSizes(space, style)) {
// TODO(ikilpatrick): Change ComputeMinAndMaxContentSizes to return
// MinAndMaxContentSizes.
sizes = current_child_->ComputeMinAndMaxContentSizes();
}
LayoutUnit child_inline_size =
ComputeInlineSizeForFragment(space, style, sizes);
NGBoxStrut margins =
ComputeMargins(space, style, space.WritingMode(), space.Direction());
if (!style.isFloating()) {
ApplyAutoMargins(space, style, child_inline_size, &margins);
}
return margins;
}
NGConstraintSpace*
NGBlockLayoutAlgorithm::CreateConstraintSpaceForCurrentChild() {
// TODO(layout-ng): Orthogonal children should also shrink to fit (in *their*
// inline axis)
bool shrink_to_fit = CurrentChildStyle().display() == EDisplay::InlineBlock ||
CurrentChildStyle().isFloating();
DCHECK(current_child_);
bool is_new_bfc = IsNewFormattingContextForInFlowBlockLevelChild(
ConstraintSpace(), CurrentChildStyle());
space_builder_->SetIsNewFormattingContext(is_new_bfc)
.SetIsShrinkToFit(shrink_to_fit)
.SetWritingMode(
FromPlatformWritingMode(CurrentChildStyle().getWritingMode()))
.SetTextDirection(CurrentChildStyle().direction());
LayoutUnit space_available = SpaceAvailableForCurrentChild();
space_builder_->SetFragmentainerSpaceAvailable(space_available);
curr_child_margins_ = CalculateMargins(*space_builder_->ToConstraintSpace(),
CurrentChildStyle());
// Clearance :
// - Collapse margins
// - Update curr_bfc_offset and parent BFC offset if needed.
// - Position all pending floats as position is known now.
// TODO(glebl): Fix the use case with clear: left and an intruding right.
// https://software.hixie.ch/utilities/js/live-dom-viewer/saved/4847
if (CurrentChildStyle().clear() != EClear::kNone) {
curr_bfc_offset_.block_offset += curr_margin_strut_.Sum();
UpdateFragmentBfcOffset(curr_bfc_offset_, ConstraintSpace(),
builder_.get());
// 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_, builder_.get());
AdjustToClearance(constraint_space_->Exclusions(), CurrentChildStyle(),
builder_->BfcOffset().value(), &content_size_);
}
// 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_);
// 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;
if (ConstraintSpace().IsNewFormattingContext()) {
curr_bfc_offset_.inline_offset += curr_child_margins_.inline_start;
}
space_builder_->SetBfcOffset(curr_bfc_offset_);
return space_builder_->ToConstraintSpace();
}
} // namespace blink