blob: 58bc8c227aa5ab83995e1a4f800aaa581f020332 [file] [log] [blame]
// Copyright 2017 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 "third_party/blink/renderer/core/layout/ng/ng_floats_utils.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/layout/layout_box.h"
#include "third_party/blink/renderer/core/layout/min_max_size.h"
#include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_container_fragment_builder.h"
#include "third_party/blink/renderer/core/layout/ng/ng_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h"
#include "third_party/blink/renderer/core/layout/ng/ng_length_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h"
#include "third_party/blink/renderer/core/layout/ng/ng_space_utils.h"
#include "third_party/blink/renderer/core/layout/ng/ng_unpositioned_float.h"
#include "third_party/blink/renderer/core/style/computed_style.h"
namespace blink {
namespace {
// 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.
NGBfcOffset AdjustToTopEdgeAlignmentRule(
const NGExclusionSpace& exclusion_space,
const NGBfcOffset& offset) {
NGBfcOffset adjusted_offset = offset;
adjusted_offset.block_offset = std::max(
adjusted_offset.block_offset, exclusion_space.LastFloatBlockStart());
return adjusted_offset;
}
NGLayoutOpportunity FindLayoutOpportunityForFloat(
const NGBfcOffset& origin_offset,
const NGExclusionSpace& exclusion_space,
const NGUnpositionedFloat& unpositioned_float,
LayoutUnit inline_size) {
NGBfcOffset adjusted_origin_point =
AdjustToTopEdgeAlignmentRule(exclusion_space, origin_offset);
LayoutUnit clearance_offset =
exclusion_space.ClearanceOffset(unpositioned_float.ClearType());
AdjustToClearance(clearance_offset, &adjusted_origin_point);
NGLogicalSize float_size(inline_size + unpositioned_float.margins.InlineSum(),
LayoutUnit());
// TODO(ikilpatrick): Don't include the block-start margin of a float which
// has fragmented.
return exclusion_space.FindLayoutOpportunity(
adjusted_origin_point, unpositioned_float.available_size.inline_size,
float_size);
}
// Creates a constraint space for an unpositioned float. origin_block_offset
// should only be set when we want to fragmentation to occur.
scoped_refptr<NGConstraintSpace> CreateConstraintSpaceForFloat(
const NGUnpositionedFloat& unpositioned_float,
const NGConstraintSpace& parent_space,
base::Optional<LayoutUnit> origin_block_offset = base::nullopt) {
const ComputedStyle& style = unpositioned_float.node.Style();
NGConstraintSpaceBuilder builder(parent_space);
if (origin_block_offset) {
DCHECK(parent_space.HasBlockFragmentation());
DCHECK_EQ(style.GetWritingMode(), parent_space.GetWritingMode());
LayoutUnit fragmentation_offset =
parent_space.FragmentainerSpaceAtBfcStart() -
origin_block_offset.value();
builder.SetFragmentainerBlockSize(parent_space.FragmentainerBlockSize());
builder.SetFragmentainerSpaceAtBfcStart(fragmentation_offset);
builder.SetFragmentationType(parent_space.BlockFragmentationType());
} else {
builder.SetFragmentationType(NGFragmentationType::kFragmentNone);
}
return builder.SetPercentageResolutionSize(unpositioned_float.percentage_size)
.SetAvailableSize(unpositioned_float.available_size)
.SetIsNewFormattingContext(true)
.SetIsShrinkToFit(true)
.SetTextDirection(style.Direction())
.ToConstraintSpace(style.GetWritingMode());
}
std::unique_ptr<NGExclusionShapeData> CreateExclusionShapeData(
const NGBoxStrut& margins,
const LayoutBox* layout_box,
const NGUnpositionedFloat& unpositioned_float,
const NGConstraintSpace& parent_space,
TextDirection direction) {
DCHECK(layout_box->GetShapeOutsideInfo());
// We make the margins on the shape-data relative to line-left/line-right.
NGBoxStrut new_margins(margins.LineLeft(direction),
margins.LineRight(direction), margins.block_start,
margins.block_end);
NGBoxStrut shape_insets;
const ComputedStyle& style = layout_box->StyleRef();
switch (style.ShapeOutside()->CssBox()) {
case CSSBoxType::kMissing:
case CSSBoxType::kMargin:
shape_insets -= new_margins;
break;
case CSSBoxType::kBorder:
break;
case CSSBoxType::kPadding:
shape_insets =
ComputeBorders(
*CreateConstraintSpaceForFloat(unpositioned_float, parent_space),
style)
.ConvertToPhysical(style.GetWritingMode(), style.Direction())
.ConvertToLogical(parent_space.GetWritingMode(),
TextDirection::kLtr);
break;
case CSSBoxType::kContent:
const scoped_refptr<NGConstraintSpace> space =
CreateConstraintSpaceForFloat(unpositioned_float, parent_space);
NGBoxStrut border_padding =
ComputeBorders(*space, style) + ComputePadding(*space, style);
shape_insets =
border_padding
.ConvertToPhysical(style.GetWritingMode(), style.Direction())
.ConvertToLogical(parent_space.GetWritingMode(),
TextDirection::kLtr);
break;
}
return std::make_unique<NGExclusionShapeData>(layout_box, new_margins,
shape_insets);
}
// Creates an exclusion from the fragment that will be placed in the provided
// layout opportunity.
scoped_refptr<NGExclusion> CreateExclusion(
const NGFragment& fragment,
const NGBfcOffset& float_margin_bfc_offset,
const NGBoxStrut& margins,
const LayoutBox* layout_box,
const NGUnpositionedFloat& unpositioned_float,
const NGConstraintSpace& parent_space,
TextDirection direction,
EFloat type) {
// TODO(ikilpatrick): Don't include the block-start margin of a float which
// has fragmented.
NGBfcOffset start_offset = float_margin_bfc_offset;
NGBfcOffset end_offset(
/* line_offset */ start_offset.line_offset +
(fragment.InlineSize() + margins.InlineSum()).ClampNegativeToZero(),
/* block_offset */ start_offset.block_offset +
(fragment.BlockSize() + margins.BlockSum()).ClampNegativeToZero());
std::unique_ptr<NGExclusionShapeData> shape_data =
layout_box->GetShapeOutsideInfo()
? CreateExclusionShapeData(margins, layout_box, unpositioned_float,
parent_space, direction)
: nullptr;
return NGExclusion::Create(NGBfcRect(start_offset, end_offset), type,
std::move(shape_data));
}
// Performs layout on a float, without fragmentation, and stores the result on
// the NGUnpositionedFloat data-structure.
void LayoutFloatWithoutFragmentation(const NGConstraintSpace& parent_space,
NGUnpositionedFloat* unpositioned_float) {
if (unpositioned_float->layout_result)
return;
const scoped_refptr<NGConstraintSpace> space =
CreateConstraintSpaceForFloat(*unpositioned_float, parent_space);
unpositioned_float->layout_result = unpositioned_float->node.Layout(*space);
}
} // namespace
LayoutUnit ComputeInlineSizeForUnpositionedFloat(
const NGConstraintSpace& parent_space,
NGUnpositionedFloat* unpositioned_float) {
DCHECK(unpositioned_float);
LayoutFloatWithoutFragmentation(parent_space, unpositioned_float);
DCHECK(unpositioned_float->layout_result);
const auto& fragment = unpositioned_float->layout_result->PhysicalFragment();
DCHECK(fragment);
DCHECK(fragment->BreakToken()->IsFinished());
return NGFragment(parent_space.GetWritingMode(), *fragment).InlineSize();
}
NGPositionedFloat PositionFloat(LayoutUnit origin_block_offset,
LayoutUnit parent_bfc_block_offset,
NGUnpositionedFloat* unpositioned_float,
const NGConstraintSpace& parent_space,
NGExclusionSpace* exclusion_space) {
DCHECK(unpositioned_float);
bool is_same_writing_mode =
unpositioned_float->node.Style().GetWritingMode() ==
parent_space.GetWritingMode();
bool is_fragmentable =
is_same_writing_mode && parent_space.HasBlockFragmentation();
scoped_refptr<NGLayoutResult> layout_result;
// We may be able to re-use the fragment from when we calculated the
// inline-size, if there is no block fragmentation.
if (!is_fragmentable) {
LayoutFloatWithoutFragmentation(parent_space, unpositioned_float);
layout_result = unpositioned_float->layout_result;
} else {
scoped_refptr<NGConstraintSpace> space = CreateConstraintSpaceForFloat(
*unpositioned_float, parent_space, origin_block_offset);
layout_result = unpositioned_float->node.Layout(
*space, unpositioned_float->token.get());
}
DCHECK(layout_result->PhysicalFragment());
NGFragment float_fragment(parent_space.GetWritingMode(),
*layout_result->PhysicalFragment());
NGBfcOffset origin_offset = {unpositioned_float->origin_bfc_line_offset,
origin_block_offset};
// Find a layout opportunity that will fit our float.
NGLayoutOpportunity opportunity = FindLayoutOpportunityForFloat(
origin_offset, *exclusion_space, *unpositioned_float,
float_fragment.InlineSize());
// Calculate the float's margin box BFC offset.
NGBfcOffset float_margin_bfc_offset = opportunity.rect.start_offset;
if (unpositioned_float->IsRight()) {
LayoutUnit float_margin_box_inline_size =
float_fragment.InlineSize() + unpositioned_float->margins.InlineSum();
float_margin_bfc_offset.line_offset +=
(opportunity.rect.InlineSize() - float_margin_box_inline_size);
}
// Add the float as an exclusion.
scoped_refptr<NGExclusion> exclusion = CreateExclusion(
float_fragment, float_margin_bfc_offset, unpositioned_float->margins,
unpositioned_float->node.GetLayoutBox(), *unpositioned_float,
parent_space, parent_space.Direction(),
unpositioned_float->IsRight() ? EFloat::kRight : EFloat::kLeft);
exclusion_space->Add(std::move(exclusion));
// Adjust the float's bfc_offset to its border-box (instead of margin-box).
NGBfcOffset float_bfc_offset(
float_margin_bfc_offset.line_offset +
unpositioned_float->margins.LineLeft(parent_space.Direction()),
float_margin_bfc_offset.block_offset +
unpositioned_float->margins.block_start);
return NGPositionedFloat(std::move(layout_result), float_bfc_offset);
}
const Vector<NGPositionedFloat> PositionFloats(
LayoutUnit origin_block_offset,
LayoutUnit parent_bfc_block_offset,
const Vector<scoped_refptr<NGUnpositionedFloat>>& unpositioned_floats,
const NGConstraintSpace& space,
NGExclusionSpace* exclusion_space) {
Vector<NGPositionedFloat> positioned_floats;
positioned_floats.ReserveCapacity(unpositioned_floats.size());
for (auto& unpositioned_float : unpositioned_floats) {
positioned_floats.push_back(
PositionFloat(origin_block_offset, parent_bfc_block_offset,
unpositioned_float.get(), space, exclusion_space));
}
return positioned_floats;
}
void AddUnpositionedFloat(
Vector<scoped_refptr<NGUnpositionedFloat>>* unpositioned_floats,
NGContainerFragmentBuilder* fragment_builder,
scoped_refptr<NGUnpositionedFloat> unpositioned_float) {
// The same float node should not be added more than once.
DCHECK(
!RemoveUnpositionedFloat(unpositioned_floats, unpositioned_float->node));
if (fragment_builder && !fragment_builder->BfcOffset()) {
fragment_builder->AddAdjoiningFloatTypes(
unpositioned_float->IsLeft() ? kFloatTypeLeft : kFloatTypeRight);
}
unpositioned_floats->push_back(std::move(unpositioned_float));
}
bool RemoveUnpositionedFloat(
Vector<scoped_refptr<NGUnpositionedFloat>>* unpositioned_floats,
NGBlockNode float_node) {
for (scoped_refptr<NGUnpositionedFloat>& unpositioned_float :
*unpositioned_floats) {
if (unpositioned_float->node == float_node) {
unpositioned_floats->erase(&unpositioned_float);
return true;
}
}
return false;
}
NGFloatTypes ToFloatTypes(EClear clear) {
switch (clear) {
default:
NOTREACHED();
FALLTHROUGH;
case EClear::kNone:
return kFloatTypeNone;
case EClear::kLeft:
return kFloatTypeLeft;
case EClear::kRight:
return kFloatTypeRight;
case EClear::kBoth:
return kFloatTypeBoth;
};
}
} // namespace blink