| // 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, |
| const NGBoxStrut& fragment_margins, |
| 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 + fragment_margins.InlineSum(), |
| LayoutUnit()); |
| 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) { |
| NGBfcOffset start_offset = float_margin_bfc_offset; |
| NGBfcOffset end_offset( |
| start_offset.line_offset + |
| (fragment.InlineSize() + margins.InlineSum()).ClampNegativeToZero(), |
| 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); |
| unpositioned_float->margins = |
| ComputeMarginsFor(*space, unpositioned_float->node.Style(), parent_space); |
| } |
| |
| } // namespace |
| |
| LayoutUnit ComputeMarginBoxInlineSizeForUnpositionedFloat( |
| 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() + |
| unpositioned_float->margins.InlineSum()) |
| .ClampNegativeToZero(); |
| } |
| |
| 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; |
| NGBoxStrut fragment_margins; |
| |
| // 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; |
| fragment_margins = unpositioned_float->margins; |
| } else { |
| scoped_refptr<NGConstraintSpace> space = CreateConstraintSpaceForFloat( |
| *unpositioned_float, parent_space, origin_block_offset); |
| layout_result = unpositioned_float->node.Layout( |
| *space, unpositioned_float->token.get()); |
| fragment_margins = ComputeMarginsFor( |
| *space, unpositioned_float->node.Style(), parent_space); |
| |
| // Make the margins fragmentation aware. |
| if (unpositioned_float->token) |
| fragment_margins.block_start = LayoutUnit(); |
| if (!layout_result->PhysicalFragment()->BreakToken()->IsFinished()) |
| fragment_margins.block_end = LayoutUnit(); |
| } |
| |
| 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, fragment_margins, |
| 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() + fragment_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, fragment_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 + |
| fragment_margins.LineLeft(parent_space.Direction()), |
| float_margin_bfc_offset.block_offset + fragment_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 |