Chromium Code Reviews| 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); |
| } |