From f68de3f45168454301fa71c00abe8afdeededfc7 Mon Sep 17 00:00:00 2001 From: Greg Spencer Date: Mon, 11 Nov 2019 16:42:40 -0800 Subject: [PATCH] Add line boundary information to LineMetrics. (#13727) This exposes the line boundary information a line by adding getLineBoundary to return the indices corresponding to a line around a TextPosition. The information is already calculated when calculating line metrics, so that we can enable moving the selection/cursor to the beginning/end of a line in a text field. --- lib/ui/text.dart | 37 ++++++++++++------- lib/ui/text/paragraph.cc | 18 +++++++++ lib/ui/text/paragraph.h | 1 + lib/web_ui/lib/src/engine/text/paragraph.dart | 31 +++++++--------- lib/web_ui/lib/src/ui/text.dart | 30 ++++++++++++--- 5 files changed, 79 insertions(+), 38 deletions(-) diff --git a/lib/ui/text.dart b/lib/ui/text.dart index a2a8df20579..6af9893c562 100644 --- a/lib/ui/text.dart +++ b/lib/ui/text.dart @@ -1866,23 +1866,32 @@ class Paragraph extends NativeFieldWrapperClass2 { } List _getPositionForOffset(double dx, double dy) native 'Paragraph_getPositionForOffset'; - /// Returns the [start, end] of the word at the given offset. Characters not - /// part of a word, such as spaces, symbols, and punctuation, have word breaks - /// on both sides. In such cases, this method will return [offset, offset+1]. - /// Word boundaries are defined more precisely in Unicode Standard Annex #29 - /// http://www.unicode.org/reports/tr29/#Word_Boundaries - List getWordBoundary(dynamic position) { - // TODO(gspencergoog): have this take only a TextPosition once the framework - // code is calling it with that. - if (position is TextPosition) { - return _getWordBoundary(position.offset); - } else { - final int offset = position; - return _getWordBoundary(offset); - } + /// Returns the [TextRange] of the word at the given [TextPosition]. + /// + /// Characters not part of a word, such as spaces, symbols, and punctuation, + /// have word breaks on both sides. In such cases, this method will return + /// [offset, offset+1]. Word boundaries are defined more precisely in Unicode + /// Standard Annex #29 http://www.unicode.org/reports/tr29/#Word_Boundaries + TextRange getWordBoundary(TextPosition position) { + final List boundary = _getWordBoundary(position.offset); + return TextRange(start: boundary[0], end: boundary[1]); } List _getWordBoundary(int offset) native 'Paragraph_getWordBoundary'; + /// Returns the [TextRange] of the line at the given [TextPosition]. + /// + /// The newline (if any) is returned as part of the range. + /// + /// Not valid until after layout. + /// + /// This can potentially be expensive, since it needs to compute the line + /// metrics, so use it sparingly. + TextRange getLineBoundary(TextPosition position) { + final List boundary = _getLineBoundary(position.offset); + return TextRange(start: boundary[0], end: boundary[1]); + } + List _getLineBoundary(int offset) native 'Paragraph_getLineBoundary'; + // Redirecting the paint function in this way solves some dependency problems // in the C++ code. If we straighten out the C++ dependencies, we can remove // this indirection. diff --git a/lib/ui/text/paragraph.cc b/lib/ui/text/paragraph.cc index 0025615e7f8..5680735b601 100644 --- a/lib/ui/text/paragraph.cc +++ b/lib/ui/text/paragraph.cc @@ -31,6 +31,7 @@ IMPLEMENT_WRAPPERTYPEINFO(ui, Paragraph); V(Paragraph, layout) \ V(Paragraph, paint) \ V(Paragraph, getWordBoundary) \ + V(Paragraph, getLineBoundary) \ V(Paragraph, getRectsForRange) \ V(Paragraph, getRectsForPlaceholders) \ V(Paragraph, getPositionForOffset) \ @@ -134,6 +135,23 @@ Dart_Handle Paragraph::getWordBoundary(unsigned offset) { return result; } +Dart_Handle Paragraph::getLineBoundary(unsigned offset) { + std::vector metrics = m_paragraph->GetLineMetrics(); + int line_start = -1; + int line_end = -1; + for (txt::LineMetrics& line : metrics) { + if (offset >= line.start_index && offset < line.end_index) { + line_start = line.start_index; + line_end = line.end_index; + break; + } + } + Dart_Handle result = Dart_NewListOf(Dart_CoreType_Int, 2); + Dart_ListSetAt(result, 0, ToDart(line_start)); + Dart_ListSetAt(result, 1, ToDart(line_end)); + return result; +} + std::vector Paragraph::computeLineMetrics() { std::vector result; std::vector metrics = m_paragraph->GetLineMetrics(); diff --git a/lib/ui/text/paragraph.h b/lib/ui/text/paragraph.h index cdd45335d3e..7aea6079378 100644 --- a/lib/ui/text/paragraph.h +++ b/lib/ui/text/paragraph.h @@ -49,6 +49,7 @@ class Paragraph : public RefCountedDartWrappable { std::vector getRectsForPlaceholders(); Dart_Handle getPositionForOffset(double dx, double dy); Dart_Handle getWordBoundary(unsigned offset); + Dart_Handle getLineBoundary(unsigned offset); std::vector computeLineMetrics(); size_t GetAllocationSize() override; diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index 0c53962ddc0..2aca66ff297 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -281,27 +281,22 @@ class EngineParagraph implements ui.Paragraph { } @override - List getWordBoundary(dynamic position) { - // TODO(gspencergoog): have this take only a TextPosition once the framework - // code is calling it with that. - if (position is ui.TextPosition) { - ui.TextPosition textPosition = position; - if (_plainText == null) { - return [textPosition.offset, textPosition.offset]; - } - - final int start = WordBreaker.prevBreakIndex(_plainText, textPosition.offset); - final int end = WordBreaker.nextBreakIndex(_plainText, textPosition.offset); - return [start, end]; - } - + ui.TextRange getWordBoundary(ui.TextPosition position) { + ui.TextPosition textPosition = position; if (_plainText == null) { - return [position, position]; + return ui.TextRange(start: textPosition.offset, end: textPosition.offset); } - final int start = WordBreaker.prevBreakIndex(_plainText, position); - final int end = WordBreaker.nextBreakIndex(_plainText, position); - return [start, end]; + final int start = WordBreaker.prevBreakIndex(_plainText, textPosition.offset); + final int end = WordBreaker.nextBreakIndex(_plainText, textPosition.offset); + return ui.TextRange(start: start, end: end); + } + + @override + ui.TextRange getLineBoundary(ui.TextPosition position) { + // TODO(flutter_web): https://github.com/flutter/flutter/issues/39537 + // Depends upon LineMetrics measurement. + return null; } @override diff --git a/lib/web_ui/lib/src/ui/text.dart b/lib/web_ui/lib/src/ui/text.dart index 774787ae648..d003c7b6eda 100644 --- a/lib/web_ui/lib/src/ui/text.dart +++ b/lib/web_ui/lib/src/ui/text.dart @@ -1237,12 +1237,23 @@ abstract class Paragraph { /// within the text. TextPosition getPositionForOffset(Offset offset); - /// Returns the [start, end] of the word at the given offset. Characters not - /// part of a word, such as spaces, symbols, and punctuation, have word breaks - /// on both sides. In such cases, this method will return [offset, offset+1]. - /// Word boundaries are defined more precisely in Unicode Standard Annex #29 - /// http://www.unicode.org/reports/tr29/#Word_Boundaries - List getWordBoundary(dynamic position); + /// Returns the [TextRange] of the word at the given [TextPosition]. + /// + /// Characters not part of a word, such as spaces, symbols, and punctuation, + /// have word breaks on both sides. In such cases, this method will return + /// [offset, offset+1]. Word boundaries are defined more precisely in Unicode + /// Standard Annex #29 http://www.unicode.org/reports/tr29/#Word_Boundaries + TextRange getWordBoundary(TextPosition position); + + /// Returns the [TextRange] of the line at the given [TextPosition]. + /// + /// The newline (if any) is returned as part of the range. + /// + /// Not valid until after layout. + /// + /// This can potentially be expensive, since it needs to compute the line + /// metrics, so use it sparingly. + TextRange getLineBoundary(TextPosition position); /// Returns a list of text boxes that enclose all placeholders in the paragraph. /// @@ -1252,6 +1263,13 @@ abstract class Paragraph { /// where positive y values indicate down. List getBoxesForPlaceholders(); + /// Returns the full list of [LineMetrics] that describe in detail the various + /// metrics of each laid out line. + /// + /// Not valid until after layout. + /// + /// This can potentially return a large amount of data, so it is not recommended + /// to repeatedly call this. Instead, cache the results. List computeLineMetrics(); }