blob: 1159e6a1aa23dd62fa5af1449d7d21be5ef81255 [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/layout/min_max_size.h"
#include "third_party/blink/renderer/core/layout/ng/ng_box_fragment.h"
#include "third_party/blink/renderer/core/layout/ng/ng_constraint_space_builder.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/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 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,
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());
return NGExclusion::Create(NGBfcRect(start_offset, end_offset), type);
}
scoped_refptr<NGConstraintSpace> CreateConstraintSpaceForFloatFromBuilder(
const NGUnpositionedFloat& unpositioned_float,
NGConstraintSpaceBuilder& builder) {
const ComputedStyle& style = unpositioned_float.node.Style();
return builder.SetPercentageResolutionSize(unpositioned_float.percentage_size)
.SetAvailableSize(unpositioned_float.available_size)
.SetIsNewFormattingContext(true)
.SetIsShrinkToFit(true)
.SetTextDirection(style.Direction())
.ToConstraintSpace(style.GetWritingMode());
}
// Creates a constraint space for an unpositioned float, with the intent to
// position it.
scoped_refptr<NGConstraintSpace> CreateConstraintSpaceForFloat(
const NGUnpositionedFloat& unpositioned_float,
const NGConstraintSpace& parent_space,
LayoutUnit origin_block_offset) {
NGConstraintSpaceBuilder builder(parent_space);
DCHECK_EQ(unpositioned_float.node.Style().GetWritingMode(),
parent_space.GetWritingMode());
if (parent_space.HasBlockFragmentation()) {
LayoutUnit fragmentation_offset =
parent_space.FragmentainerSpaceAtBfcStart() - origin_block_offset;
builder.SetFragmentainerBlockSize(parent_space.FragmentainerBlockSize());
builder.SetFragmentainerSpaceAtBfcStart(fragmentation_offset);
builder.SetFragmentationType(parent_space.BlockFragmentationType());
} else {
builder.SetFragmentationType(NGFragmentationType::kFragmentNone);
}
return CreateConstraintSpaceForFloatFromBuilder(unpositioned_float, builder);
}
// Creates a constraint space for an unpositioned float, with the intent to
// simply calculate its inline size.
scoped_refptr<NGConstraintSpace>
CreateConstraintSpaceForFloatForInlineSizeCalculation(
const NGUnpositionedFloat& unpositioned_float,
const NGConstraintSpace& parent_space) {
NGConstraintSpaceBuilder builder(parent_space);
return CreateConstraintSpaceForFloatFromBuilder(unpositioned_float, builder);
}
} // namespace
LayoutUnit ComputeInlineSizeForUnpositionedFloat(
const NGConstraintSpace& parent_space,
NGUnpositionedFloat* unpositioned_float) {
DCHECK(unpositioned_float);
const ComputedStyle& style = unpositioned_float->node.Style();
bool is_same_writing_mode =
style.GetWritingMode() == parent_space.GetWritingMode();
// If we've already performed layout on the unpositioned float, just return
// the cached value.
if (unpositioned_float->layout_result) {
DCHECK(!is_same_writing_mode);
DCHECK(unpositioned_float->layout_result->PhysicalFragment());
return NGFragment(parent_space.GetWritingMode(),
*unpositioned_float->layout_result->PhysicalFragment())
.InlineSize();
}
const scoped_refptr<NGConstraintSpace> space =
CreateConstraintSpaceForFloatForInlineSizeCalculation(*unpositioned_float,
parent_space);
// If the float has the same writing mode as the block formatting context we
// shouldn't perform a full layout just yet. Our position may determine where
// we fragment. If we cannot use NG for layout of the node, though, we do have
// to lay out (and just read out the inline size and then discard the result),
// because NG cannot figure out the size of such objects on its own,
// especially not for tables.
if (is_same_writing_mode && unpositioned_float->node.CanUseNewLayout()) {
base::Optional<MinMaxSize> min_max_size;
if (NeedMinMaxSize(*space.get(), style)) {
MinMaxSizeInput zero_input; // Floats do not intrude into floats.
min_max_size = unpositioned_float->node.ComputeMinMaxSize(zero_input);
}
return ComputeInlineSizeForFragment(*space.get(), style, min_max_size);
}
// A float which has a different writing mode can't fragment, and we
// (probably) need to perform a full layout in order to correctly determine
// its inline size. We are able to cache this result on the unpositioned_float
// at this stage. If the writing mode is the same, on the other hand, we
// cannot keep the result around, since the node may fragment (and we need to
// find its final block position before we can do so).
auto result = unpositioned_float->node.Layout(*space);
if (!is_same_writing_mode) {
// If we are performing layout on a float to determine its inline size it
// should never have fragmented.
DCHECK(!unpositioned_float->token);
unpositioned_float->layout_result = result;
}
DCHECK(result->PhysicalFragment());
const auto& fragment = *result->PhysicalFragment();
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);
LayoutUnit inline_size =
ComputeInlineSizeForUnpositionedFloat(parent_space, unpositioned_float);
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, inline_size);
#if DCHECK_IS_ON()
bool is_same_writing_mode =
unpositioned_float->node.Style().GetWritingMode() ==
parent_space.GetWritingMode();
#endif
scoped_refptr<NGLayoutResult> layout_result;
// We should only have a fragment if its writing mode is different, i.e. it
// can't fragment.
if (unpositioned_float->layout_result) {
#if DCHECK_IS_ON()
DCHECK(!is_same_writing_mode);
#endif
layout_result = unpositioned_float->layout_result;
} else {
#if DCHECK_IS_ON()
DCHECK(is_same_writing_mode);
#endif
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());
LayoutUnit float_margin_box_inline_size =
float_fragment.InlineSize() + unpositioned_float->margins.InlineSum();
// Calculate the float's margin box BFC offset.
NGBfcOffset float_margin_bfc_offset = opportunity.rect.start_offset;
if (unpositioned_float->IsRight()) {
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->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;
}
} // namespace blink