diff options
Diffstat (limited to 'chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc')
-rw-r--r-- | chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc | 243 |
1 files changed, 132 insertions, 111 deletions
diff --git a/chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc b/chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc index 6454534bcc3..a7cd5af2461 100644 --- a/chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc +++ b/chromium/third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.cc @@ -5,7 +5,7 @@ #include "third_party/blink/renderer/platform/fonts/shaping/shaping_line_breaker.h" #include "third_party/blink/renderer/platform/fonts/font.h" -#include "third_party/blink/renderer/platform/fonts/shaping/harf_buzz_shaper.h" +#include "third_party/blink/renderer/platform/fonts/shaping/harfbuzz_shaper.h" #include "third_party/blink/renderer/platform/fonts/shaping/shape_result.h" #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_inline_headers.h" #include "third_party/blink/renderer/platform/fonts/shaping/shape_result_spacing.h" @@ -16,13 +16,15 @@ namespace blink { ShapingLineBreaker::ShapingLineBreaker( const HarfBuzzShaper* shaper, const Font* font, - const ShapeResult* result, + scoped_refptr<const ShapeResult> result, const LazyLineBreakIterator* break_iterator, + const RunSegmenter::RunSegmenterRange* pre_segmented, ShapeResultSpacing<String>* spacing, const Hyphenation* hyphenation) : shaper_(shaper), font_(font), result_(result), + pre_segmented_(pre_segmented), break_iterator_(break_iterator), spacing_(spacing), hyphenation_(hyphenation), @@ -30,6 +32,11 @@ ShapingLineBreaker::ShapingLineBreaker( // ShapeResultSpacing is stateful when it has expansions. We may use it in // arbitrary order that it cannot have expansions. DCHECK(!spacing_ || !spacing_->HasExpansion()); + + // Line breaking performance relies on high-performance x-position to + // character offset lookup. Ensure that the desired cache has been computed. + DCHECK(result_); + result_->EnsurePositionData(); } namespace { @@ -103,16 +110,15 @@ unsigned ShapingLineBreaker::Hyphenate(unsigned offset, } } -unsigned ShapingLineBreaker::Hyphenate(unsigned offset, - unsigned start, - bool backwards, - bool* is_hyphenated) const { - DCHECK(is_hyphenated && !*is_hyphenated); +ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::Hyphenate( + unsigned offset, + unsigned start, + bool backwards) const { const String& text = GetText(); unsigned word_end = break_iterator_->NextBreakOpportunity(offset); if (word_end == offset) { DCHECK_EQ(offset, break_iterator_->PreviousBreakOpportunity(offset, start)); - return word_end; + return {word_end, false}; } unsigned previous_break_opportunity = break_iterator_->PreviousBreakOpportunity(offset, start); @@ -125,61 +131,57 @@ unsigned ShapingLineBreaker::Hyphenate(unsigned offset, if (offset >= word_start && ShouldHyphenate(text, previous_break_opportunity, word_end)) { unsigned prefix_length = Hyphenate(offset, word_start, word_end, backwards); - if (prefix_length) { - *is_hyphenated = true; - return word_start + prefix_length; - } + if (prefix_length) + return {word_start + prefix_length, true}; } - return backwards ? previous_break_opportunity : word_end; + return {backwards ? previous_break_opportunity : word_end, false}; } -unsigned ShapingLineBreaker::PreviousBreakOpportunity( - unsigned offset, - unsigned start, - bool* is_hyphenated) const { - DCHECK(is_hyphenated && !*is_hyphenated); +ShapingLineBreaker::BreakOpportunity +ShapingLineBreaker::PreviousBreakOpportunity(unsigned offset, + unsigned start) const { if (UNLIKELY(!IsSoftHyphenEnabled())) { const String& text = GetText(); for (;; offset--) { offset = break_iterator_->PreviousBreakOpportunity(offset, start); if (offset <= start || offset >= text.length() || text[offset - 1] != kSoftHyphenCharacter) - return offset; + return {offset, false}; } } if (UNLIKELY(hyphenation_)) - return Hyphenate(offset, start, true, is_hyphenated); + return Hyphenate(offset, start, true); - return break_iterator_->PreviousBreakOpportunity(offset, start); + return {break_iterator_->PreviousBreakOpportunity(offset, start), false}; } -unsigned ShapingLineBreaker::NextBreakOpportunity(unsigned offset, - unsigned start, - bool* is_hyphenated) const { - DCHECK(is_hyphenated && !*is_hyphenated); +ShapingLineBreaker::BreakOpportunity ShapingLineBreaker::NextBreakOpportunity( + unsigned offset, + unsigned start) const { if (UNLIKELY(!IsSoftHyphenEnabled())) { const String& text = GetText(); for (;; offset++) { offset = break_iterator_->NextBreakOpportunity(offset); if (offset >= text.length() || text[offset - 1] != kSoftHyphenCharacter) - return offset; + return {offset, false}; } } if (UNLIKELY(hyphenation_)) - return Hyphenate(offset, start, false, is_hyphenated); + return Hyphenate(offset, start, false); - return break_iterator_->NextBreakOpportunity(offset); + return {break_iterator_->NextBreakOpportunity(offset), false}; } inline scoped_refptr<ShapeResult> ShapingLineBreaker::Shape(TextDirection direction, unsigned start, unsigned end) { if (!spacing_ || !spacing_->HasSpacing()) - return shaper_->Shape(font_, direction, start, end); + return shaper_->Shape(font_, direction, start, end, pre_segmented_); - scoped_refptr<ShapeResult> result = shaper_->Shape(font_, direction, start, end); + scoped_refptr<ShapeResult> result = + shaper_->Shape(font_, direction, start, end, pre_segmented_); result->ApplySpacing(*spacing_); return result; } @@ -213,10 +215,10 @@ inline scoped_refptr<ShapeResult> ShapingLineBreaker::Shape(TextDirection direct // If we further assume that the font kerns with space then even though it's a // valid break opportunity reshaping is required as the combined width of the // two segments "Line " and "breaking" may be different from "Line breaking". -scoped_refptr<ShapeResult> ShapingLineBreaker::ShapeLine( +scoped_refptr<const ShapeResult> ShapingLineBreaker::ShapeLine( unsigned start, LayoutUnit available_space, - bool start_should_be_safe, + unsigned options, ShapingLineBreaker::Result* result_out) { DCHECK_GE(available_space, LayoutUnit(0)); unsigned range_start = result_->StartIndexForResult(); @@ -227,7 +229,8 @@ scoped_refptr<ShapeResult> ShapingLineBreaker::ShapeLine( const String& text = GetText(); // The start position in the original shape results. - float start_position_float = result_->PositionForOffset(start - range_start); + float start_position_float = + result_->CachedPositionForOffset(start - range_start); TextDirection direction = result_->Direction(); LayoutUnit start_position = SnapStart(start_position_float, direction); @@ -238,103 +241,115 @@ scoped_refptr<ShapeResult> ShapingLineBreaker::ShapeLine( FlipRtl(available_space, direction); DCHECK_GE(FlipRtl(end_position - start_position, direction), LayoutUnit(0)); unsigned candidate_break = - result_->OffsetForPosition(end_position, false) + range_start; + result_->CachedOffsetForPosition(end_position) + range_start; - unsigned first_safe = - start_should_be_safe ? result_->NextSafeToBreakOffset(start) : start; + unsigned first_safe = (options & kDontReshapeStart) + ? start + : result_->CachedNextSafeToBreakOffset(start); DCHECK_GE(first_safe, start); if (candidate_break >= range_end) { // The |result_| does not have glyphs to fill the available space, // and thus unable to compute. Return the result up to range_end. DCHECK_EQ(candidate_break, range_end); result_out->break_offset = range_end; - return ShapeToEnd(start, first_safe, range_end); + return ShapeToEnd(start, first_safe, range_start, range_end); } // candidate_break should be >= start, but rounding errors can chime in when // comparing floats. See ShapeLineZeroAvailableWidth on Linux/Mac. candidate_break = std::max(candidate_break, start); - unsigned break_opportunity = PreviousBreakOpportunity( - candidate_break, start, &result_out->is_hyphenated); - if (break_opportunity <= start) { + // If there are no break opportunity before candidate_break, overflow. + // Find the next break opportunity after the candidate_break. + BreakOpportunity break_opportunity = + PreviousBreakOpportunity(candidate_break, start); + bool is_overflow = break_opportunity.offset <= start; + if (is_overflow) { + if (options & kNoResultIfOverflow) + return nullptr; break_opportunity = - NextBreakOpportunity(std::max(candidate_break, start + 1), start, - &result_out->is_hyphenated); + NextBreakOpportunity(std::max(candidate_break, start + 1), start); // |range_end| may not be a break opportunity, but this function cannot // measure beyond it. - if (break_opportunity >= range_end) { + if (break_opportunity.offset >= range_end) { result_out->break_offset = range_end; - return ShapeToEnd(start, first_safe, range_end); + return ShapeToEnd(start, first_safe, range_start, range_end); } } - DCHECK_GT(break_opportunity, start); + DCHECK_GT(break_opportunity.offset, start); // If the start offset is not at a safe-to-break boundary the content between // the start and the next safe-to-break boundary needs to be reshaped and the // available space adjusted to take the reshaping into account. - scoped_refptr<ShapeResult> line_start_result; + scoped_refptr<const ShapeResult> line_start_result; if (first_safe != start) { - if (first_safe >= break_opportunity) { + if (first_safe >= break_opportunity.offset) { // There is no safe-to-break, reshape the whole range. - result_out->break_offset = break_opportunity; - return Shape(direction, start, break_opportunity); + result_out->break_offset = break_opportunity.offset; + return Shape(direction, start, break_opportunity.offset); } - LayoutUnit original_width = - FlipRtl(SnapEnd(result_->PositionForOffset(first_safe - range_start), - direction) - - start_position, - direction); + LayoutUnit original_width = FlipRtl( + SnapEnd(result_->CachedPositionForOffset(first_safe - range_start), + direction) - + start_position, + direction); line_start_result = Shape(direction, start, first_safe); available_space += line_start_result->SnappedWidth() - original_width; } + DCHECK_GE(first_safe, start); + DCHECK_LE(first_safe, break_opportunity.offset); - scoped_refptr<ShapeResult> line_end_result; - unsigned last_safe = break_opportunity; - while (break_opportunity > start) { + scoped_refptr<const ShapeResult> line_end_result; + unsigned last_safe = break_opportunity.offset; + if (break_opportunity.offset > start) { // If the previous valid break opportunity is not at a safe-to-break // boundary reshape between the safe-to-break offset and the valid break // offset. If the resulting width exceeds the available space the // preceding boundary is tried until the available space is sufficient. - unsigned previous_safe = - std::max(result_->PreviousSafeToBreakOffset(break_opportunity), start); - DCHECK_LE(previous_safe, break_opportunity); - if (previous_safe != break_opportunity) { - LayoutUnit safe_position = SnapStart( - result_->PositionForOffset(previous_safe - range_start), direction); - while (break_opportunity > previous_safe && previous_safe >= start) { - DCHECK_LE(break_opportunity, range_end); - line_end_result = Shape(direction, previous_safe, break_opportunity); - if (line_end_result->SnappedWidth() <= - FlipRtl(end_position - safe_position, direction)) - break; - // Doesn't fit after the reshape. Try previous break opportunity, or - // overflow if there were none. - bool is_previous_break_opportunity_hyphenated = false; - unsigned previous_break_opportunity = - PreviousBreakOpportunity(break_opportunity - 1, start, - &is_previous_break_opportunity_hyphenated); - if (previous_break_opportunity <= start) - break; - break_opportunity = previous_break_opportunity; - result_out->is_hyphenated = is_previous_break_opportunity_hyphenated; - line_end_result = nullptr; + while (true) { + DCHECK_LE(first_safe, break_opportunity.offset); + last_safe = std::max( + result_->CachedPreviousSafeToBreakOffset(break_opportunity.offset), + first_safe); + DCHECK_LE(last_safe, break_opportunity.offset); + DCHECK_GE(last_safe, first_safe); + if (last_safe == break_opportunity.offset) + break; + DCHECK_LE(break_opportunity.offset, range_end); + if (is_overflow) { + line_end_result = Shape(direction, last_safe, break_opportunity.offset); + break; } + LayoutUnit safe_position = SnapStart( + result_->CachedPositionForOffset(last_safe - range_start), direction); + line_end_result = Shape(direction, last_safe, break_opportunity.offset); + if (line_end_result->SnappedWidth() <= + FlipRtl(end_position - safe_position, direction)) + break; + + // Doesn't fit after the reshape. Try the previous break opportunity. + line_end_result = nullptr; + break_opportunity = + PreviousBreakOpportunity(break_opportunity.offset - 1, start); + if (break_opportunity.offset > start) + continue; + + // No suitable break opportunity, not exceeding the available space, + // found. Any break opportunities beyond candidate_break won't fit + // either because the ShapeResult has the full context. + // This line will overflow, but there are multiple choices to break, + // because none can fit. The one after candidate_break is better for + // ligatures, but the one before is better for kernings. + break_opportunity = PreviousBreakOpportunity(candidate_break, start); + // Loop once more to compute last_safe for the new break opportunity. + is_overflow = true; } - - if (break_opportunity > start) { - last_safe = previous_safe; - break; - } - - // No suitable break opportunity, not exceeding the available space, - // found. Choose the next valid one even though it will overflow. - break_opportunity = NextBreakOpportunity(candidate_break, start, - &result_out->is_hyphenated); - // |range_end| may not be a break opportunity, but this function cannot - // measure beyond it. - break_opportunity = std::min(break_opportunity, range_end); } + DCHECK_GE(break_opportunity.offset, last_safe); + DCHECK_EQ(break_opportunity.offset - start, + (line_start_result ? line_start_result->NumCharacters() : 0) + + (last_safe > first_safe ? last_safe - first_safe : 0) + + (line_end_result ? line_end_result->NumCharacters() : 0)); // Create shape results for the line by copying from the re-shaped result (if // reshaping was needed) and the original shape results. @@ -347,43 +362,49 @@ scoped_refptr<ShapeResult> ShapingLineBreaker::ShapeLine( if (line_end_result) line_end_result->CopyRange(last_safe, max_length, line_result.get()); - DCHECK_GT(break_opportunity, start); - // TODO(layout-dev): This hits on Mac and Mac only for a number of tests in - // virtual/layout_ng/external/wpt/css/CSS2/floats-clear/. - // DCHECK_EQ(std::min(break_opportunity, range_end) - start, - // line_result->NumCharacters()); + DCHECK_GT(break_opportunity.offset, start); + DCHECK_LE(break_opportunity.offset, range_end); + DCHECK_EQ(break_opportunity.offset - start, line_result->NumCharacters()); - result_out->break_offset = break_opportunity; - if (!result_out->is_hyphenated && - text[break_opportunity - 1] == kSoftHyphenCharacter) - result_out->is_hyphenated = true; + result_out->break_offset = break_opportunity.offset; + result_out->is_hyphenated = + break_opportunity.is_hyphenated || + text[break_opportunity.offset - 1] == kSoftHyphenCharacter; return line_result; } // Shape from the specified offset to the end of the ShapeResult. // If |start| is safe-to-break, this copies the subset of the result. -scoped_refptr<ShapeResult> ShapingLineBreaker::ShapeToEnd(unsigned start, - unsigned first_safe, - unsigned range_end) { - DCHECK_GE(start, result_->StartIndexForResult()); +scoped_refptr<const ShapeResult> ShapingLineBreaker::ShapeToEnd( + unsigned start, + unsigned first_safe, + unsigned range_start, + unsigned range_end) { + DCHECK(result_); + DCHECK_EQ(range_start, result_->StartIndexForResult()); + DCHECK_EQ(range_end, result_->EndIndexForResult()); + DCHECK_GE(start, range_start); DCHECK_LT(start, range_end); DCHECK_GE(first_safe, start); - DCHECK_EQ(range_end, result_->EndIndexForResult()); + + // If |start| is at the start of the range the entire result object may be + // reused, which avoids creating an extra copy an the sub-range logic. + if (start == range_start) + return result_; // If |start| is safe-to-break, no reshape is needed. - if (first_safe == start) { + if (start == first_safe) return result_->SubRange(start, range_end); - } // If no safe-to-break offset is found in range, reshape the entire range. TextDirection direction = result_->Direction(); - if (first_safe >= range_end) { + if (first_safe >= range_end) return Shape(direction, start, range_end); - } // Otherwise reshape to |first_safe|, then copy the rest. scoped_refptr<ShapeResult> line_result = Shape(direction, start, first_safe); result_->CopyRange(first_safe, range_end, line_result.get()); + DCHECK_EQ(range_end - start, line_result->NumCharacters()); return line_result; } |