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

Commit d670059

Browse files
authored
[web] Respect maxLines when calculating boxes for a range (#16749)
1 parent 971122b commit d670059

File tree

3 files changed

+195
-27
lines changed

3 files changed

+195
-27
lines changed

lib/web_ui/lib/src/engine/text/paragraph.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,10 @@ class EngineParagraph implements ui.Paragraph {
374374
}
375375

376376
final List<EngineLineMetrics> lines = _measurementResult.lines;
377+
if (start >= lines.last.endIndex) {
378+
return <ui.TextBox>[];
379+
}
380+
377381
final EngineLineMetrics startLine = _getLineForIndex(start);
378382
EngineLineMetrics endLine = _getLineForIndex(end);
379383

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

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

548-
assert(index == lines.last.endIndex);
549551
return lines.last;
550552
}
551553

lib/web_ui/lib/src/engine/text/ruler.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,14 +733,30 @@ class ParagraphRuler {
733733
final List<html.Rectangle<num>> clientRects = rangeSpan.getClientRects();
734734
final List<ui.TextBox> boxes = <ui.TextBox>[];
735735

736+
final double maxLinesLimit = style.maxLines == null
737+
? double.infinity
738+
: style.maxLines * lineHeightDimensions.height;
739+
740+
html.Rectangle<num> previousRect;
736741
for (html.Rectangle<num> rect in clientRects) {
742+
// If [rect] is an empty box on the same line as the previous box, don't
743+
// include it in the result.
744+
if (rect.top == previousRect?.top && rect.left == rect.right) {
745+
continue;
746+
}
747+
// As soon as we go beyond [maxLines], stop adding boxes.
748+
if (rect.top >= maxLinesLimit) {
749+
break;
750+
}
751+
737752
boxes.add(ui.TextBox.fromLTRBD(
738753
rect.left + alignOffset,
739754
rect.top,
740755
rect.right + alignOffset,
741756
rect.bottom,
742757
textDirection,
743758
));
759+
previousRect = rect;
744760
}
745761

746762
// Cleanup after measuring the boxes.

lib/web_ui/test/paragraph_test.dart

Lines changed: 174 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -441,12 +441,6 @@ void main() async {
441441
final Paragraph paragraph = builder.build();
442442
paragraph.layout(const ParagraphConstraints(width: 100));
443443

444-
// In the dom-based measurement (except Firefox), there will be some
445-
// discrepancies around line ends.
446-
final isDiscrepancyExpected =
447-
!TextMeasurementService.enableExperimentalCanvasImplementation &&
448-
browserEngine != BrowserEngine.firefox;
449-
450444
// First line: "abcd\n"
451445

452446
// At the beginning of the first line.
@@ -497,8 +491,6 @@ void main() async {
497491
paragraph.getBoxesForRange(2, 5),
498492
<TextBox>[
499493
TextBox.fromLTRBD(20.0, 0.0, 40.0, 10.0, TextDirection.ltr),
500-
if (isDiscrepancyExpected)
501-
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
502494
],
503495
);
504496

@@ -533,8 +525,6 @@ void main() async {
533525
paragraph.getBoxesForRange(10, 13),
534526
<TextBox>[
535527
TextBox.fromLTRBD(50.0, 10.0, 70.0, 20.0, TextDirection.ltr),
536-
if (isDiscrepancyExpected)
537-
TextBox.fromLTRBD(70.0, 10.0, 70.0, 20.0, TextDirection.ltr),
538528
],
539529
);
540530

@@ -573,8 +563,6 @@ void main() async {
573563
paragraph.getBoxesForRange(2, 8),
574564
<TextBox>[
575565
TextBox.fromLTRBD(20.0, 0.0, 40.0, 10.0, TextDirection.ltr),
576-
if (isDiscrepancyExpected)
577-
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
578566
TextBox.fromLTRBD(0.0, 10.0, 30.0, 20.0, TextDirection.ltr),
579567
],
580568
);
@@ -593,11 +581,7 @@ void main() async {
593581
paragraph.getBoxesForRange(3, 14),
594582
<TextBox>[
595583
TextBox.fromLTRBD(30.0, 0.0, 40.0, 10.0, TextDirection.ltr),
596-
if (isDiscrepancyExpected)
597-
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
598584
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
599-
if (isDiscrepancyExpected)
600-
TextBox.fromLTRBD(70.0, 10.0, 70.0, 20.0, TextDirection.ltr),
601585
TextBox.fromLTRBD(0.0, 20.0, 10.0, 30.0, TextDirection.ltr),
602586
],
603587
);
@@ -607,11 +591,7 @@ void main() async {
607591
paragraph.getBoxesForRange(0, 13),
608592
<TextBox>[
609593
TextBox.fromLTRBD(0.0, 0.0, 40.0, 10.0, TextDirection.ltr),
610-
if (isDiscrepancyExpected)
611-
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
612594
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
613-
if (isDiscrepancyExpected)
614-
TextBox.fromLTRBD(70.0, 10.0, 70.0, 20.0, TextDirection.ltr),
615595
],
616596
);
617597

@@ -620,16 +600,186 @@ void main() async {
620600
paragraph.getBoxesForRange(0, 15),
621601
<TextBox>[
622602
TextBox.fromLTRBD(0.0, 0.0, 40.0, 10.0, TextDirection.ltr),
623-
if (isDiscrepancyExpected)
624-
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
625603
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
626-
if (isDiscrepancyExpected)
627-
TextBox.fromLTRBD(70.0, 10.0, 70.0, 20.0, TextDirection.ltr),
628604
TextBox.fromLTRBD(0.0, 20.0, 20.0, 30.0, TextDirection.ltr),
629605
],
630606
);
631607
});
632608

