Commit 0c006dc4 authored by Emil A Eklund's avatar Emil A Eklund Committed by Commit Bot

Optimize line breaking by limiting scanning

The LazyLineBreakIterator::IsBreakable method works by comparing a given
position with the next following breakable position. This is inefficient
as well as unnecessary. Instead limit the scan to the given position + 1
as that still allows comparsion of the supplied and the resulting value.

Similarly, the LazyLineBreakIterator::NextBreakOpportunity method always
scans until the end of the string, even if that is past the threshold of
the section of the string processed. By introducing a version that takes
a length parameter, and changing ShapingLineBreaker::ShapeLine to use it
instead of the unbounded one, the scope of the forward scan can therefor
be limited to the length of the line being processed.

These two changes dramatically speed up line breaking both in legacy and
LayoutNG, especially for large text blocks with few break opportunities.

Test: perf_tests/paint/appending-text.html, perf_tests/layout/long-line-nowrap.html

Cq-Include-Trybots: luci.chromium.try:linux_layout_tests_layout_ng
Change-Id: I7a4186510f4451219dd026e8f3ab1ed0c86fa3c2
Reviewed-on: https://chromium-review.googlesource.com/1150853Reviewed-by: default avatarStefan Zager <szager@chromium.org>
Commit-Queue: Emil A Eklund <eae@chromium.org>
Cr-Commit-Position: refs/heads/master@{#578437}
parent 2ae89dce
...@@ -158,7 +158,8 @@ ShapingLineBreaker::PreviousBreakOpportunity(unsigned offset, ...@@ -158,7 +158,8 @@ ShapingLineBreaker::PreviousBreakOpportunity(unsigned offset,
ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::NextBreakOpportunity( ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::NextBreakOpportunity(
unsigned offset, unsigned offset,
unsigned start) const { unsigned start,
unsigned len) const {
if (UNLIKELY(!IsSoftHyphenEnabled())) { if (UNLIKELY(!IsSoftHyphenEnabled())) {
const String& text = GetText(); const String& text = GetText();
for (;; offset++) { for (;; offset++) {
...@@ -171,7 +172,7 @@ ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::NextBreakOpportunity( ...@@ -171,7 +172,7 @@ ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::NextBreakOpportunity(
if (UNLIKELY(hyphenation_)) if (UNLIKELY(hyphenation_))
return Hyphenate(offset, start, false); return Hyphenate(offset, start, false);
return {break_iterator_->NextBreakOpportunity(offset), false}; return {break_iterator_->NextBreakOpportunity(offset, len), false};
} }
inline scoped_refptr<ShapeResult> ShapingLineBreaker::Shape(TextDirection direction, inline scoped_refptr<ShapeResult> ShapingLineBreaker::Shape(TextDirection direction,
...@@ -267,8 +268,9 @@ scoped_refptr<const ShapeResult> ShapingLineBreaker::ShapeLine( ...@@ -267,8 +268,9 @@ scoped_refptr<const ShapeResult> ShapingLineBreaker::ShapeLine(
if (is_overflow) { if (is_overflow) {
if (options & kNoResultIfOverflow) if (options & kNoResultIfOverflow)
return nullptr; return nullptr;
break_opportunity = // No need to scan past range_end for a break oppertunity.
NextBreakOpportunity(std::max(candidate_break, start + 1), start); break_opportunity = NextBreakOpportunity(
std::max(candidate_break, start + 1), start, range_end);
// |range_end| may not be a break opportunity, but this function cannot // |range_end| may not be a break opportunity, but this function cannot
// measure beyond it. // measure beyond it.
if (break_opportunity.offset >= range_end) { if (break_opportunity.offset >= range_end) {
......
...@@ -102,7 +102,9 @@ class PLATFORM_EXPORT ShapingLineBreaker final { ...@@ -102,7 +102,9 @@ class PLATFORM_EXPORT ShapingLineBreaker final {
}; };
BreakOpportunity PreviousBreakOpportunity(unsigned offset, BreakOpportunity PreviousBreakOpportunity(unsigned offset,
unsigned start) const; unsigned start) const;
BreakOpportunity NextBreakOpportunity(unsigned offset, unsigned start) const; BreakOpportunity NextBreakOpportunity(unsigned offset,
unsigned start,
unsigned len) const;
BreakOpportunity Hyphenate(unsigned offset, BreakOpportunity Hyphenate(unsigned offset,
unsigned start, unsigned start,
bool backwards) const; bool backwards) const;
......
...@@ -48,7 +48,8 @@ class ShapingLineBreakerTest : public testing::Test { ...@@ -48,7 +48,8 @@ class ShapingLineBreakerTest : public testing::Test {
const String& string) { const String& string) {
Vector<unsigned> break_positions; Vector<unsigned> break_positions;
for (unsigned i = 0; i <= string.length(); i++) { for (unsigned i = 0; i <= string.length(); i++) {
unsigned next = breaker.NextBreakOpportunity(i, 0).offset; unsigned next =
breaker.NextBreakOpportunity(i, 0, string.length()).offset;
if (break_positions.IsEmpty() || break_positions.back() != next) if (break_positions.IsEmpty() || break_positions.back() != next)
break_positions.push_back(next); break_positions.push_back(next);
} }
......
...@@ -446,6 +446,14 @@ unsigned LazyLineBreakIterator::NextBreakOpportunity(unsigned offset) const { ...@@ -446,6 +446,14 @@ unsigned LazyLineBreakIterator::NextBreakOpportunity(unsigned offset) const {
return next_break; return next_break;
} }
unsigned LazyLineBreakIterator::NextBreakOpportunity(unsigned offset,
unsigned len) const {
DCHECK_LE(len, string_.length());
int next_break = NextBreakablePosition(offset, break_type_, len);
DCHECK_GE(next_break, 0);
return next_break;
}
unsigned LazyLineBreakIterator::PreviousBreakOpportunity(unsigned offset, unsigned LazyLineBreakIterator::PreviousBreakOpportunity(unsigned offset,
unsigned min) const { unsigned min) const {
unsigned pos = std::min(offset, string_.length()); unsigned pos = std::min(offset, string_.length());
......
...@@ -254,12 +254,19 @@ class PLATFORM_EXPORT LazyLineBreakIterator final { ...@@ -254,12 +254,19 @@ class PLATFORM_EXPORT LazyLineBreakIterator final {
} }
inline bool IsBreakable(int pos) const { inline bool IsBreakable(int pos) const {
int next_breakable = -1; // No need to scan the entire string for the next breakable position when
return IsBreakable(pos, next_breakable, break_type_); // all we need to determine is whether the current position is breakable.
// Limit length to pos + 1.
// TODO(layout-dev): We should probably try to break out an actual
// IsBreakable method from NextBreakablePosition and get rid of this hack.
int len = std::min(pos + 1, static_cast<int>(string_.length()));
int next_breakable = NextBreakablePosition(pos, break_type_, len);
return pos == next_breakable;
} }
// Returns the break opportunity at or after |offset|. // Returns the break opportunity at or after |offset|.
unsigned NextBreakOpportunity(unsigned offset) const; unsigned NextBreakOpportunity(unsigned offset) const;
unsigned NextBreakOpportunity(unsigned offset, unsigned len) const;
// Returns the break opportunity at or before |offset|. // Returns the break opportunity at or before |offset|.
unsigned PreviousBreakOpportunity(unsigned offset, unsigned min = 0) const; unsigned PreviousBreakOpportunity(unsigned offset, unsigned min = 0) 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