mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Reland "Expose more methods on ui.Paragraph: lines" (#47584) (flutter/engine#47623)
The diff is in [this commit](305d930fe1).
[C++, Objective-C, Java style guides]: https://github.com/flutter/engine/blob/main/CONTRIBUTING.md#style
This commit is contained in:
parent
80a1a1d5ec
commit
ef6aba043c
@ -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 = [
|
||||
|
||||
@ -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) \
|
||||
|
||||
@ -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<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();
|
||||
@ -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);
|
||||
|
||||
@ -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<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;
|
||||
@ -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:
|
||||
@ -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();
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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);
|
||||
@ -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(
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
@ -79,10 +79,6 @@ Future<void> 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<void> 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);
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -301,6 +301,11 @@ std::vector<LineMetrics>& 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<size_t> ParagraphSkia::GetWordBoundary(size_t offset) {
|
||||
return Paragraph::Range<size_t>(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;
|
||||
|
||||
|
||||
@ -50,6 +50,14 @@ class ParagraphSkia : public Paragraph {
|
||||
|
||||
std::vector<LineMetrics>& 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;
|
||||
|
||||
@ -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<size_t> GetWordBoundary(size_t offset) = 0;
|
||||
|
||||
virtual std::vector<LineMetrics>& 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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user