Commit 86d41cbe authored by Ian Kilpatrick's avatar Ian Kilpatrick Committed by Commit Bot

[LayoutNG] Implement line-box avoidance of floats correctly.

This asks for the layout opporunties up front, then iterates through them
attempting to construct a line for each one, until something fits.

There are two checks:
1) For the inline-size of a linebox, directly after the line breaker has run.
2) For the block-size, after we've determined the line-height.

Bug: 	635619
Change-Id: Icb88791be2c4d267b1361c4a7ed47dec71b9a87d
Reviewed-on: https://chromium-review.googlesource.com/742041
Commit-Queue: Ian Kilpatrick <ikilpatrick@chromium.org>
Reviewed-by: default avatarKoji Ishii <kojii@chromium.org>
Cr-Commit-Position: refs/heads/master@{#513318}
parent 1ee958f6
......@@ -308,12 +308,6 @@ crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-bfc-
crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-bfc-003-right-overflow.xht [ Failure ]
crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-bfc-004.xht [ Failure ]
crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-bfc-006.xht [ Failure ]
crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-top-below-inline-001l.xht [ Failure ]
crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-top-below-inline-001r.xht [ Failure ]
crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-top-below-inline-002l.xht [ Failure ]
crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-top-below-inline-002r.xht [ Failure ]
crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-top-below-inline-003l.xht [ Failure ]
crbug.com/635619 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-wrap-top-below-inline-003r.xht [ Failure ]
crbug.com/723135 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-zero-height-wrap-001.xht [ Failure ]
crbug.com/723135 virtual/layout_ng/external/wpt/css/CSS2/floats/floats-zero-height-wrap-002.xht [ Failure ]
......@@ -500,17 +494,9 @@ crbug.com/635619 virtual/layout_ng/fast/block/float/float-reparent-during-detach
crbug.com/635619 virtual/layout_ng/fast/block/float/floats-and-text-indent-rl.html [ Failure ]
crbug.com/635619 virtual/layout_ng/fast/block/float/floats-and-text-indent.html [ Failure ]
crbug.com/635619 virtual/layout_ng/fast/block/float/floats-do-not-overhang-from-block-formatting-context.html [ Failure ]
crbug.com/635619 virtual/layout_ng/fast/block/float/floats-offset-image-quirk-line-height.html [ Failure ]
crbug.com/635619 virtual/layout_ng/fast/block/float/floats-offset-image-quirk.html [ Failure ]
crbug.com/635619 virtual/layout_ng/fast/block/float/floats-offset-image-strict-line-height.html [ Failure ]
crbug.com/635619 virtual/layout_ng/fast/block/float/floats-offset-image-strict.html [ Failure ]
crbug.com/635619 virtual/layout_ng/fast/block/float/floats-offset-inline-block-quirk-line-height.html [ Failure ]
crbug.com/635619 virtual/layout_ng/fast/block/float/floats-offset-inline-block-strict-line-height.html [ Failure ]
crbug.com/635619 virtual/layout_ng/fast/block/float/floats-with-margin-should-not-wrap.html [ Failure ]
crbug.com/635619 virtual/layout_ng/fast/block/float/floats-wrap-inside-inline-001.htm [ Failure ]
crbug.com/635619 virtual/layout_ng/fast/block/float/floats-wrap-inside-inline-002.htm [ Failure ]
crbug.com/635619 virtual/layout_ng/fast/block/float/floats-wrap-inside-inline-003.htm [ Failure ]
crbug.com/635619 virtual/layout_ng/fast/block/float/floats-wrap-inside-inline-004.htm [ Failure ]
crbug.com/635619 virtual/layout_ng/fast/block/float/independent-align-positioning.html [ Failure ]
crbug.com/635619 [ Mac ] virtual/layout_ng/fast/block/float/intruding-painted-twice.html [ Failure ]
crbug.com/635619 virtual/layout_ng/fast/block/float/line-break-after-white-space-crash.html [ Pass Crash Timeout ]
......
......@@ -17,6 +17,33 @@ const char* kNGInlineItemTypeStrings[] = {
"Text", "Control", "AtomicInline", "OpenTag",
"CloseTag", "Floating", "OutOfFlowPositioned", "BidiControl"};
// Returns true if this item is "empty", i.e. if the node contains only empty
// items it will produce a single zero block-size line box.
static bool IsEmptyItem(NGInlineItem::NGInlineItemType type,
const ComputedStyle* style) {
if (type == NGInlineItem::kAtomicInline || type == NGInlineItem::kControl ||
type == NGInlineItem::kText)
return false;
if (type == NGInlineItem::kOpenTag) {
DCHECK(style);
if (!style->MarginStart().IsZero() || style->BorderStart().NonZero() ||
!style->PaddingStart().IsZero())
return false;
}
if (type == NGInlineItem::kCloseTag) {
DCHECK(style);
if (!style->MarginEnd().IsZero() || style->BorderEnd().NonZero() ||
!style->PaddingEnd().IsZero())
return false;
}
return true;
}
} // namespace
NGInlineItem::NGInlineItem(NGInlineItemType type,
......@@ -31,7 +58,8 @@ NGInlineItem::NGInlineItem(NGInlineItemType type,
layout_object_(layout_object),
type_(type),
bidi_level_(UBIDI_LTR),
shape_options_(kPreContext | kPostContext) {
shape_options_(kPreContext | kPostContext),
is_empty_item_(::blink::IsEmptyItem(type, style)) {
DCHECK_GE(end, start);
}
......
......@@ -65,6 +65,9 @@ class CORE_EXPORT NGInlineItem {
return static_cast<NGLayoutInlineShapeOptions>(shape_options_);
}
// If this item is "empty" for the purpose of empty block calculation.
bool IsEmptyItem() const { return is_empty_item_; }
unsigned StartOffset() const { return start_offset_; }
unsigned EndOffset() const { return end_offset_; }
unsigned Length() const { return end_offset_ - start_offset_; }
......@@ -111,6 +114,7 @@ class CORE_EXPORT NGInlineItem {
unsigned type_ : 4;
unsigned bidi_level_ : 8; // UBiDiLevel is defined as uint8_t.
unsigned shape_options_ : 2;
unsigned is_empty_item_ : 1;
friend class NGInlineNode;
};
......
......@@ -48,9 +48,11 @@ void NGLineInfo::SetLineStyle(const NGInlineNode& node,
}
void NGLineInfo::SetLineBfcOffset(NGBfcOffset line_bfc_offset,
LayoutUnit available_width) {
LayoutUnit available_width,
LayoutUnit width) {
line_bfc_offset_ = line_bfc_offset;
available_width_ = available_width;
width_ = width;
}
void NGLineInfo::SetLineEndShapeResult(
......
......@@ -131,8 +131,10 @@ class CORE_EXPORT NGLineInfo {
NGBfcOffset LineBfcOffset() const { return line_bfc_offset_; }
LayoutUnit AvailableWidth() const { return available_width_; }
LayoutUnit Width() const { return width_; }
void SetLineBfcOffset(NGBfcOffset line_bfc_offset,
LayoutUnit available_width);
LayoutUnit available_width,
LayoutUnit width);
// Start/end text offset of this line.
unsigned StartOffset() const { return start_offset_; }
......@@ -162,6 +164,7 @@ class CORE_EXPORT NGLineInfo {
NGBfcOffset line_bfc_offset_;
LayoutUnit available_width_;
LayoutUnit width_;
LayoutUnit text_indent_;
unsigned start_offset_;
......
......@@ -106,33 +106,6 @@ static bool ShouldRemoveNewline(const StringBuilder& before,
after_style);
}
// Returns true if this item is "empty", i.e. if the node contains only empty
// items it will produce a single zero block-size line box.
static bool IsItemEmpty(NGInlineItem::NGInlineItemType type,
const ComputedStyle* style) {
if (type == NGInlineItem::kAtomicInline || type == NGInlineItem::kControl ||
type == NGInlineItem::kText)
return false;
if (type == NGInlineItem::kOpenTag) {
DCHECK(style);
if (!style->MarginStart().IsZero() || style->BorderStart().NonZero() ||
!style->PaddingStart().IsZero())
return false;
}
if (type == NGInlineItem::kCloseTag) {
DCHECK(style);
if (!style->MarginEnd().IsZero() || style->BorderEnd().NonZero() ||
!style->PaddingEnd().IsZero())
return false;
}
return true;
}
static void AppendItem(Vector<NGInlineItem>* items,
NGInlineItem::NGInlineItemType type,
unsigned start,
......@@ -250,7 +223,7 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::
if (last_collapsible_space_ == CollapsibleSpace::kSpace &&
!style->AutoWrap())
last_collapsible_space_ = CollapsibleSpace::kSpaceNoWrap;
is_empty_inline_ &= IsItemEmpty(NGInlineItem::kText, style);
is_empty_inline_ &= items_->back().IsEmptyItem();
}
}
......@@ -278,7 +251,7 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::
AppendItem(items_, NGInlineItem::kText, start_offset, text_.length(), style,
layout_object);
is_empty_inline_ &= IsItemEmpty(NGInlineItem::kText, style);
is_empty_inline_ &= items_->back().IsEmptyItem();
start = end;
}
......@@ -339,7 +312,7 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::Append(
unsigned end_offset = text_.length();
AppendItem(items_, type, end_offset - 1, end_offset, style, layout_object);
is_empty_inline_ &= IsItemEmpty(type, style);
is_empty_inline_ &= items_->back().IsEmptyItem();
last_collapsible_space_ = CollapsibleSpace::kNone;
}
......@@ -363,7 +336,7 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendOpaque(
unsigned end_offset = text_.length();
AppendItem(items_, type, end_offset - 1, end_offset, style, layout_object);
is_empty_inline_ &= IsItemEmpty(type, nullptr);
is_empty_inline_ &= items_->back().IsEmptyItem();
}
template <typename OffsetMappingBuilder>
......@@ -374,7 +347,7 @@ void NGInlineItemsBuilderTemplate<OffsetMappingBuilder>::AppendOpaque(
unsigned end_offset = text_.length();
AppendItem(items_, type, end_offset, end_offset, style, layout_object);
is_empty_inline_ &= IsItemEmpty(type, style);
is_empty_inline_ &= items_->back().IsEmptyItem();
}
// Removes the collapsible newline at the end of |text_| if exists and the
......
......@@ -523,54 +523,122 @@ LayoutUnit NGInlineLayoutAlgorithm::ComputeContentSize(
}
scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
if (Node().IsEmptyInline())
return LayoutEmptyInline();
std::unique_ptr<NGExclusionSpace> initial_exclusion_space(
WTF::MakeUnique<NGExclusionSpace>(ConstraintSpace().ExclusionSpace()));
DCHECK(ConstraintSpace().UnpositionedFloats().IsEmpty());
DCHECK(ConstraintSpace().MarginStrut().IsEmpty());
container_builder_.SetBfcOffset(ConstraintSpace().BfcOffset());
bool is_empty_inline = Node().IsEmptyInline();
scoped_refptr<NGInlineBreakToken> break_token = BreakToken();
if (!is_empty_inline) {
DCHECK(ConstraintSpace().UnpositionedFloats().IsEmpty());
DCHECK(ConstraintSpace().MarginStrut().IsEmpty());
container_builder_.SetBfcOffset(ConstraintSpace().BfcOffset());
}
// Copy the state stack from the unfinished break token if provided. This
// enforces the layout inputs immutability constraint. If we weren't provided
// with a break token we just create an empty state stack.
box_states_ =
break_token
? WTF::MakeUnique<NGInlineLayoutStateStack>(break_token->StateStack())
: WTF::MakeUnique<NGInlineLayoutStateStack>();
// In order to get the correct list of layout opportunities, we need to
// position any "leading" items (floats) within the exclusion space first.
unsigned handled_item_index =
PositionLeadingItems(initial_exclusion_space.get());
std::unique_ptr<NGExclusionSpace> exclusion_space(
WTF::MakeUnique<NGExclusionSpace>(ConstraintSpace().ExclusionSpace()));
NGLineInfo line_info;
// If we are an empty inline, we don't have to run the full algorithm, we can
// return now as we should have positioned all of our floats.
if (is_empty_inline) {
DCHECK_EQ(handled_item_index, Node().Items().size());
container_builder_.SwapPositionedFloats(&positioned_floats_);
container_builder_.SwapUnpositionedFloats(&unpositioned_floats_);
container_builder_.SetEndMarginStrut(ConstraintSpace().MarginStrut());
container_builder_.SetExclusionSpace(std::move(initial_exclusion_space));
Vector<NGOutOfFlowPositionedDescendant> descendant_candidates;
container_builder_.GetAndClearOutOfFlowDescendantCandidates(
&descendant_candidates);
for (auto& descendant : descendant_candidates)
container_builder_.AddOutOfFlowDescendant(descendant);
NGLineBreaker line_breaker(Node(), constraint_space_, &positioned_floats_,
&unpositioned_floats_, break_token.get());
// TODO(ikilpatrick): Does this always succeed when we aren't an empty inline?
if (line_breaker.NextLine(*exclusion_space, &line_info)) {
CreateLine(&line_info, line_breaker.ExclusionSpace());
return container_builder_.ToLineBoxFragment();
}
DCHECK(container_builder_.BfcOffset());
// We query all the layout opportunities on the initial exclusion space up
// front, as if the line breaker may add floats and change the opportunities.
Vector<NGLayoutOpportunity> opportunities =
initial_exclusion_space->AllLayoutOpportunities(
ConstraintSpace().BfcOffset(), ConstraintSpace().AvailableSize());
Vector<NGPositionedFloat> positioned_floats;
DCHECK(unpositioned_floats_.IsEmpty());
std::unique_ptr<NGExclusionSpace> exclusion_space;
NGInlineBreakToken* break_token = BreakToken();
for (const auto& opportunity : opportunities) {
// Copy the state stack from the unfinished break token if provided. This
// enforces the layout inputs immutability constraint. If we weren't
// provided with a break token we just create an empty state stack.
box_states_ = break_token ? WTF::MakeUnique<NGInlineLayoutStateStack>(
break_token->StateStack())
: WTF::MakeUnique<NGInlineLayoutStateStack>();
// Reset any state that may have been modified in a previous pass.
positioned_floats.clear();
unpositioned_floats_.clear();
container_builder_.Reset();
exclusion_space =
WTF::MakeUnique<NGExclusionSpace>(*initial_exclusion_space);
NGLineInfo line_info;
NGLineBreaker line_breaker(Node(), constraint_space_, &positioned_floats,
&unpositioned_floats_, exclusion_space.get(),
handled_item_index, break_token);
// TODO(ikilpatrick): Does this always succeed when we aren't an empty
// inline?
if (!line_breaker.NextLine(opportunity, &line_info))
break;
// If this fragment will be larger than the inline-size of the opportunity,
// *and* the opportunity is smaller than the available inline-size,
// continue to the next opportunity.
if (line_info.Width() > opportunity.InlineSize() &&
opportunity.InlineSize() !=
ConstraintSpace().AvailableSize().inline_size)
continue;
CreateLine(&line_info, exclusion_space.get());
// We now can check the block-size of the fragment, and it fits within the
// opportunity.
LayoutUnit block_size = container_builder_.ComputeBlockSize();
if (block_size > opportunity.BlockSize())
continue;
LayoutUnit line_height =
container_builder_.Metrics().LineHeight().ClampNegativeToZero();
// Success!
positioned_floats_.AppendVector(positioned_floats);
container_builder_.SetBreakToken(
line_breaker.CreateBreakToken(std::move(box_states_)));
exclusion_space =
WTF::MakeUnique<NGExclusionSpace>(*line_breaker.ExclusionSpace());
}
// Place any remaining floats which couldn't fit on the line.
PositionPendingFloats(line_height, exclusion_space.get());
// Place any remaining floats which couldn't fit on the line.
LayoutUnit content_size =
container_builder_.Metrics().LineHeight().ClampNegativeToZero();
PositionPendingFloats(content_size, exclusion_space.get());
// A <br clear=both> will strech the line-box height, such that the
// block-end edge will clear any floats.
// TODO(ikilpatrick): Move this into ng_block_layout_algorithm.
container_builder_.SetBlockSize(
ComputeContentSize(line_info, *exclusion_space, line_height));
// A <br clear=both> will strech the line-box height, such that the block-end
// edge will clear any floats.
container_builder_.SetBlockSize(ComputeContentSize(
line_info, *exclusion_space,
container_builder_.Metrics().LineHeight().ClampNegativeToZero()));
break;
}
// We shouldn't have any unpositioned floats if we aren't empty.
DCHECK(unpositioned_floats_.IsEmpty());
container_builder_.SwapPositionedFloats(&positioned_floats_);
container_builder_.SetExclusionSpace(std::move(exclusion_space));
container_builder_.SetExclusionSpace(
exclusion_space ? std::move(exclusion_space)
: std::move(initial_exclusion_space));
Vector<NGOutOfFlowPositionedDescendant> descendant_candidates;
container_builder_.GetAndClearOutOfFlowDescendantCandidates(
......@@ -580,11 +648,19 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::Layout() {
return container_builder_.ToLineBoxFragment();
}
scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::LayoutEmptyInline() {
// This positions any "leading" floats within the given exclusion space.
// If we are also an empty inline, it will add any out-of-flow descendants.
// TODO(ikilpatrick): Do we need to always add the OOFs here?
unsigned NGInlineLayoutAlgorithm::PositionLeadingItems(
NGExclusionSpace* exclusion_space) {
const Vector<NGInlineItem>& items = Node().Items();
bool is_empty_inline = Node().IsEmptyInline();
LayoutUnit bfc_line_offset = ConstraintSpace().BfcOffset().line_offset;
for (const auto& item : items) {
unsigned index = BreakToken() ? BreakToken()->ItemIndex() : 0;
for (; index < items.size(); ++index) {
const auto& item = items[index];
if (item.Type() == NGInlineItem::kFloating) {
NGBlockNode node(ToLayoutBox(item.GetLayoutObject()));
NGBoxStrut margins =
......@@ -594,36 +670,24 @@ scoped_refptr<NGLayoutResult> NGInlineLayoutAlgorithm::LayoutEmptyInline() {
ConstraintSpace().AvailableSize(),
ConstraintSpace().PercentageResolutionSize(), bfc_line_offset,
bfc_line_offset, margins, node, /* break_token */ nullptr));
} else if (item.Type() == NGInlineItem::kOutOfFlowPositioned) {
} else if (is_empty_inline &&
item.Type() == NGInlineItem::kOutOfFlowPositioned) {
NGBlockNode node(ToLayoutBox(item.GetLayoutObject()));
container_builder_.AddOutOfFlowChildCandidate(
node, NGLogicalOffset(), CurrentDirection(node.Style().Direction()));
} else {
DCHECK_NE(item.Type(), NGInlineItem::kAtomicInline);
DCHECK_NE(item.Type(), NGInlineItem::kControl);
DCHECK_NE(item.Type(), NGInlineItem::kText);
}
}
std::unique_ptr<NGExclusionSpace> exclusion_space(
WTF::MakeUnique<NGExclusionSpace>(ConstraintSpace().ExclusionSpace()));
// The content_size is zero for an empty inline.
if (ConstraintSpace().FloatsBfcOffset())
PositionPendingFloats(/* content_size */ LayoutUnit(),
exclusion_space.get());
// Abort if we've found something that makes this a non-empty inline.
if (!item.IsEmptyItem()) {
DCHECK(!is_empty_inline);
break;
}
}
container_builder_.SwapPositionedFloats(&positioned_floats_);
container_builder_.SwapUnpositionedFloats(&unpositioned_floats_);
container_builder_.SetEndMarginStrut(ConstraintSpace().MarginStrut());
container_builder_.SetExclusionSpace(std::move(exclusion_space));
if (ConstraintSpace().FloatsBfcOffset() || container_builder_.BfcOffset())
PositionPendingFloats(/* content_size */ LayoutUnit(), exclusion_space);
Vector<NGOutOfFlowPositionedDescendant> descendant_candidates;
container_builder_.GetAndClearOutOfFlowDescendantCandidates(
&descendant_candidates);
for (auto& descendant : descendant_candidates)
container_builder_.AddOutOfFlowDescendant(descendant);
return container_builder_.ToLineBoxFragment();
return index;
}
void NGInlineLayoutAlgorithm::PositionPendingFloats(
......
......@@ -46,8 +46,7 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final
scoped_refptr<NGLayoutResult> Layout() override;
private:
scoped_refptr<NGLayoutResult> LayoutEmptyInline();
unsigned PositionLeadingItems(NGExclusionSpace*);
void PositionPendingFloats(LayoutUnit content_size, NGExclusionSpace*);
bool IsHorizontalWritingMode() const { return is_horizontal_writing_mode_; }
......@@ -96,7 +95,6 @@ class CORE_EXPORT NGInlineLayoutAlgorithm final
unsigned is_horizontal_writing_mode_ : 1;
unsigned quirks_mode_ : 1;
std::unique_ptr<NGExclusionSpace> exclusion_space_;
Vector<NGPositionedFloat> positioned_floats_;
Vector<scoped_refptr<NGUnpositionedFloat>> unpositioned_floats_;
};
......
......@@ -600,8 +600,13 @@ static LayoutUnit ComputeContentSize(NGInlineNode node,
LayoutUnit result;
while (!break_token || !break_token->IsFinished()) {
NGLineBreaker line_breaker(node, *space, &positioned_floats,
&unpositioned_floats, break_token.get());
if (!line_breaker.NextLine(empty_exclusion_space, &line_info))
&unpositioned_floats, &empty_exclusion_space, 0u,
break_token.get());
if (!line_breaker.NextLine(
NGLayoutOpportunity(
NGBfcOffset(),
NGLogicalSize({available_inline_size, NGSizeIndefinite})),
&line_info))
break;
break_token = line_breaker.CreateBreakToken(nullptr);
......
......@@ -9,6 +9,7 @@
#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_exclusion_space.h"
#include "core/layout/ng/ng_fragment.h"
#include "core/layout/ng/ng_layout_result.h"
#include "core/layout/ng/ng_positioned_float.h"
......@@ -24,10 +25,31 @@ NGLineBoxFragmentBuilder::NGLineBoxFragmentBuilder(
NGLineBoxFragmentBuilder::~NGLineBoxFragmentBuilder() {}
void NGLineBoxFragmentBuilder::Reset() {
children_.clear();
offsets_.clear();
metrics_ = NGLineHeightMetrics();
inline_size_ = LayoutUnit();
}
NGLogicalSize NGLineBoxFragmentBuilder::Size() const {
return {inline_size_, metrics_.LineHeight().ClampNegativeToZero()};
}
LayoutUnit NGLineBoxFragmentBuilder::ComputeBlockSize() const {
LayoutUnit block_size;
NGWritingMode writing_mode(
FromPlatformWritingMode(node_.Style().GetWritingMode()));
for (size_t i = 0; i < children_.size(); ++i) {
block_size = std::max(
block_size, offsets_[i].block_offset +
NGFragment(writing_mode, *children_[i]).BlockSize());
}
return block_size;
}
const NGPhysicalFragment* NGLineBoxFragmentBuilder::Child::PhysicalFragment()
const {
return layout_result ? layout_result->PhysicalFragment().get()
......
......@@ -29,7 +29,10 @@ class CORE_EXPORT NGLineBoxFragmentBuilder final
TextDirection);
~NGLineBoxFragmentBuilder() override;
void Reset();
NGLogicalSize Size() const final;
LayoutUnit ComputeBlockSize() const;
void SetMetrics(const NGLineHeightMetrics&);
const NGLineHeightMetrics& Metrics() const { return metrics_; }
......
......@@ -23,14 +23,18 @@ NGLineBreaker::NGLineBreaker(
const NGConstraintSpace& space,
Vector<NGPositionedFloat>* positioned_floats,
Vector<scoped_refptr<NGUnpositionedFloat>>* unpositioned_floats,
NGExclusionSpace* exclusion_space,
unsigned handled_float_index,
const NGInlineBreakToken* break_token)
: node_(node),
constraint_space_(space),
positioned_floats_(positioned_floats),
unpositioned_floats_(unpositioned_floats),
exclusion_space_(exclusion_space),
break_iterator_(node.Text()),
shaper_(node.Text().Characters16(), node.Text().length()),
spacing_(node.Text()),
handled_floats_end_item_index_(handled_float_index),
base_direction_(node_.BaseDirection()) {
if (break_token) {
item_index_ = break_token->ItemIndex();
......@@ -65,7 +69,7 @@ void NGLineBreaker::ComputeBaseDirection() {
}
// Initialize internal states for the next line.
void NGLineBreaker::PrepareNextLine(const NGExclusionSpace& exclusion_space,
void NGLineBreaker::PrepareNextLine(const NGLayoutOpportunity& opportunity,
NGLineInfo* line_info) {
NGInlineItemResults* item_results = &line_info->Results();
item_results->clear();
......@@ -83,18 +87,17 @@ void NGLineBreaker::PrepareNextLine(const NGExclusionSpace& exclusion_space,
// regardless of 'text-indent'.
line_.position = line_info->TextIndent();
line_.exclusion_space = WTF::MakeUnique<NGExclusionSpace>(exclusion_space);
// We are only able to calculate our available_width if our container has
// been positioned in the BFC coordinate space yet.
FindNextLayoutOpportunity();
line_.opportunity = opportunity;
line_.line_left_bfc_offset = opportunity.LineStartOffset();
line_.line_right_bfc_offset = opportunity.LineEndOffset();
bfc_block_offset_ = opportunity.BlockStartOffset();
}
bool NGLineBreaker::NextLine(const NGExclusionSpace& exclusion_space,
bool NGLineBreaker::NextLine(const NGLayoutOpportunity& opportunity,
NGLineInfo* line_info) {
bfc_block_offset_ = constraint_space_.BfcOffset().block_offset;
PrepareNextLine(exclusion_space, line_info);
PrepareNextLine(opportunity, line_info);
BreakLine(line_info);
line_info->SetEndOffset(offset_);
......@@ -190,52 +193,8 @@ void NGLineBreaker::BreakLine(NGLineInfo* line_info) {
line_info->SetIsLastLine(true);
}
// @return if there are floats that affect current line.
// This is different from the clearance offset in that floats outside of the
// current layout opportunities, such as floats in margin/padding, or floats
// below such floats, are not included.
bool NGLineBreaker::HasFloatsAffectingCurrentLine() const {
return line_.opportunity.InlineSize() !=
constraint_space_.AvailableSize().inline_size;
}
// Update the inline size of the first layout opportunity from the given
// content_offset.
void NGLineBreaker::FindNextLayoutOpportunity() {
NGBfcOffset origin_offset = {constraint_space_.BfcOffset().line_offset,
bfc_block_offset_};
line_.opportunity = line_.exclusion_space->FindLayoutOpportunity(
origin_offset, constraint_space_.AvailableSize(),
/* minimum_size */ NGLogicalSize());
// When floats/exclusions occupies the entire line (e.g., float: left; width:
// 100%), zero-inline-size opportunities are not included in the iterator.
// Instead, the block offset of the first opportunity is pushed down to avoid
// such floats/exclusions. Set the line box location to it.
bfc_block_offset_ = line_.opportunity.BlockStartOffset();
}
// Finds a layout opportunity that has the given minimum inline size, or the one
// without floats/exclusions (and that there will not be wider oppotunities than
// that,) and moves |bfc_block_offset_| down to it.
//
// Used to move lines down when no break opportunities can fit in a line that
// has floats.
void NGLineBreaker::FindNextLayoutOpportunityWithMinimumInlineSize(
LayoutUnit min_inline_size) {
NGBfcOffset origin_offset = {constraint_space_.BfcOffset().line_offset,
bfc_block_offset_};
NGLogicalSize minimum_size(min_inline_size, LayoutUnit());
line_.opportunity = line_.exclusion_space->FindLayoutOpportunity(
origin_offset, constraint_space_.AvailableSize(), minimum_size);
bfc_block_offset_ = line_.opportunity.BlockStartOffset();
}
void NGLineBreaker::ComputeLineLocation(NGLineInfo* line_info) const {
LayoutUnit line_bfc_offset = line_.opportunity.LineStartOffset();
LayoutUnit bfc_line_offset = line_.line_left_bfc_offset;
LayoutUnit available_width = line_.AvailableWidth();
// Indenting should move the current position to compute the size of
......@@ -245,12 +204,12 @@ void NGLineBreaker::ComputeLineLocation(NGLineInfo* line_info) const {
// Move the line box by indent. Negative indents are ink overflow, let the
// line box overflow from the container box.
if (IsLtr(line_info->BaseDirection()))
line_bfc_offset += text_indent;
bfc_line_offset += text_indent;
available_width -= text_indent;
}
line_info->SetLineBfcOffset({line_bfc_offset, bfc_block_offset_},
available_width);
line_info->SetLineBfcOffset({bfc_line_offset, bfc_block_offset_},
available_width, line_.position);
}
bool NGLineBreaker::IsFirstBreakOpportunity(unsigned offset,
......@@ -499,13 +458,10 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleFloat(
// twice.
// Ideally rewind can take floats out of floats list, but the difference is
// sutble compared to the complexity.
// TODO(kojii): Keep a list of floats in a separate vector, then "commit" them
// inside NGLineLayoutAlgorithm.
if (item_index_ < handled_floats_end_item_index_) {
MoveToNextOf(item);
return ComputeIsBreakableAfter(item_result);
}
handled_floats_end_item_index_ = item_index_ + 1;
NGBlockNode node(ToLayoutBox(item.GetLayoutObject()));
......@@ -523,30 +479,52 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleFloat(
margins, node,
/* break_token */ nullptr);
LayoutUnit inline_size = ComputeInlineSizeForUnpositionedFloat(
constraint_space_, unpositioned_float.get());
// We can only determine if our float will fit if we have an available_width
// I.e. we may not have come across any text yet, in order to be able to
// resolve the BFC position.
bool float_does_not_fit = !line_.CanFit(inline_size + margins.InlineSum());
LayoutUnit inline_margin_size =
(ComputeInlineSizeForUnpositionedFloat(constraint_space_,
unpositioned_float.get()) +
margins.InlineSum())
.ClampNegativeToZero();
// The float should be positioned after the current line if:
// - It can't fit.
// - It will be moved down due to block-start edge alignment.
// - It will be moved down due to clearance.
bool float_after_line =
!line_.CanFit(inline_margin_size) ||
exclusion_space_->LastFloatBlockStart() > bfc_block_offset_ ||
exclusion_space_->ClearanceOffset(float_style.Clear()) >
bfc_block_offset_;
// Check if we already have a pending float. That's because a float cannot be
// higher than any block or floated box generated before.
if (!unpositioned_floats_->IsEmpty() || float_does_not_fit) {
if (!unpositioned_floats_->IsEmpty() || float_after_line) {
unpositioned_floats_->push_back(std::move(unpositioned_float));
} else {
LayoutUnit origin_block_offset = bfc_block_offset_;
NGPositionedFloat positioned_float = PositionFloat(
origin_block_offset, constraint_space_.BfcOffset().block_offset,
unpositioned_float.get(), constraint_space_,
line_.exclusion_space.get());
unpositioned_float.get(), constraint_space_, exclusion_space_);
positioned_floats_->push_back(positioned_float);
// We need to recalculate the available_width as the float probably
// consumed space on the line.
FindNextLayoutOpportunity();
DCHECK_EQ(positioned_float.bfc_offset.block_offset,
bfc_block_offset_ + margins.block_start);
if (float_style.Floating() == EFloat::kLeft) {
line_.line_left_bfc_offset = std::max(
line_.line_left_bfc_offset,
positioned_float.bfc_offset.line_offset + inline_margin_size -
margins.LineLeft(TextDirection::kLtr));
} else {
line_.line_right_bfc_offset =
std::min(line_.line_right_bfc_offset,
positioned_float.bfc_offset.line_offset -
margins.LineLeft(TextDirection::kLtr));
}
DCHECK_GE(line_.line_left_bfc_offset, LayoutUnit());
DCHECK_GE(line_.line_right_bfc_offset, LayoutUnit());
DCHECK_GE(line_.AvailableWidth(), LayoutUnit());
}
MoveToNextOf(item);
......@@ -697,23 +675,6 @@ void NGLineBreaker::HandleOverflow(NGLineInfo* line_info,
// Reaching here means that the rewind point was not found.
// When the first break opportunity overflows and if the current line has
// floats or intruding floats, we need to find the next layout opportunity
// which will fit the first break opportunity.
// Doing so will move the line down to where there are narrower floats (and
// thus wider available width,) or no floats.
if (HasFloatsAffectingCurrentLine()) {
FindNextLayoutOpportunityWithMinimumInlineSize(line_.position);
// Moving the line down widened the available width. Need to rewind items
// that depend on old available width, but it's not trivial to rewind all
// the states. For the simplicity, rewind to the beginning of the line.
Rewind(line_info, 0);
SetCurrentStyle(line_info->LineStyle());
line_.position = line_info->TextIndent();
BreakLine(line_info);
return;
}
// Let this line overflow.
// If there was a break opporunity, the overflow should stop there.
if (break_before)
......@@ -726,7 +687,7 @@ void NGLineBreaker::Rewind(NGLineInfo* line_info, unsigned new_end) {
NGInlineItemResults* item_results = &line_info->Results();
DCHECK_LT(new_end, item_results->size());
// TODO(ikilpatrick): Add rewinding of exclusions.
// TODO(ikilpatrick): Add DCHECK that we never rewind past any floats.
if (new_end) {
// Use |results[new_end - 1].end_offset| because it may have been truncated
......@@ -741,8 +702,6 @@ void NGLineBreaker::Rewind(NGLineInfo* line_info, unsigned new_end) {
// TODO(kojii): Should we keep results for the next line? We don't need to
// re-layout atomic inlines.
// TODO(kojii): Removing processed floats is likely a problematic. Keep
// floats in this line, or keep it for the next line.
item_results->Shrink(new_end);
line_info->SetIsLastLine(false);
......
......@@ -34,19 +34,19 @@ class CORE_EXPORT NGLineBreaker {
const NGConstraintSpace&,
Vector<NGPositionedFloat>*,
Vector<scoped_refptr<NGUnpositionedFloat>>*,
NGExclusionSpace*,
unsigned handled_float_index,
const NGInlineBreakToken* = nullptr);
~NGLineBreaker() {}
// Compute the next line break point and produces NGInlineItemResults for
// the line.
bool NextLine(const NGExclusionSpace&, NGLineInfo*);
bool NextLine(const NGLayoutOpportunity&, NGLineInfo*);
// Create an NGInlineBreakToken for the last line returned by NextLine().
scoped_refptr<NGInlineBreakToken> CreateBreakToken(
std::unique_ptr<const NGInlineLayoutStateStack>) const;
NGExclusionSpace* ExclusionSpace() { return line_.exclusion_space.get(); }
private:
// This struct holds information for the current line.
struct LineData {
......@@ -59,7 +59,8 @@ class CORE_EXPORT NGLineBreaker {
// The current opportunity.
NGLayoutOpportunity opportunity;
std::unique_ptr<NGExclusionSpace> exclusion_space;
LayoutUnit line_left_bfc_offset;
LayoutUnit line_right_bfc_offset;
// We don't create "certain zero-height line boxes".
// https://drafts.csswg.org/css2/visuren.html#phantom-line-box
......@@ -72,7 +73,10 @@ class CORE_EXPORT NGLineBreaker {
// the next line.
bool is_after_forced_break = false;
LayoutUnit AvailableWidth() const { return opportunity.InlineSize(); }
LayoutUnit AvailableWidth() const {
DCHECK_GE(line_right_bfc_offset, line_left_bfc_offset);
return line_right_bfc_offset - line_left_bfc_offset;
}
bool CanFit() const { return position <= AvailableWidth(); }
bool CanFit(LayoutUnit extra) const {
return position + extra <= AvailableWidth();
......@@ -81,11 +85,7 @@ class CORE_EXPORT NGLineBreaker {
void BreakLine(NGLineInfo*);
void PrepareNextLine(const NGExclusionSpace&, NGLineInfo*);
bool HasFloatsAffectingCurrentLine() const;
void FindNextLayoutOpportunity();
void FindNextLayoutOpportunityWithMinimumInlineSize(LayoutUnit);
void PrepareNextLine(const NGLayoutOpportunity&, NGLineInfo*);
void ComputeLineLocation(NGLineInfo*) const;
......@@ -142,9 +142,12 @@ class CORE_EXPORT NGLineBreaker {
const NGConstraintSpace& constraint_space_;
Vector<NGPositionedFloat>* positioned_floats_;
Vector<scoped_refptr<NGUnpositionedFloat>>* unpositioned_floats_;
NGExclusionSpace* exclusion_space_;
unsigned item_index_ = 0;
unsigned offset_ = 0;
bool previous_line_had_forced_break_ = false;
LayoutUnit bfc_line_offset_;
LayoutUnit bfc_block_offset_;
LazyLineBreakIterator break_iterator_;
HarfBuzzShaper shaper_;
......@@ -152,7 +155,7 @@ class CORE_EXPORT NGLineBreaker {
const Hyphenation* hyphenation_ = nullptr;
// Keep track of handled float items. See HandleFloat().
unsigned handled_floats_end_item_index_ = 0;
unsigned handled_floats_end_item_index_;
// The current base direction for the bidi algorithm.
// This is copied from NGInlineNode, then updated after each forced line break
......
......@@ -49,8 +49,13 @@ class NGLineBreakerTest : public NGBaseLayoutAlgorithmTest {
NGLineInfo line_info;
while (!break_token || !break_token->IsFinished()) {
NGLineBreaker line_breaker(node, *space, &positioned_floats,
&unpositioned_floats, break_token.get());
if (!line_breaker.NextLine(exclusion_space, &line_info))
&unpositioned_floats, &exclusion_space, 0u,
break_token.get());
if (!line_breaker.NextLine(
NGLayoutOpportunity(
NGBfcOffset(),
NGLogicalSize({available_width, NGSizeIndefinite})),
&line_info))
break;
break_token = line_breaker.CreateBreakToken(nullptr);
......
......@@ -51,6 +51,22 @@ NGLayoutOpportunity NGExclusionSpace::FindLayoutOpportunity(
return NGLayoutOpportunity();
}
Vector<NGLayoutOpportunity> NGExclusionSpace::AllLayoutOpportunities(
const NGBfcOffset& offset,
const NGLogicalSize& available_size) const {
Vector<NGLayoutOpportunity> opportunities;
NGLayoutOpportunityIterator opportunity_iter(*this, available_size, offset);
while (true) {
opportunities.push_back(opportunity_iter.Next());
if (opportunity_iter.IsAtEnd())
break;
}
return opportunities;
}
LayoutUnit NGExclusionSpace::ClearanceOffset(EClear clear_type) const {
switch (clear_type) {
case EClear::kNone:
......
......@@ -36,6 +36,10 @@ class CORE_EXPORT NGExclusionSpace {
const NGLogicalSize& available_size,
const NGLogicalSize& minimum_size) const;
Vector<NGLayoutOpportunity> AllLayoutOpportunities(
const NGBfcOffset& offset,
const NGLogicalSize& available_size) const;
// Returns the clearance offset based on the provided {@code clear_type}.
LayoutUnit ClearanceOffset(EClear clear_type) const;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment