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
8 changes: 5 additions & 3 deletions lib/web_ui/lib/src/engine/text/paragraph.dart
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@ class EngineParagraph implements ui.Paragraph {
}

final List<EngineLineMetrics> lines = _measurementResult.lines;
if (start >= lines.last.endIndex) {
return <ui.TextBox>[];
}

final EngineLineMetrics startLine = _getLineForIndex(start);
EngineLineMetrics endLine = _getLineForIndex(end);

Expand Down Expand Up @@ -535,8 +539,7 @@ class EngineParagraph implements ui.Paragraph {
EngineLineMetrics _getLineForIndex(int index) {
assert(_hasLineMetrics);
final List<EngineLineMetrics> lines = _measurementResult.lines;
assert(index >= lines.first.startIndex);
assert(index <= lines.last.endIndex);
assert(index >= 0);

for (int i = 0; i < lines.length; i++) {
final EngineLineMetrics line = lines[i];
Expand All @@ -545,7 +548,6 @@ class EngineParagraph implements ui.Paragraph {
}
}

assert(index == lines.last.endIndex);
return lines.last;
}

Expand Down
16 changes: 16 additions & 0 deletions lib/web_ui/lib/src/engine/text/ruler.dart
Original file line number Diff line number Diff line change
Expand Up @@ -733,14 +733,30 @@ class ParagraphRuler {
final List<html.Rectangle<num>> clientRects = rangeSpan.getClientRects();
final List<ui.TextBox> boxes = <ui.TextBox>[];

final double maxLinesLimit = style.maxLines == null
? double.infinity
: style.maxLines * lineHeightDimensions.height;

html.Rectangle<num> previousRect;
for (html.Rectangle<num> rect in clientRects) {
// If [rect] is an empty box on the same line as the previous box, don't
// include it in the result.
if (rect.top == previousRect?.top && rect.left == rect.right) {
continue;
}
// As soon as we go beyond [maxLines], stop adding boxes.
if (rect.top >= maxLinesLimit) {
break;
}

boxes.add(ui.TextBox.fromLTRBD(
rect.left + alignOffset,
rect.top,
rect.right + alignOffset,
rect.bottom,
textDirection,
));
previousRect = rect;
}

// Cleanup after measuring the boxes.
Expand Down
198 changes: 174 additions & 24 deletions lib/web_ui/test/paragraph_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -441,12 +441,6 @@ void main() async {
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 100));

// In the dom-based measurement (except Firefox), there will be some
// discrepancies around line ends.
final isDiscrepancyExpected =
!TextMeasurementService.enableExperimentalCanvasImplementation &&
browserEngine != BrowserEngine.firefox;

// First line: "abcd\n"

// At the beginning of the first line.
Expand Down Expand Up @@ -497,8 +491,6 @@ void main() async {
paragraph.getBoxesForRange(2, 5),
<TextBox>[
TextBox.fromLTRBD(20.0, 0.0, 40.0, 10.0, TextDirection.ltr),
if (isDiscrepancyExpected)
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
],
);

Expand Down Expand Up @@ -533,8 +525,6 @@ void main() async {
paragraph.getBoxesForRange(10, 13),
<TextBox>[
TextBox.fromLTRBD(50.0, 10.0, 70.0, 20.0, TextDirection.ltr),
if (isDiscrepancyExpected)
TextBox.fromLTRBD(70.0, 10.0, 70.0, 20.0, TextDirection.ltr),
],
);

Expand Down Expand Up @@ -573,8 +563,6 @@ void main() async {
paragraph.getBoxesForRange(2, 8),
<TextBox>[
TextBox.fromLTRBD(20.0, 0.0, 40.0, 10.0, TextDirection.ltr),
if (isDiscrepancyExpected)
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 30.0, 20.0, TextDirection.ltr),
],
);
Expand All @@ -593,11 +581,7 @@ void main() async {
paragraph.getBoxesForRange(3, 14),
<TextBox>[
TextBox.fromLTRBD(30.0, 0.0, 40.0, 10.0, TextDirection.ltr),
if (isDiscrepancyExpected)
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
if (isDiscrepancyExpected)
TextBox.fromLTRBD(70.0, 10.0, 70.0, 20.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 20.0, 10.0, 30.0, TextDirection.ltr),
],
);
Expand All @@ -607,11 +591,7 @@ void main() async {
paragraph.getBoxesForRange(0, 13),
<TextBox>[
TextBox.fromLTRBD(0.0, 0.0, 40.0, 10.0, TextDirection.ltr),
if (isDiscrepancyExpected)
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
if (isDiscrepancyExpected)
TextBox.fromLTRBD(70.0, 10.0, 70.0, 20.0, TextDirection.ltr),
],
);

