From fba4fdb4e623a69687563b07380d709d893493ca Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Tue, 24 Jul 2018 14:26:06 -0700 Subject: [PATCH] Fix off by one error in TextPainter. (#19429) --- .../lib/src/painting/text_painter.dart | 2 +- .../test/painting/text_painter_test.dart | 113 ++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index 9022683f82..12d39e81e2 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart @@ -426,7 +426,7 @@ class TextPainter { } Offset _getOffsetFromDownstream(int offset, Rect caretPrototype) { - final int nextCodeUnit = _text.codeUnitAt(offset); + final int nextCodeUnit = _text.codeUnitAt(offset - 1); if (nextCodeUnit == null) return null; final int nextRuneOffset = _isUtf16Surrogate(nextCodeUnit) ? offset + 2 : offset + 1; diff --git a/packages/flutter/test/painting/text_painter_test.dart b/packages/flutter/test/painting/text_painter_test.dart index fef937b5f4..b70e2b1acd 100644 --- a/packages/flutter/test/painting/text_painter_test.dart +++ b/packages/flutter/test/painting/text_painter_test.dart @@ -179,4 +179,117 @@ void main() { expect(painter.minIntrinsicWidth, 90.0); expect(painter.maxIntrinsicWidth, 180.0); }, skip: true); // https://github.com/flutter/flutter/issues/13512 + + test('TextPainter handles newlines properly', () { + final TextPainter painter = new TextPainter() + ..textDirection = TextDirection.ltr; + + String text = 'aaa'; + painter.text = new TextSpan(text: text); + painter.layout(); + + Offset caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 0), ui.Rect.zero); + expect(caretOffset.dx, closeTo(0.0, 0.0001)); + caretOffset = painter.getOffsetForCaret(new ui.TextPosition(offset: text.length), ui.Rect.zero); + expect(caretOffset.dx, painter.width); + expect(caretOffset.dy, closeTo(0.0, 0.0001)); + + // Check that getOffsetForCaret handles a trailing newline when affinity is downstream. + text = 'aaa\n'; + painter.text = new TextSpan(text: text); + painter.layout(); + caretOffset = painter.getOffsetForCaret(new ui.TextPosition(offset: text.length), ui.Rect.zero); + expect(caretOffset.dx, closeTo(0.0, 0.0001)); + expect(caretOffset.dy, closeTo(14.0, 0.0001)); + + // Check that getOffsetForCaret handles a trailing newline when affinity is upstream. + text = 'aaa\n'; + painter.text = new TextSpan(text: text); + painter.layout(); + caretOffset = painter.getOffsetForCaret(new ui.TextPosition(offset: text.length, affinity: TextAffinity.upstream), ui.Rect.zero); + expect(caretOffset.dx, painter.width); + expect(caretOffset.dy, closeTo(0.0, 0.0001)); + + // Correctly moves through second line with downstream affinity. + text = 'aaa\naaa'; + painter.text = new TextSpan(text: text); + painter.layout(); + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 4), ui.Rect.zero); + expect(caretOffset.dx, closeTo(0.0, 0.0001)); + expect(caretOffset.dy, closeTo(14.0, 0.0001)); + + // Correctly moves through second line with upstream affinity. + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 4, affinity: TextAffinity.upstream), ui.Rect.zero); + expect(caretOffset.dx, closeTo(42.0, 0.0001)); + expect(caretOffset.dy, closeTo(0.0, 0.0001)); + + // Correctly handles multiple trailing newlines. + text = 'aaa\n\n\n'; + painter.text = new TextSpan(text: text); + painter.layout(); + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 4), ui.Rect.zero); + expect(caretOffset.dx, closeTo(0.0, 0.0001)); + expect(caretOffset.dy, closeTo(14.0, 0.001)); + + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 5), ui.Rect.zero); + expect(caretOffset.dx, closeTo(0.0, 0.0001)); + expect(caretOffset.dy, closeTo(28.0, 0.001)); + + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 6), ui.Rect.zero); + expect(caretOffset.dx, closeTo(0.0, 0.0001)); + expect(caretOffset.dy, closeTo(42.0, 0.0001)); + + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 6, affinity: TextAffinity.upstream), ui.Rect.zero); + expect(caretOffset.dx, closeTo(0.0, 0.0001)); + expect(caretOffset.dy, closeTo(28.0, 0.0001)); + + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 5, affinity: TextAffinity.upstream), ui.Rect.zero); + expect(caretOffset.dx, closeTo(0.0, 0.0001)); + expect(caretOffset.dy, closeTo(14.0, 0.0001)); + + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 4, affinity: TextAffinity.upstream), ui.Rect.zero); + expect(caretOffset.dx, closeTo(42.0, 0.0001)); + expect(caretOffset.dy, closeTo(0.0, 0.0001)); + + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 3, affinity: TextAffinity.upstream), ui.Rect.zero); + expect(caretOffset.dx, closeTo(42.0, 0.0001)); + expect(caretOffset.dy, closeTo(0.0, 0.0001)); + + // Correctly handles multiple leading newlines + text = '\n\n\naaa'; + painter.text = new TextSpan(text: text); + painter.layout(); + + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 3), ui.Rect.zero); + expect(caretOffset.dx, closeTo(0.0, 0.0001)); + expect(caretOffset.dy, closeTo(42.0, 0.0001)); + + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 2), ui.Rect.zero); + expect(caretOffset.dx, closeTo(0.0, 0.0001)); + expect(caretOffset.dy, closeTo(28.0, 0.0001)); + + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 1), ui.Rect.zero); + expect(caretOffset.dx, closeTo(0.0, 0.0001)); + expect(caretOffset.dy,closeTo(14.0, 0.0001)); + + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 0), ui.Rect.zero); + expect(caretOffset.dx, closeTo(0.0, 0.0001)); + expect(caretOffset.dy, closeTo(0.0, 0.0001)); + + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 0, affinity: TextAffinity.upstream), ui.Rect.zero); + expect(caretOffset.dx, closeTo(0.0, 0.0001)); + expect(caretOffset.dy, closeTo(0.0, 0.0001)); + + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 1, affinity: TextAffinity.upstream), ui.Rect.zero); + expect(caretOffset.dx, closeTo(0.0, 0.0001)); + expect(caretOffset.dy, closeTo(0.0, 0.0001)); + + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 2, affinity: TextAffinity.upstream), ui.Rect.zero); + expect(caretOffset.dx, closeTo(0.0, 0.0001)); + expect(caretOffset.dy, closeTo(14.0, 0.0001)); + + caretOffset = painter.getOffsetForCaret(const ui.TextPosition(offset: 3, affinity: TextAffinity.upstream), ui.Rect.zero); + expect(caretOffset.dx, closeTo(0.0, 0.0001)); + expect(caretOffset.dy, closeTo(28.0, 0.0001)); + }); }