Skip to content

Commit 81dabea

Browse files
koji-1009TytaniumDev
authored andcommitted
Fix cursor position when Unicode Zs category is entered in TextField (flutter#152215)
Changed the cursor position to be the same as before flutter 3.22.0 when 17 character codes in the Unicode Zs category are entered into a TextField. Extend the support for flutter#149698. As a result, flutter#149099 is resolved. The code for the Unicode-Zs category is based on the following page. https://www.compart.com/en/unicode/category/Zs Fixes flutter#149099
1 parent 00f3754 commit 81dabea

File tree

2 files changed

+78
-17
lines changed

2 files changed

+78
-17
lines changed

packages/flutter/lib/src/painting/text_painter.dart

+9-5
Original file line numberDiff line numberDiff line change
@@ -356,11 +356,15 @@ class _TextLayout {
356356
// Luckily they have the same bidi embedding level as the paragraph as per
357357
// https://unicode.org/reports/tr9/#L1, so we can anchor the caret to the
358358
// last logical trailing space.
359-
final bool hasTrailingSpaces = switch (rawString.codeUnitAt(rawString.length - 1)) {
360-
0x9 || // horizontal tab
361-
0x3000 || // ideographic space
362-
0x20 => true, // space
363-
_ => false,
359+
// Whitespace character definitions refer to Java/ICU, not Unicode-Zs.
360+
// https://github.com/unicode-org/icu/blob/23d9628f88a2d0127c564ad98297061c36d3ce77/icu4c/source/common/unicode/uchar.h#L3388-L3425
361+
final String lastCodeUnit = rawString[rawString.length - 1];
362+
final bool hasTrailingSpaces = switch (lastCodeUnit.codeUnitAt(0)) {
363+
0x0009 => true, // horizontal tab
364+
0x00A0 || // no-break space
365+
0x2007 || // figure space
366+
0x202F => false, // narrow no-break space
367+
_ => RegExp(r'\p{Space_Separator}', unicode: true).hasMatch(lastCodeUnit),
364368
};
365369

366370
final double baseline = lineMetrics.baseline;

packages/flutter/test/painting/text_painter_test.dart

+69-12
Original file line numberDiff line numberDiff line change
@@ -145,20 +145,77 @@ void main() {
145145
caretOffset = painter.getOffsetForCaret(ui.TextPosition(offset: text.length), ui.Rect.zero);
146146
expect(caretOffset.dx, painter.width);
147147

148-
// Test with trailing full-width space
149-
const String textWithFullWidthSpace = 'A\u{3000}';
150-
checkCaretOffsetsLtr(textWithFullWidthSpace);
151-
painter.text = const TextSpan(text: textWithFullWidthSpace);
152-
painter.layout();
153-
caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 0), ui.Rect.zero);
154-
expect(caretOffset.dx, 0);
155-
caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 1), ui.Rect.zero);
156-
expect(caretOffset.dx, painter.width / 2);
157-
caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: textWithFullWidthSpace.length), ui.Rect.zero);
158-
expect(caretOffset.dx, painter.width);
148+
/// Verify the handling of spaces by SkParagraph and TextPainter.
149+
///
150+
/// Test characters that are in the Unicode-Zs category but are not treated as whitespace characters by SkParagraph.
151+
/// The following character codes are intentionally excluded from the test target.
152+
/// * '\u{00A0}' (no-break space)
153+
/// * '\u{2007}' (figure space)
154+
/// * '\u{202F}' (narrow no-break space)
155+
void verifyCharacterIsConsideredTrailingSpace(String character) {
156+
final String reason = 'character: ${character.codeUnitAt(0).toRadixString(16)}';
157+
158+
text = 'A$character';
159+
checkCaretOffsetsLtr(text);
160+
painter.text = TextSpan(text: text);
161+
painter.layout();
162+
caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 0), ui.Rect.zero);
163+
expect(caretOffset.dx, 0.0, reason: reason);
164+
caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 1), ui.Rect.zero);
165+
expect(caretOffset.dx, 14.0, reason: reason);
166+
caretOffset = painter.getOffsetForCaret(ui.TextPosition(offset: text.length), ui.Rect.zero);
167+
expect(caretOffset.dx, painter.width, reason: reason);
168+
169+
painter.layout(maxWidth: 14.0);
170+
final List<ui.LineMetrics> lines = painter.computeLineMetrics();
171+
expect(lines.length, 1, reason: reason);
172+
expect(lines.first.width, 14.0, reason: reason);
173+
}
174+
175+
// Test with trailing space.
176+
verifyCharacterIsConsideredTrailingSpace('\u{0020}');
177+
178+
// Test with trailing full-width space.
179+
verifyCharacterIsConsideredTrailingSpace('\u{3000}');
180+
181+
// Test with trailing ogham space mark.
182+
verifyCharacterIsConsideredTrailingSpace('\u{1680}');
183+
184+
// Test with trailing en quad.
185+
verifyCharacterIsConsideredTrailingSpace('\u{2000}');
186+
187+
// Test with trailing em quad.
188+
verifyCharacterIsConsideredTrailingSpace('\u{2001}');
189+
190+
// Test with trailing en space.
191+
verifyCharacterIsConsideredTrailingSpace('\u{2002}');
192+
193+
// Test with trailing em space.
194+
verifyCharacterIsConsideredTrailingSpace('\u{2003}');
195+
196+
// Test with trailing three-per-em space.
197+
verifyCharacterIsConsideredTrailingSpace('\u{2004}');
198+
199+
// Test with trailing four-per-em space.
200+
verifyCharacterIsConsideredTrailingSpace('\u{2005}');
201+
202+
// Test with trailing six-per-em space.
203+
verifyCharacterIsConsideredTrailingSpace('\u{2006}');
204+
205+
// Test with trailing punctuation space.
206+
verifyCharacterIsConsideredTrailingSpace('\u{2008}');
207+
208+
// Test with trailing thin space.
209+
verifyCharacterIsConsideredTrailingSpace('\u{2009}');
210+
211+
// Test with trailing hair space.
212+
verifyCharacterIsConsideredTrailingSpace('\u{200A}');
213+
214+
// Test with trailing medium mathematical space(MMSP).
215+
verifyCharacterIsConsideredTrailingSpace('\u{205F}');
159216

160217
painter.dispose();
161-
});
218+
}, skip: isBrowser && !isSkiaWeb); // https://github.com/flutter/flutter/issues/56308
162219

163220
test('TextPainter caret test with WidgetSpan', () {
164221
// Regression test for https://github.com/flutter/flutter/issues/98458.

0 commit comments

Comments
 (0)