Expand All @@ -620,16 +600,186 @@ void main() async {
paragraph.getBoxesForRange(0, 15),
<TextBox>[
TextBox.fromLTRBD(0.0, 0.0, 40.0, 10.0, TextDirection.ltr),
if (isDiscrepancyExpected)
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
if (isDiscrepancyExpected)
TextBox.fromLTRBD(70.0, 10.0, 70.0, 20.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 20.0, 20.0, 30.0, TextDirection.ltr),
],
);
});

testEachMeasurement('getBoxesForRange with maxLines', () {
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
fontFamily: 'Ahem',
fontStyle: FontStyle.normal,
fontWeight: FontWeight.normal,
fontSize: 10,
textDirection: TextDirection.ltr,
maxLines: 2,
));
builder.addText('abcd\n');
builder.addText('abcdefg\n');
builder.addText('ab');
final Paragraph paragraph = builder.build();
paragraph.layout(const ParagraphConstraints(width: 100));

// First line: "abcd\n"

// At the beginning of the first line.
expect(
paragraph.getBoxesForRange(0, 0),
<TextBox>[],
);
// At the end of the first line.
expect(
paragraph.getBoxesForRange(4, 4),
<TextBox>[],
);
// Between "b" and "c" in the first line.
expect(
paragraph.getBoxesForRange(2, 2),
<TextBox>[],
);
// The range "ab" in the first line.
expect(
paragraph.getBoxesForRange(0, 2),
<TextBox>[
TextBox.fromLTRBD(0.0, 0.0, 20.0, 10.0, TextDirection.ltr),
],
);
// The range "bc" in the first line.
expect(
paragraph.getBoxesForRange(1, 3),
<TextBox>[
TextBox.fromLTRBD(10.0, 0.0, 30.0, 10.0, TextDirection.ltr),
],
);
// The range "d" in the first line.
expect(
paragraph.getBoxesForRange(3, 4),
<TextBox>[
TextBox.fromLTRBD(30.0, 0.0, 40.0, 10.0, TextDirection.ltr),
],
);
// The range "\n" in the first line.
expect(
paragraph.getBoxesForRange(4, 5),
<TextBox>[
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
],
);
// The range "cd\n" in the first line.
expect(
paragraph.getBoxesForRange(2, 5),
<TextBox>[
TextBox.fromLTRBD(20.0, 0.0, 40.0, 10.0, TextDirection.ltr),
],
);

// Second line: "abcdefg\n"

// At the beginning of the second line.
expect(
paragraph.getBoxesForRange(5, 5),
<TextBox>[],
);
// At the end of the second line.
expect(
paragraph.getBoxesForRange(12, 12),
<TextBox>[],
);
// The range "efg" in the second line.
expect(
paragraph.getBoxesForRange(9, 12),
<TextBox>[
TextBox.fromLTRBD(40.0, 10.0, 70.0, 20.0, TextDirection.ltr),
],
);
// The range "bcde" in the second line.
expect(
paragraph.getBoxesForRange(6, 10),
<TextBox>[
TextBox.fromLTRBD(10.0, 10.0, 50.0, 20.0, TextDirection.ltr),
],
);
// The range "fg\n" in the second line.
expect(
paragraph.getBoxesForRange(10, 13),
<TextBox>[
TextBox.fromLTRBD(50.0, 10.0, 70.0, 20.0, TextDirection.ltr),
],
);

// Last (third) line: "ab"

// At the beginning of the last line.
expect(
paragraph.getBoxesForRange(13, 13),
<TextBox>[],
);
// At the end of the last line.
expect(
paragraph.getBoxesForRange(15, 15),
<TextBox>[],
);
// The range "a" in the last line.
expect(
paragraph.getBoxesForRange(14, 15),
<TextBox>[],
);
// The range "ab" in the last line.
expect(
paragraph.getBoxesForRange(13, 15),
<TextBox>[],
);


// Combine multiple lines

// The range "cd\nabc".
expect(
paragraph.getBoxesForRange(2, 8),
<TextBox>[
TextBox.fromLTRBD(20.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 30.0, 20.0, TextDirection.ltr),
],
);

// The range "\nabcd".
expect(
paragraph.getBoxesForRange(4, 9),
<TextBox>[
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 40.0, 20.0, TextDirection.ltr),
],
);

// The range "d\nabcdefg\na".
expect(
paragraph.getBoxesForRange(3, 14),
<TextBox>[
TextBox.fromLTRBD(30.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
],
);

// The range "abcd\nabcdefg\n".
expect(
paragraph.getBoxesForRange(0, 13),
<TextBox>[
TextBox.fromLTRBD(0.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
],
);

// The range "abcd\nabcdefg\nab".
expect(
paragraph.getBoxesForRange(0, 15),
<TextBox>[
TextBox.fromLTRBD(0.0, 0.0, 40.0, 10.0, TextDirection.ltr),
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
],
);
});

test('longestLine', () {
// [Paragraph.longestLine] is only supported by canvas-based measurement.
TextMeasurementService.enableExperimentalCanvasImplementation = true;
Expand Down