| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/painting.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| const bool skipTestsWithKnownBugs = true; |
| const bool skipExpectsWithKnownBugs = false; |
| |
| void main() { |
| test('TextPainter - basic words', () { |
| final TextPainter painter = TextPainter() |
| ..textDirection = TextDirection.ltr; |
| |
| painter.text = const TextSpan( |
| text: 'ABC DEF\nGHI', |
| style: TextStyle(fontSize: 10.0), |
| ); |
| painter.layout(); |
| |
| expect( |
| painter.getWordBoundary(const TextPosition(offset: 1)), |
| const TextRange(start: 0, end: 3), |
| ); |
| expect( |
| painter.getWordBoundary(const TextPosition(offset: 5)), |
| const TextRange(start: 4, end: 7), |
| ); |
| expect( |
| painter.getWordBoundary(const TextPosition(offset: 9)), |
| const TextRange(start: 8, end: 11), |
| ); |
| painter.dispose(); |
| }); |
| |
| test('TextPainter - bidi overrides in LTR', () { |
| final TextPainter painter = TextPainter() |
| ..textDirection = TextDirection.ltr; |
| |
| painter.text = const TextSpan( |
| text: '${Unicode.RLO}HEBREW1 ${Unicode.LRO}english2${Unicode.PDF} HEBREW3${Unicode.PDF}', |
| // 0 12345678 9 101234567 18 90123456 27 |
| style: TextStyle(fontSize: 10.0), |
| ); |
| TextSpan textSpan = painter.text! as TextSpan; |
| expect(textSpan.text!.length, 28); |
| painter.layout(); |
| |
| // The skips here are because the old rendering code considers the bidi formatting characters |
| // to be part of the word sometimes and not others, which is fine, but we'd mildly prefer if |
| // we were consistently considering them part of words always. |
| final TextRange hebrew1 = painter.getWordBoundary(const TextPosition(offset: 4)); |
| expect(hebrew1, const TextRange(start: 0, end: 8), skip: skipExpectsWithKnownBugs); // https://github.com/flutter/flutter/issues/87536 |
| final TextRange english2 = painter.getWordBoundary(const TextPosition(offset: 14)); |
| expect(english2, const TextRange(start: 9, end: 19), skip: skipExpectsWithKnownBugs); // https://github.com/flutter/flutter/issues/87536 |
| final TextRange hebrew3 = painter.getWordBoundary(const TextPosition(offset: 24)); |
| expect(hebrew3, const TextRange(start: 20, end: 28)); |
| |
| // >>>>>>>>>>>>>>> embedding level 2 |
| // <============================================== embedding level 1 |
| // ------------------------------------------------> embedding level 0 |
| // 0 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 2 |
| // 0 6 5 4 3 2 1 0 9 0 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1 7 <- index of character in string |
| // Paints as: 3 W E R B E H e n g l i s h 2 1 W E R B E H |
| // 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 <- pixel offset at boundary |
| // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 |
| // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 |
| |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 0, affinity: TextAffinity.upstream), Rect.zero), |
| Offset.zero, |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 0), Rect.zero), |
| Offset.zero, |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 1, affinity: TextAffinity.upstream), Rect.zero), |
| const Offset(240.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 1), Rect.zero), |
| const Offset(240.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 7, affinity: TextAffinity.upstream), Rect.zero), |
| const Offset(180.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 7), Rect.zero), |
| const Offset(180.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 8, affinity: TextAffinity.upstream), Rect.zero), |
| const Offset(170.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 8), Rect.zero), |
| const Offset(170.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 9, affinity: TextAffinity.upstream), Rect.zero), |
| const Offset(160.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 9), Rect.zero), |
| const Offset(160.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 10, affinity: TextAffinity.upstream), Rect.zero), |
| const Offset(80.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 10), Rect.zero), |
| const Offset(80.0, 0.0), |
| ); |
| |
| expect( |
| painter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: 27)), |
| const <TextBox>[ |
| TextBox.fromLTRBD(160.0, 0.0, 240.0, 10.0, TextDirection.rtl), // HEBREW1 |
| TextBox.fromLTRBD( 80.0, 0.0, 160.0, 10.0, TextDirection.ltr), // english2 |
| TextBox.fromLTRBD( 0.0, 0.0, 80.0, 10.0, TextDirection.rtl), // HEBREW3 |
| ], |
| // Horizontal offsets are currently one pixel off in places; vertical offsets are good. |
| // The list is currently in the wrong order (so selection boxes will paint in the wrong order). |
| ); |
| |
| textSpan = painter.text! as TextSpan; |
| final List<List<TextBox>> list = <List<TextBox>>[ |
| for (int index = 0; index < textSpan.text!.length; index += 1) |
| painter.getBoxesForSelection(TextSelection(baseOffset: index, extentOffset: index + 1)), |
| ]; |
| expect(list, const <List<TextBox>>[ |
| <TextBox>[], // U+202E, non-printing Unicode bidi formatting character |
| <TextBox>[TextBox.fromLTRBD(230.0, 0.0, 240.0, 10.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(220.0, 0.0, 230.0, 10.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(210.0, 0.0, 220.0, 10.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(200.0, 0.0, 210.0, 10.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(190.0, 0.0, 200.0, 10.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(180.0, 0.0, 190.0, 10.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(170.0, 0.0, 180.0, 10.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(160.0, 0.0, 170.0, 10.0, TextDirection.rtl)], |
| <TextBox>[], // U+202D, non-printing Unicode bidi formatting character |
| <TextBox>[TextBox.fromLTRBD(80.0, 0.0, 90.0, 10.0, TextDirection.ltr)], |
| <TextBox>[TextBox.fromLTRBD(90.0, 0.0, 100.0, 10.0, TextDirection.ltr)], |
| <TextBox>[TextBox.fromLTRBD(100.0, 0.0, 110.0, 10.0, TextDirection.ltr)], |
| <TextBox>[TextBox.fromLTRBD(110.0, 0.0, 120.0, 10.0, TextDirection.ltr)], |
| <TextBox>[TextBox.fromLTRBD(120.0, 0.0, 130.0, 10.0, TextDirection.ltr)], |
| <TextBox>[TextBox.fromLTRBD(130.0, 0.0, 140.0, 10.0, TextDirection.ltr)], |
| <TextBox>[TextBox.fromLTRBD(140.0, 0.0, 150.0, 10.0, TextDirection.ltr)], |
| <TextBox>[TextBox.fromLTRBD(150.0, 0.0, 160.0, 10.0, TextDirection.ltr)], |
| <TextBox>[], // U+202C, non-printing Unicode bidi formatting character |
| <TextBox>[TextBox.fromLTRBD(70.0, 0.0, 80.0, 10.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(60.0, 0.0, 70.0, 10.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(50.0, 0.0, 60.0, 10.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(40.0, 0.0, 50.0, 10.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(30.0, 0.0, 40.0, 10.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(20.0, 0.0, 30.0, 10.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(10.0, 0.0, 20.0, 10.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(0.0, 0.0, 10.0, 10.0, TextDirection.rtl)], |
| <TextBox>[], // U+202C, non-printing Unicode bidi formatting character |
| // The list currently has one extra bogus entry (the last entry, for the |
| // trailing U+202C PDF, should be empty but is one-pixel-wide instead). |
| ], skip: skipExpectsWithKnownBugs); // https://github.com/flutter/flutter/issues/87536 |
| painter.dispose(); |
| }, skip: skipTestsWithKnownBugs); // https://github.com/flutter/flutter/issues/87536 |
| |
| test('TextPainter - bidi overrides in RTL', () { |
| final TextPainter painter = TextPainter() |
| ..textDirection = TextDirection.rtl; |
| |
| painter.text = const TextSpan( |
| text: '${Unicode.RLO}HEBREW1 ${Unicode.LRO}english2${Unicode.PDF} HEBREW3${Unicode.PDF}', |
| // 0 12345678 9 101234567 18 90123456 27 |
| style: TextStyle(fontSize: 10.0), |
| ); |
| final TextSpan textSpan = painter.text! as TextSpan; |
| expect(textSpan.text!.length, 28); |
| painter.layout(); |
| |
| final TextRange hebrew1 = painter.getWordBoundary(const TextPosition(offset: 4)); |
| expect(hebrew1, const TextRange(start: 0, end: 8), skip: skipExpectsWithKnownBugs); // https://github.com/flutter/flutter/issues/87536 |
| final TextRange english2 = painter.getWordBoundary(const TextPosition(offset: 14)); |
| expect(english2, const TextRange(start: 9, end: 19), skip: skipExpectsWithKnownBugs); // https://github.com/flutter/flutter/issues/87536 |
| final TextRange hebrew3 = painter.getWordBoundary(const TextPosition(offset: 24)); |
| expect(hebrew3, const TextRange(start: 20, end: 28)); |
| |
| // >>>>>>>>>>>>>>> embedding level 2 |
| // <================================================== embedding level 1 |
| // 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 |
| // 7 6 5 4 3 2 1 0 9 0 1 2 3 4 5 6 7 8 7 6 5 4 3 2 1 0 <- index of character in string |
| // Paints as: 3 W E R B E H e n g l i s h 2 1 W E R B E H |
| // 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 <- pixel offset at boundary |
| // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 |
| // 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 |
| |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 0, affinity: TextAffinity.upstream), Rect.zero), |
| const Offset(240.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 0), Rect.zero), |
| const Offset(240.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 1, affinity: TextAffinity.upstream), Rect.zero), |
| const Offset(240.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 1), Rect.zero), |
| const Offset(240.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 7, affinity: TextAffinity.upstream), Rect.zero), |
| const Offset(180.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 7), Rect.zero), |
| const Offset(180.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 8, affinity: TextAffinity.upstream), Rect.zero), |
| const Offset(170.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 8), Rect.zero), |
| const Offset(170.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 9, affinity: TextAffinity.upstream), Rect.zero), |
| const Offset(160.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 9), Rect.zero), |
| const Offset(160.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 10, affinity: TextAffinity.upstream), Rect.zero), |
| const Offset(80.0, 0.0), |
| ); |
| expect( |
| painter.getOffsetForCaret(const TextPosition(offset: 10), Rect.zero), |
| const Offset(80.0, 0.0), |
| ); |
| |
| expect( |
| painter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: 27)), |
| const <TextBox>[ |
| TextBox.fromLTRBD(160.0, 0.0, 240.0, 10.0, TextDirection.rtl), // HEBREW1 |
| TextBox.fromLTRBD( 80.0, 0.0, 160.0, 10.0, TextDirection.ltr), // english2 |
| TextBox.fromLTRBD( 0.0, 0.0, 80.0, 10.0, TextDirection.rtl), // HEBREW3 |
| ], |
| // Horizontal offsets are currently one pixel off in places; vertical offsets are good. |
| // The list is currently in the wrong order (so selection boxes will paint in the wrong order). |
| skip: skipExpectsWithKnownBugs, // https://github.com/flutter/flutter/issues/87536 |
| ); |
| painter.dispose(); |
| }, skip: skipTestsWithKnownBugs); // https://github.com/flutter/flutter/issues/87536 |
| |
| test('TextPainter - forced line-wrapping with bidi', () { |
| final TextPainter painter = TextPainter() |
| ..textDirection = TextDirection.ltr; |
| |
| painter.text = const TextSpan( |
| text: 'A\u05D0', // A, Alef |
| style: TextStyle(fontSize: 10.0), |
| ); |
| final TextSpan textSpan = painter.text! as TextSpan; |
| expect(textSpan.text!.length, 2); |
| painter.layout(maxWidth: 10.0); |
| |
| for (int index = 0; index <= 2; index += 1) { |
| expect( |
| painter.getWordBoundary(const TextPosition(offset: 0)), |
| const TextRange(start: 0, end: 2), |
| ); |
| } |
| |
| expect( // before the A |
| painter.getOffsetForCaret(const TextPosition(offset: 0, affinity: TextAffinity.upstream), Rect.zero), |
| Offset.zero, |
| ); |
| expect( // before the A |
| painter.getOffsetForCaret(const TextPosition(offset: 0), Rect.zero), |
| Offset.zero, |
| ); |
| |
| expect( // between A and Alef, after the A |
| painter.getOffsetForCaret(const TextPosition(offset: 1, affinity: TextAffinity.upstream), Rect.zero), |
| const Offset(10.0, 0.0), |
| ); |
| expect( // between A and Alef, before the Alef |
| painter.getOffsetForCaret(const TextPosition(offset: 1), Rect.zero), |
| const Offset(10.0, 10.0), |
| ); |
| |
| expect( // after the Alef |
| painter.getOffsetForCaret(const TextPosition(offset: 2, affinity: TextAffinity.upstream), Rect.zero), |
| const Offset(0.0, 10.0), |
| ); |
| expect( // after the Alef |
| painter.getOffsetForCaret(const TextPosition(offset: 2), Rect.zero), |
| const Offset(0.0, 10.0), |
| ); |
| |
| expect( |
| painter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: 2)), |
| const <TextBox>[ |
| TextBox.fromLTRBD(0.0, 0.0, 10.0, 10.0, TextDirection.ltr), // A |
| TextBox.fromLTRBD(0.0, 10.0, 10.0, 20.0, TextDirection.rtl), // Alef |
| ], |
| ); |
| expect( |
| painter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: 1)), |
| const <TextBox>[ |
| TextBox.fromLTRBD(0.0, 0.0, 10.0, 10.0, TextDirection.ltr), // A |
| ], |
| ); |
| expect( |
| painter.getBoxesForSelection(const TextSelection(baseOffset: 1, extentOffset: 2)), |
| const <TextBox>[ |
| TextBox.fromLTRBD(0.0, 10.0, 10.0, 20.0, TextDirection.rtl), // Alef |
| ], |
| ); |
| painter.dispose(); |
| }, skip: isBrowser); // https://github.com/flutter/flutter/issues/32238 |
| |
| test('TextPainter - line wrap mid-word', () { |
| final TextPainter painter = TextPainter() |
| ..textDirection = TextDirection.ltr; |
| |
| painter.text = const TextSpan( |
| style: TextStyle(fontSize: 10.0), |
| children: <TextSpan>[ |
| TextSpan( |
| text: 'hello', // width 50 |
| ), |
| TextSpan( |
| text: 'lovely', // width 120 |
| style: TextStyle(fontSize: 20.0), |
| ), |
| TextSpan( |
| text: 'world', // width 50 |
| ), |
| ], |
| ); |
| painter.layout(maxWidth: 110.0); // half-way through "lovely" |
| |
| expect( |
| painter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: 16)), |
| const <TextBox>[ |
| TextBox.fromLTRBD( 0.0, 8.0, 50.0, 18.0, TextDirection.ltr), |
| TextBox.fromLTRBD(50.0, 0.0, 110.0, 20.0, TextDirection.ltr), |
| TextBox.fromLTRBD( 0.0, 20.0, 60.0, 40.0, TextDirection.ltr), |
| TextBox.fromLTRBD(60.0, 28.0, 110.0, 38.0, TextDirection.ltr), |
| ], |
| // horizontal offsets are one pixel off in places; vertical offsets are good |
| skip: skipExpectsWithKnownBugs, // https://github.com/flutter/flutter/issues/87536 |
| ); |
| painter.dispose(); |
| }, skip: skipTestsWithKnownBugs); // https://github.com/flutter/flutter/issues/87536 |
| |
| test('TextPainter - line wrap mid-word, bidi - LTR base', () { |
| final TextPainter painter = TextPainter() |
| ..textDirection = TextDirection.ltr; |
| |
| painter.text = const TextSpan( |
| style: TextStyle(fontSize: 10.0), |
| children: <TextSpan>[ |
| TextSpan( |
| text: 'hello', // width 50 |
| ), |
| TextSpan( |
| text: '\u062C\u0645\u064A\u0644', // width 80 |
| style: TextStyle(fontSize: 20.0), |
| ), |
| TextSpan( |
| text: 'world', // width 50 |
| ), |
| ], |
| ); |
| painter.layout(maxWidth: 90.0); // half-way through the Arabic word |
| |
| expect( |
| painter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: 16)), |
| const <TextBox>[ |
| TextBox.fromLTRBD( 0.0, 8.0, 50.0, 18.0, TextDirection.ltr), |
| TextBox.fromLTRBD(50.0, 0.0, 90.0, 20.0, TextDirection.rtl), |
| TextBox.fromLTRBD( 0.0, 20.0, 40.0, 40.0, TextDirection.rtl), |
| TextBox.fromLTRBD(40.0, 28.0, 90.0, 38.0, TextDirection.ltr), |
| ], |
| // horizontal offsets are one pixel off in places; vertical offsets are good |
| skip: skipExpectsWithKnownBugs, // https://github.com/flutter/flutter/issues/87536 |
| ); |
| |
| final List<List<TextBox>> list = <List<TextBox>>[ |
| for (int index = 0; index < 5+4+5; index += 1) |
| painter.getBoxesForSelection(TextSelection(baseOffset: index, extentOffset: index + 1)), |
| ]; |
| |
| expect(list, const <List<TextBox>>[ |
| <TextBox>[TextBox.fromLTRBD(0.0, 8.0, 10.0, 18.0, TextDirection.ltr)], |
| <TextBox>[TextBox.fromLTRBD(10.0, 8.0, 20.0, 18.0, TextDirection.ltr)], |
| <TextBox>[TextBox.fromLTRBD(20.0, 8.0, 30.0, 18.0, TextDirection.ltr)], |
| <TextBox>[TextBox.fromLTRBD(30.0, 8.0, 40.0, 18.0, TextDirection.ltr)], |
| <TextBox>[TextBox.fromLTRBD(40.0, 8.0, 50.0, 18.0, TextDirection.ltr)], |
| <TextBox>[TextBox.fromLTRBD(70.0, 0.0, 90.0, 20.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(50.0, 0.0, 70.0, 20.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(20.0, 20.0, 40.0, 40.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(0.0, 20.0, 20.0, 40.0, TextDirection.rtl)], |
| <TextBox>[TextBox.fromLTRBD(40.0, 28.0, 50.0, 38.0, TextDirection.ltr)], |
| <TextBox>[TextBox.fromLTRBD(50.0, 28.0, 60.0, 38.0, TextDirection.ltr)], |
| <TextBox>[TextBox.fromLTRBD(60.0, 28.0, 70.0, 38.0, TextDirection.ltr)], |
| <TextBox>[TextBox.fromLTRBD(70.0, 28.0, 80.0, 38.0, TextDirection.ltr)], |
| <TextBox>[TextBox.fromLTRBD(80.0, 28.0, 90.0, 38.0, TextDirection.ltr)], |
| ]); |
| painter.dispose(); |
| }, skip: skipTestsWithKnownBugs); // https://github.com/flutter/flutter/issues/87536 |
| |
| test('TextPainter - line wrap mid-word, bidi - RTL base', () { |
| final TextPainter painter = TextPainter() |
| ..textDirection = TextDirection.rtl; |
| |
| painter.text = const TextSpan( |
| style: TextStyle(fontSize: 10.0), |
| children: <TextSpan>[ |
| TextSpan( |
| text: 'hello', // width 50 |
| ), |
| TextSpan( |
| text: '\u062C\u0645\u064A\u0644', // width 80 |
| style: TextStyle(fontSize: 20.0), |
| ), |
| TextSpan( |
| text: 'world', // width 50 |
| ), |
| ], |
| ); |
| painter.layout(maxWidth: 90.0); // half-way through the Arabic word |
| |
| expect( |
| painter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: 16)), |
| const <TextBox>[ |
| TextBox.fromLTRBD(40.0, 8.0, 90.0, 18.0, TextDirection.ltr), |
| TextBox.fromLTRBD( 0.0, 0.0, 40.0, 20.0, TextDirection.rtl), |
| TextBox.fromLTRBD(50.0, 20.0, 90.0, 40.0, TextDirection.rtl), |
| TextBox.fromLTRBD( 0.0, 28.0, 50.0, 38.0, TextDirection.ltr), |
| ], |
| // Horizontal offsets are currently one pixel off in places; vertical offsets are good. |
| // The list is currently in the wrong order (so selection boxes will paint in the wrong order). |
| skip: skipExpectsWithKnownBugs, // https://github.com/flutter/flutter/issues/87536 |
| ); |
| painter.dispose(); |
| }, skip: skipTestsWithKnownBugs); // https://github.com/flutter/flutter/issues/87536 |
| |
| test('TextPainter - multiple levels', () { |
| final TextPainter painter = TextPainter() |
| ..textDirection = TextDirection.rtl; |
| |
| final String pyramid = rlo(lro(rlo(lro(rlo(''))))); |
| painter.text = TextSpan( |
| text: pyramid, |
| style: const TextStyle(fontSize: 10.0), |
| ); |
| painter.layout(); |
| |
| expect( |
| painter.getBoxesForSelection(TextSelection(baseOffset: 0, extentOffset: pyramid.length)), |
| const <TextBox>[ |
| TextBox.fromLTRBD(90.0, 0.0, 100.0, 10.0, TextDirection.rtl), // outer R, start (right) |
| TextBox.fromLTRBD(10.0, 0.0, 20.0, 10.0, TextDirection.ltr), // level 1 L, start (left) |
| TextBox.fromLTRBD(70.0, 0.0, 80.0, 10.0, TextDirection.rtl), // level 2 R, start (right) |
| TextBox.fromLTRBD(30.0, 0.0, 40.0, 10.0, TextDirection.ltr), // level 3 L, start (left) |
| TextBox.fromLTRBD(40.0, 0.0, 60.0, 10.0, TextDirection.rtl), // inner-most RR |
| TextBox.fromLTRBD(60.0, 0.0, 70.0, 10.0, TextDirection.ltr), // lever 3 L, end (right) |
| TextBox.fromLTRBD(20.0, 0.0, 30.0, 10.0, TextDirection.rtl), // level 2 R, end (left) |
| TextBox.fromLTRBD(80.0, 0.0, 90.0, 10.0, TextDirection.ltr), // level 1 L, end (right) |
| TextBox.fromLTRBD( 0.0, 0.0, 10.0, 10.0, TextDirection.rtl), // outer R, end (left) |
| ], |
| // Horizontal offsets are currently one pixel off in places; vertical offsets are good. |
| // The list is currently in the wrong order (so selection boxes will paint in the wrong order). |
| // Also currently there's an extraneous box at the start of the list. |
| skip: skipExpectsWithKnownBugs, // https://github.com/flutter/flutter/issues/87536 |
| ); |
| painter.dispose(); |
| }, skip: skipTestsWithKnownBugs); // https://github.com/flutter/flutter/issues/87536 |
| |
| test('TextPainter - getPositionForOffset - RTL in LTR', () { |
| final TextPainter painter = TextPainter() |
| ..textDirection = TextDirection.ltr; |
| |
| painter.text = const TextSpan( |
| text: 'ABC\u05D0\u05D1\u05D2DEF', // A B C Alef Bet Gimel D E F -- but the Hebrew letters are RTL |
| style: TextStyle(fontSize: 10.0), |
| ); |
| painter.layout(); |
| |
| // TODO(ianh): Remove the toString()s once https://github.com/flutter/engine/pull/4283 lands |
| expect( |
| // Aaa Bbb Ccc Gimel Bet Alef Ddd Eee Fff |
| // ^ |
| painter.getPositionForOffset(const Offset(0.0, 5.0)).toString(), |
| const TextPosition(offset: 0).toString(), |
| ); |
| expect( |
| // Aaa Bbb Ccc Gimel Bet Alef Ddd Eee Fff |
| // ^ |
| painter.getPositionForOffset(const Offset(-100.0, 5.0)).toString(), |
| const TextPosition(offset: 0).toString(), |
| ); |
| expect( |
| // Aaa Bbb Ccc Gimel Bet Alef Ddd Eee Fff |
| // ^ |
| painter.getPositionForOffset(const Offset(4.0, 5.0)).toString(), |
| const TextPosition(offset: 0).toString(), |
| ); |
| expect( |
| // Aaa Bbb Ccc Gimel Bet Alef Ddd Eee Fff |
| // ^ |
| painter.getPositionForOffset(const Offset(8.0, 5.0)).toString(), |
| const TextPosition(offset: 1, affinity: TextAffinity.upstream).toString(), |
| ); |
| expect( |
| // Aaa Bbb Ccc Gimel Bet Alef Ddd Eee Fff |
| // ^ |
| painter.getPositionForOffset(const Offset(12.0, 5.0)).toString(), |
| const TextPosition(offset: 1).toString(), |
| // currently we say upstream instead of downstream |
| skip: skipExpectsWithKnownBugs, // https://github.com/flutter/flutter/issues/87536 |
| ); |
| expect( |
| // Aaa Bbb Ccc Gimel Bet Alef Ddd Eee Fff |
| // ^ |
| painter.getPositionForOffset(const Offset(28.0, 5.0)).toString(), |
| const TextPosition(offset: 3, affinity: TextAffinity.upstream).toString(), |
| ); |
| expect( |
| // Aaa Bbb Ccc Gimel Bet Alef Ddd Eee Fff |
| // ^ |
| painter.getPositionForOffset(const Offset(32.0, 5.0)).toString(), |
| const TextPosition(offset: 6, affinity: TextAffinity.upstream).toString(), |
| skip: skipExpectsWithKnownBugs, // this is part of https://github.com/flutter/flutter/issues/11375 |
| ); |
| expect( |
| // Aaa Bbb Ccc Gimel Bet Alef Ddd Eee Fff |
| // ^ |
| painter.getPositionForOffset(const Offset(58.0, 5.0)).toString(), |
| const TextPosition(offset: 3).toString(), |
| skip: skipExpectsWithKnownBugs, // this is part of https://github.com/flutter/flutter/issues/11375 |
| ); |
| expect( |
| // Aaa Bbb Ccc Gimel Bet Alef Ddd Eee Fff |
| // ^ |
| painter.getPositionForOffset(const Offset(62.0, 5.0)).toString(), |
| const TextPosition(offset: 6).toString(), |
| ); |
| expect( |
| // Aaa Bbb Ccc Gimel Bet Alef Ddd Eee Fff |
| // ^ |
| painter.getPositionForOffset(const Offset(88.0, 5.0)).toString(), |
| const TextPosition(offset: 9, affinity: TextAffinity.upstream).toString(), |
| ); |
| expect( |
| // Aaa Bbb Ccc Gimel Bet Alef Ddd Eee Fff |
| // ^ |
| painter.getPositionForOffset(const Offset(100.0, 5.0)).toString(), |
| const TextPosition(offset: 9, affinity: TextAffinity.upstream).toString(), |
| ); |
| painter.dispose(); |
| }, skip: skipTestsWithKnownBugs); // https://github.com/flutter/flutter/issues/87536 |
| |
| test('TextPainter - getPositionForOffset - LTR in RTL', () { |
| final TextPainter painter = TextPainter() |
| ..textDirection = TextDirection.rtl; |
| |
| painter.text = const TextSpan( |
| text: '\u05D0\u05D1\u05D2ABC\u05D3\u05D4\u05D5', |
| style: TextStyle(fontSize: 10.0), |
| ); |
| painter.layout(); |
| |
| // TODO(ianh): Remove the toString()s once https://github.com/flutter/engine/pull/4283 lands |
| expect( |
| // Vav He Dalet Aaa Bbb Ccc Gimel Bet Alef |
| // ^ |
| painter.getPositionForOffset(const Offset(-4.0, 5.0)).toString(), |
| const TextPosition(offset: 9, affinity: TextAffinity.upstream).toString(), |
| ); |
| expect( |
| // Vav He Dalet Aaa Bbb Ccc Gimel Bet Alef |
| // ^ |
| painter.getPositionForOffset(const Offset(28.0, 5.0)).toString(), |
| const TextPosition(offset: 6).toString(), |
| ); |
| expect( |
| // Vav He Dalet Aaa Bbb Ccc Gimel Bet Alef |
| // ^ |
| painter.getPositionForOffset(const Offset(32.0, 5.0)).toString(), |
| const TextPosition(offset: 3).toString(), |
| skip: skipExpectsWithKnownBugs, // this is part of https://github.com/flutter/flutter/issues/11375 |
| ); |
| expect( |
| // Vav He Dalet Aaa Bbb Ccc Gimel Bet Alef |
| // ^ |
| painter.getPositionForOffset(const Offset(58.0, 5.0)).toString(), |
| const TextPosition(offset: 6, affinity: TextAffinity.upstream).toString(), |
| skip: skipExpectsWithKnownBugs, // this is part of https://github.com/flutter/flutter/issues/11375 |
| ); |
| expect( |
| // Vav He Dalet Aaa Bbb Ccc Gimel Bet Alef |
| // ^ |
| painter.getPositionForOffset(const Offset(62.0, 5.0)).toString(), |
| const TextPosition(offset: 3, affinity: TextAffinity.upstream).toString(), |
| ); |
| painter.dispose(); |
| }, skip: skipTestsWithKnownBugs); // https://github.com/flutter/flutter/issues/87536 |
| |
| test('TextPainter - Spaces', () { |
| final TextPainter painter = TextPainter() |
| ..textDirection = TextDirection.ltr; |
| |
| painter.text = const TextSpan( |
| text: ' ', |
| style: TextStyle(fontSize: 100.0), |
| children: <TextSpan>[ |
| TextSpan( |
| text: ' ', |
| style: TextStyle(fontSize: 10.0), |
| ), |
| TextSpan( |
| text: ' ', |
| style: TextStyle(fontSize: 200.0), |
| ), |
| // Add a non-whitespace character because the renderer's line breaker |
| // may strip trailing whitespace on a line. |
| TextSpan(text: 'A'), |
| ], |
| ); |
| painter.layout(); |
| |
| // This renders as three (invisible) boxes: |
| // |
| // |<--------200------->| |
| // ____________________ |
| // | ^ | |
| // | : | |
| // | : | |
| // | : | |
| // | : | |
| // ___________ | : 160 | |
| // | ^ | | : | |
| // |<-+-100--->|10| : | |
| // | : |__| : | |
| // | : 80 | |8 : | |
| // _|__v________|__|________v___________| BASELINE |
| // | ^20 |__|2 ^ | |
| // |_____v_____| | | | |
| // | | 40 | |
| // | | | |
| // |________v___________| |
| |
| expect(painter.width, 410.0); |
| expect(painter.height, 200.0); |
| expect(painter.computeDistanceToActualBaseline(TextBaseline.alphabetic), moreOrLessEquals(160.0, epsilon: 0.001)); |
| expect(painter.preferredLineHeight, 100.0); |
| |
| expect( |
| painter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: 3)), |
| const <TextBox>[ |
| TextBox.fromLTRBD( 0.0, 80.0, 100.0, 180.0, TextDirection.ltr), |
| TextBox.fromLTRBD(100.0, 152.0, 110.0, 162.0, TextDirection.ltr), |
| TextBox.fromLTRBD(110.0, 0.0, 310.0, 200.0, TextDirection.ltr), |
| ], |
| // Horizontal offsets are currently one pixel off in places; vertical offsets are good. |
| skip: skipExpectsWithKnownBugs, // https://github.com/flutter/flutter/issues/87536 |
| ); |
| painter.dispose(); |
| }, skip: skipTestsWithKnownBugs); // https://github.com/flutter/flutter/issues/87536 |
| |
| test('TextPainter - empty text baseline', () { |
| final TextPainter painter = TextPainter() |
| ..textDirection = TextDirection.ltr; |
| painter.text = const TextSpan( |
| text: '', |
| style: TextStyle(fontFamily: 'FlutterTest', fontSize: 100.0, height: 1.0), |
| ); |
| painter.layout(); |
| expect(painter.computeDistanceToActualBaseline(TextBaseline.alphabetic), 75.0); |
| painter.dispose(); |
| }); |
| } |
| |
| String lro(String s) => '${Unicode.LRO}L${s}L${Unicode.PDF}'; |
| String rlo(String s) => '${Unicode.RLO}R${s}R${Unicode.PDF}'; |