Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Reland "Expose more methods on ui.Paragraph: lines" (#47584) #47623

Merged
merged 2 commits into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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