609+
testEachMeasurement('getBoxesForRange with maxLines', () {
610+
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
611+
fontFamily: 'Ahem',
612+
fontStyle: FontStyle.normal,
613+
fontWeight: FontWeight.normal,
614+
fontSize: 10,
615+
textDirection: TextDirection.ltr,
616+
maxLines: 2,
617+
));
618+
builder.addText('abcd\n');
619+
builder.addText('abcdefg\n');
620+
builder.addText('ab');
621+
final Paragraph paragraph = builder.build();
622+
paragraph.layout(const ParagraphConstraints(width: 100));
623+
624+
// First line: "abcd\n"
625+
626+
// At the beginning of the first line.
627+
expect(
628+
paragraph.getBoxesForRange(0, 0),
629+
<TextBox>[],
630+
);
631+
// At the end of the first line.
632+
expect(
633+
paragraph.getBoxesForRange(4, 4),
634+
<TextBox>[],
635+
);
636+
// Between "b" and "c" in the first line.
637+
expect(
638+
paragraph.getBoxesForRange(2, 2),
639+
<TextBox>[],
640+
);
641+
// The range "ab" in the first line.
642+
expect(
643+
paragraph.getBoxesForRange(0, 2),
644+
<TextBox>[
645+
TextBox.fromLTRBD(0.0, 0.0, 20.0, 10.0, TextDirection.ltr),
646+
],
647+
);
648+
// The range "bc" in the first line.
649+
expect(
650+
paragraph.getBoxesForRange(1, 3),
651+
<TextBox>[
652+
TextBox.fromLTRBD(10.0, 0.0, 30.0, 10.0, TextDirection.ltr),
653+
],
654+
);
655+
// The range "d" in the first line.
656+
expect(
657+
paragraph.getBoxesForRange(3, 4),
658+
<TextBox>[
659+
TextBox.fromLTRBD(30.0, 0.0, 40.0, 10.0, TextDirection.ltr),
660+
],
661+
);
662+
// The range "\n" in the first line.
663+
expect(
664+
paragraph.getBoxesForRange(4, 5),
665+
<TextBox>[
666+
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
667+
],
668+
);
669+
// The range "cd\n" in the first line.
670+
expect(
671+
paragraph.getBoxesForRange(2, 5),
672+
<TextBox>[
673+
TextBox.fromLTRBD(20.0, 0.0, 40.0, 10.0, TextDirection.ltr),
674+
],
675+
);
676+
677+
// Second line: "abcdefg\n"
678+
679+
// At the beginning of the second line.
680+
expect(
681+
paragraph.getBoxesForRange(5, 5),
682+
<TextBox>[],
683+
);
684+
// At the end of the second line.
685+
expect(
686+
paragraph.getBoxesForRange(12, 12),
687+
<TextBox>[],
688+
);
689+
// The range "efg" in the second line.
690+
expect(
691+
paragraph.getBoxesForRange(9, 12),
692+
<TextBox>[
693+
TextBox.fromLTRBD(40.0, 10.0, 70.0, 20.0, TextDirection.ltr),
694+
],
695+
);
696+
// The range "bcde" in the second line.
697+
expect(
698+
paragraph.getBoxesForRange(6, 10),
699+
<TextBox>[
700+
TextBox.fromLTRBD(10.0, 10.0, 50.0, 20.0, TextDirection.ltr),
701+
],
702+
);
703+
// The range "fg\n" in the second line.
704+
expect(
705+
paragraph.getBoxesForRange(10, 13),
706+
<TextBox>[
707+
TextBox.fromLTRBD(50.0, 10.0, 70.0, 20.0, TextDirection.ltr),
708+
],
709+
);
710+
711+
// Last (third) line: "ab"
712+
713+
// At the beginning of the last line.
714+
expect(
715+
paragraph.getBoxesForRange(13, 13),
716+
<TextBox>[],
717+
);
718+
// At the end of the last line.
719+
expect(
720+
paragraph.getBoxesForRange(15, 15),
721+
<TextBox>[],
722+
);
723+
// The range "a" in the last line.
724+
expect(
725+
paragraph.getBoxesForRange(14, 15),
726+
<TextBox>[],
727+
);
728+
// The range "ab" in the last line.
729+
expect(
730+
paragraph.getBoxesForRange(13, 15),
731+
<TextBox>[],
732+
);
733+
734+
735+
// Combine multiple lines
736+
737+
// The range "cd\nabc".
738+
expect(
739+
paragraph.getBoxesForRange(2, 8),
740+
<TextBox>[
741+
TextBox.fromLTRBD(20.0, 0.0, 40.0, 10.0, TextDirection.ltr),
742+
TextBox.fromLTRBD(0.0, 10.0, 30.0, 20.0, TextDirection.ltr),
743+
],
744+
);
745+
746+
// The range "\nabcd".
747+
expect(
748+
paragraph.getBoxesForRange(4, 9),
749+
<TextBox>[
750+
TextBox.fromLTRBD(40.0, 0.0, 40.0, 10.0, TextDirection.ltr),
751+
TextBox.fromLTRBD(0.0, 10.0, 40.0, 20.0, TextDirection.ltr),
752+
],
753+
);
754+
755+
// The range "d\nabcdefg\na".
756+
expect(
757+
paragraph.getBoxesForRange(3, 14),
758+
<TextBox>[
759+
TextBox.fromLTRBD(30.0, 0.0, 40.0, 10.0, TextDirection.ltr),
760+
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
761+
],
762+
);
763+
764+
// The range "abcd\nabcdefg\n".
765+
expect(
766+
paragraph.getBoxesForRange(0, 13),
767+
<TextBox>[
768+
TextBox.fromLTRBD(0.0, 0.0, 40.0, 10.0, TextDirection.ltr),
769+
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
770+
],
771+
);
772+
773+
// The range "abcd\nabcdefg\nab".
774+
expect(
775+
paragraph.getBoxesForRange(0, 15),
776+
<TextBox>[
777+
TextBox.fromLTRBD(0.0, 0.0, 40.0, 10.0, TextDirection.ltr),
778+
TextBox.fromLTRBD(0.0, 10.0, 70.0, 20.0, TextDirection.ltr),
779+
],
780+
);
781+
});
782+
633783
test('longestLine', () {
634784
// [Paragraph.longestLine] is only supported by canvas-based measurement.
635785
TextMeasurementService.enableExperimentalCanvasImplementation = true;

0 commit comments

Comments
 (0)