diff --git a/lib/ui/BUILD.gn b/lib/ui/BUILD.gn index 4c9673e478fdb..57f454e04fd51 100644 --- a/lib/ui/BUILD.gn +++ b/lib/ui/BUILD.gn @@ -164,6 +164,7 @@ source_set("ui") { "//flutter/shell/common:display", "//flutter/shell/common:platform_message_handler", "//flutter/third_party/txt", + "//third_party/skia/modules/skparagraph", ] deps = [ diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index 310de114e377a..6b048b4af3596 100644 --- a/lib/ui/dart_ui.cc +++ b/lib/ui/dart_ui.cc @@ -218,6 +218,9 @@ typedef CanvasPath Path; V(Paragraph, didExceedMaxLines, 1) \ V(Paragraph, dispose, 1) \ V(Paragraph, getLineBoundary, 2) \ + V(Paragraph, getLineMetricsAt, 3) \ + V(Paragraph, getLineNumberAt, 2) \ + V(Paragraph, getNumberOfLines, 1) \ V(Paragraph, getPositionForOffset, 3) \ V(Paragraph, getRectsForPlaceholders, 1) \ V(Paragraph, getRectsForRange, 5) \ diff --git a/lib/ui/text.dart b/lib/ui/text.dart index 8bf5a8ccec707..276c326106dc3 100644 --- a/lib/ui/text.dart +++ b/lib/ui/text.dart @@ -2772,6 +2772,18 @@ class LineMetrics { required this.lineNumber, }); + LineMetrics._( + this.hardBreak, + this.ascent, + this.descent, + this.unscaledAscent, + this.height, + this.width, + this.left, + this.baseline, + this.lineNumber, + ); + /// True if this line ends with an explicit line break (e.g. '\n') or is the end /// of the paragraph. False otherwise. final bool hardBreak; @@ -2992,6 +3004,32 @@ abstract class Paragraph { /// to repeatedly call this. Instead, cache the results. List computeLineMetrics(); + /// Returns the [LineMetrics] for the line at `lineNumber`, or null if the + /// given `lineNumber` is greater than or equal to [numberOfLines]. + LineMetrics? getLineMetricsAt(int lineNumber); + + /// The total number of visible lines in the paragraph. + /// + /// Returns a non-negative number. If `maxLines` is non-null, the value of + /// [numberOfLines] never exceeds `maxLines`. + int get numberOfLines; + + /// Returns the line number of the line that contains the code unit that + /// `codeUnitOffset` points to. + /// + /// This method returns null if the given `codeUnitOffset` is out of bounds, or + /// is logically after the last visible codepoint. This includes the case where + /// its codepoint belongs to a visible line, but the text layout library + /// replaced it with an ellipsis. + /// + /// If the target code unit points to a control character that introduces + /// mandatory line breaks (most notably the line feed character `LF`, typically + /// represented in strings as the escape sequence "\n"), to conform to + /// [the unicode rules](https://unicode.org/reports/tr14/#LB4), the control + /// character itself is always considered to be at the end of "current" line + /// rather than the beginning of the new line. + int? getLineNumberAt(int codeUnitOffset); + /// Release the resources used by this object. The object is no longer usable /// after this method is called. void dispose(); @@ -3169,6 +3207,23 @@ base class _NativeParagraph extends NativeFieldWrapperClass1 implements Paragrap @Native)>(symbol: 'Paragraph::computeLineMetrics') external Float64List _computeLineMetrics(); + @override + LineMetrics? getLineMetricsAt(int lineNumber) => _getLineMetricsAt(lineNumber, LineMetrics._); + @Native, Uint32, Handle)>(symbol: 'Paragraph::getLineMetricsAt') + external LineMetrics? _getLineMetricsAt(int lineNumber, Function constructor); + + @override + @Native)>(symbol: 'Paragraph::getNumberOfLines') + external int get numberOfLines; + + @override + int? getLineNumberAt(int codeUnitOffset) { + final int lineNumber = _getLineNumber(codeUnitOffset); + return lineNumber < 0 ? null : lineNumber; + } + @Native, Uint32)>(symbol: 'Paragraph::getLineNumberAt') + external int _getLineNumber(int codeUnitOffset); + @override void dispose() { assert(!_disposed); diff --git a/lib/ui/text/paragraph.cc b/lib/ui/text/paragraph.cc index 624ed145dcf38..fedf84e02c7de 100644 --- a/lib/ui/text/paragraph.cc +++ b/lib/ui/text/paragraph.cc @@ -8,10 +8,14 @@ #include "flutter/common/task_runners.h" #include "flutter/fml/logging.h" #include "flutter/fml/task_runner.h" +#include "third_party/dart/runtime/include/dart_api.h" +#include "third_party/skia/modules/skparagraph/include/DartTypes.h" +#include "third_party/skia/modules/skparagraph/include/Paragraph.h" #include "third_party/tonic/converter/dart_converter.h" #include "third_party/tonic/dart_args.h" #include "third_party/tonic/dart_binding_macros.h" #include "third_party/tonic/dart_library_natives.h" +#include "third_party/tonic/logging/dart_invoke.h" namespace flutter { @@ -122,12 +126,12 @@ Dart_Handle Paragraph::getWordBoundary(unsigned offset) { return tonic::DartConverter::ToDart(result); } -Dart_Handle Paragraph::getLineBoundary(unsigned offset) { +Dart_Handle Paragraph::getLineBoundary(unsigned utf16Offset) { 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) { + if (utf16Offset >= line.start_index && utf16Offset <= line.end_index) { line_start = line.start_index; line_end = line.end_index; break; @@ -137,7 +141,7 @@ Dart_Handle Paragraph::getLineBoundary(unsigned offset) { return tonic::DartConverter::ToDart(result); } -tonic::Float64List Paragraph::computeLineMetrics() { +tonic::Float64List Paragraph::computeLineMetrics() const { std::vector metrics = m_paragraph->GetLineMetrics(); // Layout: @@ -165,6 +169,42 @@ tonic::Float64List Paragraph::computeLineMetrics() { return result; } +Dart_Handle Paragraph::getLineMetricsAt(int lineNumber, + Dart_Handle constructor) const { + skia::textlayout::LineMetrics line; + const bool found = m_paragraph->GetLineMetricsAt(lineNumber, &line); + if (!found) { + return Dart_Null(); + } + std::array arguments = { + Dart_NewBoolean(line.fHardBreak), + Dart_NewDouble(line.fAscent), + Dart_NewDouble(line.fDescent), + Dart_NewDouble(line.fUnscaledAscent), + // We add then round to get the height. The + // definition of height here is different + // than the one in LibTxt. + Dart_NewDouble(round(line.fAscent + line.fDescent)), + Dart_NewDouble(line.fWidth), + Dart_NewDouble(line.fLeft), + Dart_NewDouble(line.fBaseline), + Dart_NewInteger(line.fLineNumber), + }; + + Dart_Handle handle = + Dart_InvokeClosure(constructor, arguments.size(), arguments.data()); + tonic::CheckAndHandleError(handle); + return handle; +} + +size_t Paragraph::getNumberOfLines() const { + return m_paragraph->GetNumberOfLines(); +} + +int Paragraph::getLineNumberAt(size_t utf16Offset) const { + return m_paragraph->GetLineNumberAt(utf16Offset); +} + void Paragraph::dispose() { m_paragraph.reset(); ClearDartWrapper(); diff --git a/lib/ui/text/paragraph.h b/lib/ui/text/paragraph.h index 1a5d8217aafc4..311c33a7fc17e 100644 --- a/lib/ui/text/paragraph.h +++ b/lib/ui/text/paragraph.h @@ -47,7 +47,10 @@ class Paragraph : public RefCountedDartWrappable { Dart_Handle getPositionForOffset(double dx, double dy); Dart_Handle getWordBoundary(unsigned offset); Dart_Handle getLineBoundary(unsigned offset); - tonic::Float64List computeLineMetrics(); + tonic::Float64List computeLineMetrics() const; + Dart_Handle getLineMetricsAt(int lineNumber, Dart_Handle constructor) const; + size_t getNumberOfLines() const; + int getLineNumberAt(size_t utf16Offset) const; void dispose(); diff --git a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart index dff531d06c244..bc988188b46d9 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -3237,6 +3237,18 @@ extension SkParagraphExtension on SkParagraph { List getLineMetrics() => _getLineMetrics().toDart.cast(); + @JS('getLineMetricsAt') + external SkLineMetrics? _getLineMetricsAt(JSNumber index); + SkLineMetrics? getLineMetricsAt(double index) => _getLineMetricsAt(index.toJS); + + @JS('getNumberOfLines') + external JSNumber _getNumberOfLines(); + double getNumberOfLines() => _getNumberOfLines().toDartDouble; + + @JS('getLineNumberAt') + external JSNumber _getLineNumberAt(JSNumber index); + double getLineNumberAt(double index) => _getLineNumberAt(index.toJS).toDartDouble; + @JS('getLongestLine') external JSNumber _getLongestLine(); double getLongestLine() => _getLongestLine().toDartDouble; diff --git a/lib/web_ui/lib/src/engine/canvaskit/text.dart b/lib/web_ui/lib/src/engine/canvaskit/text.dart index 0948916b5d799..6284ffe55bd37 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/text.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/text.dart @@ -728,6 +728,26 @@ class CkParagraph implements ui.Paragraph { return result; } + @override + ui.LineMetrics? getLineMetricsAt(int lineNumber) { + assert(!_disposed, 'Paragraph has been disposed.'); + final SkLineMetrics? metrics = skiaObject.getLineMetricsAt(lineNumber.toDouble()); + return metrics == null ? null : CkLineMetrics._(metrics); + } + + @override + int get numberOfLines { + assert(!_disposed, 'Paragraph has been disposed.'); + return skiaObject.getNumberOfLines().toInt(); + } + + @override + int? getLineNumberAt(int codeUnitOffset) { + assert(!_disposed, 'Paragraph has been disposed.'); + final int lineNumber = skiaObject.getLineNumberAt(codeUnitOffset.toDouble()).toInt(); + return lineNumber >= 0 ? lineNumber : null; + } + bool _disposed = false; @override diff --git a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart index c00c1bcac9b17..af721f72b2864 100644 --- a/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart +++ b/lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart @@ -101,6 +101,15 @@ class SkwasmParagraph extends SkwasmObjectWrapper implements ui.Pa @override bool get didExceedMaxLines => paragraphGetDidExceedMaxLines(handle); + @override + int get numberOfLines => paragraphGetLineCount(handle); + + @override + int? getLineNumberAt(int codeUnitOffset) { + final int lineNumber = paragraphGetLineNumberAt(handle, codeUnitOffset); + return lineNumber >= 0 ? lineNumber : null; + } + @override void layout(ui.ParagraphConstraints constraints) { paragraphLayout(handle, constraints.width); @@ -214,6 +223,12 @@ class SkwasmParagraph extends SkwasmObjectWrapper implements ui.Pa (int index) => SkwasmLineMetrics._(paragraphGetLineMetricsAtIndex(handle, index)) ); } + + @override + ui.LineMetrics? getLineMetricsAt(int index) { + final LineMetricsHandle lineMetrics = paragraphGetLineMetricsAtIndex(handle, index); + return lineMetrics == nullptr ? SkwasmLineMetrics._(lineMetrics) : null; + } } void withScopedFontList( diff --git a/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart b/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart index f527d576bc41b..c749a8512d38c 100644 --- a/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart @@ -40,7 +40,7 @@ class CanvasParagraph implements ui.Paragraph { final EngineParagraphStyle paragraphStyle; /// The full textual content of the paragraph. - late String plainText; + final String plainText; /// Whether this paragraph can be drawn on a bitmap canvas. /// @@ -221,17 +221,12 @@ class CanvasParagraph implements ui.Paragraph { @override ui.TextRange getLineBoundary(ui.TextPosition position) { - final int index = position.offset; - - int i; - for (i = 0; i < lines.length - 1; i++) { - final ParagraphLine line = lines[i]; - if (index >= line.startIndex && index < line.endIndex) { - break; - } + if (lines.isEmpty) { + return ui.TextRange.empty; } - - final ParagraphLine line = lines[i]; + final int? lineNumber = getLineNumberAt(position.offset); + // Fallback to the last line for backward compatibility. + final ParagraphLine line = lineNumber != null ? lines[lineNumber] : lines.last; return ui.TextRange(start: line.startIndex, end: line.endIndex - line.trailingNewlines); } @@ -240,6 +235,32 @@ class CanvasParagraph implements ui.Paragraph { return lines.map((ParagraphLine line) => line.lineMetrics).toList(); } + @override + EngineLineMetrics? getLineMetricsAt(int lineNumber) { + return 0 <= lineNumber && lineNumber < lines.length + ? lines[lineNumber].lineMetrics + : null; + } + + @override + int get numberOfLines => lines.length; + + @override + int? getLineNumberAt(int codeUnitOffset) => _findLine(codeUnitOffset, 0, lines.length); + + int? _findLine(int codeUnitOffset, int startLine, int endLine) { + if (endLine <= startLine || codeUnitOffset < lines[startLine].startIndex || lines[endLine - 1].endIndex <= codeUnitOffset) { + return null; + } + if (endLine == startLine + 1) { + return startLine; + } + // endLine >= startLine + 2 thus we have + // startLine + 1 <= midIndex <= endLine - 1 + final int midIndex = (startLine + endLine) ~/ 2; + return _findLine(codeUnitOffset, midIndex, endLine) ?? _findLine(codeUnitOffset, startLine, midIndex); + } + bool _disposed = false; @override diff --git a/lib/web_ui/lib/src/engine/text/layout_service.dart b/lib/web_ui/lib/src/engine/text/layout_service.dart index ae46ae685689f..d03a70b91883d 100644 --- a/lib/web_ui/lib/src/engine/text/layout_service.dart +++ b/lib/web_ui/lib/src/engine/text/layout_service.dart @@ -390,7 +390,10 @@ class TextLayoutService { // it possible to do hit testing. Once we find the box, we look inside that // box to find where exactly the `offset` is located. - final ParagraphLine line = _findLineForY(offset.dy); + final ParagraphLine? line = _findLineForY(offset.dy); + if (line == null) { + return const ui.TextPosition(offset: 0); + } // [offset] is to the left of the line. if (offset.dx <= line.left) { return ui.TextPosition( @@ -416,7 +419,10 @@ class TextLayoutService { return ui.TextPosition(offset: line.startIndex); } - ParagraphLine _findLineForY(double y) { + ParagraphLine? _findLineForY(double y) { + if (lines.isEmpty) { + return null; + } // We could do a binary search here but it's not worth it because the number // of line is typically low, and each iteration is a cheap comparison of // doubles. diff --git a/lib/web_ui/lib/text.dart b/lib/web_ui/lib/text.dart index 5d39948ea83c3..ba09ee5068af4 100644 --- a/lib/web_ui/lib/text.dart +++ b/lib/web_ui/lib/text.dart @@ -695,6 +695,9 @@ abstract class Paragraph { TextRange getLineBoundary(TextPosition position); List getBoxesForPlaceholders(); List computeLineMetrics(); + LineMetrics? getLineMetricsAt(int lineNumber); + int get numberOfLines; + int? getLineNumberAt(int codeUnitOffset); void dispose(); bool get debugDisposed; } diff --git a/lib/web_ui/skwasm/text/paragraph.cpp b/lib/web_ui/skwasm/text/paragraph.cpp index dcd1cd2e01b83..a44baf4d1ac39 100644 --- a/lib/web_ui/skwasm/text/paragraph.cpp +++ b/lib/web_ui/skwasm/text/paragraph.cpp @@ -74,14 +74,18 @@ SKWASM_EXPORT size_t paragraph_getLineCount(Paragraph* paragraph) { SKWASM_EXPORT int paragraph_getLineNumberAt(Paragraph* paragraph, size_t characterIndex) { - return paragraph->getLineNumberAt(characterIndex); + return paragraph->getLineNumberAtUTF16Offset(characterIndex); } SKWASM_EXPORT LineMetrics* paragraph_getLineMetricsAtIndex(Paragraph* paragraph, - size_t index) { + size_t lineNumber) { auto metrics = new LineMetrics(); - paragraph->getLineMetricsAt(index, metrics); - return metrics; + if (paragraph->getLineMetricsAt(lineNumber, metrics)) { + return metrics; + } else { + delete metrics; + return nullptr; + } } struct TextBoxList { diff --git a/lib/web_ui/test/canvaskit/text_test.dart b/lib/web_ui/test/canvaskit/text_test.dart index 244614594daa0..2c788cffd2bcd 100644 --- a/lib/web_ui/test/canvaskit/text_test.dart +++ b/lib/web_ui/test/canvaskit/text_test.dart @@ -124,6 +124,43 @@ void testMain() { }); }); + test('empty paragraph', () { + const double fontSize = 10.0; + final ui.Paragraph paragraph = ui.ParagraphBuilder(CkParagraphStyle( + fontSize: fontSize, + )).build(); + paragraph.layout(const ui.ParagraphConstraints(width: double.infinity)); + + expect(paragraph.getLineMetricsAt(0), isNull); + expect(paragraph.numberOfLines, 0); + expect(paragraph.getLineNumberAt(0), isNull); + }); + + test('Basic line related metrics', () { + const double fontSize = 10; + final ui.ParagraphBuilder builder = ui.ParagraphBuilder(CkParagraphStyle( + fontStyle: ui.FontStyle.normal, + fontWeight: ui.FontWeight.normal, + fontSize: fontSize, + maxLines: 1, + ellipsis: 'BBB', + ))..addText('A' * 100); + final ui.Paragraph paragraph = builder.build(); + paragraph.layout(const ui.ParagraphConstraints(width: 100.0)); + + expect(paragraph.numberOfLines, 1); + + expect(paragraph.getLineMetricsAt(-1), isNull); + expect(paragraph.getLineMetricsAt(0), isNotNull); + expect(paragraph.getLineMetricsAt(1), isNull); + + expect(paragraph.getLineNumberAt(-1), isNull); + expect(paragraph.getLineNumberAt(0), 0); + expect(paragraph.getLineNumberAt(6), 0); + // The last 3 characters on the first line are ellipsized with BBB. + expect(paragraph.getLineMetricsAt(7), isNull); + }); + test('rounding hack disabled by default', () { expect(ui.ParagraphBuilder.shouldDisableRoundingHack, isTrue); diff --git a/lib/web_ui/test/html/text_test.dart b/lib/web_ui/test/html/text_test.dart index d9c7780fde69c..fc0db1f58d3f4 100644 --- a/lib/web_ui/test/html/text_test.dart +++ b/lib/web_ui/test/html/text_test.dart @@ -79,10 +79,6 @@ Future testMain() async { expect(paragraph.height, fontSize * 2.0); // because it wraps expect(paragraph.width, fontSize * 5.0); expect(paragraph.minIntrinsicWidth, fontSize * 4.0); - - // TODO(yjbanov): due to https://github.com/flutter/flutter/issues/21965 - // Flutter reports a different number. Ours is correct - // though. expect(paragraph.maxIntrinsicWidth, fontSize * 9.0); expect(paragraph.alphabeticBaseline, fontSize * .8); expect( @@ -94,6 +90,31 @@ Future testMain() async { } }); + test('Basic line related metrics', () { + const double fontSize = 10; + final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle( + fontStyle: FontStyle.normal, + fontWeight: FontWeight.normal, + fontSize: fontSize, + maxLines: 1, + ellipsis: 'BBB', + ))..addText('A' * 100); + final Paragraph paragraph = builder.build(); + paragraph.layout(const ParagraphConstraints(width: 100.0)); + + expect(paragraph.numberOfLines, 1); + + expect(paragraph.getLineMetricsAt(-1), isNull); + expect(paragraph.getLineMetricsAt(0), isNotNull); + expect(paragraph.getLineMetricsAt(1), isNull); + + expect(paragraph.getLineNumberAt(-1), isNull); + expect(paragraph.getLineNumberAt(0), 0); + expect(paragraph.getLineNumberAt(6), 0); + // The last 3 characters on the first line are ellipsized with BBB. + expect(paragraph.getLineNumberAt(7), isNull); + }); + test('Can disable rounding hack', () { if (!ParagraphBuilder.shouldDisableRoundingHack) { ParagraphBuilder.setDisableRoundingHack(true); diff --git a/testing/dart/paragraph_test.dart b/testing/dart/paragraph_test.dart index 771cf98ac575f..4af61bb936345 100644 --- a/testing/dart/paragraph_test.dart +++ b/testing/dart/paragraph_test.dart @@ -219,6 +219,74 @@ void main() { expect(line.end, 10); }); + test('getLineMetricsAt', () { + const double fontSize = 10.0; + final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle( + fontSize: fontSize, + textDirection: TextDirection.rtl, + height: 2.0, + )); + builder.addText('Test\npppp'); + final Paragraph paragraph = builder.build(); + paragraph.layout(const ParagraphConstraints(width: 100.0)); + final LineMetrics? line = paragraph.getLineMetricsAt(1); + expect(line?.hardBreak, isTrue); + expect(line?.ascent, 15.0); + expect(line?.descent, 5.0); + expect(line?.height, 20.0); + expect(line?.width, 4 * 10.0); + expect(line?.left, 100.0 - 40.0); + expect(line?.baseline, 20.0 + 15.0); + expect(line?.lineNumber, 1); + }); + + test('line number', () { + const double fontSize = 10.0; + final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(fontSize: fontSize)); + builder.addText('Test\n\nTest'); + final Paragraph paragraph = builder.build(); + paragraph.layout(const ParagraphConstraints(width: 100.0)); + expect(paragraph.numberOfLines, 3); + expect(paragraph.getLineNumberAt(4), 0); // first LF + expect(paragraph.getLineNumberAt(5), 1); // second LF + expect(paragraph.getLineNumberAt(6), 2); // "T" in the second "Test" + }); + + test('empty paragraph', () { + const double fontSize = 10.0; + final Paragraph paragraph = ParagraphBuilder(ParagraphStyle( + fontSize: fontSize, + )).build(); + paragraph.layout(const ParagraphConstraints(width: double.infinity)); + + expect(paragraph.getLineMetricsAt(0), isNull); + expect(paragraph.numberOfLines, 0); + expect(paragraph.getLineNumberAt(0), isNull); + }); + + test('OOB indices as input', () { + const double fontSize = 10.0; + final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle( + fontSize: fontSize, + maxLines: 1, + ellipsis: 'BBB', + ))..addText('A' * 100); + final Paragraph paragraph = builder.build(); + paragraph.layout(const ParagraphConstraints(width: 100)); + + expect(paragraph.numberOfLines, 1); + + expect(paragraph.getLineMetricsAt(-1), isNull); + expect(paragraph.getLineMetricsAt(0), isNotNull); + expect(paragraph.getLineMetricsAt(1), isNull); + + expect(paragraph.getLineNumberAt(-1), isNull); + expect(paragraph.getLineNumberAt(0), 0); + expect(paragraph.getLineNumberAt(6), 0); + // The last 3 characters on the first line are ellipsized with BBB. + expect(paragraph.getLineMetricsAt(7), isNull); + }); + test('painting a disposed paragraph does not crash', () { final Paragraph paragraph = ParagraphBuilder(ParagraphStyle()).build(); paragraph.dispose(); diff --git a/third_party/txt/src/skia/paragraph_skia.cc b/third_party/txt/src/skia/paragraph_skia.cc index 67d67b1a04d2b..cbb7b85d01f41 100644 --- a/third_party/txt/src/skia/paragraph_skia.cc +++ b/third_party/txt/src/skia/paragraph_skia.cc @@ -301,6 +301,11 @@ std::vector& ParagraphSkia::GetLineMetrics() { return line_metrics_.value(); } +bool ParagraphSkia::GetLineMetricsAt(int lineNumber, + skt::LineMetrics* lineMetrics) const { + return paragraph_->getLineMetricsAt(lineNumber, lineMetrics); +}; + double ParagraphSkia::GetMinIntrinsicWidth() { return SkScalarToDouble(paragraph_->getMinIntrinsicWidth()); } @@ -378,6 +383,14 @@ Paragraph::Range ParagraphSkia::GetWordBoundary(size_t offset) { return Paragraph::Range(range.start, range.end); } +size_t ParagraphSkia::GetNumberOfLines() const { + return paragraph_->lineNumber(); +} + +int ParagraphSkia::GetLineNumberAt(size_t codeUnitIndex) const { + return paragraph_->getLineNumberAtUTF16Offset(codeUnitIndex); +} + TextStyle ParagraphSkia::SkiaToTxt(const skt::TextStyle& skia) { TextStyle txt; diff --git a/third_party/txt/src/skia/paragraph_skia.h b/third_party/txt/src/skia/paragraph_skia.h index 5779445e09a71..bc23b78538b45 100644 --- a/third_party/txt/src/skia/paragraph_skia.h +++ b/third_party/txt/src/skia/paragraph_skia.h @@ -50,6 +50,14 @@ class ParagraphSkia : public Paragraph { std::vector& GetLineMetrics() override; + bool GetLineMetricsAt( + int lineNumber, + skia::textlayout::LineMetrics* lineMetrics) const override; + + size_t GetNumberOfLines() const override; + + int GetLineNumberAt(size_t utf16Offset) const override; + bool DidExceedMaxLines() override; void Layout(double width) override; diff --git a/third_party/txt/src/txt/paragraph.h b/third_party/txt/src/txt/paragraph.h index 2d736fb3935c5..b8ee55d4366f5 100644 --- a/third_party/txt/src/txt/paragraph.h +++ b/third_party/txt/src/txt/paragraph.h @@ -22,6 +22,8 @@ #include "line_metrics.h" #include "paragraph_style.h" #include "third_party/skia/include/core/SkRect.h" +#include "third_party/skia/modules/skparagraph/include/Metrics.h" +#include "third_party/skia/modules/skparagraph/include/Paragraph.h" class SkCanvas; @@ -178,6 +180,22 @@ class Paragraph { virtual Range GetWordBoundary(size_t offset) = 0; virtual std::vector& GetLineMetrics() = 0; + + virtual bool GetLineMetricsAt( + int lineNumber, + skia::textlayout::LineMetrics* lineMetrics) const = 0; + + // Returns the total number of visible lines in the paragraph. + virtual size_t GetNumberOfLines() const = 0; + + // Returns the zero-indexed line number that contains the given code unit + // offset. Returns -1 if the given offset is out of bounds, or points to a + // codepoint that is logically after the last visible codepoint. + // + // If the offset points to a hard line break, this method returns the line + // number of the line this hard line break breaks, intead of the new line it + // creates. + virtual int GetLineNumberAt(size_t utf16Offset) const = 0; }; } // namespace txt