From 4325bd2f65febaf47bc15e17f71e522d0dfef95e Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Tue, 19 Sep 2023 22:55:00 -0700 Subject: [PATCH 1/8] Expose more line metrics methods from ui.Paragraph --- lib/ui/dart_ui.cc | 3 + lib/ui/text.dart | 55 +++++++++++++ lib/ui/text/paragraph.cc | 46 ++++++++++- lib/ui/text/paragraph.h | 5 +- .../src/engine/canvaskit/canvaskit_api.dart | 12 +++ lib/web_ui/lib/src/engine/canvaskit/text.dart | 19 +++++ .../engine/skwasm/skwasm_impl/paragraph.dart | 15 ++++ .../lib/src/engine/text/canvas_paragraph.dart | 30 +++++++- .../lib/src/engine/text/fragmenter.dart | 3 +- .../lib/src/engine/text/layout_service.dart | 10 ++- lib/web_ui/lib/src/engine/text/paragraph.dart | 30 ++++++++ lib/web_ui/lib/text.dart | 3 + lib/web_ui/skwasm/text/paragraph.cpp | 7 +- testing/dart/paragraph_test.dart | 77 +++++++++++++++++++ third_party/txt/src/skia/paragraph_skia.cc | 13 ++++ third_party/txt/src/skia/paragraph_skia.h | 8 ++ third_party/txt/src/txt/paragraph.h | 17 ++++ 17 files changed, 341 insertions(+), 12 deletions(-) diff --git a/lib/ui/dart_ui.cc b/lib/ui/dart_ui.cc index 976b373d29d08..aa652c2244786 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..4901c0a6b83ff 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 out of bounds. + /// + /// For instance, `getLineMetricsAt(0)` returns the [LineMetrics] of the first + /// line in the paragraph, when the paragraph isn't empty. + LineMetrics? getLineMetricsAt(int lineNumber); + + /// The total number of lines in the paragraph. + /// + /// Returns a non-negative number. If `maxLines` is set to a non-null value + /// that is greater than one, the `numberOfLines` value is always smaller than + /// or equal to `maxLines`. + int get numberOfLines; + + /// Returns the line number of the line that contains the code unit that + /// `codeUnitOffset` points to, or null if the given `codeUnitOffset` is out + /// of bounds. + /// + /// 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..6f54558e33dc0 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(); + } + Dart_Handle arguments[9] = { + 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, sizeof(arguments) / sizeof(Dart_Handle), arguments); + 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 f167a19c3be9c..89ea9a3cdbbbd 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -3227,6 +3227,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('lineNumber') + 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..89be53c5527ce 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/text.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/text.dart @@ -728,6 +728,25 @@ 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? getLineNumber(int codeUnitOffset) { + assert(!_disposed, 'Paragraph has been disposed.'); + return skiaObject.getLineNumberAt(codeUnitOffset.toDouble()).toInt(); + } + 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..2d03f7ad22210 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? getLineNumber(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..c99fc59d84dcb 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. /// @@ -240,6 +240,34 @@ class CanvasParagraph implements ui.Paragraph { return lines.map((ParagraphLine line) => line.lineMetrics).toList(); } + @override + EngineLineMetrics? getLineMetricsAt(int lineNumber) { + assert(lineNumber >= 0); + return lineNumber < lines.length + ? lines[lineNumber].lineMetrics + : null; + } + + @override + int get numberOfLines => lines.length; + + @override + int? getLineNumber(int codeUnitOffset) { + assert(codeUnitOffset >= 0); + return _findLine(codeUnitOffset, 0, numberOfLines); + } + + int? _findLine(int codeUnitOffset, int startLine, int endLine) { + if (endLine <= startLine || codeUnitOffset < lines[startLine].startIndex || lines[endLine].endIndex <= codeUnitOffset) { + return null; + } + if (endLine == startLine + 1) { + return startLine; + } + 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/fragmenter.dart b/lib/web_ui/lib/src/engine/text/fragmenter.dart index 9fdd82e167c03..dc5ff2c777c37 100644 --- a/lib/web_ui/lib/src/engine/text/fragmenter.dart +++ b/lib/web_ui/lib/src/engine/text/fragmenter.dart @@ -29,6 +29,7 @@ abstract class TextFragment { /// Whether this fragment's range overlaps with the range from [start] to [end]. bool overlapsWith(int start, int end) { - return start < this.end && this.start < end; + assert(end > start); + return start <= this.end && this.start < end; } } 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/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index a6ccc5c4adad8..c9d8c92a2e1d8 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -208,6 +208,36 @@ class ParagraphLine { return buffer.toString(); } + // Find the closest [LayoutFragment] to the given horizontal offset `dx`, that + // is not an [EllipsisFragment]. + LayoutFragment? closestFragmentAtOffset(double dx) { + final List fs = switch (fragments) { + [...final List rest, EllipsisFragment()] + || final List rest => rest, + }; + + ({LayoutFragment fragment, double distance})? closestFragment; + for (final LayoutFragment fragment in fs) { + assert(fragment is! EllipsisFragment); + final double distance; + if (dx < fragment.left) { + distance = fragment.left - dx; + } else if (dx > fragment.right) { + distance = dx - fragment.right; + } else { + return fragment; + } + assert(distance > 0); + + final double? minDistance = closestFragment?.distance; + if (minDistance == null || minDistance > distance) { + closestFragment = (fragment: fragment, distance: distance); + } + } + final LayoutFragment? fragment = closestFragment?.fragment; + return fragment; + } + @override int get hashCode => Object.hash( lineMetrics, diff --git a/lib/web_ui/lib/text.dart b/lib/web_ui/lib/text.dart index 5d39948ea83c3..64116073265af 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? getLineNumber(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..466a2ef12976a 100644 --- a/lib/web_ui/skwasm/text/paragraph.cpp +++ b/lib/web_ui/skwasm/text/paragraph.cpp @@ -74,14 +74,13 @@ 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; + return paragraph->getLineMetricsAt(lineNumber, metrics) ? metrics : nullptr; } struct TextBoxList { diff --git a/testing/dart/paragraph_test.dart b/testing/dart/paragraph_test.dart index 771cf98ac575f..b6849cbd3e1d1 100644 --- a/testing/dart/paragraph_test.dart +++ b/testing/dart/paragraph_test.dart @@ -219,6 +219,83 @@ 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('getLineMetricsAt', () { + const double fontSize = 10.0; + final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle( + fontSize: fontSize, + textDirection: TextDirection.rtl, + height: 2.0, + )); + builder.addText('Test\nTest'); + 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, 40.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 Paragraph paragraph = ParagraphBuilder(ParagraphStyle( + fontSize: fontSize, + )).build(); + paragraph.layout(const ParagraphConstraints(width: double.infinity)); + + expect(paragraph.getLineMetricsAt(-1), isNull); + expect(paragraph.getLineNumberAt(-1), 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 76d737224aa79..44df69b8a4e37 100644 --- a/third_party/txt/src/skia/paragraph_skia.cc +++ b/third_party/txt/src/skia/paragraph_skia.cc @@ -265,6 +265,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()); } @@ -342,6 +347,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..5cce853933cda 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,21 @@ 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 lines the paragraph has. + virtual size_t GetNumberOfLines() const = 0; + + // Returns the line number (zero-indexed) that contains the given code unit + // offset, or -1 if the given offset is out of bounds. + // + // 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 From 79aea2e6e819f5f09590060cdfaa4fc5dea76358 Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:06:03 -0700 Subject: [PATCH 2/8] documentation --- lib/ui/text.dart | 20 ++++++------- .../src/engine/canvaskit/canvaskit_api.dart | 2 +- lib/web_ui/lib/src/engine/text/paragraph.dart | 30 ------------------- third_party/txt/src/txt/paragraph.h | 7 +++-- 4 files changed, 15 insertions(+), 44 deletions(-) diff --git a/lib/ui/text.dart b/lib/ui/text.dart index 4901c0a6b83ff..276c326106dc3 100644 --- a/lib/ui/text.dart +++ b/lib/ui/text.dart @@ -3005,22 +3005,22 @@ abstract class Paragraph { List computeLineMetrics(); /// Returns the [LineMetrics] for the line at `lineNumber`, or null if the - /// given `lineNumber` is out of bounds. - /// - /// For instance, `getLineMetricsAt(0)` returns the [LineMetrics] of the first - /// line in the paragraph, when the paragraph isn't empty. + /// given `lineNumber` is greater than or equal to [numberOfLines]. LineMetrics? getLineMetricsAt(int lineNumber); - /// The total number of lines in the paragraph. + /// The total number of visible lines in the paragraph. /// - /// Returns a non-negative number. If `maxLines` is set to a non-null value - /// that is greater than one, the `numberOfLines` value is always smaller than - /// or equal to `maxLines`. + /// 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, or null if the given `codeUnitOffset` is out - /// of bounds. + /// `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 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 89ea9a3cdbbbd..c42bc62e09a76 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart @@ -3231,7 +3231,7 @@ extension SkParagraphExtension on SkParagraph { external SkLineMetrics? _getLineMetricsAt(JSNumber index); SkLineMetrics? getLineMetricsAt(double index) => _getLineMetricsAt(index.toJS); - @JS('lineNumber') + @JS('getNumberOfLines') external JSNumber _getNumberOfLines(); double getNumberOfLines() => _getNumberOfLines().toDartDouble; diff --git a/lib/web_ui/lib/src/engine/text/paragraph.dart b/lib/web_ui/lib/src/engine/text/paragraph.dart index c9d8c92a2e1d8..a6ccc5c4adad8 100644 --- a/lib/web_ui/lib/src/engine/text/paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/paragraph.dart @@ -208,36 +208,6 @@ class ParagraphLine { return buffer.toString(); } - // Find the closest [LayoutFragment] to the given horizontal offset `dx`, that - // is not an [EllipsisFragment]. - LayoutFragment? closestFragmentAtOffset(double dx) { - final List fs = switch (fragments) { - [...final List rest, EllipsisFragment()] - || final List rest => rest, - }; - - ({LayoutFragment fragment, double distance})? closestFragment; - for (final LayoutFragment fragment in fs) { - assert(fragment is! EllipsisFragment); - final double distance; - if (dx < fragment.left) { - distance = fragment.left - dx; - } else if (dx > fragment.right) { - distance = dx - fragment.right; - } else { - return fragment; - } - assert(distance > 0); - - final double? minDistance = closestFragment?.distance; - if (minDistance == null || minDistance > distance) { - closestFragment = (fragment: fragment, distance: distance); - } - } - final LayoutFragment? fragment = closestFragment?.fragment; - return fragment; - } - @override int get hashCode => Object.hash( lineMetrics, diff --git a/third_party/txt/src/txt/paragraph.h b/third_party/txt/src/txt/paragraph.h index 5cce853933cda..b8ee55d4366f5 100644 --- a/third_party/txt/src/txt/paragraph.h +++ b/third_party/txt/src/txt/paragraph.h @@ -185,11 +185,12 @@ class Paragraph { int lineNumber, skia::textlayout::LineMetrics* lineMetrics) const = 0; - // Returns the total number of lines the paragraph has. + // Returns the total number of visible lines in the paragraph. virtual size_t GetNumberOfLines() const = 0; - // Returns the line number (zero-indexed) that contains the given code unit - // offset, or -1 if the given offset is out of bounds. + // 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 From cf02536df6583ed8368fdcd701a63b70adb191d7 Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Wed, 20 Sep 2023 15:19:14 -0700 Subject: [PATCH 3/8] add tests --- lib/web_ui/lib/src/engine/canvaskit/text.dart | 5 ++- .../engine/skwasm/skwasm_impl/paragraph.dart | 2 +- .../lib/src/engine/text/canvas_paragraph.dart | 5 +-- lib/web_ui/lib/text.dart | 2 +- lib/web_ui/test/canvaskit/text_test.dart | 37 ++++++++++++++++++ lib/web_ui/test/html/text_test.dart | 29 ++++++++++++-- testing/dart/paragraph_test.dart | 39 +++++++------------ 7 files changed, 84 insertions(+), 35 deletions(-) diff --git a/lib/web_ui/lib/src/engine/canvaskit/text.dart b/lib/web_ui/lib/src/engine/canvaskit/text.dart index 89be53c5527ce..6284ffe55bd37 100644 --- a/lib/web_ui/lib/src/engine/canvaskit/text.dart +++ b/lib/web_ui/lib/src/engine/canvaskit/text.dart @@ -742,9 +742,10 @@ class CkParagraph implements ui.Paragraph { } @override - int? getLineNumber(int codeUnitOffset) { + int? getLineNumberAt(int codeUnitOffset) { assert(!_disposed, 'Paragraph has been disposed.'); - return skiaObject.getLineNumberAt(codeUnitOffset.toDouble()).toInt(); + final int lineNumber = skiaObject.getLineNumberAt(codeUnitOffset.toDouble()).toInt(); + return lineNumber >= 0 ? lineNumber : null; } bool _disposed = false; 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 2d03f7ad22210..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 @@ -105,7 +105,7 @@ class SkwasmParagraph extends SkwasmObjectWrapper implements ui.Pa int get numberOfLines => paragraphGetLineCount(handle); @override - int? getLineNumber(int codeUnitOffset) { + int? getLineNumberAt(int codeUnitOffset) { final int lineNumber = paragraphGetLineNumberAt(handle, codeUnitOffset); return lineNumber >= 0 ? lineNumber : null; } 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 c99fc59d84dcb..8a7ea0b130670 100644 --- a/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart @@ -242,8 +242,7 @@ class CanvasParagraph implements ui.Paragraph { @override EngineLineMetrics? getLineMetricsAt(int lineNumber) { - assert(lineNumber >= 0); - return lineNumber < lines.length + return 0 <= lineNumber && lineNumber < lines.length ? lines[lineNumber].lineMetrics : null; } @@ -252,7 +251,7 @@ class CanvasParagraph implements ui.Paragraph { int get numberOfLines => lines.length; @override - int? getLineNumber(int codeUnitOffset) { + int? getLineNumberAt(int codeUnitOffset) { assert(codeUnitOffset >= 0); return _findLine(codeUnitOffset, 0, numberOfLines); } diff --git a/lib/web_ui/lib/text.dart b/lib/web_ui/lib/text.dart index 64116073265af..ba09ee5068af4 100644 --- a/lib/web_ui/lib/text.dart +++ b/lib/web_ui/lib/text.dart @@ -697,7 +697,7 @@ abstract class Paragraph { List computeLineMetrics(); LineMetrics? getLineMetricsAt(int lineNumber); int get numberOfLines; - int? getLineNumber(int codeUnitOffset); + int? getLineNumberAt(int codeUnitOffset); void dispose(); bool get debugDisposed; } 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 b6849cbd3e1d1..4af61bb936345 100644 --- a/testing/dart/paragraph_test.dart +++ b/testing/dart/paragraph_test.dart @@ -240,27 +240,6 @@ void main() { expect(line?.lineNumber, 1); }); - test('getLineMetricsAt', () { - const double fontSize = 10.0; - final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle( - fontSize: fontSize, - textDirection: TextDirection.rtl, - height: 2.0, - )); - builder.addText('Test\nTest'); - 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, 40.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)); @@ -287,13 +266,25 @@ void main() { test('OOB indices as input', () { const double fontSize = 10.0; - final Paragraph paragraph = ParagraphBuilder(ParagraphStyle( + final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle( fontSize: fontSize, - )).build(); - paragraph.layout(const ParagraphConstraints(width: double.infinity)); + 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', () { From 7f1329eb893f4c89ef1d9ea87290995225c7f4ab Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:45:18 -0700 Subject: [PATCH 4/8] fix tests --- lib/web_ui/lib/src/engine/text/canvas_paragraph.dart | 9 ++++----- lib/web_ui/lib/src/engine/text/fragmenter.dart | 3 +-- 2 files changed, 5 insertions(+), 7 deletions(-) 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 8a7ea0b130670..0b32cefc18f41 100644 --- a/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart @@ -251,18 +251,17 @@ class CanvasParagraph implements ui.Paragraph { int get numberOfLines => lines.length; @override - int? getLineNumberAt(int codeUnitOffset) { - assert(codeUnitOffset >= 0); - return _findLine(codeUnitOffset, 0, numberOfLines); - } + 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].endIndex <= codeUnitOffset) { + 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); } diff --git a/lib/web_ui/lib/src/engine/text/fragmenter.dart b/lib/web_ui/lib/src/engine/text/fragmenter.dart index dc5ff2c777c37..9fdd82e167c03 100644 --- a/lib/web_ui/lib/src/engine/text/fragmenter.dart +++ b/lib/web_ui/lib/src/engine/text/fragmenter.dart @@ -29,7 +29,6 @@ abstract class TextFragment { /// Whether this fragment's range overlaps with the range from [start] to [end]. bool overlapsWith(int start, int end) { - assert(end > start); - return start <= this.end && this.start < end; + return start < this.end && this.start < end; } } From 9e34175cf83a5b8fc705a53c9aa5ab8ba319b699 Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Wed, 11 Oct 2023 18:11:57 -0700 Subject: [PATCH 5/8] review --- .../lib/src/engine/text/canvas_paragraph.dart | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) 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 0b32cefc18f41..5c5f2658b462f 100644 --- a/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart @@ -221,17 +221,11 @@ 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; - } + final int? lineNumber = getLineNumberAt(position.offset); + if (lineNumber == null) { + return ui.TextRange.empty; } - - final ParagraphLine line = lines[i]; + final ParagraphLine line = lines[lineNumber]; return ui.TextRange(start: line.startIndex, end: line.endIndex - line.trailingNewlines); } From 67a8a57f0838f60a37faddc177917052723b6ba0 Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Thu, 19 Oct 2023 11:56:52 -0700 Subject: [PATCH 6/8] review --- lib/web_ui/skwasm/text/paragraph.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/web_ui/skwasm/text/paragraph.cpp b/lib/web_ui/skwasm/text/paragraph.cpp index 466a2ef12976a..a44baf4d1ac39 100644 --- a/lib/web_ui/skwasm/text/paragraph.cpp +++ b/lib/web_ui/skwasm/text/paragraph.cpp @@ -80,7 +80,12 @@ SKWASM_EXPORT int paragraph_getLineNumberAt(Paragraph* paragraph, SKWASM_EXPORT LineMetrics* paragraph_getLineMetricsAtIndex(Paragraph* paragraph, size_t lineNumber) { auto metrics = new LineMetrics(); - return paragraph->getLineMetricsAt(lineNumber, metrics) ? metrics : nullptr; + if (paragraph->getLineMetricsAt(lineNumber, metrics)) { + return metrics; + } else { + delete metrics; + return nullptr; + } } struct TextBoxList { From 9a9c9a6ef830eb53ea8053b82af605985677c7f6 Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Fri, 27 Oct 2023 01:03:04 -0700 Subject: [PATCH 7/8] fix test --- lib/web_ui/lib/src/engine/text/canvas_paragraph.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 5c5f2658b462f..c749a8512d38c 100644 --- a/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart +++ b/lib/web_ui/lib/src/engine/text/canvas_paragraph.dart @@ -221,11 +221,12 @@ class CanvasParagraph implements ui.Paragraph { @override ui.TextRange getLineBoundary(ui.TextPosition position) { - final int? lineNumber = getLineNumberAt(position.offset); - if (lineNumber == null) { + if (lines.isEmpty) { return ui.TextRange.empty; } - final ParagraphLine line = lines[lineNumber]; + 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); } From b505da8abdc58d90154b1e2c4fc3ece3b78e7158 Mon Sep 17 00:00:00 2001 From: LongCat is Looong <31859944+LongCatIsLooong@users.noreply.github.com> Date: Fri, 27 Oct 2023 13:41:47 -0700 Subject: [PATCH 8/8] review --- lib/ui/text/paragraph.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/ui/text/paragraph.cc b/lib/ui/text/paragraph.cc index 6f54558e33dc0..fedf84e02c7de 100644 --- a/lib/ui/text/paragraph.cc +++ b/lib/ui/text/paragraph.cc @@ -176,7 +176,7 @@ Dart_Handle Paragraph::getLineMetricsAt(int lineNumber, if (!found) { return Dart_Null(); } - Dart_Handle arguments[9] = { + std::array arguments = { Dart_NewBoolean(line.fHardBreak), Dart_NewDouble(line.fAscent), Dart_NewDouble(line.fDescent), @@ -191,8 +191,8 @@ Dart_Handle Paragraph::getLineMetricsAt(int lineNumber, Dart_NewInteger(line.fLineNumber), }; - Dart_Handle handle = Dart_InvokeClosure( - constructor, sizeof(arguments) / sizeof(Dart_Handle), arguments); + Dart_Handle handle = + Dart_InvokeClosure(constructor, arguments.size(), arguments.data()); tonic::CheckAndHandleError(handle); return handle; }