diff options
Diffstat (limited to 'ui')
-rw-r--r-- | ui/gfx/render_text.cc | 69 | ||||
-rw-r--r-- | ui/gfx/render_text.h | 19 | ||||
-rw-r--r-- | ui/gfx/render_text_linux.cc | 38 | ||||
-rw-r--r-- | ui/gfx/render_text_linux.h | 20 | ||||
-rw-r--r-- | ui/gfx/render_text_unittest.cc | 337 | ||||
-rw-r--r-- | ui/gfx/render_text_win.cc | 61 | ||||
-rw-r--r-- | ui/gfx/render_text_win.h | 12 |
7 files changed, 114 insertions, 442 deletions
diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc index 6d15e5c..1111842 100644 --- a/ui/gfx/render_text.cc +++ b/ui/gfx/render_text.cc @@ -247,12 +247,6 @@ bool RenderText::MoveCursorTo(const SelectionModel& selection_model) { sel.set_caret_pos(end.caret_pos()); sel.set_caret_placement(end.caret_placement()); } - - if (!IsCursorablePosition(sel.selection_start()) || - !IsCursorablePosition(sel.selection_end()) || - !IsCursorablePosition(sel.caret_pos())) - return false; - bool changed = !sel.Equals(selection_model_); SetSelectionModel(sel); return changed; @@ -287,28 +281,41 @@ void RenderText::SelectAll() { SetSelectionModel(sel); } +// TODO(xji): it does not work for languages do not use space as word breaker, +// such as Chinese. Should use BreakIterator. void RenderText::SelectWord() { + size_t selection_start = GetSelectionStart(); size_t cursor_position = GetCursorPosition(); + // First we setup selection_start_ and selection_end_. There are so many cases + // because we try to emulate what select-word looks like in a gtk textfield. + // See associated testcase for different cases. + if (cursor_position > 0 && cursor_position < text().length()) { + if (u_isalnum(text()[cursor_position])) { + selection_start = cursor_position; + cursor_position++; + } else + selection_start = cursor_position - 1; + } else if (cursor_position == 0) { + selection_start = cursor_position; + if (text().length() > 0) + cursor_position++; + } else { + selection_start = cursor_position - 1; + } - base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); - bool success = iter.Init(); - DCHECK(success); - if (!success) - return; - - size_t selection_start = cursor_position; - for (; selection_start != 0; --selection_start) { - if (iter.IsStartOfWord(selection_start) || - iter.IsEndOfWord(selection_start)) + // Now we move selection_start_ to beginning of selection. Selection boundary + // is defined as the position where we have alpha-num character on one side + // and non-alpha-num char on the other side. + for (; selection_start > 0; selection_start--) { + if (IsPositionAtWordSelectionBoundary(selection_start)) break; } - if (selection_start == cursor_position) - ++cursor_position; - - for (; cursor_position < text().length(); ++cursor_position) { - if (iter.IsEndOfWord(cursor_position) || - iter.IsStartOfWord(cursor_position)) + // Now we move selection_end_ to end of selection. Selection boundary + // is defined as the position where we have alpha-num character on one side + // and non-alpha-num char on the other side. + for (; cursor_position < text().length(); cursor_position++) { + if (IsPositionAtWordSelectionBoundary(cursor_position)) break; } @@ -455,10 +462,6 @@ const Rect& RenderText::GetUpdatedCursorBounds() { return cursor_bounds_; } -size_t RenderText::GetIndexOfNextGrapheme(size_t position) { - return IndexOfAdjacentGrapheme(position, true); -} - RenderText::RenderText() : text_(), selection_model_(), @@ -554,7 +557,8 @@ SelectionModel RenderText::RightEndSelectionModel() { } size_t RenderText::GetIndexOfPreviousGrapheme(size_t position) { - return IndexOfAdjacentGrapheme(position, false); + // TODO(msw): Handle complex script. + return std::max(static_cast<long>(position - 1), static_cast<long>(0)); } std::vector<Rect> RenderText::GetSubstringBounds(size_t from, size_t to) { @@ -628,10 +632,13 @@ void RenderText::MoveCursorTo(size_t position, bool select) { SelectionModel::CaretPlacement placement = (caret_pos == cursor) ? SelectionModel::LEADING : SelectionModel::TRAILING; size_t selection_start = select ? GetSelectionStart() : cursor; - if (IsCursorablePosition(cursor)) { - SelectionModel sel(selection_start, cursor, caret_pos, placement); - SetSelectionModel(sel); - } + SelectionModel sel(selection_start, cursor, caret_pos, placement); + SetSelectionModel(sel); +} + +bool RenderText::IsPositionAtWordSelectionBoundary(size_t pos) { + return pos == 0 || (u_isalnum(text()[pos - 1]) && !u_isalnum(text()[pos])) || + (!u_isalnum(text()[pos - 1]) && u_isalnum(text()[pos])); } void RenderText::UpdateCachedBoundsAndOffset() { diff --git a/ui/gfx/render_text.h b/ui/gfx/render_text.h index 4a1f9a9..82eab91 100644 --- a/ui/gfx/render_text.h +++ b/ui/gfx/render_text.h @@ -193,9 +193,7 @@ class UI_EXPORT RenderText { // Set the selection_model_ to the value of |selection|. // The selection model components are modified if invalid. // Returns true if the cursor position or selection range changed. - // If |selectin_start_| or |selection_end_| or |caret_pos_| in - // |selection_model| is not a cursorable position (not on grapheme boundary), - // it is a NO-OP and returns false. + // TODO(xji): need to check the cursor is set at grapheme boundary. bool MoveCursorTo(const SelectionModel& selection_model); // Move the cursor to the position associated with the clicked point. @@ -256,9 +254,6 @@ class UI_EXPORT RenderText { // Subsequent text, cursor, or bounds changes may invalidate returned values. const Rect& GetUpdatedCursorBounds(); - // Get the logical index of the grapheme following the argument |position|. - virtual size_t GetIndexOfNextGrapheme(size_t position); - protected: RenderText(); @@ -293,10 +288,6 @@ class UI_EXPORT RenderText { // TODO(msw) Re-evaluate this function's necessity and signature. virtual std::vector<Rect> GetSubstringBounds(size_t from, size_t to); - // Return true if cursor can appear in front of the character at |position|, - // which means it is a grapheme boundary or the first character in the text. - virtual bool IsCursorablePosition(size_t position) = 0; - // Apply composition style (underline) to composition range and selection // style (foreground) to selection range. void ApplyCompositionAndSelectionStyles(StyleRanges* style_ranges) const; @@ -314,20 +305,16 @@ class UI_EXPORT RenderText { FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ApplyStyleRange); FRIEND_TEST_ALL_PREFIXES(RenderTextTest, StyleRangesAdjust); - // Return an index belonging to the |next| or previous logical grapheme. - // The return value is bounded by 0 and the text length, inclusive. - virtual size_t IndexOfAdjacentGrapheme(size_t index, bool next) = 0; - // Sets the selection model, the argument is assumed to be valid. void SetSelectionModel(const SelectionModel& selection_model); // Set the cursor to |position|, with the caret trailing the previous // grapheme, or if there is no previous grapheme, leading the cursor position. // If |select| is false, the selection start is moved to the same position. - // If the |position| is not a cursorable position (not on grapheme boundary), - // it is a NO-OP. void MoveCursorTo(size_t position, bool select); + bool IsPositionAtWordSelectionBoundary(size_t pos); + // Update the cached bounds and display offset to ensure that the current // cursor is within the visible display area. void UpdateCachedBoundsAndOffset(); diff --git a/ui/gfx/render_text_linux.cc b/ui/gfx/render_text_linux.cc index d5ac9c1..1e5e865 100644 --- a/ui/gfx/render_text_linux.cc +++ b/ui/gfx/render_text_linux.cc @@ -123,19 +123,13 @@ void RenderTextLinux::Draw(Canvas* canvas) { } SelectionModel RenderTextLinux::FindCursorPosition(const Point& point) { + // TODO(xji): when points outside of text, return HOME/END position. PangoLayout* layout = EnsureLayout(); if (text().empty()) return SelectionModel(0, 0, SelectionModel::LEADING); Point p(ToTextPoint(point)); - - // When the point is outside of text, return HOME/END position. - if (p.x() < 0) - return LeftEndSelectionModel(); - else if (p.x() > GetStringWidth()) - return RightEndSelectionModel(); - int caret_pos, trailing; pango_layout_xy_to_index(layout, p.x() * PANGO_SCALE, p.y() * PANGO_SCALE, &caret_pos, &trailing); @@ -218,7 +212,7 @@ SelectionModel RenderTextLinux::LeftEndSelectionModel() { return SelectionModel(text().length(), caret, SelectionModel::LEADING); } else { // RTL. size_t caret = Utf16IndexOfAdjacentGrapheme(item->offset + item->length, - false); + PREVIOUS); return SelectionModel(text().length(), caret, SelectionModel::TRAILING); } } @@ -233,7 +227,7 @@ SelectionModel RenderTextLinux::RightEndSelectionModel() { PangoItem* item = last_visual_run->item; if (item->analysis.level % 2 == 0) { // LTR. size_t caret = Utf16IndexOfAdjacentGrapheme(item->offset + item->length, - false); + PREVIOUS); return SelectionModel(text().length(), caret, SelectionModel::TRAILING); } else { // RTL. size_t caret = Utf8IndexToUtf16Index(item->offset); @@ -244,18 +238,16 @@ SelectionModel RenderTextLinux::RightEndSelectionModel() { return SelectionModel(0, 0, SelectionModel::LEADING); } -bool RenderTextLinux::IsCursorablePosition(size_t position) { - if (position == 0 && text().empty()) - return true; - +size_t RenderTextLinux::GetIndexOfPreviousGrapheme(size_t position) { EnsureLayout(); - return (position >= 0 && position < static_cast<size_t>(num_log_attrs_) && - log_attrs_[position].is_cursor_position); + size_t index = Utf16IndexToUtf8Index(position); + return Utf16IndexOfAdjacentGrapheme(index, PREVIOUS); } -size_t RenderTextLinux::IndexOfAdjacentGrapheme(size_t index, bool next) { +size_t RenderTextLinux::GetIndexOfNextGrapheme(size_t position) { EnsureLayout(); - return Utf16IndexOfAdjacentGrapheme(Utf16IndexToUtf8Index(index), next); + size_t index = Utf16IndexToUtf8Index(position); + return Utf16IndexOfAdjacentGrapheme(index, NEXT); } GSList* RenderTextLinux::GetRunContainingPosition(size_t position) const { @@ -274,12 +266,12 @@ GSList* RenderTextLinux::GetRunContainingPosition(size_t position) const { size_t RenderTextLinux::Utf8IndexOfAdjacentGrapheme( size_t utf8_index_of_current_grapheme, - bool next) const { + RelativeLogicalPosition pos) const { const char* ch = layout_text_ + utf8_index_of_current_grapheme; int char_offset = static_cast<int>(g_utf8_pointer_to_offset(layout_text_, ch)); int start_char_offset = char_offset; - if (!next) { + if (pos == PREVIOUS) { if (char_offset > 0) { do { --char_offset; @@ -300,23 +292,23 @@ size_t RenderTextLinux::Utf8IndexOfAdjacentGrapheme( size_t RenderTextLinux::Utf16IndexOfAdjacentGrapheme( size_t utf8_index_of_current_grapheme, - bool next) const { + RelativeLogicalPosition pos) const { size_t utf8_index = Utf8IndexOfAdjacentGrapheme( - utf8_index_of_current_grapheme, next); + utf8_index_of_current_grapheme, pos); return Utf8IndexToUtf16Index(utf8_index); } SelectionModel RenderTextLinux::FirstSelectionModelInsideRun( const PangoItem* item) const { size_t caret = Utf8IndexToUtf16Index(item->offset); - size_t cursor = Utf16IndexOfAdjacentGrapheme(item->offset, true); + size_t cursor = Utf16IndexOfAdjacentGrapheme(item->offset, NEXT); return SelectionModel(cursor, caret, SelectionModel::TRAILING); } SelectionModel RenderTextLinux::LastSelectionModelInsideRun( const PangoItem* item) const { size_t caret = Utf16IndexOfAdjacentGrapheme(item->offset + item->length, - false); + PREVIOUS); return SelectionModel(caret, caret, SelectionModel::LEADING); } diff --git a/ui/gfx/render_text_linux.h b/ui/gfx/render_text_linux.h index 641e261..5d67b41 100644 --- a/ui/gfx/render_text_linux.h +++ b/ui/gfx/render_text_linux.h @@ -39,22 +39,28 @@ class RenderTextLinux : public RenderText { BreakType break_type) OVERRIDE; virtual SelectionModel LeftEndSelectionModel() OVERRIDE; virtual SelectionModel RightEndSelectionModel() OVERRIDE; - virtual bool IsCursorablePosition(size_t position) OVERRIDE; + virtual size_t GetIndexOfPreviousGrapheme(size_t position) OVERRIDE; private: - virtual size_t IndexOfAdjacentGrapheme(size_t index, bool next) OVERRIDE; + enum RelativeLogicalPosition { + PREVIOUS, + NEXT + }; + + // Get the logical start index of the next grapheme after |position|. + size_t GetIndexOfNextGrapheme(size_t position); // Returns the run that contains |position|. Return NULL if not found. GSList* GetRunContainingPosition(size_t position) const; // Given |utf8_index_of_current_grapheme|, returns the UTF8 or UTF16 index of - // next grapheme in the text if |next| is true, otherwise, returns the index - // of previous grapheme. Returns 0 if there is no previous grapheme, and - // returns the |text_| length if there is no next grapheme. + // next grapheme in the text if |pos| is NEXT, otherwise, returns the index of + // previous grapheme. Returns 0 if there is no previous grapheme, and returns + // the |text_| length if there is no next grapheme. size_t Utf8IndexOfAdjacentGrapheme(size_t utf8_index_of_current_grapheme, - bool next) const; + RelativeLogicalPosition pos) const; size_t Utf16IndexOfAdjacentGrapheme(size_t utf8_index_of_current_grapheme, - bool next) const; + RelativeLogicalPosition pos) const; // Given a |run|, returns the SelectionModel that contains the logical first // or last caret position inside (not at a boundary of) the run. diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc index 6898537..26389df 100644 --- a/ui/gfx/render_text_unittest.cc +++ b/ui/gfx/render_text_unittest.cc @@ -15,14 +15,14 @@ class RenderTextTest : public testing::Test { TEST_F(RenderTextTest, DefaultStyle) { // Defaults to empty text with no styles. - scoped_ptr<RenderText> render_text(RenderText::CreateRenderText()); + scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateRenderText()); EXPECT_TRUE(render_text->text().empty()); EXPECT_TRUE(render_text->style_ranges().empty()); // Test that the built-in default style is applied for new text. render_text->SetText(ASCIIToUTF16("abc")); EXPECT_EQ(1U, render_text->style_ranges().size()); - StyleRange style; + gfx::StyleRange style; EXPECT_EQ(style.font.GetFontName(), render_text->style_ranges()[0].font.GetFontName()); EXPECT_EQ(style.foreground, render_text->style_ranges()[0].foreground); @@ -38,8 +38,8 @@ TEST_F(RenderTextTest, DefaultStyle) { TEST_F(RenderTextTest, CustomDefaultStyle) { // Test a custom default style. - scoped_ptr<RenderText> render_text(RenderText::CreateRenderText()); - StyleRange color; + scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateRenderText()); + gfx::StyleRange color; color.foreground = SK_ColorRED; render_text->set_default_style(color); render_text->SetText(ASCIIToUTF16("abc")); @@ -54,7 +54,7 @@ TEST_F(RenderTextTest, CustomDefaultStyle) { EXPECT_EQ(color.foreground, render_text->style_ranges()[0].foreground); // Test ApplyDefaultStyle after setting a new default. - StyleRange strike; + gfx::StyleRange strike; strike.strike = true; render_text->set_default_style(strike); render_text->ApplyDefaultStyle(); @@ -64,24 +64,24 @@ TEST_F(RenderTextTest, CustomDefaultStyle) { } TEST_F(RenderTextTest, ApplyStyleRange) { - scoped_ptr<RenderText> render_text(RenderText::CreateRenderText()); + scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateRenderText()); render_text->SetText(ASCIIToUTF16("01234")); EXPECT_EQ(1U, render_text->style_ranges().size()); // Test ApplyStyleRange (no-op on empty range). - StyleRange empty; + gfx::StyleRange empty; empty.range = ui::Range(1, 1); render_text->ApplyStyleRange(empty); EXPECT_EQ(1U, render_text->style_ranges().size()); // Test ApplyStyleRange (no-op on invalid range). - StyleRange invalid; + gfx::StyleRange invalid; invalid.range = ui::Range::InvalidRange(); render_text->ApplyStyleRange(invalid); EXPECT_EQ(1U, render_text->style_ranges().size()); // Apply a style with a range contained by an existing range. - StyleRange underline; + gfx::StyleRange underline; underline.underline = true; underline.range = ui::Range(2, 3); render_text->ApplyStyleRange(underline); @@ -94,7 +94,7 @@ TEST_F(RenderTextTest, ApplyStyleRange) { EXPECT_FALSE(render_text->style_ranges()[2].underline); // Apply a style with a range equal to another range. - StyleRange color; + gfx::StyleRange color; color.foreground = SK_ColorWHITE; color.range = ui::Range(2, 3); render_text->ApplyStyleRange(color); @@ -111,7 +111,7 @@ TEST_F(RenderTextTest, ApplyStyleRange) { // Apply a style with a range containing an existing range. // This new style also overlaps portions of neighboring ranges. - StyleRange strike; + gfx::StyleRange strike; strike.strike = true; strike.range = ui::Range(1, 4); render_text->ApplyStyleRange(strike); @@ -124,7 +124,7 @@ TEST_F(RenderTextTest, ApplyStyleRange) { EXPECT_FALSE(render_text->style_ranges()[2].strike); // Apply a style overlapping all ranges. - StyleRange strike_underline; + gfx::StyleRange strike_underline; strike_underline.strike = true; strike_underline.underline = true; strike_underline.range = ui::Range(0, render_text->text().length()); @@ -144,7 +144,7 @@ TEST_F(RenderTextTest, ApplyStyleRange) { TEST_F(RenderTextTest, StyleRangesAdjust) { // Test that style ranges adjust to the text size. - scoped_ptr<RenderText> render_text(RenderText::CreateRenderText()); + scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateRenderText()); render_text->SetText(ASCIIToUTF16("abcdef")); EXPECT_EQ(1U, render_text->style_ranges().size()); EXPECT_EQ(ui::Range(0, 6), render_text->style_ranges()[0].range); @@ -155,7 +155,7 @@ TEST_F(RenderTextTest, StyleRangesAdjust) { EXPECT_EQ(ui::Range(0, 3), render_text->style_ranges()[0].range); // Test that the last range extends to the length of longer text. - StyleRange strike; + gfx::StyleRange strike; strike.strike = true; strike.range = ui::Range(2, 3); render_text->ApplyStyleRange(strike); @@ -178,313 +178,4 @@ TEST_F(RenderTextTest, StyleRangesAdjust) { EXPECT_FALSE(render_text->style_ranges()[0].strike); } -void RunMoveCursorLeftRightTest(RenderText* render_text, - const std::vector<SelectionModel>& expected, - bool move_right) { - for (int i = 0; i < static_cast<int>(expected.size()); ++i) { - SelectionModel sel = expected[i]; - EXPECT_TRUE(render_text->selection_model().Equals(sel)); - if (move_right) - render_text->MoveCursorRight(CHARACTER_BREAK, false); - else - render_text->MoveCursorLeft(CHARACTER_BREAK, false); - } - - SelectionModel sel = expected[expected.size() - 1]; - if (move_right) - render_text->MoveCursorRight(LINE_BREAK, false); - else - render_text->MoveCursorLeft(LINE_BREAK, false); - EXPECT_TRUE(render_text->selection_model().Equals(sel)); -} - -TEST_F(RenderTextTest, MoveCursorLeftRightInLtr) { - scoped_ptr<RenderText> render_text(RenderText::CreateRenderText()); - - // Pure LTR. - render_text->SetText(ASCIIToUTF16("abc")); - // |expected| saves the expected SelectionModel when moving cursor from left - // to right. - std::vector<SelectionModel> expected; - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); - expected.push_back(SelectionModel(1, 0, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(2, 1, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING)); - // The last element is to test the clamped line ends. - expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING)); - RunMoveCursorLeftRightTest(render_text.get(), expected, true); - - expected.clear(); - expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(2, 2, SelectionModel::LEADING)); - expected.push_back(SelectionModel(1, 1, SelectionModel::LEADING)); - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); - RunMoveCursorLeftRightTest(render_text.get(), expected, false); -} - -TEST_F(RenderTextTest, MoveCursorLeftRightInLtrRtl) { - scoped_ptr<RenderText> render_text(RenderText::CreateRenderText()); - // LTR-RTL - render_text->SetText(WideToUTF16(L"abc\x05d0\x05d1\x05d2")); - // The last one is the expected END position. - std::vector<SelectionModel> expected; - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); - expected.push_back(SelectionModel(1, 0, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(2, 1, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(5, 5, SelectionModel::LEADING)); - expected.push_back(SelectionModel(4, 4, SelectionModel::LEADING)); - expected.push_back(SelectionModel(3, 3, SelectionModel::LEADING)); - expected.push_back(SelectionModel(6, 3, SelectionModel::LEADING)); - expected.push_back(SelectionModel(6, 3, SelectionModel::LEADING)); - RunMoveCursorLeftRightTest(render_text.get(), expected, true); - - expected.clear(); - expected.push_back(SelectionModel(6, 3, SelectionModel::LEADING)); - expected.push_back(SelectionModel(4, 3, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(5, 4, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(6, 5, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(2, 2, SelectionModel::LEADING)); - expected.push_back(SelectionModel(1, 1, SelectionModel::LEADING)); - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); - RunMoveCursorLeftRightTest(render_text.get(), expected, false); -} - -TEST_F(RenderTextTest, MoveCursorLeftRightInLtrRtlLtr) { - scoped_ptr<RenderText> render_text(RenderText::CreateRenderText()); - // LTR-RTL-LTR. - render_text->SetText(WideToUTF16(L"a"L"\x05d1"L"b")); - std::vector<SelectionModel> expected; - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); - expected.push_back(SelectionModel(1, 0, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(1, 1, SelectionModel::LEADING)); - expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING)); - RunMoveCursorLeftRightTest(render_text.get(), expected, true); - - expected.clear(); - expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(2, 2, SelectionModel::LEADING)); - expected.push_back(SelectionModel(2, 1, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); - RunMoveCursorLeftRightTest(render_text.get(), expected, false); -} - -TEST_F(RenderTextTest, MoveCursorLeftRightInRtl) { - scoped_ptr<RenderText> render_text(RenderText::CreateRenderText()); - // Pure RTL. - render_text->SetText(WideToUTF16(L"\x05d0\x05d1\x05d2")); - render_text->MoveCursorRight(LINE_BREAK, false); - std::vector<SelectionModel> expected; - -#if defined(OS_LINUX) - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); -#else - expected.push_back(SelectionModel(3, 0, SelectionModel::LEADING)); -#endif - expected.push_back(SelectionModel(1, 0, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(2, 1, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING)); -#if defined(OS_LINUX) - expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING)); -#else - expected.push_back(SelectionModel(0, 2, SelectionModel::TRAILING)); - // TODO(xji): expected (0, 2, TRAILING), actual (3, 0, LEADING). - // cursor moves from leftmost to rightmost. - // expected.push_back(SelectionModel(0, 2, SelectionModel::TRAILING)); -#endif - RunMoveCursorLeftRightTest(render_text.get(), expected, false); - - expected.clear(); - -#if defined(OS_LINUX) - expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING)); -#else - expected.push_back(SelectionModel(0, 2, SelectionModel::TRAILING)); -#endif - expected.push_back(SelectionModel(2, 2, SelectionModel::LEADING)); - expected.push_back(SelectionModel(1, 1, SelectionModel::LEADING)); - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); -#if defined(OS_LINUX) - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); -#else - expected.push_back(SelectionModel(3, 0, SelectionModel::LEADING)); - expected.push_back(SelectionModel(3, 0, SelectionModel::LEADING)); -#endif - RunMoveCursorLeftRightTest(render_text.get(), expected, true); -} - -TEST_F(RenderTextTest, MoveCursorLeftRightInRtlLtr) { - scoped_ptr<RenderText> render_text(RenderText::CreateRenderText()); - // RTL-LTR - render_text->SetText(WideToUTF16(L"\x05d0\x05d1\x05d2"L"abc")); - render_text->MoveCursorRight(LINE_BREAK, false); - std::vector<SelectionModel> expected; -#if defined(OS_LINUX) - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); - expected.push_back(SelectionModel(1, 0, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(2, 1, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(5, 5, SelectionModel::LEADING)); - expected.push_back(SelectionModel(4, 4, SelectionModel::LEADING)); - expected.push_back(SelectionModel(3, 3, SelectionModel::LEADING)); - expected.push_back(SelectionModel(6, 3, SelectionModel::LEADING)); - expected.push_back(SelectionModel(6, 3, SelectionModel::LEADING)); -#else - expected.push_back(SelectionModel(6, 5, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(5, 5, SelectionModel::LEADING)); - expected.push_back(SelectionModel(4, 4, SelectionModel::LEADING)); - expected.push_back(SelectionModel(3, 3, SelectionModel::LEADING)); - expected.push_back(SelectionModel(1, 0, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(2, 1, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(0, 2, SelectionModel::TRAILING)); - // TODO(xji): expected (0, 2, TRAILING), actual (3, 0, LEADING). - // cursor moves from leftmost to middle. - // expected.push_back(SelectionModel(0, 2, SelectionModel::TRAILING)); -#endif - - RunMoveCursorLeftRightTest(render_text.get(), expected, false); - - expected.clear(); -#if defined(OS_LINUX) - expected.push_back(SelectionModel(6, 3, SelectionModel::LEADING)); - expected.push_back(SelectionModel(4, 3, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(5, 4, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(6, 5, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(2, 2, SelectionModel::LEADING)); - expected.push_back(SelectionModel(1, 1, SelectionModel::LEADING)); - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); -#else - expected.push_back(SelectionModel(0, 2, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(2, 2, SelectionModel::LEADING)); - expected.push_back(SelectionModel(1, 1, SelectionModel::LEADING)); - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); - expected.push_back(SelectionModel(4, 3, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(5, 4, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(6, 5, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(6, 5, SelectionModel::TRAILING)); -#endif - RunMoveCursorLeftRightTest(render_text.get(), expected, true); -} - -TEST_F(RenderTextTest, MoveCursorLeftRightInRtlLtrRtl) { - scoped_ptr<RenderText> render_text(RenderText::CreateRenderText()); - // RTL-LTR-RTL. - render_text->SetText(WideToUTF16(L"\x05d0"L"a"L"\x05d1")); - render_text->MoveCursorRight(LINE_BREAK, false); - std::vector<SelectionModel> expected; -#if defined(OS_LINUX) - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); - expected.push_back(SelectionModel(1, 0, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(1, 1, SelectionModel::LEADING)); - expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING)); -#else - expected.push_back(SelectionModel(3, 2, SelectionModel::LEADING)); - expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(1, 1, SelectionModel::LEADING)); - expected.push_back(SelectionModel(1, 0, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(0, 0, SelectionModel::TRAILING)); - // TODO(xji): expected (0, 0, TRAILING), actual (2, 1, LEADING). - // cursor moves from leftmost to middle. - // expected.push_back(SelectionModel(0, 0, SelectionModel::TRAILING)); -#endif - RunMoveCursorLeftRightTest(render_text.get(), expected, false); - - expected.clear(); -#if defined(OS_LINUX) - expected.push_back(SelectionModel(3, 2, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(2, 2, SelectionModel::LEADING)); - expected.push_back(SelectionModel(2, 1, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); -#else - expected.push_back(SelectionModel(0, 0, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(0, 0, SelectionModel::LEADING)); - expected.push_back(SelectionModel(2, 1, SelectionModel::TRAILING)); - expected.push_back(SelectionModel(2, 2, SelectionModel::LEADING)); - expected.push_back(SelectionModel(3, 2, SelectionModel::LEADING)); - expected.push_back(SelectionModel(3, 2, SelectionModel::LEADING)); -#endif - RunMoveCursorLeftRightTest(render_text.get(), expected, true); -} - -// TODO(xji): temporarily disable in platform Win since the complex script -// characters turned into empty square due to font regression. So, not able -// to test 2 characters belong to the same grapheme. -#if defined(OS_LINUX) -TEST_F(RenderTextTest, MoveCursorLeftRight_ComplexScript) { - scoped_ptr<RenderText> render_text(RenderText::CreateRenderText()); - - render_text->SetText(WideToUTF16(L"\x0915\x093f\x0915\x094d\x0915")); - EXPECT_EQ(0U, render_text->GetCursorPosition()); - render_text->MoveCursorRight(CHARACTER_BREAK, false); - EXPECT_EQ(2U, render_text->GetCursorPosition()); - render_text->MoveCursorRight(CHARACTER_BREAK, false); - EXPECT_EQ(4U, render_text->GetCursorPosition()); - render_text->MoveCursorRight(CHARACTER_BREAK, false); - EXPECT_EQ(5U, render_text->GetCursorPosition()); - render_text->MoveCursorRight(CHARACTER_BREAK, false); - EXPECT_EQ(5U, render_text->GetCursorPosition()); - - render_text->MoveCursorLeft(CHARACTER_BREAK, false); - EXPECT_EQ(4U, render_text->GetCursorPosition()); - render_text->MoveCursorLeft(CHARACTER_BREAK, false); - EXPECT_EQ(2U, render_text->GetCursorPosition()); - render_text->MoveCursorLeft(CHARACTER_BREAK, false); - EXPECT_EQ(0U, render_text->GetCursorPosition()); - render_text->MoveCursorLeft(CHARACTER_BREAK, false); - EXPECT_EQ(0U, render_text->GetCursorPosition()); -} -#endif - -TEST_F(RenderTextTest, MoveCursorLeftRightWithSelection) { - scoped_ptr<RenderText> render_text(RenderText::CreateRenderText()); - render_text->SetText(WideToUTF16(L"abc\x05d0\x05d1\x05d2")); - // Left arrow on select ranging (6, 4). - render_text->MoveCursorRight(LINE_BREAK, false); - EXPECT_EQ(6U, render_text->GetCursorPosition()); - render_text->MoveCursorLeft(CHARACTER_BREAK, false); - EXPECT_EQ(4U, render_text->GetCursorPosition()); - render_text->MoveCursorLeft(CHARACTER_BREAK, false); - EXPECT_EQ(5U, render_text->GetCursorPosition()); - render_text->MoveCursorLeft(CHARACTER_BREAK, false); - EXPECT_EQ(6U, render_text->GetCursorPosition()); - render_text->MoveCursorRight(CHARACTER_BREAK, true); - EXPECT_EQ(6U, render_text->GetSelectionStart()); - EXPECT_EQ(5U, render_text->GetCursorPosition()); - render_text->MoveCursorRight(CHARACTER_BREAK, true); - EXPECT_EQ(6U, render_text->GetSelectionStart()); - EXPECT_EQ(4U, render_text->GetCursorPosition()); - render_text->MoveCursorLeft(CHARACTER_BREAK, false); - EXPECT_EQ(6U, render_text->GetCursorPosition()); - - // Right arrow on select ranging (4, 6). - render_text->MoveCursorLeft(LINE_BREAK, false); - EXPECT_EQ(0U, render_text->GetCursorPosition()); - render_text->MoveCursorRight(CHARACTER_BREAK, false); - EXPECT_EQ(1U, render_text->GetCursorPosition()); - render_text->MoveCursorRight(CHARACTER_BREAK, false); - EXPECT_EQ(2U, render_text->GetCursorPosition()); - render_text->MoveCursorRight(CHARACTER_BREAK, false); - EXPECT_EQ(3U, render_text->GetCursorPosition()); - render_text->MoveCursorRight(CHARACTER_BREAK, false); - EXPECT_EQ(5U, render_text->GetCursorPosition()); - render_text->MoveCursorRight(CHARACTER_BREAK, false); - EXPECT_EQ(4U, render_text->GetCursorPosition()); - render_text->MoveCursorLeft(CHARACTER_BREAK, true); - EXPECT_EQ(4U, render_text->GetSelectionStart()); - EXPECT_EQ(5U, render_text->GetCursorPosition()); - render_text->MoveCursorLeft(CHARACTER_BREAK, true); - EXPECT_EQ(4U, render_text->GetSelectionStart()); - EXPECT_EQ(6U, render_text->GetCursorPosition()); - render_text->MoveCursorRight(CHARACTER_BREAK, false); - EXPECT_EQ(4U, render_text->GetCursorPosition()); -} - } // namespace gfx diff --git a/ui/gfx/render_text_win.cc b/ui/gfx/render_text_win.cc index f6b983d..c2c9acf 100644 --- a/ui/gfx/render_text_win.cc +++ b/ui/gfx/render_text_win.cc @@ -237,6 +237,10 @@ SelectionModel RenderTextWin::RightEndSelectionModel() { return SelectionModel(cursor, caret, placement); } +size_t RenderTextWin::GetIndexOfPreviousGrapheme(size_t position) { + return IndexOfAdjacentGrapheme(position, false); +} + std::vector<Rect> RenderTextWin::GetSubstringBounds(size_t from, size_t to) { ui::Range range(from, to); DCHECK(ui::Range(0, text().length()).Contains(range)); @@ -291,41 +295,6 @@ std::vector<Rect> RenderTextWin::GetSubstringBounds(size_t from, size_t to) { return bounds; } -bool RenderTextWin::IsCursorablePosition(size_t position) { - if (position == 0 || position == text().length()) - return true; - - size_t run_index = GetRunContainingPosition(position); - if (run_index >= runs_.size()) - return false; - - internal::TextRun* run = runs_[run_index]; - size_t start = run->range.start(); - if (position == start) - return true; - return run->logical_clusters[position - start] != - run->logical_clusters[position - start - 1]; -} - -size_t RenderTextWin::IndexOfAdjacentGrapheme(size_t index, bool next) { - size_t run_index = GetRunContainingPosition(index); - internal::TextRun* run = run_index < runs_.size() ? runs_[run_index] : NULL; - long start = run ? run->range.start() : 0; - long length = run ? run->range.length() : text().length(); - long ch = index - start; - WORD cluster = run ? run->logical_clusters[ch] : 0; - - if (!next) { - do { - ch--; - } while (ch >= 0 && run && run->logical_clusters[ch] == cluster); - } else { - while (ch < length && run && run->logical_clusters[ch] == cluster) - ch++; - } - return std::max(static_cast<long>(std::min(ch, length) + start), 0L); -} - void RenderTextWin::ItemizeLogicalText() { text_is_dirty_ = false; STLDeleteContainerPointers(runs_.begin(), runs_.end()); @@ -492,16 +461,34 @@ size_t RenderTextWin::GetRunContainingPoint(const Point& point) const { return run; } +size_t RenderTextWin::IndexOfAdjacentGrapheme(size_t index, bool next) const { + size_t run_index = GetRunContainingPosition(index); + internal::TextRun* run = run_index < runs_.size() ? runs_[run_index] : NULL; + long start = run ? run->range.start() : 0; + long length = run ? run->range.length() : text().length(); + long ch = index - start; + WORD cluster = run ? run->logical_clusters[ch] : 0; + + if (!next) { + do { + ch--; + } while (ch >= 0 && run && run->logical_clusters[ch] == cluster); + } else { + while (ch < length && run && run->logical_clusters[ch] == cluster) + ch++; + } + return std::max(static_cast<long>(std::min(ch, length) + start), 0L); +} SelectionModel RenderTextWin::FirstSelectionModelInsideRun( - internal::TextRun* run) { + internal::TextRun* run) const { size_t caret = run->range.start(); size_t cursor = IndexOfAdjacentGrapheme(caret, true); return SelectionModel(cursor, caret, SelectionModel::TRAILING); } SelectionModel RenderTextWin::LastSelectionModelInsideRun( - internal::TextRun* run) { + internal::TextRun* run) const { size_t caret = IndexOfAdjacentGrapheme(run->range.end(), false); return SelectionModel(caret, caret, SelectionModel::LEADING); } diff --git a/ui/gfx/render_text_win.h b/ui/gfx/render_text_win.h index 53eea69..d812f50 100644 --- a/ui/gfx/render_text_win.h +++ b/ui/gfx/render_text_win.h @@ -72,12 +72,10 @@ class RenderTextWin : public RenderText { BreakType break_type) OVERRIDE; virtual SelectionModel LeftEndSelectionModel() OVERRIDE; virtual SelectionModel RightEndSelectionModel() OVERRIDE; + virtual size_t GetIndexOfPreviousGrapheme(size_t position) OVERRIDE; virtual std::vector<Rect> GetSubstringBounds(size_t from, size_t to) OVERRIDE; - virtual bool IsCursorablePosition(size_t position) OVERRIDE; private: - virtual size_t IndexOfAdjacentGrapheme(size_t index, bool next) OVERRIDE; - void ItemizeLogicalText(); void LayoutVisualText(HDC hdc); @@ -86,11 +84,15 @@ class RenderTextWin : public RenderText { size_t GetRunContainingPosition(size_t position) const; size_t GetRunContainingPoint(const Point& point) const; + // Return an index belonging to the |next| or previous logical grapheme. + // The return value is bounded by 0 and the text length, inclusive. + size_t IndexOfAdjacentGrapheme(size_t index, bool next) const; + // Given a |run|, returns the SelectionModel that contains the logical first // or last caret position inside (not at a boundary of) the run. // The returned value represents a cursor/caret position without a selection. - SelectionModel FirstSelectionModelInsideRun(internal::TextRun*); - SelectionModel LastSelectionModelInsideRun(internal::TextRun*); + SelectionModel FirstSelectionModelInsideRun(internal::TextRun*) const; + SelectionModel LastSelectionModelInsideRun(internal::TextRun*) const; // Get the selection model visually left/right of |selection| by one grapheme. // The returned value represents a cursor/caret position without a selection. |