| // 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 "core/layout/ng/ng_floats_utils.h" |
| |
| #include "core/layout/MinMaxSize.h" |
| #include "core/layout/ng/ng_box_fragment.h" |
| #include "core/layout/ng/ng_constraint_space_builder.h" |
| #include "core/layout/ng/ng_layout_result.h" |
| #include "core/layout/ng/ng_length_utils.h" |
| #include "core/layout/ng/ng_positioned_float.h" |
| #include "core/layout/ng/ng_space_utils.h" |
| #include "core/style/ComputedStyle.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. |
| NGLogicalOffset AdjustToTopEdgeAlignmentRule( |
| const NGExclusionSpace& exclusion_space, |
| const NGLogicalOffset& offset) { |
| NGLogicalOffset adjusted_offset = offset; |
| adjusted_offset.block_offset = std::max( |
| adjusted_offset.block_offset, exclusion_space.LastFloatBlockStart()); |
| |
| return adjusted_offset; |
| } |
| |
| NGLayoutOpportunity FindLayoutOpportunityForFloat( |
| const NGLogicalOffset& origin_offset, |
| const NGExclusionSpace& exclusion_space, |
| const NGUnpositionedFloat& unpositioned_float, |
| LayoutUnit inline_size) { |
| NGLogicalOffset adjusted_origin_point = |
| AdjustToTopEdgeAlignmentRule(exclusion_space, origin_offset); |
| WTF::Optional<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, float_size); |
| } |
| |
| // Calculates the logical offset for opportunity. |
| NGLogicalOffset CalculateLogicalOffsetForOpportunity( |
| const NGLayoutOpportunity& opportunity, |
| const LayoutUnit float_offset, |
| const LayoutUnit parent_bfc_block_offset, |
| const NGUnpositionedFloat* unpositioned_float) { |
| DCHECK(unpositioned_float); |
| auto margins = unpositioned_float->margins; |
| // Adjust to child's margin. |
| NGLogicalOffset result = margins.InlineBlockStartOffset(); |
| |
| // Offset from the opportunity's block/inline start. |
| result += opportunity.offset; |
| |
| // Adjust to float: right offset if needed. |
| result.inline_offset += float_offset; |
| |
| result -= {unpositioned_float->bfc_inline_offset, parent_bfc_block_offset}; |
| |
| return result; |
| } |
| |
| // 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; |
| |
| // TODO(ikilpatrick): Don't include the block-start margin of a float which |
| // has fragmented. |
| rect.size.inline_size = fragment.InlineSize() + margins.InlineSum(); |
| rect.size.block_size = fragment.BlockSize() + margins.BlockSum(); |
| return exclusion; |
| } |
| |
| // TODO(ikilpatrick): origin_block_offset looks wrong for fragmentation here. |
| WTF::Optional<LayoutUnit> CalculateFragmentationOffset( |
| const LayoutUnit origin_block_offset, |
| const NGUnpositionedFloat& unpositioned_float, |
| const NGConstraintSpace& parent_space) { |
| const ComputedStyle& style = unpositioned_float.node.Style(); |
| DCHECK(FromPlatformWritingMode(style.GetWritingMode()) == |
| parent_space.WritingMode()); |
| |
| if (parent_space.HasBlockFragmentation()) { |
| return parent_space.FragmentainerSpaceAvailable() - origin_block_offset; |
| } |
| |
| return WTF::nullopt; |
| } |
| |
| // Creates a constraint space for an unpositioned float. |
| RefPtr<NGConstraintSpace> CreateConstraintSpaceForFloat( |
| const NGUnpositionedFloat& unpositioned_float, |
| const NGConstraintSpace& parent_space, |
| WTF::Optional<LayoutUnit> fragmentation_offset = WTF::nullopt) { |
| const ComputedStyle& style = unpositioned_float.node.Style(); |
| |
| NGConstraintSpaceBuilder builder(parent_space); |
| |
| if (fragmentation_offset) { |
| builder.SetFragmentainerSpaceAvailable(fragmentation_offset.value()) |
| .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(FromPlatformWritingMode(style.GetWritingMode())); |
| } |
| |
| } // 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 = FromPlatformWritingMode(style.GetWritingMode()) == |
| parent_space.WritingMode(); |
| |
| // 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); |
| return NGFragment( |
| parent_space.WritingMode(), |
| unpositioned_float->layout_result->PhysicalFragment().Get()) |
| .InlineSize(); |
| } |
| |
| const RefPtr<NGConstraintSpace> space = |
| CreateConstraintSpaceForFloat(*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 (is_same_writing_mode) { |
| WTF::Optional<MinMaxSize> min_max_size; |
| if (NeedMinMaxSize(*space.Get(), style)) |
| min_max_size = unpositioned_float->node.ComputeMinMaxSize(); |
| return ComputeInlineSizeForFragment(*space.Get(), style, min_max_size); |
| } |
| |
| // If we are performing layout on a float to determine its inline size it |
| // should never have fragmented. |
| DCHECK(!unpositioned_float->token); |
| |
| // 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. |
| unpositioned_float->layout_result = unpositioned_float->node.Layout(*space); |
| |
| const NGPhysicalFragment* fragment = |
| unpositioned_float->layout_result->PhysicalFragment().Get(); |
| |
| DCHECK(fragment->BreakToken()->IsFinished()); |
| |
| return NGFragment(parent_space.WritingMode(), 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); |
| |
| NGLogicalOffset origin_offset = {unpositioned_float->origin_bfc_inline_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 = |
| FromPlatformWritingMode( |
| unpositioned_float->node.Style().GetWritingMode()) == |
| parent_space.WritingMode(); |
| #endif |
| |
| 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 |
| WTF::Optional<LayoutUnit> fragmentation_offset = |
| CalculateFragmentationOffset(origin_block_offset, *unpositioned_float, |
| parent_space); |
| |
| RefPtr<NGConstraintSpace> space = CreateConstraintSpaceForFloat( |
| *unpositioned_float, parent_space, fragmentation_offset); |
| layout_result = unpositioned_float->node.Layout( |
| *space, unpositioned_float->token.Get()); |
| } |
| |
| NGBoxFragment float_fragment( |
| parent_space.WritingMode(), |
| ToNGPhysicalBoxFragment(layout_result.Get()->PhysicalFragment().Get())); |
| |
| // TODO(glebl): This should check for infinite opportunity instead. |
| if (opportunity.IsEmpty()) { |
| // Because of the implementation specific of the layout opportunity iterator |
| // an empty opportunity can mean 2 things: |
| // - search for layout opportunities is exhausted. |
| // - opportunity has an infinite size. That's because CS is infinite. |
| opportunity = NGLayoutOpportunity( |
| NGLogicalOffset(), |
| NGLogicalSize(float_fragment.InlineSize(), float_fragment.BlockSize())); |
| } |
| |
| // Calculate the float offset if needed. |
| LayoutUnit float_offset; |
| if (unpositioned_float->IsRight()) { |
| LayoutUnit float_margin_box_inline_size = |
| float_fragment.InlineSize() + unpositioned_float->margins.InlineSum(); |
| float_offset = opportunity.size.inline_size - float_margin_box_inline_size; |
| } |
| |
| // Add the float as an exclusion. |
| const NGExclusion exclusion = CreateExclusion( |
| float_fragment, opportunity, float_offset, unpositioned_float->margins, |
| unpositioned_float->IsRight() ? NGExclusion::Type::kFloatRight |
| : NGExclusion::Type::kFloatLeft); |
| exclusion_space->Add(exclusion); |
| |
| NGLogicalOffset logical_offset = CalculateLogicalOffsetForOpportunity( |
| opportunity, float_offset, parent_bfc_block_offset, unpositioned_float); |
| |
| return NGPositionedFloat(std::move(layout_result), logical_offset); |
| } |
| |
| const Vector<NGPositionedFloat> PositionFloats( |
| LayoutUnit origin_block_offset, |
| LayoutUnit parent_bfc_block_offset, |
| const Vector<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 |