Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(73)

Unified Diff: third_party/WebKit/Source/core/layout/ng/inline/ng_line_breaker.cc

Issue 2871733003: [LayoutNG] Refactor NGLineBreaker for ShapingLineBreaker (Closed)
Patch Set: Cleanup Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « third_party/WebKit/Source/core/layout/ng/inline/ng_line_breaker.h ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 c922def3ee83bb50eab69d404af2a44f84af82a7..7cadf42c80ba54a217a2467542e15aa34e2a4a4a 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
@@ -4,6 +4,7 @@
#include "core/layout/ng/inline/ng_line_breaker.h"
+#include "core/layout/ng/inline/ng_inline_break_token.h"
#include "core/layout/ng/inline/ng_inline_layout_algorithm.h"
#include "core/layout/ng/inline/ng_inline_node.h"
#include "core/layout/ng/inline/ng_text_fragment.h"
@@ -13,85 +14,312 @@
#include "core/layout/ng/ng_fragment_builder.h"
#include "core/layout/ng/ng_layout_opportunity_iterator.h"
#include "core/style/ComputedStyle.h"
+#include "platform/fonts/shaping/HarfBuzzShaper.h"
+#include "platform/fonts/shaping/ShapingLineBreaker.h"
#include "platform/text/TextBreakIterator.h"
namespace blink {
-static bool IsHangable(UChar ch) {
- return ch == ' ';
-}
+namespace {
-void NGLineBreaker::BreakLines(NGInlineLayoutAlgorithm* algorithm,
- const String& text_content,
- unsigned current_offset) {
- DCHECK(!text_content.IsEmpty());
- LazyLineBreakIterator line_break_iterator(text_content, locale_);
- const unsigned end_offset = text_content.length();
- while (current_offset < end_offset) {
- // Find the next break opportunity.
- int tmp_next_breakable_offset = -1;
- line_break_iterator.IsBreakable(current_offset + 1,
- tmp_next_breakable_offset);
- current_offset =
- tmp_next_breakable_offset >= 0 ? tmp_next_breakable_offset : end_offset;
- DCHECK_LE(current_offset, end_offset);
-
- // Advance the break opportunity to the end of hangable characters; e.g.,
- // spaces.
- // Unlike the ICU line breaker, LazyLineBreakIterator breaks before
- // breakable spaces, and expect the line breaker to handle spaces
- // differently. This logic computes in the ICU way; break after spaces, and
- // handle spaces as hangable characters.
- unsigned start_of_hangables = current_offset;
- while (current_offset < end_offset &&
- IsHangable(text_content[current_offset]))
- current_offset++;
-
- // Set the end to the next break opportunity.
- algorithm->SetEnd(current_offset);
-
- // If there are more available spaces, mark the break opportunity and fetch
- // more text.
- // TODO(layout-ng): check if the height of the linebox can fit within
- // the current opportunity.
- if (algorithm->CanFitOnLine()) {
- algorithm->SetBreakOpportunity();
- continue;
+// Use a mock of ShapingLineBreaker for test/debug purposes.
+#define MOCK_SHAPE_LINE
+
+#if defined(MOCK_SHAPE_LINE)
+// The mock for ShapingLineBreaker::ShapeLine().
+// Given the design of ShapingLineBreaker, expected semantics are:
+// - The returned offset is always > item.StartOffset().
+// - offset < item.EndOffset():
+// - width <= available_width: the break opportunity to fit is found.
+// - width > available_width: the first break opportunity did not fit.
+// - offset == item.EndOffset():
+// - width <= available_width: the break opportunity at the end of the item
+// fits.
+// - width > available_width: the first break opportunity is at the end of
+// the item and it does not fit.
+// - offset > item.EndOffset():, the first break opportunity is beyond the
+// end of item and thus cannot measure. In this case, inline_size shows the
+// width until the end of the item. It may fit or may not.
+std::pair<unsigned, LayoutUnit> ShapeLineMock(
+ const NGInlineItem& item,
+ unsigned offset,
+ LayoutUnit available_width,
+ const LazyLineBreakIterator& break_iterator) {
+ bool has_break_opportunities = false;
+ LayoutUnit inline_size;
+ while (true) {
+ unsigned next_break = break_iterator.NextBreakOpportunity(offset + 1);
+ LayoutUnit next_inline_size =
+ inline_size +
+ item.InlineSize(offset, std::min(next_break, item.EndOffset()));
+ if (next_inline_size > available_width) {
+ if (!has_break_opportunities)
+ return std::make_pair(next_break, next_inline_size);
+ return std::make_pair(offset, inline_size);
}
+ if (next_break >= item.EndOffset())
+ return std::make_pair(next_break, next_inline_size);
+ offset = next_break;
+ inline_size = next_inline_size;
+ has_break_opportunities = true;
+ }
+}
+#endif
+
+LineBreakType GetLineBreakType(const ComputedStyle& style) {
+ if (style.AutoWrap()) {
+ if (style.WordBreak() == kBreakAllWordBreak ||
+ style.WordBreak() == kBreakWordBreak)
+ return LineBreakType::kBreakAll;
+ if (style.WordBreak() == kKeepAllWordBreak)
+ return LineBreakType::kKeepAll;
+ }
+ return LineBreakType::kNormal;
+}
+
+} // namespace
+
+NGLineBreaker::NGLineBreaker(NGInlineNode* node,
+ const NGConstraintSpace* space,
+ NGInlineBreakToken* break_token)
+ : node_(node), constraint_space_(space), item_index_(0), offset_(0) {
+ if (break_token) {
+ item_index_ = break_token->ItemIndex();
+ offset_ = break_token->TextOffset();
+ node->AssertOffset(item_index_, offset_);
+ }
+}
- // Compute hangable characters if exists.
- if (current_offset != start_of_hangables) {
- algorithm->SetStartOfHangables(start_of_hangables);
- // If text before hangables can fit, include it in the current line.
- if (algorithm->CanFitOnLine())
- algorithm->SetBreakOpportunity();
+void NGLineBreaker::NextLine(NGInlineItemResults* item_results,
+ NGInlineLayoutAlgorithm* algorithm) {
+ BreakLine(item_results, algorithm);
+
+ // TODO(kojii): When editing, or caret is enabled, trailing spaces at wrap
+ // point should not be removed. For other cases, we can a) remove, b) leave
+ // characters without glyphs, or c) leave both characters and glyphs without
+ // measuring. Need to decide which one works the best.
+ SkipCollapsibleWhitespaces();
+}
+
+void NGLineBreaker::BreakLine(NGInlineItemResults* item_results,
+ NGInlineLayoutAlgorithm* algorithm) {
+ DCHECK(item_results->IsEmpty());
+ const Vector<NGInlineItem>& items = node_->Items();
+ const String& text = node_->Text();
+ const ComputedStyle& style = node_->Style();
+ LazyLineBreakIterator break_iterator(text, style.LocaleForLineBreakIterator(),
+ GetLineBreakType(style));
+#if !defined(MOCK_SHAPE_LINE)
+ HarfBuzzShaper shaper(text.Characters16(), text.length());
+#endif
+ LayoutUnit available_width = algorithm->AvailableWidth();
+ LayoutUnit position;
+ while (item_index_ < items.size()) {
+ const NGInlineItem& item = items[item_index_];
+ item_results->push_back(
+ NGInlineItemResult(item_index_, offset_, item.EndOffset()));
+ NGInlineItemResult* item_result = &item_results->back();
+
+ // If the start offset is at the item boundary, try to add the entire item.
+ if (offset_ == item.StartOffset()) {
+ if (item.Type() == NGInlineItem::kText) {
+ item_result->inline_size = item.InlineSize();
+ } else if (item.Type() == NGInlineItem::kAtomicInline) {
+ LayoutAtomicInline(item, item_result);
+ } else if (item.Type() == NGInlineItem::kFloating) {
+ algorithm->LayoutAndPositionFloat(position, item.GetLayoutObject());
+ // Floats may change the available width if they fit.
+ available_width = algorithm->AvailableWidth();
+ // Floats are already positioned in the container_builder.
+ item_results->pop_back();
+ offset_ = item.EndOffset();
+ item_index_++;
+ continue;
+ } else {
+ offset_ = item.EndOffset();
+ item_index_++;
+ continue;
+ }
+ LayoutUnit next_position = position + item_result->inline_size;
+ if (next_position <= available_width) {
+ offset_ = item.EndOffset();
+ item_index_++;
+ position = next_position;
+ continue;
+ }
+
+ // The entire item does not fit. Handle non-text items as overflow,
+ // since only text item is breakable.
+ if (item.Type() != NGInlineItem::kText) {
+ offset_ = item.EndOffset();
+ item_index_++;
+ return HandleOverflow(item_results, break_iterator);
+ }
}
- if (!algorithm->HasBreakOpportunity()) {
- // The first word (break opportunity) did not fit on the line.
- // Create a line including items that don't fit, allowing them to
- // overflow.
- if (!algorithm->CreateLine())
- return;
+ // Either the start or the break is in the mid of a text item.
+ DCHECK_EQ(item.Type(), NGInlineItem::kText);
+ DCHECK_LT(offset_, item.EndOffset());
+ break_iterator.SetLocale(item.Style()->LocaleForLineBreakIterator());
+ break_iterator.SetBreakType(GetLineBreakType(*item.Style()));
+#if defined(MOCK_SHAPE_LINE)
+ unsigned break_offset;
+ std::tie(break_offset, item_result->inline_size) = ShapeLineMock(
+ item, offset_, available_width - position, break_iterator);
+#else
+ // TODO(kojii): We need to instantiate ShapingLineBreaker here because it
+ // has item-specific info as context. Should they be part of ShapeLine() to
+ // instantiate once, or is this just fine since instatiation is not
+ // expensive?
+ DCHECK_EQ(item.TextShapeResult()->StartIndexForResult(),
+ item.StartOffset());
+ DCHECK_EQ(item.TextShapeResult()->EndIndexForResult(), item.EndOffset());
+ ShapingLineBreaker breaker(&shaper, &item.Style()->GetFont(),
+ item.TextShapeResult(), &break_iterator);
+ unsigned break_offset;
+ item_result->shape_result =
+ breaker.ShapeLine(offset_, available_width - position, &break_offset);
+ item_result->inline_size = item_result->shape_result->SnappedWidth();
+#endif
+ DCHECK_GT(break_offset, offset_);
+ position += item_result->inline_size;
+
+ // If the break found within the item, break here.
+ if (break_offset < item.EndOffset()) {
+ offset_ = item_result->end_offset = break_offset;
+ if (position <= available_width)
+ break;
+ // The first break opportunity of the item does not fit.
} else {
- if (!algorithm->CreateLineUpToLastBreakOpportunity())
- return;
-
- // Items after the last break opportunity were sent to the next line.
- // Set the break opportunity, or create a line if the word doesn't fit.
- if (algorithm->HasItems()) {
- if (algorithm->CanFitOnLine())
- algorithm->SetBreakOpportunity();
- else if (!algorithm->CreateLine())
- return;
- }
+ // No break opporunity in the item, or the first break opportunity is at
+ // the end of the item. If it fits, continue to the next item.
+ offset_ = item_result->end_offset = item.EndOffset();
+ item_index_++;
+ if (position <= available_width)
+ continue;
+ }
+
+ // We need to look at next item if we're overflowing, and the break
+ // opportunity is beyond this item.
+ if (break_offset > item.EndOffset())
+ continue;
+ return HandleOverflow(item_results, break_iterator);
+ }
+}
+
+void NGLineBreaker::LayoutAtomicInline(const NGInlineItem& item,
+ NGInlineItemResult* item_result) {
+ DCHECK_EQ(item.Type(), NGInlineItem::kAtomicInline);
+ NGBlockNode* node = new NGBlockNode(item.GetLayoutObject());
+ const ComputedStyle& style = node->Style();
+ NGConstraintSpaceBuilder constraint_space_builder(constraint_space_);
+ RefPtr<NGConstraintSpace> constraint_space =
+ constraint_space_builder.SetIsNewFormattingContext(true)
+ .SetIsShrinkToFit(true)
+ .SetTextDirection(style.Direction())
+ .ToConstraintSpace(FromPlatformWritingMode(style.GetWritingMode()));
+ item_result->layout_result = node->Layout(constraint_space.Get());
+
+ item_result->inline_size =
+ NGBoxFragment(constraint_space_->WritingMode(),
+ ToNGPhysicalBoxFragment(
+ item_result->layout_result->PhysicalFragment().Get()))
+ .InlineSize();
+}
+
+// Handles when the last item overflows.
+// At this point, item_results does not fit into the current line, and there
+// are no break opportunities in item_results.back().
+void NGLineBreaker::HandleOverflow(
+ NGInlineItemResults* item_results,
+ const LazyLineBreakIterator& break_iterator) {
+ DCHECK_GT(offset_, 0u);
+
+ // Find the last break opportunity. If none, let this line overflow.
+ unsigned line_start_offset = item_results->front().start_offset;
+ unsigned break_offset =
+ break_iterator.PreviousBreakOpportunity(offset_ - 1, line_start_offset);
+ if (!break_offset || break_offset <= line_start_offset) {
+ AppendCloseTags(item_results);
+ return;
+ }
+
+ // Truncate the end of the line to the break opportunity.
+ const Vector<NGInlineItem>& items = node_->Items();
+ unsigned new_end = item_results->size();
+ while (true) {
+ NGInlineItemResult* item_result = &(*item_results)[--new_end];
+ if (item_result->start_offset < break_offset) {
+ // The break is at the mid of the item. Adjust the end_offset to the new
+ // break offset.
+ const NGInlineItem& item = items[item_result->item_index];
+ item.AssertEndOffset(break_offset);
+ DCHECK_EQ(item.Type(), NGInlineItem::kText);
+ DCHECK_NE(item_result->end_offset, break_offset);
+ item_result->end_offset = break_offset;
+ item_result->inline_size =
+ item.InlineSize(item_result->start_offset, item_result->end_offset);
+ // TODO(kojii): May need to reshape. Add to ShapingLineBreaker?
+ new_end++;
+ break;
+ }
+ if (item_result->start_offset == break_offset) {
+ // The new break offset is at the item boundary. Remove items up to the
+ // new break offset.
+ // TODO(kojii): Remove open tags as well.
+ break;
}
}
+ DCHECK_GT(new_end, 0u);
+
+ // 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);
+
+ // Update the current item index and offset to the new break point.
+ const NGInlineItemResult& last_item_result = item_results->back();
+ offset_ = last_item_result.end_offset;
+ item_index_ = last_item_result.item_index;
+ if (items[item_index_].EndOffset() == offset_)
+ item_index_++;
+}
+
+void NGLineBreaker::SkipCollapsibleWhitespaces() {
+ const Vector<NGInlineItem>& items = node_->Items();
+ if (item_index_ >= items.size())
+ return;
+ const NGInlineItem& item = items[item_index_];
+ if (item.Type() != NGInlineItem::kText || !item.Style()->CollapseWhiteSpace())
+ return;
+
+ DCHECK_LT(offset_, item.EndOffset());
+ if (node_->Text()[offset_] == kSpaceCharacter) {
+ // Skip one whitespace. Collapsible spaces are collapsed to single space in
+ // NGInlineItemBuilder, so this removes all collapsible spaces.
+ offset_++;
+ if (offset_ == item.EndOffset())
+ item_index_++;
+ }
+}
+
+void NGLineBreaker::AppendCloseTags(NGInlineItemResults* item_results) {
+ const Vector<NGInlineItem>& items = node_->Items();
+ for (; item_index_ < items.size(); item_index_++) {
+ const NGInlineItem& item = items[item_index_];
+ if (item.Type() != NGInlineItem::kCloseTag)
+ break;
+ DCHECK_EQ(offset_, item.EndOffset());
+ item_results->push_back(NGInlineItemResult(item_index_, offset_, offset_));
+ }
+}
- // If inline children ended with items left in the line builder, create a line
- // for them.
- if (algorithm->HasItems())
- algorithm->CreateLine();
+RefPtr<NGInlineBreakToken> NGLineBreaker::CreateBreakToken() const {
+ const Vector<NGInlineItem>& items = node_->Items();
+ if (item_index_ >= items.size())
+ return nullptr;
+ return NGInlineBreakToken::Create(node_, item_index_, offset_);
}
} // namespace blink
« no previous file with comments | « third_party/WebKit/Source/core/layout/ng/inline/ng_line_breaker.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698