| Index: ui/gfx/render_text_unittest.cc
|
| diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc
|
| index c51ef30315f664206e9501ccc234b411236c93a4..3657ef0353f9d09393b13f488032fa535cefc30a 100644
|
| --- a/ui/gfx/render_text_unittest.cc
|
| +++ b/ui/gfx/render_text_unittest.cc
|
| @@ -10,6 +10,7 @@
|
|
|
| #include <algorithm>
|
| #include <memory>
|
| +#include <numeric>
|
|
|
| #include "base/format_macros.h"
|
| #include "base/i18n/break_iterator.h"
|
| @@ -472,6 +473,12 @@ class RenderTextTest : public testing::Test,
|
| }
|
| #endif
|
|
|
| + Rect GetSelectionBoundsUnion() {
|
| + const std::vector<Rect> bounds =
|
| + render_text_->GetSubstringBoundsForTesting(render_text_->selection());
|
| + return std::accumulate(bounds.begin(), bounds.end(), Rect(), UnionRects);
|
| + }
|
| +
|
| Canvas* canvas() { return &canvas_; }
|
| TestSkiaTextRenderer* renderer() { return &renderer_; }
|
| test::RenderTextTestApi* test_api() { return test_api_.get(); };
|
| @@ -514,6 +521,10 @@ class RenderTextHarfBuzzTest : public RenderTextTest {
|
| return GetRenderTextHarfBuzz()->ShapeRunWithFont(text, font, params, run);
|
| }
|
|
|
| + int GetCursorYForTesting(int line_num = 0) {
|
| + return GetRenderText()->GetLineOffset(line_num).y() + 1;
|
| + }
|
| +
|
| private:
|
| DISALLOW_COPY_AND_ASSIGN(RenderTextHarfBuzzTest);
|
| };
|
| @@ -788,10 +799,14 @@ TEST_P(RenderTextHarfBuzzTest, ObscuredText) {
|
|
|
| // FindCursorPosition() should not return positions between a surrogate pair.
|
| render_text->SetDisplayRect(Rect(0, 0, 20, 20));
|
| - EXPECT_EQ(render_text->FindCursorPosition(Point(0, 0)).caret_pos(), 0U);
|
| - EXPECT_EQ(render_text->FindCursorPosition(Point(20, 0)).caret_pos(), 2U);
|
| + const int cursor_y = GetCursorYForTesting();
|
| + EXPECT_EQ(render_text->FindCursorPosition(Point(0, cursor_y)).caret_pos(),
|
| + 0U);
|
| + EXPECT_EQ(render_text->FindCursorPosition(Point(20, cursor_y)).caret_pos(),
|
| + 2U);
|
| for (int x = -1; x <= 20; ++x) {
|
| - SelectionModel selection = render_text->FindCursorPosition(Point(x, 0));
|
| + SelectionModel selection =
|
| + render_text->FindCursorPosition(Point(x, cursor_y));
|
| EXPECT_TRUE(selection.caret_pos() == 0U || selection.caret_pos() == 2U);
|
| }
|
|
|
| @@ -1653,10 +1668,21 @@ TEST_P(RenderTextHarfBuzzTest, MoveCursorLeftRight_MeiryoUILigatures) {
|
| // (code point) has unique bounds, so mid-glyph cursoring should be possible.
|
| render_text->SetFontList(FontList("Meiryo UI, 12px"));
|
| render_text->SetText(WideToUTF16(L"ff ffi"));
|
| + render_text->SetDisplayRect(gfx::Rect(100, 100));
|
| + test_api()->EnsureLayout();
|
| EXPECT_EQ(0U, render_text->cursor_position());
|
| +
|
| + gfx::Rect last_selection_bounds = GetSelectionBoundsUnion();
|
| for (size_t i = 0; i < render_text->text().length(); ++i) {
|
| - render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE);
|
| + render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_RETAIN);
|
| EXPECT_EQ(i + 1, render_text->cursor_position());
|
| +
|
| + // Verify the selection bounds also increase and that the correct bounds are
|
| + // returned even when the grapheme boundary lies within a glyph.
|
| + const gfx::Rect selection_bounds = GetSelectionBoundsUnion();
|
| + EXPECT_GT(selection_bounds.right(), last_selection_bounds.right());
|
| + EXPECT_EQ(selection_bounds.x(), last_selection_bounds.x());
|
| + last_selection_bounds = selection_bounds;
|
| }
|
| EXPECT_EQ(6U, render_text->cursor_position());
|
| }
|
| @@ -1754,6 +1780,7 @@ TEST_P(RenderTextHarfBuzzTest, MidGraphemeSelectionBounds) {
|
| const base::string16 cases[] = { kHindi, kThai };
|
|
|
| RenderText* render_text = GetRenderText();
|
| + render_text->SetDisplayRect(Rect(100, 1000));
|
| for (size_t i = 0; i < arraysize(cases); i++) {
|
| SCOPED_TRACE(base::StringPrintf("Testing cases[%" PRIuS "]", i));
|
| render_text->SetText(cases[i]);
|
| @@ -1762,8 +1789,17 @@ TEST_P(RenderTextHarfBuzzTest, MidGraphemeSelectionBounds) {
|
| EXPECT_TRUE(render_text->SelectRange(Range(2, 1)));
|
| EXPECT_EQ(Range(2, 1), render_text->selection());
|
| EXPECT_EQ(1U, render_text->cursor_position());
|
| +
|
| + // Verify that the selection bounds extend over the entire grapheme, even if
|
| + // the selection is set amid the grapheme.
|
| + test_api()->EnsureLayout();
|
| + const gfx::Rect mid_grapheme_bounds = GetSelectionBoundsUnion();
|
| + render_text->SelectAll(false);
|
| + EXPECT_EQ(GetSelectionBoundsUnion(), mid_grapheme_bounds);
|
| +
|
| // Although selection bounds may be set within a multi-character grapheme,
|
| // cursor movement (e.g. via arrow key) should avoid those indices.
|
| + EXPECT_TRUE(render_text->SelectRange(Range(2, 1)));
|
| render_text->MoveCursor(CHARACTER_BREAK, CURSOR_LEFT, SELECTION_NONE);
|
| EXPECT_EQ(0U, render_text->cursor_position());
|
| render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, SELECTION_NONE);
|
| @@ -1783,11 +1819,86 @@ TEST_P(RenderTextHarfBuzzTest, FindCursorPosition) {
|
| const Range range(render_text->GetGlyphBounds(j));
|
| // Test a point just inside the leading edge of the glyph bounds.
|
| int x = range.is_reversed() ? range.GetMax() - 1 : range.GetMin() + 1;
|
| - EXPECT_EQ(j, render_text->FindCursorPosition(Point(x, 0)).caret_pos());
|
| + EXPECT_EQ(
|
| + j, render_text->FindCursorPosition(Point(x, GetCursorYForTesting()))
|
| + .caret_pos());
|
| + }
|
| + }
|
| +}
|
| +
|
| +// Tests that FindCursorPosition behaves correctly for multi-line text.
|
| +TEST_P(RenderTextHarfBuzzTest, FindCursorPositionMultiline) {
|
| + const wchar_t* kTestStrings[] = {L"abc def",
|
| + L"\x5d0\x5d1\x5d2 \x5d3\x5d4\x5d5" /*rtl*/};
|
| +
|
| + SetGlyphWidth(5);
|
| + RenderText* render_text = GetRenderText();
|
| + render_text->SetDisplayRect(Rect(25, 1000));
|
| + render_text->SetMultiline(true);
|
| +
|
| + for (size_t i = 0; i < arraysize(kTestStrings); i++) {
|
| + render_text->SetText(WideToUTF16(kTestStrings[i]));
|
| + test_api()->EnsureLayout();
|
| + EXPECT_EQ(2u, test_api()->lines().size());
|
| +
|
| + const bool is_ltr =
|
| + render_text->GetDisplayTextDirection() == base::i18n::LEFT_TO_RIGHT;
|
| + for (size_t j = 0; j < render_text->text().length(); ++j) {
|
| + SCOPED_TRACE(base::StringPrintf(
|
| + "Testing index %" PRIuS " for case %" PRIuS "", j, i));
|
| + render_text->SelectRange(Range(j, j + 1));
|
| +
|
| + // Test a point inside the leading edge of the glyph bounds.
|
| + const Rect bounds = GetSelectionBoundsUnion();
|
| + const Point cursor_position(is_ltr ? bounds.x() + 1 : bounds.right() - 1,
|
| + bounds.y() + 1);
|
| +
|
| + const SelectionModel model =
|
| + render_text->FindCursorPosition(cursor_position);
|
| + EXPECT_EQ(j, model.caret_pos());
|
| + EXPECT_EQ(CURSOR_FORWARD, model.caret_affinity());
|
| }
|
| }
|
| }
|
|
|
| +// Ensure FindCursorPosition returns positions only at valid grapheme
|
| +// boundaries.
|
| +TEST_P(RenderTextHarfBuzzTest, FindCursorPosition_GraphemeBoundaries) {
|
| + struct {
|
| + base::string16 text;
|
| + std::set<size_t> expected_cursor_positions;
|
| + } cases[] = {
|
| + // LTR 2-character grapheme, LTR abc, LTR 2-character grapheme.
|
| + {WideToUTF16(L"\x0915\x093f"
|
| + L"abc"
|
| + L"\x0915\x093f"),
|
| + {0, 2, 3, 4, 5, 7}},
|
| + // LTR ab, LTR 2-character grapheme, LTR cd.
|
| + {WideToUTF16(L"ab"
|
| + L"\x0915\x093f"
|
| + L"cd"),
|
| + {0, 1, 2, 4, 5, 6}},
|
| + // LTR ab, surrogate pair composed of two 16 bit characters, LTR cd.
|
| + {UTF8ToUTF16("ab"
|
| + "\xF0\x9D\x84\x9E"
|
| + "cd"),
|
| + {0, 1, 2, 4, 5, 6}}};
|
| +
|
| + RenderText* render_text = GetRenderText();
|
| + render_text->SetDisplayRect(gfx::Rect(100, 30));
|
| + for (size_t i = 0; i < arraysize(cases); i++) {
|
| + SCOPED_TRACE(base::StringPrintf("Testing case %" PRIuS "", i));
|
| + render_text->SetText(cases[i].text);
|
| + test_api()->EnsureLayout();
|
| + std::set<size_t> obtained_cursor_positions;
|
| + size_t cursor_y = GetCursorYForTesting();
|
| + for (int x = -5; x < 105; x++)
|
| + obtained_cursor_positions.insert(
|
| + render_text->FindCursorPosition(gfx::Point(x, cursor_y)).caret_pos());
|
| + EXPECT_EQ(cases[i].expected_cursor_positions, obtained_cursor_positions);
|
| + }
|
| +}
|
| +
|
| TEST_P(RenderTextTest, EdgeSelectionModels) {
|
| // Simple Latin text.
|
| const base::string16 kLatin = WideToUTF16(L"abc");
|
| @@ -2863,12 +2974,12 @@ TEST_P(RenderTextHarfBuzzTest, Multiline_Newline) {
|
| // Ranges of the characters on each line preceding the newline.
|
| const Range line_char_ranges[3];
|
| } kTestStrings[] = {
|
| - {L"abc\ndef", 2ul, { Range(0, 3), Range(4, 7), Range::InvalidRange() } },
|
| - {L"a \n b ", 2ul, { Range(0, 2), Range(3, 6), Range::InvalidRange() } },
|
| - {L"ab\n", 2ul, { Range(0, 2), Range(), Range::InvalidRange() } },
|
| - {L"a\n\nb", 3ul, { Range(0, 1), Range(), Range(3, 4) } },
|
| - {L"\nab", 2ul, { Range(), Range(1, 3), Range::InvalidRange() } },
|
| - {L"\n", 2ul, { Range(), Range(), Range::InvalidRange() } },
|
| + {L"abc\ndef", 2ul, {Range(0, 3), Range(4, 7), Range::InvalidRange()}},
|
| + {L"a \n b ", 2ul, {Range(0, 2), Range(3, 6), Range::InvalidRange()}},
|
| + {L"ab\n", 2ul, {Range(0, 2), Range(), Range::InvalidRange()}},
|
| + {L"a\n\nb", 3ul, {Range(0, 1), Range(2, 3), Range(3, 4)}},
|
| + {L"\nab", 2ul, {Range(0, 1), Range(1, 3), Range::InvalidRange()}},
|
| + {L"\n", 2ul, {Range(0, 1), Range(), Range::InvalidRange()}},
|
| };
|
|
|
| RenderText* render_text = GetRenderText();
|
| @@ -3898,6 +4009,7 @@ TEST_P(RenderTextHarfBuzzTest, GetDecoratedWordAtPoint_LTR) {
|
| render_text->ApplyStyle(ITALIC, true, Range(3, 8));
|
| render_text->ApplyStyle(DIAGONAL_STRIKE, true, Range(5, 7));
|
| render_text->ApplyStyle(STRIKE, true, Range(1, 7));
|
| + const int cursor_y = GetCursorYForTesting();
|
|
|
| const std::vector<RenderText::FontSpan> font_spans =
|
| render_text->GetFontSpansForTesting();
|
| @@ -3931,14 +4043,14 @@ TEST_P(RenderTextHarfBuzzTest, GetDecoratedWordAtPoint_LTR) {
|
| {
|
| SCOPED_TRACE(base::StringPrintf("Query to the left of text bounds"));
|
| EXPECT_TRUE(render_text->GetDecoratedWordAtPoint(
|
| - Point(-5, 5), &decorated_word, &baseline_point));
|
| + Point(-5, cursor_y), &decorated_word, &baseline_point));
|
| VerifyDecoratedWordsAreEqual(expected_word_1, decorated_word);
|
| EXPECT_TRUE(left_glyph_word_1.Contains(baseline_point));
|
| }
|
| {
|
| SCOPED_TRACE(base::StringPrintf("Query to the right of text bounds"));
|
| EXPECT_TRUE(render_text->GetDecoratedWordAtPoint(
|
| - Point(105, 5), &decorated_word, &baseline_point));
|
| + Point(105, cursor_y), &decorated_word, &baseline_point));
|
| VerifyDecoratedWordsAreEqual(expected_word_2, decorated_word);
|
| EXPECT_TRUE(left_glyph_word_2.Contains(baseline_point));
|
| }
|
| @@ -3982,6 +4094,7 @@ TEST_P(RenderTextHarfBuzzTest, GetDecoratedWordAtPoint_RTL) {
|
| render_text->ApplyStyle(ITALIC, true, Range(0, 3));
|
| render_text->ApplyStyle(DIAGONAL_STRIKE, true, Range(0, 2));
|
| render_text->ApplyStyle(STRIKE, true, Range(2, 5));
|
| + const int cursor_y = GetCursorYForTesting();
|
|
|
| const std::vector<RenderText::FontSpan> font_spans =
|
| render_text->GetFontSpansForTesting();
|
| @@ -4014,14 +4127,14 @@ TEST_P(RenderTextHarfBuzzTest, GetDecoratedWordAtPoint_RTL) {
|
| {
|
| SCOPED_TRACE(base::StringPrintf("Query to the left of text bounds"));
|
| EXPECT_TRUE(render_text->GetDecoratedWordAtPoint(
|
| - Point(-5, 5), &decorated_word, &baseline_point));
|
| + Point(-5, cursor_y), &decorated_word, &baseline_point));
|
| VerifyDecoratedWordsAreEqual(expected_word_2, decorated_word);
|
| EXPECT_TRUE(left_glyph_word_2.Contains(baseline_point));
|
| }
|
| {
|
| SCOPED_TRACE(base::StringPrintf("Query to the right of text bounds"));
|
| EXPECT_TRUE(render_text->GetDecoratedWordAtPoint(
|
| - Point(105, 5), &decorated_word, &baseline_point));
|
| + Point(105, cursor_y), &decorated_word, &baseline_point));
|
| VerifyDecoratedWordsAreEqual(expected_word_1, decorated_word);
|
| EXPECT_TRUE(left_glyph_word_1.Contains(baseline_point));
|
| }
|
| @@ -4076,6 +4189,82 @@ TEST_P(RenderTextHarfBuzzTest, GetDecoratedWordAtPoint_Return) {
|
| &baseline_point));
|
| }
|
|
|
| +// Tests text selection made at end points of individual lines of multiline
|
| +// text.
|
| +TEST_P(RenderTextHarfBuzzTest, LineEndSelections) {
|
| + const wchar_t* const ltr = L"abc\n\ndef";
|
| + const wchar_t* const rtl = L"\x5d0\x5d1\x5d2\n\n\x5d3\x5d4\x5d5";
|
| + const int left_x = -100;
|
| + const int right_x = 200;
|
| + struct {
|
| + const wchar_t* const text;
|
| + const int line_num;
|
| + const int x;
|
| + const wchar_t* const selected_text;
|
| + } cases[] = {
|
| + {ltr, 1, left_x, L"abc\n"},
|
| + {ltr, 1, right_x, L"abc\n\n"},
|
| + {ltr, 2, left_x, L"abc\n\n"},
|
| + {ltr, 2, right_x, ltr},
|
| +
|
| + {rtl, 1, left_x, L"\x5d0\x5d1\x5d2\n\n"},
|
| + {rtl, 1, right_x, L"\x5d0\x5d1\x5d2\n"},
|
| + {rtl, 2, left_x, rtl},
|
| + {rtl, 2, right_x, L"\x5d0\x5d1\x5d2\n\n"},
|
| + };
|
| +
|
| + RenderText* render_text = GetRenderText();
|
| + render_text->SetMultiline(true);
|
| + render_text->SetDisplayRect(Rect(200, 1000));
|
| +
|
| + for (size_t i = 0; i < arraysize(cases); i++) {
|
| + SCOPED_TRACE(base::StringPrintf("Testing case %" PRIuS "", i));
|
| + render_text->SetText(WideToUTF16(cases[i].text));
|
| + test_api()->EnsureLayout();
|
| +
|
| + EXPECT_EQ(3u, test_api()->lines().size());
|
| + // Position the cursor at the logical beginning of text.
|
| + render_text->SelectRange(Range(0));
|
| +
|
| + render_text->MoveCursorTo(
|
| + Point(cases[i].x, GetCursorYForTesting(cases[i].line_num)), true);
|
| + EXPECT_EQ(WideToUTF16(cases[i].selected_text),
|
| + GetSelectedText(render_text));
|
| + }
|
| +}
|
| +
|
| +// Tests that GetSubstringBounds returns the correct bounds for multiline text.
|
| +TEST_P(RenderTextHarfBuzzTest, GetSubstringBoundsMultiline) {
|
| + RenderText* render_text = GetRenderText();
|
| + render_text->SetMultiline(true);
|
| + render_text->SetDisplayRect(Rect(200, 1000));
|
| + render_text->SetText(WideToUTF16(L"abc\n\ndef"));
|
| + test_api()->EnsureLayout();
|
| +
|
| + const std::vector<Range> line_char_range = {Range(0, 3), Range(4, 5),
|
| + Range(5, 8)};
|
| +
|
| + // Test bounds for individual lines.
|
| + EXPECT_EQ(3u, test_api()->lines().size());
|
| + Rect expected_total_bounds;
|
| + for (size_t i = 0; i < test_api()->lines().size(); i++) {
|
| + SCOPED_TRACE(base::StringPrintf("Testing bounds for line %" PRIuS "", i));
|
| + const internal::Line& line = test_api()->lines()[i];
|
| + const Size line_size(std::ceil(line.size.width()),
|
| + std::ceil(line.size.height()));
|
| + const Rect expected_line_bounds =
|
| + render_text->GetLineOffset(i) + Rect(line_size);
|
| + expected_total_bounds.Union(expected_line_bounds);
|
| +
|
| + render_text->SelectRange(line_char_range[i]);
|
| + EXPECT_EQ(expected_line_bounds, GetSelectionBoundsUnion());
|
| + }
|
| +
|
| + // Test complete bounds.
|
| + render_text->SelectAll(false);
|
| + EXPECT_EQ(expected_total_bounds, GetSelectionBoundsUnion());
|
| +}
|
| +
|
| // Prefix for test instantiations intentionally left blank since each test
|
| // fixture class has a single parameterization.
|
| #if defined(OS_MACOSX)
|
|
|