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

Commit 06da8b0

Browse files
authored
Fix getLineBoundary for TextAffinity (#28008)
Fix a bug in a calculation when selection is at a wordwrap
1 parent a2e6047 commit 06da8b0

File tree

2 files changed

+166
-1
lines changed

2 files changed

+166
-1
lines changed

lib/ui/text.dart

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3253,7 +3253,25 @@ class Paragraph extends NativeFieldWrapperClass1 {
32533253
/// metrics, so use it sparingly.
32543254
TextRange getLineBoundary(TextPosition position) {
32553255
final List<int> boundary = _getLineBoundary(position.offset);
3256-
return TextRange(start: boundary[0], end: boundary[1]);
3256+
final TextRange line = TextRange(start: boundary[0], end: boundary[1]);
3257+
3258+
final List<int> nextBoundary = _getLineBoundary(position.offset + 1);
3259+
final TextRange nextLine = TextRange(start: nextBoundary[0], end: nextBoundary[1]);
3260+
// If there is no next line, because we're at the end of the field, return
3261+
// line.
3262+
if (!nextLine.isValid) {
3263+
return line;
3264+
}
3265+
3266+
// _getLineBoundary only considers the offset and assumes that the
3267+
// TextAffinity is upstream. In the case that TextPosition is just after a
3268+
// wordwrap (downstream), we need to return the line for the next offset.
3269+
if (position.affinity == TextAffinity.downstream && line != nextLine
3270+
&& position.offset == line.end && line.end == nextLine.start) {
3271+
final List<int> nextBoundary = _getLineBoundary(position.offset + 1);
3272+
return TextRange(start: nextBoundary[0], end: nextBoundary[1]);
3273+
}
3274+
return line;
32573275
}
32583276
List<int> _getLineBoundary(int offset) native 'Paragraph_getLineBoundary';
32593277

testing/dart/paragraph_test.dart

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,151 @@ void main() {
5959
);
6060
}
6161
});
62+
63+
test('getLineBoundary', () {
64+
const double fontSize = 10.0;
65+
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
66+
fontFamily: 'Ahem',
67+
fontStyle: FontStyle.normal,
68+
fontWeight: FontWeight.normal,
69+
fontSize: fontSize,
70+
));
71+
builder.addText('Test Ahem');
72+
final Paragraph paragraph = builder.build();
73+
paragraph.layout(ParagraphConstraints(width: fontSize * 5.0));
74+
75+
// Wraps to two lines.
76+
expect(paragraph.height, closeTo(fontSize * 2.0, 0.001));
77+
78+
final TextPosition wrapPositionDown = TextPosition(
79+
offset: 5,
80+
affinity: TextAffinity.downstream,
81+
);
82+
TextRange line = paragraph.getLineBoundary(wrapPositionDown);
83+
expect(line.start, 5);
84+
expect(line.end, 9);
85+
86+
final TextPosition wrapPositionUp = TextPosition(
87+
offset: 5,
88+
affinity: TextAffinity.upstream,
89+
);
90+
line = paragraph.getLineBoundary(wrapPositionUp);
91+
expect(line.start, 0);
92+
expect(line.end, 5);
93+
94+
final TextPosition wrapPositionStart = TextPosition(
95+
offset: 0,
96+
affinity: TextAffinity.downstream,
97+
);
98+
line = paragraph.getLineBoundary(wrapPositionStart);
99+
expect(line.start, 0);
100+
expect(line.end, 5);
101+
102+
final TextPosition wrapPositionEnd = TextPosition(
103+
offset: 9,
104+
affinity: TextAffinity.downstream,
105+
);
106+
line = paragraph.getLineBoundary(wrapPositionEnd);
107+
expect(line.start, 5);
108+
expect(line.end, 9);
109+
});
110+
111+
test('getLineBoundary RTL', () {
112+
const double fontSize = 10.0;
113+
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
114+
fontFamily: 'Ahem',
115+
fontStyle: FontStyle.normal,
116+
fontWeight: FontWeight.normal,
117+
fontSize: fontSize,
118+
textDirection: TextDirection.rtl,
119+
));
120+
builder.addText('القاهرةالقاهرة');
121+
final Paragraph paragraph = builder.build();
122+
paragraph.layout(ParagraphConstraints(width: fontSize * 5.0));
123+
124+
// Wraps to three lines.
125+
expect(paragraph.height, closeTo(fontSize * 3.0, 0.001));
126+
127+
final TextPosition wrapPositionDown = TextPosition(
128+
offset: 5,
129+
affinity: TextAffinity.downstream,
130+
);
131+
TextRange line = paragraph.getLineBoundary(wrapPositionDown);
132+
expect(line.start, 5);
133+
expect(line.end, 10);
134+
135+
final TextPosition wrapPositionUp = TextPosition(
136+
offset: 5,
137+
affinity: TextAffinity.upstream,
138+
);
139+
line = paragraph.getLineBoundary(wrapPositionUp);
140+
expect(line.start, 0);
141+
expect(line.end, 5);
142+
143+
final TextPosition wrapPositionStart = TextPosition(
144+
offset: 0,
145+
affinity: TextAffinity.downstream,
146+
);
147+
line = paragraph.getLineBoundary(wrapPositionStart);
148+
expect(line.start, 0);
149+
expect(line.end, 5);
150+
151+
final TextPosition wrapPositionEnd = TextPosition(
152+
offset: 9,
153+
affinity: TextAffinity.downstream,
154+
);
155+
line = paragraph.getLineBoundary(wrapPositionEnd);
156+
expect(line.start, 5);
157+
expect(line.end, 10);
158+
});
159+
160+
test('getLineBoundary empty line', () {
161+
const double fontSize = 10.0;
162+
final ParagraphBuilder builder = ParagraphBuilder(ParagraphStyle(
163+
fontFamily: 'Ahem',
164+
fontStyle: FontStyle.normal,
165+
fontWeight: FontWeight.normal,
166+
fontSize: fontSize,
167+
textDirection: TextDirection.rtl,
168+
));
169+
builder.addText('Test\n\nAhem');
170+
final Paragraph paragraph = builder.build();
171+
paragraph.layout(ParagraphConstraints(width: fontSize * 5.0));
172+
173+
// Three lines due to line breaks, with the middle line being empty.
174+
expect(paragraph.height, closeTo(fontSize * 3.0, 0.001));
175+
176+
final TextPosition emptyLinePosition = TextPosition(
177+
offset: 5,
178+
affinity: TextAffinity.downstream,
179+
);
180+
TextRange line = paragraph.getLineBoundary(emptyLinePosition);
181+
expect(line.start, 5);
182+
expect(line.end, 5);
183+
184+
// Since these are hard newlines, TextAffinity has no effect here.
185+
final TextPosition emptyLinePositionUpstream = TextPosition(
186+
offset: 5,
187+
affinity: TextAffinity.upstream,
188+
);
189+
line = paragraph.getLineBoundary(emptyLinePositionUpstream);
190+
expect(line.start, 5);
191+
expect(line.end, 5);
192+
193+
final TextPosition endOfFirstLinePosition = TextPosition(
194+
offset: 4,
195+
affinity: TextAffinity.downstream,
196+
);
197+
line = paragraph.getLineBoundary(endOfFirstLinePosition);
198+
expect(line.start, 0);
199+
expect(line.end, 4);
200+
201+
final TextPosition startOfLastLinePosition = TextPosition(
202+
offset: 6,
203+
affinity: TextAffinity.downstream,
204+
);
205+
line = paragraph.getLineBoundary(startOfLastLinePosition);
206+
expect(line.start, 6);
207+
expect(line.end, 10);
208+
});
62209
}

0 commit comments

Comments
 (0)