Index: third_party/WebKit/Source/core/layout/ng/inline/ng_line_breaker.cc |
diff --git a/third_party/WebKit/Source/core/layout/ng/inline/ng_line_breaker.cc b/third_party/WebKit/Source/core/layout/ng/inline/ng_line_breaker.cc |
index 0c8776b066dfe64117a4a239477a50cb66857c44..bcc114c5831e193b6f06e5d1ef77255ad114b69a 100644 |
--- a/third_party/WebKit/Source/core/layout/ng/inline/ng_line_breaker.cc |
+++ b/third_party/WebKit/Source/core/layout/ng/inline/ng_line_breaker.cc |
@@ -66,14 +66,9 @@ NGLineBreaker::NGLineBreaker( |
constraint_space_(space), |
container_builder_(container_builder), |
unpositioned_floats_(unpositioned_floats), |
- item_index_(0), |
- offset_(0), |
break_iterator_(node.Text()), |
shaper_(node.Text().Characters16(), node.Text().length()), |
- spacing_(node.Text()), |
- auto_wrap_(false), |
- should_create_line_box_(false), |
- is_after_forced_break_(false) { |
+ spacing_(node.Text()) { |
if (break_token) { |
item_index_ = break_token->ItemIndex(); |
offset_ = break_token->TextOffset(); |
@@ -99,9 +94,34 @@ bool NGLineBreaker::IsFirstFormattedLine() const { |
return true; |
} |
+// Initialize internal states for the next line. |
+void NGLineBreaker::PrepareNextLine(NGLineInfo* line_info) { |
+ NGInlineItemResults* item_results = &line_info->Results(); |
+ item_results->clear(); |
+ line_info->SetLineStyle(node_, *constraint_space_, IsFirstFormattedLine(), |
+ line_.is_after_forced_break); |
+ SetCurrentStyle(line_info->LineStyle()); |
+ line_.is_after_forced_break = false; |
+ line_.should_create_line_box = false; |
+ |
+ // Use 'text-indent' as the initial position. This lets tab positions to align |
+ // regardless of 'text-indent'. |
+ line_.position = line_info->TextIndent(); |
+ |
+ // We are only able to calculate our available_width if our container has |
+ // been positioned in the BFC coordinate space yet. |
+ if (container_builder_->BfcOffset()) |
+ UpdateAvailableWidth(); |
+ else |
+ line_.opportunity.reset(); |
+} |
+ |
bool NGLineBreaker::NextLine(NGLineInfo* line_info, |
const NGLogicalOffset& content_offset) { |
content_offset_ = content_offset; |
+ |
+ PrepareNextLine(line_info); |
+ |
BreakLine(line_info); |
// TODO(kojii): When editing, or caret is enabled, trailing spaces at wrap |
@@ -115,7 +135,7 @@ bool NGLineBreaker::NextLine(NGLineInfo* line_info, |
// TODO(kojii): There are cases where we need to PlaceItems() without creating |
// line boxes. These cases need to be reviewed. |
- if (should_create_line_box_) |
+ if (line_.should_create_line_box) |
ComputeLineLocation(line_info); |
return true; |
@@ -123,26 +143,9 @@ bool NGLineBreaker::NextLine(NGLineInfo* line_info, |
void NGLineBreaker::BreakLine(NGLineInfo* line_info) { |
NGInlineItemResults* item_results = &line_info->Results(); |
- item_results->clear(); |
const Vector<NGInlineItem>& items = node_.Items(); |
- line_info->SetLineStyle(node_, *constraint_space_, IsFirstFormattedLine(), |
- is_after_forced_break_); |
- SetCurrentStyle(line_info->LineStyle()); |
- is_after_forced_break_ = false; |
- should_create_line_box_ = false; |
LineBreakState state = LineBreakState::kNotBreakable; |
- // Use 'text-indent' as the initial position. This lets tab positions to align |
- // regardless of 'text-indent'. |
- position_ = line_info->TextIndent(); |
- |
- // We are only able to calculate our available_width if our container has |
- // been positioned in the BFC coordinate space yet. |
- if (container_builder_->BfcOffset()) |
- UpdateAvailableWidth(); |
- else |
- opportunity_.reset(); |
- |
while (item_index_ < items.size()) { |
// CloseTag prohibits to break before. |
const NGInlineItem& item = items[item_index_]; |
@@ -157,8 +160,8 @@ void NGLineBreaker::BreakLine(NGLineInfo* line_info) { |
line_info->SetIsLastLine(false); |
return; |
} |
- if (state == LineBreakState::kIsBreakable && HasAvailableWidth() && |
- position_ > AvailableWidth()) |
+ if (state == LineBreakState::kIsBreakable && line_.HasAvailableWidth() && |
+ !line_.CanFit()) |
return HandleOverflow(line_info); |
item_results->push_back( |
@@ -171,7 +174,7 @@ void NGLineBreaker::BreakLine(NGLineInfo* line_info) { |
} else if (item.Type() == NGInlineItem::kControl) { |
state = HandleControlItem(item, item_result); |
if (state == LineBreakState::kForcedBreak) { |
- is_after_forced_break_ = true; |
+ line_.is_after_forced_break = true; |
line_info->SetIsLastLine(true); |
return; |
} |
@@ -184,8 +187,8 @@ void NGLineBreaker::BreakLine(NGLineInfo* line_info) { |
MoveToNextOf(item); |
} |
} |
- if (state == LineBreakState::kIsBreakable && HasAvailableWidth() && |
- position_ > AvailableWidth()) |
+ if (state == LineBreakState::kIsBreakable && line_.HasAvailableWidth() && |
+ !line_.CanFit()) |
return HandleOverflow(line_info); |
line_info->SetIsLastLine(true); |
} |
@@ -193,21 +196,50 @@ void NGLineBreaker::BreakLine(NGLineInfo* line_info) { |
// Update the inline size of the first layout opportunity from the given |
// content_offset. |
void NGLineBreaker::UpdateAvailableWidth() { |
- NGLogicalOffset offset = container_builder_->BfcOffset().value(); |
- offset += content_offset_; |
+ const NGLogicalOffset& bfc_offset = container_builder_->BfcOffset().value(); |
+ NGLayoutOpportunityIterator iter(constraint_space_->Exclusions().get(), |
+ constraint_space_->AvailableSize(), |
+ bfc_offset + content_offset_); |
+ line_.opportunity = iter.Next(); |
+ line_.has_floats_or_exclusions = !iter.IsAtEnd(); |
ianjkilpatrick
2017/07/21 18:18:43
this variable name is a little misleading for me,
kojii
2017/07/23 07:01:10
No, this flag is about whether it has at least one
|
+ |
+ // 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. |
ianjkilpatrick
2017/07/21 18:18:43
so is this approaching it the wrong way?
Wouldn'
kojii
2017/07/23 07:01:10
I'm fine either way, I don't see much differences,
|
+ content_offset_.block_offset = |
+ line_.opportunity.value().BlockStartOffset() - bfc_offset.block_offset; |
+} |
+// When no break opportunities can fit in a line that has floats, such a line |
+// should be moved down below the floats. |
+// Given the minimum required inline size, this function finds such an |
+// opporunity and moves |content_offset_.block_offset| down. |
+void NGLineBreaker::MoveDownBelowFloats(LayoutUnit min_inline_size) { |
+ const NGLogicalOffset& bfc_offset = container_builder_->BfcOffset().value(); |
NGLayoutOpportunityIterator iter(constraint_space_->Exclusions().get(), |
- constraint_space_->AvailableSize(), offset); |
- opportunity_ = iter.Next(); |
+ constraint_space_->AvailableSize(), |
+ bfc_offset + content_offset_); |
+ while (true) { |
+ line_.opportunity = iter.Next(); |
+ if (iter.IsAtEnd() || |
+ line_.opportunity.value().InlineSize() >= min_inline_size) { |
+ line_.has_floats_or_exclusions = !iter.IsAtEnd(); |
+ content_offset_.block_offset = |
+ line_.opportunity.value().BlockStartOffset() - |
+ bfc_offset.block_offset; |
+ return; |
+ } |
+ } |
} |
void NGLineBreaker::ComputeLineLocation(NGLineInfo* line_info) const { |
// Both NGLayoutOpportunity and BfcOffset are in visual order that |
// "inline-start" are actually "line-left". |
// https://drafts.csswg.org/css-writing-modes-3/#line-left |
- LayoutUnit line_left = opportunity_.value().InlineStartOffset() - |
+ LayoutUnit line_left = line_.opportunity.value().InlineStartOffset() - |
constraint_space_->BfcOffset().inline_offset; |
- line_info->SetLineLocation(line_left, AvailableWidth(), |
+ line_info->SetLineLocation(line_left, line_.AvailableWidth(), |
content_offset_.block_offset); |
} |
@@ -223,17 +255,17 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleText( |
const NGInlineItem& item, |
NGInlineItemResult* item_result) { |
DCHECK_EQ(item.Type(), NGInlineItem::kText); |
- should_create_line_box_ = true; |
+ line_.should_create_line_box = true; |
- LayoutUnit available_width = AvailableWidth(); |
+ LayoutUnit available_width = line_.AvailableWidth(); |
// If the start offset is at the item boundary, try to add the entire item. |
if (offset_ == item.StartOffset()) { |
item_result->inline_size = item.InlineSize(); |
- LayoutUnit next_position = position_ + item_result->inline_size; |
+ LayoutUnit next_position = line_.position + item_result->inline_size; |
if (!auto_wrap_ || next_position <= available_width) { |
item_result->shape_result = item.TextShapeResult(); |
- position_ = next_position; |
+ line_.position = next_position; |
MoveToNextOf(item); |
if (auto_wrap_ && break_iterator_.IsBreakable(item.EndOffset())) |
return LineBreakState::kIsBreakable; |
@@ -244,8 +276,8 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleText( |
if (auto_wrap_) { |
// Try to break inside of this text item. |
- BreakText(item_result, item, available_width - position_); |
- LayoutUnit next_position = position_ + item_result->inline_size; |
+ BreakText(item_result, item, available_width - line_.position); |
+ LayoutUnit next_position = line_.position + item_result->inline_size; |
bool is_overflow = next_position > available_width; |
// If overflow and no break opportunities exist, and if 'break-word', try to |
@@ -254,13 +286,13 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleText( |
IsFirstBreakOpportunity(item_result->end_offset, results)) { |
DCHECK_EQ(break_iterator_.BreakType(), LineBreakType::kNormal); |
break_iterator_.SetBreakType(LineBreakType::kBreakCharacter); |
- BreakText(item_result, item, available_width - position_); |
+ BreakText(item_result, item, available_width - line_.position); |
break_iterator_.SetBreakType(LineBreakType::kNormal); |
- next_position = position_ + item_result->inline_size; |
+ next_position = line_.position + item_result->inline_size; |
is_overflow = next_position > available_width; |
} |
- position_ = next_position; |
+ line_.position = next_position; |
item_result->no_break_opportunities_inside = is_overflow; |
if (item_result->end_offset < item.EndOffset()) { |
offset_ = item_result->end_offset; |
@@ -280,7 +312,7 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleText( |
DCHECK_EQ(item_result->end_offset, item.EndOffset()); |
item_result->no_break_opportunities_inside = true; |
item_result->prohibit_break_after = true; |
- position_ += item_result->inline_size; |
+ line_.position += item_result->inline_size; |
MoveToNextOf(item); |
return LineBreakState::kNotBreakable; |
} |
@@ -333,7 +365,7 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleControlItem( |
const NGInlineItem& item, |
NGInlineItemResult* item_result) { |
DCHECK_EQ(item.Length(), 1u); |
- should_create_line_box_ = true; |
+ line_.should_create_line_box = true; |
UChar character = node_.Text()[item.StartOffset()]; |
if (character == kNewlineCharacter) { |
@@ -344,8 +376,8 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleControlItem( |
DCHECK(item.Style()); |
const ComputedStyle& style = *item.Style(); |
const Font& font = style.GetFont(); |
- item_result->inline_size = font.TabWidth(style.GetTabSize(), position_); |
- position_ += item_result->inline_size; |
+ item_result->inline_size = font.TabWidth(style.GetTabSize(), line_.position); |
+ line_.position += item_result->inline_size; |
MoveToNextOf(item); |
// TODO(kojii): Implement break around the tab character. |
return LineBreakState::kIsBreakable; |
@@ -356,7 +388,7 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleAtomicInline( |
NGInlineItemResult* item_result, |
const NGLineInfo& line_info) { |
DCHECK_EQ(item.Type(), NGInlineItem::kAtomicInline); |
- should_create_line_box_ = true; |
+ line_.should_create_line_box = true; |
LayoutBox* layout_box = ToLayoutBox(item.GetLayoutObject()); |
NGBlockNode node = NGBlockNode(layout_box); |
@@ -391,7 +423,7 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleAtomicInline( |
constraint_space_->WritingMode(), style.Direction()); |
item_result->inline_size += item_result->margins.InlineSum(); |
- position_ += item_result->inline_size; |
+ line_.position += item_result->inline_size; |
MoveToNextOf(item); |
if (auto_wrap_) |
return LineBreakState::kIsBreakable; |
@@ -417,6 +449,18 @@ NGLineBreaker::LineBreakState NGLineBreaker::HandleAtomicInline( |
// TODO(glebl): Add the support of clearance for inline floats. |
void NGLineBreaker::HandleFloat(const NGInlineItem& item, |
NGInlineItemResults* item_results) { |
+ // When rewind occurs, an item may be handled multiple times. |
ianjkilpatrick
2017/07/21 18:18:43
so, i don't think we want to be able to rewind pas
kojii
2017/07/23 07:01:09
I'm not following this...we already don't position
|
+ // Since floats are put into a separate list, avoid handling same floats |
+ // twice. |
+ // Ideally rewind can take floats out of floats list, but the difference is |
+ // sutble compared to the complexity. |
+ if (item_index_ < handled_floats_end_item_index_) { |
+ item_results->pop_back(); |
+ MoveToNextOf(item); |
+ return; |
+ } |
+ handled_floats_end_item_index_ = item_index_ + 1; |
+ |
NGBlockNode node(ToLayoutBox(item.GetLayoutObject())); |
const ComputedStyle& float_style = node.Style(); |
@@ -439,11 +483,10 @@ void NGLineBreaker::HandleFloat(const NGInlineItem& item, |
// 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 = |
- (!constraint_space_->FloatsBfcOffset() || |
- container_builder_->BfcOffset()) && |
- (!HasAvailableWidth() || |
- position_ + inline_size + margins.InlineSum() > AvailableWidth()); |
+ bool float_does_not_fit = (!constraint_space_->FloatsBfcOffset() || |
+ container_builder_->BfcOffset()) && |
+ (!line_.HasAvailableWidth() || |
+ !line_.CanFit(inline_size + margins.InlineSum())); |
// Check if we already have a pending float. That's because a float cannot be |
// higher than any block or floated box generated before. |
@@ -493,7 +536,7 @@ void NGLineBreaker::HandleOpenTag(const NGInlineItem& item, |
constraint_space_->Direction()); |
item_result->inline_size = item_result->margins.inline_start + |
borders.inline_start + paddings.inline_start; |
- position_ += item_result->inline_size; |
+ line_.position += item_result->inline_size; |
// While the spec defines "non-zero margins, padding, or borders" prevents |
// line boxes to be zero-height, tests indicate that only inline direction |
@@ -501,7 +544,7 @@ void NGLineBreaker::HandleOpenTag(const NGInlineItem& item, |
// Force to create a box, because such inline boxes affect line heights. |
item_result->needs_box_when_empty = |
item_result->inline_size || item_result->margins.inline_start; |
- should_create_line_box_ |= item_result->needs_box_when_empty; |
+ line_.should_create_line_box |= item_result->needs_box_when_empty; |
} |
} |
SetCurrentStyle(style); |
@@ -521,11 +564,11 @@ void NGLineBreaker::HandleCloseTag(const NGInlineItem& item, |
NGBoxStrut paddings = ComputePadding(*constraint_space_, style); |
item_result->inline_size = item_result->margins.inline_end + |
borders.inline_end + paddings.inline_end; |
- position_ += item_result->inline_size; |
+ line_.position += item_result->inline_size; |
item_result->needs_box_when_empty = |
item_result->inline_size || item_result->margins.inline_end; |
- should_create_line_box_ |= item_result->needs_box_when_empty; |
+ line_.should_create_line_box |= item_result->needs_box_when_empty; |
} |
DCHECK(item.GetLayoutObject() && item.GetLayoutObject()->Parent()); |
SetCurrentStyle(item.GetLayoutObject()->Parent()->StyleRef()); |
@@ -538,8 +581,8 @@ void NGLineBreaker::HandleCloseTag(const NGInlineItem& item, |
void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) { |
NGInlineItemResults* item_results = &line_info->Results(); |
const Vector<NGInlineItem>& items = node_.Items(); |
- LayoutUnit available_width = AvailableWidth(); |
- LayoutUnit rewind_width = available_width - position_; |
+ LayoutUnit available_width = line_.AvailableWidth(); |
+ LayoutUnit rewind_width = available_width - line_.position; |
DCHECK_LT(rewind_width, 0); |
// Search for a break opportunity that can fit. |
@@ -595,7 +638,22 @@ void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) { |
} |
} |
- // The rewind point did not found, let this line overflow. |
+ // Reaching here means that the rewind point was not found. |
+ |
+ // If the current line has floats or exclusions, push this line down below the |
+ // floats/exclusions. |
+ if (line_.has_floats_or_exclusions) { |
+ MoveDownBelowFloats(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); |
+ 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) |
return Rewind(line_info, break_before); |
@@ -605,14 +663,25 @@ void NGLineBreaker::HandleOverflow(NGLineInfo* line_info) { |
void NGLineBreaker::Rewind(NGLineInfo* line_info, unsigned new_end) { |
NGInlineItemResults* item_results = &line_info->Results(); |
+ DCHECK_LT(new_end, item_results->size()); |
+ |
+ if (new_end) { |
+ // Use |results[new_end - 1].end_offset| because it may have been truncated |
+ // and may not be equal to |results[new_end].start_offset|. |
+ MoveToNextOf((*item_results)[new_end - 1]); |
+ } else { |
+ // When rewinding all items, use |results[0].start_offset|. |
+ const NGInlineItemResult& first_remove = (*item_results)[new_end]; |
+ item_index_ = first_remove.item_index; |
+ offset_ = first_remove.start_offset; |
+ } |
+ |
// 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); |
- MoveToNextOf(item_results->back()); |
- DCHECK_LT(item_index_, node_.Items().size()); |
line_info->SetIsLastLine(false); |
} |