Commit 362b9e1a authored by Emil A Eklund's avatar Emil A Eklund Committed by Commit Bot

[LayoutNG] Fix O(N^2) in NGInlineNode::ShapeText

When shaping the content of an inline node in NGInlineNode::ShapeText we
assume that the number of runs is small and iterate over all of the runs
for each inline item. This assumption holds true for most content, there
are certain cases where the assumption is incorrect however, such as the
euckr-decode-ksc_5601.html WPT test where each CJK glyph is wrapped in a
span and separated by space characters of a different glyph. This causes
each of the 10k+ items to iterate over 10+ runs which is very expensive.

This patch adds an optional context argument to ShapeResult::SubRange so
that context (the relevant run) may be maintained across invocations and
thereby avoids an extra loop over all of the runs for each SubRange call.

Change-Id: I2ee610a60e33fe4389277fc275a9b8d670453a31
Reviewed-on: https://chromium-review.googlesource.com/c/1354561Reviewed-by: default avatarAdenilson Cavalcanti <cavalcantii@chromium.org>
Commit-Queue: Emil A Eklund <eae@chromium.org>
Cr-Commit-Position: refs/heads/master@{#612403}
parent 96ea4700
......@@ -597,6 +597,7 @@ void NGInlineNode::ShapeText(const String& text_content,
// If the text is from multiple items, split the ShapeResult to
// corresponding items.
unsigned opaque_context = 0;
for (; index < end_index; index++) {
NGInlineItem& item = (*items)[index];
if (item.Type() != NGInlineItem::kText)
......@@ -608,8 +609,8 @@ void NGInlineNode::ShapeText(const String& text_content,
//
// When multiple code units shape to one glyph, such as ligatures, the
// item that has its first code unit keeps the glyph.
item.shape_result_ =
shape_result->SubRange(item.StartOffset(), item.EndOffset());
item.shape_result_ = shape_result->SubRange(
item.StartOffset(), item.EndOffset(), &opaque_context);
}
}
}
......
......@@ -1292,7 +1292,8 @@ float ShapeResult::LineRightBounds() const {
void ShapeResult::CopyRange(unsigned start_offset,
unsigned end_offset,
ShapeResult* target) const {
ShapeResult* target,
unsigned* start_run_index) const {
if (!runs_.size())
return;
......@@ -1308,7 +1309,9 @@ void ShapeResult::CopyRange(unsigned start_offset,
: target->EndIndex() - std::max(start_offset, StartIndex());
unsigned target_run_size_before = target->runs_.size();
float total_width = 0;
for (const auto& run : runs_) {
unsigned run_index = start_run_index ? *start_run_index : 0;
for (; run_index < runs_.size(); run_index++) {
const auto& run = runs_[run_index];
unsigned run_start = run->start_index_;
unsigned run_end = run_start + run->num_characters_;
......@@ -1323,6 +1326,14 @@ void ShapeResult::CopyRange(unsigned start_offset,
target->num_characters_ += sub_run->num_characters_;
target->num_glyphs_ += sub_run->glyph_data_.size();
target->runs_.push_back(std::move(sub_run));
// No need to process runs after the end of the range.
if ((!Rtl() && end_offset <= run_end) ||
(Rtl() && start_offset > run_start)) {
if (start_run_index)
*start_run_index = run_index;
break;
}
}
}
......@@ -1370,11 +1381,13 @@ void ShapeResult::CopyRange(unsigned start_offset,
#endif
}
scoped_refptr<ShapeResult> ShapeResult::SubRange(unsigned start_offset,
unsigned end_offset) const {
scoped_refptr<ShapeResult> ShapeResult::SubRange(
unsigned start_offset,
unsigned end_offset,
unsigned* start_run_index) const {
scoped_refptr<ShapeResult> sub_range =
Create(primary_font_.get(), 0, Direction());
CopyRange(start_offset, end_offset, sub_range.get());
CopyRange(start_offset, end_offset, sub_range.get(), start_run_index);
return sub_range;
}
......
......@@ -238,11 +238,27 @@ class PLATFORM_EXPORT ShapeResult : public RefCounted<ShapeResult> {
const TextRun&) const;
// Append a copy of a range within an existing result to another result.
void CopyRange(unsigned start, unsigned end, ShapeResult*) const;
//
// For sequential copies the opaque_context in/out parameter can be used to
// improve performance by avoding a linear scan to find the first run for the
// range. It should be set to zero for the first call and the resulting out
// value for one call is the appropiate input value for the next.
// NOTE: opaque_context assumes non-overlapping ranges.
void CopyRange(unsigned start,
unsigned end,
ShapeResult*,
unsigned* opaque_context = nullptr) const;
// Create a new ShapeResult instance from a range within an existing result.
//
// For sequential copies the opaque_context in/out parameter can be used to
// improve performance by avoding a linear scan to find the first run for the
// range. It should be set to zero for the first call and the resulting out
// value for one call is the appropiate input value for the next.
// NOTE: opaque_context assumes non-overlapping ranges.
scoped_refptr<ShapeResult> SubRange(unsigned start_offset,
unsigned end_offset) const;
unsigned end_offset,
unsigned* opaque_context = nullptr) const;
// Create a new ShapeResult instance with the start offset adjusted.
scoped_refptr<ShapeResult> CopyAdjustedOffset(unsigned start_offset) 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