Skip to content

Commit

Permalink
Reland "Expose more methods on ui.Paragraph: lines" (flutter#47584) (f…
Browse files Browse the repository at this point in the history
…lutter#47623)

The diff is in [this commit](flutter@305d930).

[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
  • Loading branch information
LongCatIsLooong authored Nov 2, 2023
1 parent dd620fc commit 5871f52
Show file tree
Hide file tree
Showing 18 changed files with 373 additions and 25 deletions.
1 change: 1 addition & 0 deletions lib/ui/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -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 = [
Expand Down
3 changes: 3 additions & 0 deletions lib/ui/dart_ui.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down
55 changes: 55 additions & 0 deletions lib/ui/text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -2992,6 +3004,32 @@ abstract class Paragraph {
/// to repeatedly call this. Instead, cache the results.
List<LineMetrics> 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();
Expand Down Expand Up @@ -3169,6 +3207,23 @@ base class _NativeParagraph extends NativeFieldWrapperClass1 implements Paragrap
@Native<Handle Function(Pointer<Void>)>(symbol: 'Paragraph::computeLineMetrics')
external Float64List _computeLineMetrics();

@override
LineMetrics? getLineMetricsAt(int lineNumber) => _getLineMetricsAt(lineNumber, LineMetrics._);
@Native<Handle Function(Pointer<Void>, Uint32, Handle)>(symbol: 'Paragraph::getLineMetricsAt')
external LineMetrics? _getLineMetricsAt(int lineNumber, Function constructor);

@override
@Native<Uint32 Function(Pointer<Void>)>(symbol: 'Paragraph::getNumberOfLines')
external int get numberOfLines;

@override
int? getLineNumberAt(int codeUnitOffset) {
final int lineNumber = _getLineNumber(codeUnitOffset);
return lineNumber < 0 ? null : lineNumber;
}
@Native<Int32 Function(Pointer<Void>, Uint32)>(symbol: 'Paragraph::getLineNumberAt')
external int _getLineNumber(int codeUnitOffset);

@override
void dispose() {
assert(!_disposed);
Expand Down
46 changes: 43 additions & 3 deletions lib/ui/text/paragraph.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -122,12 +126,12 @@ Dart_Handle Paragraph::getWordBoundary(unsigned offset) {
return tonic::DartConverter<decltype(result)>::ToDart(result);
}

Dart_Handle Paragraph::getLineBoundary(unsigned offset) {
Dart_Handle Paragraph::getLineBoundary(unsigned utf16Offset) {
std::vector<txt::LineMetrics> 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;
Expand All @@ -137,7 +141,7 @@ Dart_Handle Paragraph::getLineBoundary(unsigned offset) {
return tonic::DartConverter<decltype(result)>::ToDart(result);
}

tonic::Float64List Paragraph::computeLineMetrics() {
tonic::Float64List Paragraph::computeLineMetrics() const {
std::vector<txt::LineMetrics> metrics = m_paragraph->GetLineMetrics();

// Layout:
Expand Down Expand Up @@ -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<Dart_Handle, 9> 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();
Expand Down
5 changes: 4 additions & 1 deletion lib/ui/text/paragraph.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,10 @@ class Paragraph : public RefCountedDartWrappable<Paragraph> {
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();

Expand Down
12 changes: 12 additions & 0 deletions lib/web_ui/lib/src/engine/canvaskit/canvaskit_api.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3237,6 +3237,18 @@ extension SkParagraphExtension on SkParagraph {
List<SkLineMetrics> getLineMetrics() =>
_getLineMetrics().toDart.cast<SkLineMetrics>();

@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;
Expand Down
20 changes: 20 additions & 0 deletions lib/web_ui/lib/src/engine/canvaskit/text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 15 additions & 0 deletions lib/web_ui/lib/src/engine/skwasm/skwasm_impl/paragraph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@ class SkwasmParagraph extends SkwasmObjectWrapper<RawParagraph> 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);
Expand Down Expand Up @@ -214,6 +223,12 @@ class SkwasmParagraph extends SkwasmObjectWrapper<RawParagraph> 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(
Expand Down
43 changes: 32 additions & 11 deletions lib/web_ui/lib/src/engine/text/canvas_paragraph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand Down Expand Up @@ -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);
}

Expand All @@ -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
Expand Down
10 changes: 8 additions & 2 deletions lib/web_ui/lib/src/engine/text/layout_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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.
Expand Down
3 changes: 3 additions & 0 deletions lib/web_ui/lib/text.dart
Original file line number Diff line number Diff line change
Expand Up @@ -695,6 +695,9 @@ abstract class Paragraph {
TextRange getLineBoundary(TextPosition position);
List<TextBox> getBoxesForPlaceholders();
List<LineMetrics> computeLineMetrics();
LineMetrics? getLineMetricsAt(int lineNumber);
int get numberOfLines;
int? getLineNumberAt(int codeUnitOffset);
void dispose();
bool get debugDisposed;
}
Expand Down
12 changes: 8 additions & 4 deletions lib/web_ui/skwasm/text/paragraph.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading

0 comments on commit 5871f52

Please sign in to comment.