// Copyright 2017 The Chromium 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 'dart:io'; 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 = new TextPainter() ..textDirection = TextDirection.ltr; painter.text = const TextSpan( text: 'ABC DEF\nGHI', style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0), ); painter.layout(); expect( painter.getWordBoundary(const TextPosition(offset: 1, affinity: TextAffinity.downstream)), const TextRange(start: 0, end: 3), ); expect( painter.getWordBoundary(const TextPosition(offset: 5, affinity: TextAffinity.downstream)), const TextRange(start: 4, end: 7), ); expect( painter.getWordBoundary(const TextPosition(offset: 9, affinity: TextAffinity.downstream)), const TextRange(start: 8, end: 11), ); }); test('TextPainter - bidi overrides in LTR', () { final TextPainter painter = new 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: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0), ); expect(painter.text.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, affinity: TextAffinity.downstream)); expect(hebrew1, const TextRange(start: 0, end: 8), skip: skipExpectsWithKnownBugs); final TextRange english2 = painter.getWordBoundary(const TextPosition(offset: 14, affinity: TextAffinity.downstream)); expect(english2, const TextRange(start: 9, end: 19), skip: skipExpectsWithKnownBugs); final TextRange hebrew3 = painter.getWordBoundary(const TextPosition(offset: 24, affinity: TextAffinity.downstream)); 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), const Offset(0.0, 0.0), ); expect( painter.getOffsetForCaret(const TextPosition(offset: 0, affinity: TextAffinity.downstream), Rect.zero), const Offset(0.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, affinity: TextAffinity.downstream), 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, affinity: TextAffinity.downstream), 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, affinity: TextAffinity.downstream), 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, affinity: TextAffinity.downstream), 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, affinity: TextAffinity.downstream), Rect.zero), const Offset(80.0, 0.0), ); expect( painter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: 27)), const <TextBox>[ const TextBox.fromLTRBD(160.0, 0.0, 240.0, 10.0, TextDirection.rtl), // HEBREW1 const TextBox.fromLTRBD( 80.0, 0.0, 160.0, 10.0, TextDirection.ltr), // english2 const 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). ); final List<List<TextBox>> list = <List<TextBox>>[]; for (int index = 0; index < painter.text.text.length; index += 1) list.add(painter.getBoxesForSelection(new TextSelection(baseOffset: index, extentOffset: index + 1))); expect(list, const <List<TextBox>>[ const <TextBox>[], // U+202E, non-printing Unicode bidi formatting character const <TextBox>[const TextBox.fromLTRBD(230.0, 0.0, 240.0, 10.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(220.0, 0.0, 230.0, 10.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(210.0, 0.0, 220.0, 10.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(200.0, 0.0, 210.0, 10.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(190.0, 0.0, 200.0, 10.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(180.0, 0.0, 190.0, 10.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(170.0, 0.0, 180.0, 10.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(160.0, 0.0, 170.0, 10.0, TextDirection.rtl)], const <TextBox>[], // U+202D, non-printing Unicode bidi formatting character const <TextBox>[const TextBox.fromLTRBD(80.0, 0.0, 90.0, 10.0, TextDirection.ltr)], const <TextBox>[const TextBox.fromLTRBD(90.0, 0.0, 100.0, 10.0, TextDirection.ltr)], const <TextBox>[const TextBox.fromLTRBD(100.0, 0.0, 110.0, 10.0, TextDirection.ltr)], const <TextBox>[const TextBox.fromLTRBD(110.0, 0.0, 120.0, 10.0, TextDirection.ltr)], const <TextBox>[const TextBox.fromLTRBD(120.0, 0.0, 130.0, 10.0, TextDirection.ltr)], const <TextBox>[const TextBox.fromLTRBD(130.0, 0.0, 140.0, 10.0, TextDirection.ltr)], const <TextBox>[const TextBox.fromLTRBD(140.0, 0.0, 150.0, 10.0, TextDirection.ltr)], const <TextBox>[const TextBox.fromLTRBD(150.0, 0.0, 160.0, 10.0, TextDirection.ltr)], const <TextBox>[], // U+202C, non-printing Unicode bidi formatting character const <TextBox>[const TextBox.fromLTRBD(70.0, 0.0, 80.0, 10.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(60.0, 0.0, 70.0, 10.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(50.0, 0.0, 60.0, 10.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(40.0, 0.0, 50.0, 10.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(30.0, 0.0, 40.0, 10.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(20.0, 0.0, 30.0, 10.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(10.0, 0.0, 20.0, 10.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(0.0, 0.0, 10.0, 10.0, TextDirection.rtl)], const <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); }, skip: skipTestsWithKnownBugs); test('TextPainter - bidi overrides in RTL', () { final TextPainter painter = new 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: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0), ); expect(painter.text.text.length, 28); painter.layout(); final TextRange hebrew1 = painter.getWordBoundary(const TextPosition(offset: 4, affinity: TextAffinity.downstream)); expect(hebrew1, const TextRange(start: 0, end: 8), skip: skipExpectsWithKnownBugs); final TextRange english2 = painter.getWordBoundary(const TextPosition(offset: 14, affinity: TextAffinity.downstream)); expect(english2, const TextRange(start: 9, end: 19), skip: skipExpectsWithKnownBugs); final TextRange hebrew3 = painter.getWordBoundary(const TextPosition(offset: 24, affinity: TextAffinity.downstream)); 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, affinity: TextAffinity.downstream), 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, affinity: TextAffinity.downstream), 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, affinity: TextAffinity.downstream), 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, affinity: TextAffinity.downstream), 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, affinity: TextAffinity.downstream), 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, affinity: TextAffinity.downstream), Rect.zero), const Offset(80.0, 0.0), ); expect( painter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: 27)), const <TextBox>[ const TextBox.fromLTRBD(160.0, 0.0, 240.0, 10.0, TextDirection.rtl), // HEBREW1 const TextBox.fromLTRBD( 80.0, 0.0, 160.0, 10.0, TextDirection.ltr), // english2 const 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, ); }, skip: skipTestsWithKnownBugs); test('TextPainter - forced line-wrapping with bidi', () { final TextPainter painter = new TextPainter() ..textDirection = TextDirection.ltr; painter.text = const TextSpan( text: 'A\u05D0', // A, Alef style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0), ); expect(painter.text.text.length, 2); painter.layout(maxWidth: 10.0); for (int index = 0; index <= 2; index += 1) { expect( painter.getWordBoundary(const TextPosition(offset: 0, affinity: TextAffinity.downstream)), const TextRange(start: 0, end: 2), ); } expect( // before the A painter.getOffsetForCaret(const TextPosition(offset: 0, affinity: TextAffinity.upstream), Rect.zero), const Offset(0.0, 0.0), ); expect( // before the A painter.getOffsetForCaret(const TextPosition(offset: 0, affinity: TextAffinity.downstream), Rect.zero), const Offset(0.0, 0.0), ); 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, affinity: TextAffinity.downstream), 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, affinity: TextAffinity.downstream), Rect.zero), const Offset(0.0, 10.0), ); expect( painter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: 2)), const <TextBox>[ const TextBox.fromLTRBD(0.0, 0.0, 10.0, 10.0, TextDirection.ltr), // A const TextBox.fromLTRBD(0.0, 10.0, 10.0, 20.0, TextDirection.rtl), // Alef ], ); expect( painter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: 1)), const <TextBox>[ const TextBox.fromLTRBD(0.0, 0.0, 10.0, 10.0, TextDirection.ltr), // A ], ); expect( painter.getBoxesForSelection(const TextSelection(baseOffset: 1, extentOffset: 2)), const <TextBox>[ const TextBox.fromLTRBD(0.0, 10.0, 10.0, 20.0, TextDirection.rtl), // Alef ], ); }, // Ahem-based tests don't yet quite work on Windows or some MacOS environments skip: Platform.isWindows || Platform.isMacOS); test('TextPainter - line wrap mid-word', () { final TextPainter painter = new TextPainter() ..textDirection = TextDirection.ltr; painter.text = const TextSpan( style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0), children: const <TextSpan>[ const TextSpan( text: 'hello', // width 50 ), const TextSpan( text: 'lovely', // width 120 style: const TextStyle(fontFamily: 'Ahem', fontSize: 20.0), ), const 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>[ const TextBox.fromLTRBD( 0.0, 8.0, 50.0, 18.0, TextDirection.ltr), const TextBox.fromLTRBD(50.0, 0.0, 110.0, 20.0, TextDirection.ltr), const TextBox.fromLTRBD( 0.0, 20.0, 60.0, 40.0, TextDirection.ltr), const TextBox.fromLTRBD(60.0, 28.0, 110.0, 38.0, TextDirection.ltr), ], skip: skipExpectsWithKnownBugs, // horizontal offsets are one pixel off in places; vertical offsets are good ); }, skip: skipTestsWithKnownBugs); test('TextPainter - line wrap mid-word, bidi - LTR base', () { final TextPainter painter = new TextPainter() ..textDirection = TextDirection.ltr; painter.text = const TextSpan( style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0), children: const <TextSpan>[ const TextSpan( text: 'hello', // width 50 ), const TextSpan( text: '\u062C\u0645\u064A\u0644', // width 80 style: const TextStyle(fontFamily: 'Ahem', fontSize: 20.0), ), const 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>[ const TextBox.fromLTRBD( 0.0, 8.0, 50.0, 18.0, TextDirection.ltr), const TextBox.fromLTRBD(50.0, 0.0, 90.0, 20.0, TextDirection.rtl), const TextBox.fromLTRBD( 0.0, 20.0, 40.0, 40.0, TextDirection.rtl), const TextBox.fromLTRBD(40.0, 28.0, 90.0, 38.0, TextDirection.ltr), ], skip: skipExpectsWithKnownBugs, // horizontal offsets are one pixel off in places; vertical offsets are good ); final List<List<TextBox>> list = <List<TextBox>>[]; for (int index = 0; index < 5+4+5; index += 1) list.add(painter.getBoxesForSelection(new TextSelection(baseOffset: index, extentOffset: index + 1))); print(list); expect(list, const <List<TextBox>>[ const <TextBox>[const TextBox.fromLTRBD(0.0, 8.0, 10.0, 18.0, TextDirection.ltr)], const <TextBox>[const TextBox.fromLTRBD(10.0, 8.0, 20.0, 18.0, TextDirection.ltr)], const <TextBox>[const TextBox.fromLTRBD(20.0, 8.0, 30.0, 18.0, TextDirection.ltr)], const <TextBox>[const TextBox.fromLTRBD(30.0, 8.0, 40.0, 18.0, TextDirection.ltr)], const <TextBox>[const TextBox.fromLTRBD(40.0, 8.0, 50.0, 18.0, TextDirection.ltr)], const <TextBox>[const TextBox.fromLTRBD(70.0, 0.0, 90.0, 20.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(50.0, 0.0, 70.0, 20.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(20.0, 20.0, 40.0, 40.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(0.0, 20.0, 20.0, 40.0, TextDirection.rtl)], const <TextBox>[const TextBox.fromLTRBD(40.0, 28.0, 50.0, 38.0, TextDirection.ltr)], const <TextBox>[const TextBox.fromLTRBD(50.0, 28.0, 60.0, 38.0, TextDirection.ltr)], const <TextBox>[const TextBox.fromLTRBD(60.0, 28.0, 70.0, 38.0, TextDirection.ltr)], const <TextBox>[const TextBox.fromLTRBD(70.0, 28.0, 80.0, 38.0, TextDirection.ltr)], const <TextBox>[const TextBox.fromLTRBD(80.0, 28.0, 90.0, 38.0, TextDirection.ltr)] ]); }, skip: skipTestsWithKnownBugs); test('TextPainter - line wrap mid-word, bidi - RTL base', () { final TextPainter painter = new TextPainter() ..textDirection = TextDirection.rtl; painter.text = const TextSpan( style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0), children: const <TextSpan>[ const TextSpan( text: 'hello', // width 50 ), const TextSpan( text: '\u062C\u0645\u064A\u0644', // width 80 style: const TextStyle(fontFamily: 'Ahem', fontSize: 20.0), ), const 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>[ const TextBox.fromLTRBD(40.0, 8.0, 90.0, 18.0, TextDirection.ltr), const TextBox.fromLTRBD( 0.0, 0.0, 40.0, 20.0, TextDirection.rtl), const TextBox.fromLTRBD(50.0, 20.0, 90.0, 40.0, TextDirection.rtl), const 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, ); }, skip: skipTestsWithKnownBugs); test('TextPainter - multiple levels', () { final TextPainter painter = new TextPainter() ..textDirection = TextDirection.rtl; final String pyramid = rlo(lro(rlo(lro(rlo(''))))); painter.text = new TextSpan( text: pyramid, style: const TextStyle(fontFamily: 'Ahem', fontSize: 10.0), ); painter.layout(); expect( painter.getBoxesForSelection(new TextSelection(baseOffset: 0, extentOffset: pyramid.length)), const <TextBox>[ const TextBox.fromLTRBD(90.0, 0.0, 100.0, 10.0, TextDirection.rtl), // outer R, start (right) const TextBox.fromLTRBD(10.0, 0.0, 20.0, 10.0, TextDirection.ltr), // level 1 L, start (left) const TextBox.fromLTRBD(70.0, 0.0, 80.0, 10.0, TextDirection.rtl), // level 2 R, start (right) const TextBox.fromLTRBD(30.0, 0.0, 40.0, 10.0, TextDirection.ltr), // level 3 L, start (left) const TextBox.fromLTRBD(40.0, 0.0, 60.0, 10.0, TextDirection.rtl), // inner-most RR const TextBox.fromLTRBD(60.0, 0.0, 70.0, 10.0, TextDirection.ltr), // lever 3 L, end (right) const TextBox.fromLTRBD(20.0, 0.0, 30.0, 10.0, TextDirection.rtl), // level 2 R, end (left) const TextBox.fromLTRBD(80.0, 0.0, 90.0, 10.0, TextDirection.ltr), // level 1 L, end (right) const 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, ); }, skip: skipTestsWithKnownBugs); test('TextPainter - getPositionForOffset - RTL in LTR', () { final TextPainter painter = new 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: const TextStyle(fontFamily: 'Ahem', 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, affinity: TextAffinity.downstream).toString(), ); expect( // Aaa Bbb Ccc Gimel Bet Alef Ddd Eee Fff // ^ painter.getPositionForOffset(const Offset(-100.0, 5.0)).toString(), const TextPosition(offset: 0, affinity: TextAffinity.downstream).toString(), ); expect( // Aaa Bbb Ccc Gimel Bet Alef Ddd Eee Fff // ^ painter.getPositionForOffset(const Offset(4.0, 5.0)).toString(), const TextPosition(offset: 0, affinity: TextAffinity.downstream).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, affinity: TextAffinity.downstream).toString(), skip: skipExpectsWithKnownBugs, // currently we say upstream instead of downstream ); 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, affinity: TextAffinity.downstream).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, affinity: TextAffinity.downstream).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(), ); }, skip: skipTestsWithKnownBugs); test('TextPainter - getPositionForOffset - LTR in RTL', () { final TextPainter painter = new TextPainter() ..textDirection = TextDirection.rtl; painter.text = const TextSpan( text: '\u05D0\u05D1\u05D2ABC\u05D3\u05D4\u05D5', style: const TextStyle(fontFamily: 'Ahem', 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, affinity: TextAffinity.downstream).toString(), ); expect( // Vav He Dalet Aaa Bbb Ccc Gimel Bet Alef // ^ painter.getPositionForOffset(const Offset(32.0, 5.0)).toString(), const TextPosition(offset: 3, affinity: TextAffinity.downstream).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(), ); }, skip: skipTestsWithKnownBugs); test('TextPainter - Spaces', () { final TextPainter painter = new TextPainter() ..textDirection = TextDirection.ltr; painter.text = const TextSpan( text: ' ', style: const TextStyle(fontFamily: 'Ahem', fontSize: 100.0), children: const <TextSpan>[ const TextSpan( text: ' ', style: const TextStyle(fontSize: 10.0), ), const TextSpan( text: ' ', style: const TextStyle(fontSize: 200.0), ), // Add a non-whitespace character because the renderer's line breaker // may strip trailing whitespace on a line. const 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), 160.0); expect(painter.preferredLineHeight, 100.0); expect( painter.getBoxesForSelection(const TextSelection(baseOffset: 0, extentOffset: 3)), const <TextBox>[ const TextBox.fromLTRBD( 0.0, 80.0, 100.0, 180.0, TextDirection.ltr), const TextBox.fromLTRBD(100.0, 152.0, 110.0, 162.0, TextDirection.ltr), const 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, ); }, skip: skipTestsWithKnownBugs); test('TextPainter - empty text baseline', () { final TextPainter painter = new TextPainter() ..textDirection = TextDirection.ltr; painter.text = const TextSpan( text: '', style: const TextStyle(fontFamily: 'Ahem', fontSize: 100.0, height: 1.0), ); painter.layout(); expect( // Returns -1 painter.computeDistanceToActualBaseline(TextBaseline.alphabetic), 80.0, skip: skipExpectsWithKnownBugs, ); }, skip: skipTestsWithKnownBugs); } String lro(String s) => '${Unicode.LRO}L${s}L${Unicode.PDF}'; String rlo(String s) => '${Unicode.RLO}R${s}R${Unicode.PDF}';