Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
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
11 changes: 9 additions & 2 deletions lib/web_ui/lib/src/engine/text/measurement.dart
Original file line number Diff line number Diff line change
Expand Up @@ -412,12 +412,15 @@ class DomTextMeasurementService extends TextMeasurementService {
_applySubPixelRoundingHack(minIntrinsicWidth, maxIntrinsicWidth);
final double ideographicBaseline = alphabeticBaseline * _baselineRatioHack;

final String text = paragraph._plainText;
List<EngineLineMetrics> lines;
if (paragraph._plainText != null) {
if (text != null) {
final double lineWidth = maxIntrinsicWidth;
lines = <EngineLineMetrics>[
EngineLineMetrics.withText(
paragraph._plainText,
text,
startIndex: 0,
endIndex: text.length,
hardBreak: true,
width: lineWidth,
lineNumber: 0,
Expand Down Expand Up @@ -767,6 +770,8 @@ class LinesCalculator {
);
lines.add(EngineLineMetrics.withText(
_text.substring(_lineStart, breakingPoint) + _style.ellipsis,
startIndex: _lineStart,
endIndex: chunkEnd,
hardBreak: false,
width: measureSubstring(_lineStart, breakingPoint) + _ellipsisWidth,
lineNumber: lines.length,
Expand Down Expand Up @@ -823,6 +828,8 @@ class LinesCalculator {
final int lineNumber = lines.length;
final EngineLineMetrics metrics = EngineLineMetrics.withText(
_text.substring(_lineStart, endWithoutNewlines),
startIndex: _lineStart,
endIndex: lineEnd,
hardBreak: isHardBreak,
width: measureSubstring(_lineStart, endWithoutSpace),
lineNumber: lineNumber,
Expand Down
34 changes: 30 additions & 4 deletions lib/web_ui/lib/src/engine/text/paragraph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ class EngineLineMetrics implements ui.LineMetrics {
this.left,
this.baseline,
this.lineNumber,
}) : text = null;
}) : text = null,
startIndex = -1,
endIndex = -1;

EngineLineMetrics.withText(
this.text, {
@required this.startIndex,
@required this.endIndex,
@required this.hardBreak,
this.ascent,
this.descent,
Expand All @@ -36,6 +40,15 @@ class EngineLineMetrics implements ui.LineMetrics {
/// The textual content representing this line.
final String text;

/// The index (inclusive) in the text where this line begins.
final int startIndex;

/// The index (exclusive) in the text where this line ends.
///
/// When the line contains an overflow, then [endIndex] goes until the end of
/// the text and doesn't stop at the overflow cutoff.
final int endIndex;

@override
final bool hardBreak;

Expand Down Expand Up @@ -66,6 +79,8 @@ class EngineLineMetrics implements ui.LineMetrics {
@override
int get hashCode => ui.hashValues(
text,
startIndex,
endIndex,
hardBreak,
ascent,
descent,
Expand All @@ -88,6 +103,8 @@ class EngineLineMetrics implements ui.LineMetrics {
}
final EngineLineMetrics typedOther = other;
return text == typedOther.text &&
startIndex == typedOther.startIndex &&
endIndex == typedOther.endIndex &&
hardBreak == typedOther.hardBreak &&
ascent == typedOther.ascent &&
descent == typedOther.descent &&
Expand Down Expand Up @@ -431,9 +448,18 @@ class EngineParagraph implements ui.Paragraph {

@override
ui.TextRange getLineBoundary(ui.TextPosition position) {
// TODO(flutter_web): https://github.com/flutter/flutter/issues/39537
// Depends upon LineMetrics measurement.
return null;
final List<EngineLineMetrics> lines = _measurementResult.lines;
if (lines != null) {
final int offset = position.offset;

for (int i = 0; i < lines.length; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would binary search be overkill in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes :)

Most paragraphs are 1-4 lines.

final EngineLineMetrics line = lines[i];
if (offset >= line.startIndex && offset < line.endIndex) {
return ui.TextRange(start: line.startIndex, end: line.endIndex);
}
}
}
return ui.TextRange.empty;
}

@override
Expand Down
70 changes: 70 additions & 0 deletions lib/web_ui/test/paragraph_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,74 @@ void main() async {
TextMeasurementService.clearCache();
TextMeasurementService.enableExperimentalCanvasImplementation = false;
});

testEachMeasurement('getLineBoundary (single-line)', () {
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
));
builder.addText('One single line');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 400.0));

// "One single line".length == 15
for (int i = 0; i < 15; i++) {
expect(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a reason: to this expect that tells what i is, so that it's easier to debug.

For example:
reason: 'failed at offset $i'

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice pro-tip! Thanks I'll add it.

paragraph.getLineBoundary(TextPosition(offset: i)),
TextRange(start: 0, end: 15),
reason: 'failed at offset $i',
);
}
});

test('getLineBoundary (multi-line)', () {
// [Paragraph.getLineBoundary] for multi-line paragraphs is only supported
// by canvas-based measurement.
TextMeasurementService.enableExperimentalCanvasImplementation = true;
TextMeasurementService.initialize(rulerCacheCapacity: 2);

final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
));
builder.addText('First line\n');
builder.addText('Second line\n');
builder.addText('Third line');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 400.0));

// "First line\n".length == 11
for (int i = 0; i < 11; i++) {
expect(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a reason:

paragraph.getLineBoundary(TextPosition(offset: i)),
TextRange(start: 0, end: 11),
reason: 'failed at offset $i',
);
}

// "Second line\n".length == 12
for (int i = 11; i < 23; i++) {
expect(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a reason:

paragraph.getLineBoundary(TextPosition(offset: i)),
TextRange(start: 11, end: 23),
reason: 'failed at offset $i',
);
}

// "Third line".length == 10
for (int i = 23; i < 33; i++) {
expect(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a reason:

paragraph.getLineBoundary(TextPosition(offset: i)),
TextRange(start: 23, end: 33),
reason: 'failed at offset $i',
);
}

TextMeasurementService.clearCache();
TextMeasurementService.enableExperimentalCanvasImplementation = false;
});
}
Loading