blob: d99bdffac8e0884284b2d1e2dc8b680a27197146 [file] [log] [blame]
// 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_box_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 {
using LineBoxPair = std::pair<const NGPhysicalLineBoxFragment*,
const NGPhysicalLineBoxFragment*>;
void GatherInlineContainerFragmentsFromLinebox(
NGBoxFragmentBuilder::InlineContainingBlockMap* inline_containing_block_map,
HashMap<const LayoutObject*, LineBoxPair>* containing_linebox_map,
const NGPhysicalLineBoxFragment* linebox,
const NGPhysicalOffset linebox_offset) {
for (auto& descendant : NGInlineFragmentTraversal::DescendantsOf(*linebox)) {
if (!descendant.fragment->IsBox())
continue;
LayoutObject* key = descendant.fragment->GetLayoutObject();
// TODO(atotic) Is traversing continuations the right thing to do?
if (key->IsLayoutInline()) // key for inlines is continuation root.
key = key->GetNode()->GetLayoutObject();
auto it = inline_containing_block_map->find(key);
if (it == inline_containing_block_map->end()) {
// Default case, not one of the blocks we are looking for.
continue;
}
base::Optional<NGBoxFragmentBuilder::InlineContainingBlockGeometry>&
containing_block_geometry = it->value;
LineBoxPair& containing_lineboxes =
containing_linebox_map->insert(key, LineBoxPair{nullptr, nullptr})
.stored_value->value;
DCHECK(containing_block_geometry.has_value() ||
!containing_lineboxes.first);
// |DescendantsOf| returns the offset from the given fragment. Since
// we give it the line box, need to add the |linebox_offset|.
NGPhysicalOffsetRect fragment_rect(
linebox_offset + descendant.offset_to_container_box,
descendant.fragment->Size());
if (containing_lineboxes.first == linebox) {
containing_block_geometry.value().start_fragment_union_rect.Unite(
fragment_rect);
} else if (!containing_lineboxes.first) {
containing_lineboxes.first = linebox;
containing_block_geometry =
NGBoxFragmentBuilder::InlineContainingBlockGeometry{
fragment_rect, NGPhysicalOffsetRect()};
}
// Skip fragments within an empty line boxes for the end fragment.
if (containing_lineboxes.second == linebox) {
containing_block_geometry.value().end_fragment_union_rect.Unite(
fragment_rect);
} else if (!containing_lineboxes.second || !linebox->IsEmptyLineBox()) {
containing_lineboxes.second = linebox;
containing_block_geometry.value().end_fragment_union_rect = fragment_rect;
}
}
}
void NGBoxFragmentBuilder::RemoveChildren() {
child_break_tokens_.resize(0);
inline_break_tokens_.resize(0);
children_.resize(0);
offsets_.resize(0);
}
NGBoxFragmentBuilder& NGBoxFragmentBuilder::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), /* style */ nullptr, /* item_index */ 0,
/* text_offset */ 0, NGInlineBreakToken::kDefault));
}
return *this;
}
auto token = NGBlockBreakToken::CreateBreakBefore(child);
child_break_tokens_.push_back(token);
return *this;
}
NGBoxFragmentBuilder& NGBoxFragmentBuilder::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;
}
NGBoxFragmentBuilder& NGBoxFragmentBuilder::PropagateBreak(
const 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;
}
NGBoxFragmentBuilder& NGBoxFragmentBuilder::PropagateBreak(
const NGPhysicalFragment& child_fragment) {
if (!did_break_) {
const auto* token = child_fragment.BreakToken();
did_break_ = token && !token->IsFinished();
}
return *this;
}
void NGBoxFragmentBuilder::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 NGBoxFragmentBuilder::BoxType() const {
if (box_type_ != NGPhysicalFragment::NGBoxType::kNormalBox)
return box_type_;
// When implicit, compute from LayoutObject.
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;
DCHECK(node_) << "Must call SetBoxType if there is no node";
DCHECK_EQ(is_new_fc_, node_.CreatesNewFormattingContext())
<< "Forgot to call builder.SetIsNewFormattingContext";
if (is_new_fc_)
return NGPhysicalFragment::NGBoxType::kBlockFlowRoot;
return NGPhysicalFragment::NGBoxType::kNormalBox;
}
void NGBoxFragmentBuilder::AddBaseline(NGBaselineRequest request,
LayoutUnit offset) {
#if DCHECK_IS_ON()
for (const auto& baseline : baselines_)
DCHECK(baseline.request != request);
#endif
baselines_.emplace_back(request, offset);
}
EBreakBetween NGBoxFragmentBuilder::JoinedBreakBetweenValue(
EBreakBetween break_before) const {
return JoinFragmentainerBreakValues(previous_break_after_, break_before);
}
scoped_refptr<NGLayoutResult> NGBoxFragmentBuilder::ToBoxFragment(
WritingMode block_or_line_writing_mode) {
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 if (needs_finished_break_token_) {
break_token_ = NGBlockBreakToken::Create(node_, used_block_size_,
has_last_resort_break_);
}
}
scoped_refptr<const NGPhysicalBoxFragment> fragment =
NGPhysicalBoxFragment::Create(this, block_or_line_writing_mode);
return base::AdoptRef(new NGLayoutResult(std::move(fragment), this));
}
scoped_refptr<NGLayoutResult> NGBoxFragmentBuilder::Abort(
NGLayoutResult::NGLayoutResultStatus status) {
return base::AdoptRef(new NGLayoutResult(status, this));
}
// Finds InlineContainingBlockGeometry that define inline containing blocks.
// |inline_containing_block_map| is a map whose keys specify which
// inline containing blocks are required.
void NGBoxFragmentBuilder::ComputeInlineContainerFragments(
InlineContainingBlockMap* inline_containing_block_map) {
if (!inline_containing_block_map->size())
return;
// This function has detailed knowledge of inline fragment tree structure,
// and will break if this changes.
DCHECK_GE(InlineSize(), LayoutUnit());
DCHECK_GE(BlockSize(), LayoutUnit());
// std::pair.first points to start linebox fragment.
// std::pair.second points to ending linebox fragment.
using LineBoxPair = std::pair<const NGPhysicalLineBoxFragment*,
const NGPhysicalLineBoxFragment*>;
HashMap<const LayoutObject*, LineBoxPair> containing_linebox_map;
for (wtf_size_t i = 0; i < children_.size(); i++) {
if (children_[i]->IsLineBox()) {
const NGPhysicalLineBoxFragment* linebox =
ToNGPhysicalLineBoxFragment(children_[i].get());
const NGPhysicalOffset linebox_offset = offsets_[i].ConvertToPhysical(
GetWritingMode(), Direction(),
ToNGPhysicalSize(Size(), GetWritingMode()), linebox->Size());
GatherInlineContainerFragmentsFromLinebox(inline_containing_block_map,
&containing_linebox_map,
linebox, linebox_offset);
} else if (children_[i]->IsBox()) {
const NGPhysicalBoxFragment* box_fragment =
ToNGPhysicalBoxFragment(children_[i].get());
bool is_anonymous_container =
box_fragment->GetLayoutObject() &&
box_fragment->GetLayoutObject()->IsAnonymousBlock();
if (!is_anonymous_container)
continue;
// If child is an anonymous container, this might be a special case of
// split inlines. The inline container fragments might be inside
// anonymous boxes. To find inline container fragments, traverse
// lineboxes inside anonymous box.
// For more on this special case, see "css container is an inline,
// with inline splitting" comment in
// NGOutOfFlowLayoutPart::LayoutDescendant.
const NGPhysicalOffset box_offset = offsets_[i].ConvertToPhysical(
GetWritingMode(), Direction(),
ToNGPhysicalSize(Size(), GetWritingMode()), box_fragment->Size());
// Traverse lineboxes of anonymous box.
for (const auto& child : box_fragment->Children()) {
if (child->IsLineBox()) {
const NGPhysicalLineBoxFragment* linebox =
ToNGPhysicalLineBoxFragment(child.get());
const NGPhysicalOffset linebox_offset = child.Offset() + box_offset;
GatherInlineContainerFragmentsFromLinebox(inline_containing_block_map,
&containing_linebox_map,
linebox, linebox_offset);
}
}
}
}
}
} // namespace blink