| // 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); |
| } |
| |
| 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); |
| } |
| |
| 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(*CreateConstraintSpaceForFloatForInlineSizeCalculation( |
| 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 = |
| CreateConstraintSpaceForFloatForInlineSizeCalculation( |
| 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)); |
| } |
| |
| } // 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) { |
| // TODO(layout-ng): Should this use IsParallelWritingMode()? |
| 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()) { |
| return ComputeInlineSizeForFragment(*space.get(), unpositioned_float->node); |
| } |
| |
| // Here we need to lay out the float. However, it is possible that we are |
| // not inside Layout, in which case we may not be able to actually lay out |
| // the node. Instead, we have to fallback to legacy sizing. |
| if (!unpositioned_float->node.GetLayoutBox() |
| ->GetFrameView() |
| ->IsInPerformLayout()) { |
| LayoutBox* box = unpositioned_float->node.GetLayoutBox(); |
| LayoutBox::LogicalExtentComputedValues values; |
| box->ComputeLogicalWidth(values); |
| return values.extent_; |
| } |
| |
| // 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->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) { |
| if (fragment_builder && !fragment_builder->BfcOffset()) { |
| fragment_builder->AddAdjoiningFloatTypes( |
| unpositioned_float->IsLeft() ? kFloatTypeLeft : kFloatTypeRight); |
| } |
| unpositioned_floats->push_back(std::move(unpositioned_float)); |
| } |
| |
| 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 |