blob: 300445ecf41941d1fe3100d9265a17d56232902b [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 "core/layout/ng/ng_fragment_builder.h"
#include "core/layout/LayoutObject.h"
#include "core/layout/ng/inline/ng_inline_break_token.h"
#include "core/layout/ng/inline/ng_inline_fragment_traversal.h"
#include "core/layout/ng/inline/ng_inline_node.h"
#include "core/layout/ng/inline/ng_physical_line_box_fragment.h"
#include "core/layout/ng/ng_block_break_token.h"
#include "core/layout/ng/ng_block_node.h"
#include "core/layout/ng/ng_break_token.h"
#include "core/layout/ng/ng_exclusion_space.h"
#include "core/layout/ng/ng_fragment.h"
#include "core/layout/ng/ng_fragmentation_utils.h"
#include "core/layout/ng/ng_layout_result.h"
#include "core/layout/ng/ng_physical_box_fragment.h"
#include "core/layout/ng/ng_positioned_float.h"
namespace blink {
NGFragmentBuilder::NGFragmentBuilder(NGLayoutInputNode node,
scoped_refptr<const ComputedStyle> style,
WritingMode writing_mode,
TextDirection direction)
: NGContainerFragmentBuilder(style, writing_mode, direction),
node_(node),
layout_object_(node.GetLayoutObject()),
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() {}
NGFragmentBuilder& NGFragmentBuilder::SetIntrinsicBlockSize(
LayoutUnit intrinsic_block_size) {
intrinsic_block_size_ = intrinsic_block_size;
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. Only
// the last break token is needed to be reported to the parent.
DCHECK(child->BreakToken());
DCHECK(child->BreakToken()->InputNode() == node_);
last_inline_break_token_ =
child->BreakToken()->IsFinished() ? nullptr : child->BreakToken();
break;
case NGPhysicalBoxFragment::kFragmentText:
DCHECK(!child->BreakToken());
break;
default:
NOTREACHED();
break;
}
return NGContainerFragmentBuilder::AddChild(std::move(child), child_offset);
}
NGFragmentBuilder& NGFragmentBuilder::AddBreakBeforeChild(
NGLayoutInputNode child) {
if (children_.IsEmpty()) {
// Ideally, breaking should only occur *between* children, not *before* a
// first child.
//
// TODO(crbug.com/796077): There's one exception here - class C break points
// [1]. We should identify them here and not set the "last resort break"
// flag in that case.
//
// [1] https://www.w3.org/TR/css-break-3/#possible-breaks
has_last_resort_break_ = true;
}
if (child.IsInline()) {
if (last_inline_break_token_) {
// We need to resume at this inline location in the next fragmentainer,
// but broken floats, which are resumed and positioned by the parent block
// layout algorithm, need to be ignored by the inline layout algorithm.
ToNGInlineBreakToken(last_inline_break_token_.get())->SetIgnoreFloats();
} else {
last_inline_break_token_ = NGInlineBreakToken::Create(
ToNGInlineNode(child), 0, 0, false,
std::make_unique<NGInlineLayoutStateStack>());
}
return *this;
}
// TODO(mstensho): Come up with a more intuitive way of creating an unfinished
// break token. We currently need to pass a Vector here, just to end up in the
// right NGBlockBreakToken constructor - the one that sets the token as
// unfinished.
Vector<scoped_refptr<NGBreakToken>> dummy;
auto token = NGBlockBreakToken::Create(child, LayoutUnit(), dummy);
child_break_tokens_.push_back(token);
return *this;
}
NGFragmentBuilder& NGFragmentBuilder::PropagateBreak(
scoped_refptr<NGLayoutResult> child_layout_result) {
if (!did_break_)
return PropagateBreak(child_layout_result->PhysicalFragment());
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(inline_size_, LayoutUnit());
DCHECK_GE(block_size_, 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(inline_size_, LayoutUnit());
break;
case WritingMode::kVerticalRl:
case WritingMode::kSidewaysRl:
if (IsLtr(Direction()))
zero_offset = NGLogicalOffset(LayoutUnit(), block_size_);
else
zero_offset = NGLogicalOffset(inline_size_, block_size_);
break;
case WritingMode::kVerticalLr:
case WritingMode::kSidewaysLr:
if (IsLtr(Direction()))
zero_offset = NGLogicalOffset();
else
zero_offset = NGLogicalOffset(inline_size_, LayoutUnit());
break;
}
oof_positioned_candidates_.push_back(
NGOutOfFlowPositionedCandidate{descendant, zero_offset});
}
NGPhysicalFragment::NGBoxType NGFragmentBuilder::BoxType() const {
if (box_type_ != NGPhysicalFragment::NGBoxType::kNormalBox)
return box_type_;
// When implicit, compute from LayoutObject.
if (!layout_object_ || layout_object_->Style() != &Style())
return NGPhysicalFragment::NGBoxType::kAnonymousBox;
if (layout_object_->IsFloating())
return NGPhysicalFragment::NGBoxType::kFloating;
if (layout_object_->IsOutOfFlowPositioned())
return NGPhysicalFragment::NGBoxType::kOutOfFlowPositioned;
if (layout_object_->IsAtomicInlineLevel())
return NGPhysicalFragment::NGBoxType::kInlineBlock;
return NGPhysicalFragment::NGBoxType::kNormalBox;
}
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_EQ(offsets_.size(), children_.size());
NGPhysicalSize physical_size = Size().ConvertToPhysical(GetWritingMode());
NGPhysicalOffsetRect contents_visual_rect({}, physical_size);
for (size_t i = 0; i < children_.size(); ++i) {
NGPhysicalFragment* child = children_[i].get();
child->SetOffset(offsets_[i].ConvertToPhysical(
GetWritingMode(), Direction(), physical_size, child->Size()));
child->PropagateContentsVisualRect(&contents_visual_rect);
}
scoped_refptr<NGBreakToken> break_token;
if (node_) {
if (last_inline_break_token_) {
DCHECK(!last_inline_break_token_->IsFinished());
child_break_tokens_.push_back(std::move(last_inline_break_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(), physical_size, children_,
contents_visual_rect, 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_floats_, std::move(exclusion_space_), bfc_offset_,
end_margin_strut_, intrinsic_block_size_, initial_break_before_,
previous_break_after_, 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,
unpositioned_floats_, nullptr, bfc_offset_, end_margin_strut_,
LayoutUnit(), EBreakBetween::kAuto, EBreakBetween::kAuto, 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(inline_size_, LayoutUnit());
DCHECK_GE(block_size_, LayoutUnit());
container_size->inline_size = inline_size_;
container_size->block_size = block_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;
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;
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.width);
}
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