| // Copyright 2016 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_fragment_builder.h" |
| |
| #include "third_party/blink/renderer/core/layout/layout_object.h" |
| #include "third_party/blink/renderer/core/layout/ng/exclusions/ng_exclusion_space.h" |
| #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_break_token.h" |
| #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_fragment_traversal.h" |
| #include "third_party/blink/renderer/core/layout/ng/inline/ng_inline_node.h" |
| #include "third_party/blink/renderer/core/layout/ng/inline/ng_physical_line_box_fragment.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_block_break_token.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_block_node.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_break_token.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_fragmentation_utils.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_layout_result.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_physical_box_fragment.h" |
| #include "third_party/blink/renderer/core/layout/ng/ng_positioned_float.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| NGPhysicalFragment::NGBoxType BoxTypeFromLayoutObject( |
| const LayoutObject* layout_object) { |
| DCHECK(layout_object); |
| if (layout_object->IsFloating()) |
| return NGPhysicalFragment::NGBoxType::kFloating; |
| if (layout_object->IsOutOfFlowPositioned()) |
| return NGPhysicalFragment::NGBoxType::kOutOfFlowPositioned; |
| if (layout_object->IsAtomicInlineLevel()) |
| return NGPhysicalFragment::NGBoxType::kAtomicInline; |
| if (layout_object->IsInline()) |
| return NGPhysicalFragment::NGBoxType::kInlineBox; |
| return NGPhysicalFragment::NGBoxType::kNormalBox; |
| } |
| |
| } // namespace |
| |
| NGFragmentBuilder::NGFragmentBuilder(NGLayoutInputNode node, |
| scoped_refptr<const ComputedStyle> style, |
| WritingMode writing_mode, |
| TextDirection direction) |
| : NGContainerFragmentBuilder(style, writing_mode, direction), |
| node_(node), |
| layout_object_(node.GetLayoutBox()), |
| box_type_(NGPhysicalFragment::NGBoxType::kNormalBox), |
| is_old_layout_root_(false), |
| did_break_(false) {} |
| |
| NGFragmentBuilder::NGFragmentBuilder(LayoutObject* layout_object, |
| scoped_refptr<const ComputedStyle> style, |
| WritingMode writing_mode, |
| TextDirection direction) |
| : NGContainerFragmentBuilder(style, writing_mode, direction), |
| node_(nullptr), |
| layout_object_(layout_object), |
| box_type_(NGPhysicalFragment::NGBoxType::kNormalBox), |
| is_old_layout_root_(false), |
| did_break_(false) {} |
| |
| NGFragmentBuilder::~NGFragmentBuilder() = default; |
| |
| NGFragmentBuilder& NGFragmentBuilder::SetIntrinsicBlockSize( |
| LayoutUnit intrinsic_block_size) { |
| intrinsic_block_size_ = intrinsic_block_size; |
| return *this; |
| } |
| |
| NGFragmentBuilder& NGFragmentBuilder::SetBorders(const NGBoxStrut& border) { |
| DCHECK_NE(BoxType(), NGPhysicalFragment::kInlineBox); |
| borders_ = border; |
| return *this; |
| } |
| |
| NGFragmentBuilder& NGFragmentBuilder::SetPadding(const NGBoxStrut& padding) { |
| DCHECK_NE(BoxType(), NGPhysicalFragment::kInlineBox); |
| padding_ = padding; |
| return *this; |
| } |
| |
| NGFragmentBuilder& NGFragmentBuilder::SetPadding( |
| const NGLineBoxStrut& padding) { |
| DCHECK_EQ(BoxType(), NGPhysicalFragment::kInlineBox); |
| // Convert to flow-relative, because ToInlineBoxFragment() will convert |
| // the padding to physical coordinates using flow-relative writing-mode. |
| padding_ = NGBoxStrut(padding, IsFlippedLinesWritingMode(GetWritingMode())); |
| return *this; |
| } |
| |
| NGContainerFragmentBuilder& NGFragmentBuilder::AddChild( |
| scoped_refptr<NGPhysicalFragment> child, |
| const NGLogicalOffset& child_offset) { |
| switch (child->Type()) { |
| case NGPhysicalBoxFragment::kFragmentBox: |
| if (child->BreakToken()) |
| child_break_tokens_.push_back(child->BreakToken()); |
| break; |
| case NGPhysicalBoxFragment::kFragmentLineBox: |
| // NGInlineNode produces multiple line boxes in an anonymous box. We won't |
| // know up front which line box to insert a fragment break before (due to |
| // widows), so keep them all until we know. |
| DCHECK(child->BreakToken()); |
| DCHECK(child->BreakToken()->InputNode() == node_); |
| inline_break_tokens_.push_back(child->BreakToken()); |
| break; |
| case NGPhysicalBoxFragment::kFragmentText: |
| DCHECK(!child->BreakToken()); |
| break; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| |
| return NGContainerFragmentBuilder::AddChild(std::move(child), child_offset); |
| } |
| |
| void NGFragmentBuilder::RemoveChildren() { |
| child_break_tokens_.clear(); |
| inline_break_tokens_.clear(); |
| children_.clear(); |
| offsets_.clear(); |
| } |
| |
| NGFragmentBuilder& NGFragmentBuilder::AddBreakBeforeChild( |
| NGLayoutInputNode child) { |
| if (child.IsInline()) { |
| if (inline_break_tokens_.IsEmpty()) { |
| // In some cases we may want to break before the first line, as a last |
| // resort. We need a break token for that as well, so that the machinery |
| // will understand that we should resume at the beginning of the inline |
| // formatting context, rather than concluding that we're done with the |
| // whole thing. |
| inline_break_tokens_.push_back(NGInlineBreakToken::Create( |
| ToNGInlineNode(child), nullptr, 0, 0, NGInlineBreakToken::kDefault, |
| std::make_unique<NGInlineLayoutStateStack>())); |
| } |
| return *this; |
| } |
| auto token = NGBlockBreakToken::CreateBreakBefore(child); |
| child_break_tokens_.push_back(token); |
| return *this; |
| } |
| |
| NGFragmentBuilder& NGFragmentBuilder::AddBreakBeforeLine(int line_number) { |
| DCHECK_GT(line_number, 0); |
| DCHECK_LE(unsigned(line_number), inline_break_tokens_.size()); |
| int lines_to_remove = inline_break_tokens_.size() - line_number; |
| if (lines_to_remove > 0) { |
| // Remove widows that should be pushed to the next fragment. We'll also |
| // remove all other child fragments than line boxes (typically floats) that |
| // come after the first line that's moved, as those also have to be re-laid |
| // out in the next fragment. |
| inline_break_tokens_.resize(line_number); |
| DCHECK_GT(children_.size(), 0UL); |
| for (int i = children_.size() - 1; i >= 0; i--) { |
| DCHECK_NE(i, 0); |
| if (!children_[i]->IsLineBox()) |
| continue; |
| if (!--lines_to_remove) { |
| // This is the first line that is going to the next fragment. Remove it, |
| // and everything after it. |
| children_.resize(i); |
| offsets_.resize(i); |
| break; |
| } |
| } |
| } |
| |
| // We need to resume at the right inline location in the next fragment, but |
| // broken floats, which are resumed and positioned by the parent block layout |
| // algorithm, need to be ignored by the inline layout algorithm. |
| ToNGInlineBreakToken(inline_break_tokens_.back().get())->SetIgnoreFloats(); |
| return *this; |
| } |
| |
| NGFragmentBuilder& NGFragmentBuilder::PropagateBreak( |
| scoped_refptr<NGLayoutResult> child_layout_result) { |
| if (!did_break_) |
| PropagateBreak(child_layout_result->PhysicalFragment()); |
| if (child_layout_result->HasForcedBreak()) |
| SetHasForcedBreak(); |
| else |
| PropagateSpaceShortage(child_layout_result->MinimalSpaceShortage()); |
| return *this; |
| } |
| |
| NGFragmentBuilder& NGFragmentBuilder::PropagateBreak( |
| scoped_refptr<NGPhysicalFragment> child_fragment) { |
| if (!did_break_) { |
| const auto* token = child_fragment->BreakToken(); |
| did_break_ = token && !token->IsFinished(); |
| } |
| return *this; |
| } |
| |
| void NGFragmentBuilder::AddOutOfFlowLegacyCandidate( |
| NGBlockNode node, |
| const NGStaticPosition& static_position, |
| LayoutObject* inline_container) { |
| DCHECK_GE(InlineSize(), LayoutUnit()); |
| DCHECK_GE(BlockSize(), LayoutUnit()); |
| |
| NGOutOfFlowPositionedDescendant descendant{node, static_position, |
| inline_container}; |
| // Need 0,0 physical coordinates as child offset. Because offset |
| // is stored as logical, must convert physical 0,0 to logical. |
| NGLogicalOffset zero_offset; |
| switch (GetWritingMode()) { |
| case WritingMode::kHorizontalTb: |
| if (IsLtr(Direction())) |
| zero_offset = NGLogicalOffset(); |
| else |
| zero_offset = NGLogicalOffset(InlineSize(), LayoutUnit()); |
| break; |
| case WritingMode::kVerticalRl: |
| case WritingMode::kSidewaysRl: |
| if (IsLtr(Direction())) |
| zero_offset = NGLogicalOffset(LayoutUnit(), BlockSize()); |
| else |
| zero_offset = NGLogicalOffset(InlineSize(), BlockSize()); |
| break; |
| case WritingMode::kVerticalLr: |
| case WritingMode::kSidewaysLr: |
| if (IsLtr(Direction())) |
| zero_offset = NGLogicalOffset(); |
| else |
| zero_offset = NGLogicalOffset(InlineSize(), LayoutUnit()); |
| break; |
| } |
| oof_positioned_candidates_.push_back( |
| NGOutOfFlowPositionedCandidate{descendant, zero_offset}); |
| } |
| |
| NGPhysicalFragment::NGBoxType NGFragmentBuilder::BoxType() const { |
| if (box_type_ != NGPhysicalFragment::NGBoxType::kNormalBox) { |
| DCHECK_EQ(box_type_, BoxTypeFromLayoutObject(layout_object_)); |
| return box_type_; |
| } |
| |
| // When implicit, compute from LayoutObject. |
| return BoxTypeFromLayoutObject(layout_object_); |
| } |
| |
| NGFragmentBuilder& NGFragmentBuilder::SetBoxType( |
| NGPhysicalFragment::NGBoxType box_type) { |
| box_type_ = box_type; |
| return *this; |
| } |
| |
| NGFragmentBuilder& NGFragmentBuilder::SetIsOldLayoutRoot() { |
| is_old_layout_root_ = true; |
| return *this; |
| } |
| |
| void NGFragmentBuilder::AddBaseline(NGBaselineRequest request, |
| LayoutUnit offset) { |
| #if DCHECK_IS_ON() |
| for (const auto& baseline : baselines_) |
| DCHECK(baseline.request != request); |
| #endif |
| baselines_.push_back(NGBaseline{request, offset}); |
| } |
| |
| EBreakBetween NGFragmentBuilder::JoinedBreakBetweenValue( |
| EBreakBetween break_before) const { |
| return JoinFragmentainerBreakValues(previous_break_after_, break_before); |
| } |
| |
| scoped_refptr<NGLayoutResult> NGFragmentBuilder::ToBoxFragment() { |
| DCHECK_NE(BoxType(), NGPhysicalFragment::kInlineBox); |
| return ToBoxFragment(GetWritingMode()); |
| } |
| |
| scoped_refptr<NGLayoutResult> NGFragmentBuilder::ToInlineBoxFragment() { |
| // The logical coordinate for inline box uses line-relative writing-mode, not |
| // flow-relative. |
| DCHECK_EQ(BoxType(), NGPhysicalFragment::kInlineBox); |
| return ToBoxFragment(ToLineWritingMode(GetWritingMode())); |
| } |
| |
| scoped_refptr<NGLayoutResult> NGFragmentBuilder::ToBoxFragment( |
| WritingMode block_or_line_writing_mode) { |
| DCHECK_EQ(offsets_.size(), children_.size()); |
| |
| NGPhysicalSize physical_size = Size().ConvertToPhysical(GetWritingMode()); |
| |
| NGPhysicalOffsetRect contents_ink_overflow({}, physical_size); |
| for (size_t i = 0; i < children_.size(); ++i) { |
| NGPhysicalFragment* child = children_[i].get(); |
| child->SetOffset(offsets_[i].ConvertToPhysical( |
| block_or_line_writing_mode, Direction(), physical_size, child->Size())); |
| child->PropagateContentsInkOverflow(&contents_ink_overflow); |
| } |
| |
| scoped_refptr<NGBreakToken> break_token; |
| if (node_) { |
| if (!inline_break_tokens_.IsEmpty()) { |
| if (auto token = inline_break_tokens_.back()) { |
| if (!token->IsFinished()) |
| child_break_tokens_.push_back(std::move(token)); |
| } |
| } |
| if (did_break_) { |
| break_token = NGBlockBreakToken::Create( |
| node_, used_block_size_, child_break_tokens_, has_last_resort_break_); |
| } else { |
| break_token = NGBlockBreakToken::Create(node_, used_block_size_, |
| has_last_resort_break_); |
| } |
| } |
| |
| scoped_refptr<NGPhysicalBoxFragment> fragment = |
| base::AdoptRef(new NGPhysicalBoxFragment( |
| layout_object_, Style(), style_variant_, physical_size, children_, |
| borders_.ConvertToPhysical(GetWritingMode(), Direction()), |
| padding_.ConvertToPhysical(GetWritingMode(), Direction()), |
| contents_ink_overflow, baselines_, BoxType(), is_old_layout_root_, |
| border_edges_.ToPhysical(GetWritingMode()), std::move(break_token))); |
| |
| Vector<NGPositionedFloat> positioned_floats; |
| |
| return base::AdoptRef(new NGLayoutResult( |
| std::move(fragment), oof_positioned_descendants_, positioned_floats, |
| unpositioned_list_marker_, std::move(exclusion_space_), bfc_line_offset_, |
| bfc_block_offset_, end_margin_strut_, intrinsic_block_size_, |
| minimal_space_shortage_, initial_break_before_, previous_break_after_, |
| has_forced_break_, is_pushed_by_floats_, adjoining_floats_, |
| NGLayoutResult::kSuccess)); |
| } |
| |
| scoped_refptr<NGLayoutResult> NGFragmentBuilder::Abort( |
| NGLayoutResult::NGLayoutResultStatus status) { |
| Vector<NGOutOfFlowPositionedDescendant> oof_positioned_descendants; |
| Vector<NGPositionedFloat> positioned_floats; |
| return base::AdoptRef(new NGLayoutResult( |
| nullptr, oof_positioned_descendants, positioned_floats, |
| NGUnpositionedListMarker(), nullptr, bfc_line_offset_, bfc_block_offset_, |
| end_margin_strut_, LayoutUnit(), LayoutUnit(), EBreakBetween::kAuto, |
| EBreakBetween::kAuto, false, false, kFloatTypeNone, status)); |
| } |
| |
| // Finds FragmentPairs that define inline containing blocks. |
| // inline_container_fragments is a map whose keys specify which |
| // inline containing blocks are required. |
| // Not finding a required block is an unexpected behavior (DCHECK). |
| void NGFragmentBuilder::ComputeInlineContainerFragments( |
| HashMap<const LayoutObject*, FragmentPair>* inline_container_fragments, |
| NGLogicalSize* container_size) { |
| // This function has detailed knowledge of inline fragment tree structure, |
| // and will break if this changes. |
| DCHECK_GE(InlineSize(), LayoutUnit()); |
| DCHECK_GE(BlockSize(), LayoutUnit()); |
| *container_size = Size(); |
| |
| for (size_t i = 0; i < children_.size(); i++) { |
| if (children_[i]->IsLineBox()) { |
| const NGPhysicalLineBoxFragment* linebox = |
| ToNGPhysicalLineBoxFragment(children_[i].get()); |
| for (auto& descendant : |
| NGInlineFragmentTraversal::DescendantsOf(*linebox)) { |
| LayoutObject* key = {}; |
| if (descendant.fragment->IsText()) { |
| key = descendant.fragment->GetLayoutObject(); |
| DCHECK(key); |
| key = key->Parent(); |
| DCHECK(key); |
| } else if (descendant.fragment->IsBox()) { |
| key = descendant.fragment->GetLayoutObject(); |
| } |
| if (key && inline_container_fragments->Contains(key)) { |
| NGFragmentBuilder::FragmentPair value = |
| inline_container_fragments->at(key); |
| if (!value.start_fragment) { |
| value.start_fragment = descendant.fragment.get(); |
| value.start_fragment_union_rect.offset = |
| descendant.offset_to_container_box; |
| value.start_fragment_union_rect = |
| NGPhysicalOffsetRect(descendant.offset_to_container_box, |
| value.start_fragment->Size()); |
| value.start_linebox_fragment = linebox; |
| value.start_linebox_offset = offsets_.at(i); |
| } |
| if (!value.end_fragment || value.end_linebox_fragment != linebox) { |
| value.end_fragment = descendant.fragment.get(); |
| value.end_fragment_union_rect = NGPhysicalOffsetRect( |
| descendant.offset_to_container_box, value.end_fragment->Size()); |
| value.end_linebox_fragment = linebox; |
| value.end_linebox_offset = offsets_.at(i); |
| } |
| // Extend the union size |
| if (value.start_linebox_fragment == linebox) { |
| // std::max because initial box might have larger extent than its |
| // descendants. |
| value.start_fragment_union_rect.size.width = |
| std::max(descendant.offset_to_container_box.left + |
| descendant.fragment->Size().width - |
| value.start_fragment_union_rect.offset.left, |
| value.start_fragment_union_rect.size.width); |
| value.start_fragment_union_rect.size.height = |
| std::max(descendant.offset_to_container_box.top + |
| descendant.fragment->Size().height - |
| value.start_fragment_union_rect.offset.top, |
| value.start_fragment_union_rect.size.height); |
| } |
| if (value.end_linebox_fragment == linebox) { |
| value.end_fragment_union_rect.size.width = |
| std::max(descendant.offset_to_container_box.left + |
| descendant.fragment->Size().width - |
| value.start_fragment_union_rect.offset.left, |
| value.end_fragment_union_rect.size.width); |
| value.end_fragment_union_rect.size.height = |
| std::max(descendant.offset_to_container_box.top + |
| descendant.fragment->Size().height - |
| value.start_fragment_union_rect.offset.top, |
| value.end_fragment_union_rect.size.height); |
| } |
| inline_container_fragments->Set(key, value); |
| } |
| } |
| } |
| } |
| } |
| |
| } // namespace blink |