diff options
author | msw@chromium.org <msw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-28 21:01:51 +0000 |
---|---|---|
committer | msw@chromium.org <msw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-09-28 21:01:51 +0000 |
commit | 6bc200d5ff9f3f466144d55504fa8d94755ee8ef (patch) | |
tree | fe5e65c48b44b166f8114c07c660aabe20cae4e2 /ui/gfx | |
parent | 9c2c1ea8f79577f6ba6b735ec3e9be573f3b5172 (diff) | |
download | chromium_src-6bc200d5ff9f3f466144d55504fa8d94755ee8ef.zip chromium_src-6bc200d5ff9f3f466144d55504fa8d94755ee8ef.tar.gz chromium_src-6bc200d5ff9f3f466144d55504fa8d94755ee8ef.tar.bz2 |
Support obscured RenderTextWin passwords.
Use asterisks instead of actual text when obscured on Win.
Use GetLayoutText() and index conversion in RenderTextWin.
Add UpdateObscuredText(), called on Set[Text|Obscured]().
Shortcut word breaking when obscured like RenderTextLinux.
Expand upon tests and enable on Windows; refactoring; etc.
Fix and simplify TextfieldViewsModelTest.Clipboard.
( It incorrectly expected obscured text to support word-break cursor movement )
( It was disabled on Linux Aura for long-fixed http://crbug.com/97845 )
BUG=97845,138222
TEST=Views examples and ftp auth --enable-views-textfield password textfields work, etc.
R=xji@chromium.org,asvitkine@chromium.org
TBR=sky@chromium.org
Review URL: https://chromiumcodereview.appspot.com/10821079
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@159335 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'ui/gfx')
-rw-r--r-- | ui/gfx/render_text.cc | 22 | ||||
-rw-r--r-- | ui/gfx/render_text.h | 24 | ||||
-rw-r--r-- | ui/gfx/render_text_linux.cc | 38 | ||||
-rw-r--r-- | ui/gfx/render_text_linux.h | 8 | ||||
-rw-r--r-- | ui/gfx/render_text_mac.cc | 12 | ||||
-rw-r--r-- | ui/gfx/render_text_mac.h | 4 | ||||
-rw-r--r-- | ui/gfx/render_text_unittest.cc | 54 | ||||
-rw-r--r-- | ui/gfx/render_text_win.cc | 103 | ||||
-rw-r--r-- | ui/gfx/render_text_win.h | 4 |
9 files changed, 174 insertions, 95 deletions
diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc index 4971dca..a8246c2 100644 --- a/ui/gfx/render_text.cc +++ b/ui/gfx/render_text.cc @@ -399,6 +399,7 @@ void RenderText::SetText(const string16& text) { if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT) text_direction_ = base::i18n::UNKNOWN_DIRECTION; + UpdateObscuredText(); ResetLayout(); } @@ -444,6 +445,7 @@ void RenderText::SetObscured(bool obscured) { if (obscured != obscured_) { obscured_ = obscured; cached_bounds_and_offset_valid_ = false; + UpdateObscuredText(); ResetLayout(); } } @@ -619,7 +621,7 @@ base::i18n::TextDirection RenderText::GetTextDirection() { // Derive the direction from the display text, which differs from text() // in the case of obscured (password) textfields. text_direction_ = - base::i18n::GetFirstStrongCharacterDirection(GetDisplayText()); + base::i18n::GetFirstStrongCharacterDirection(GetLayoutText()); break; case DIRECTIONALITY_FROM_UI: text_direction_ = base::i18n::IsRTL() ? base::i18n::RIGHT_TO_LEFT : @@ -795,12 +797,8 @@ void RenderText::SetSelectionModel(const SelectionModel& model) { cached_bounds_and_offset_valid_ = false; } -string16 RenderText::GetDisplayText() const { - if (!obscured_) - return text_; - size_t obscured_text_length = - static_cast<size_t>(ui::UTF16IndexToOffset(text_, 0, text_.length())); - return string16(obscured_text_length, kPasswordReplacementChar); +const string16& RenderText::GetLayoutText() const { + return obscured() ? obscured_text_ : text(); } void RenderText::ApplyCompositionAndSelectionStyles( @@ -952,6 +950,16 @@ void RenderText::MoveCursorTo(size_t position, bool select) { (cursor == 0) ? CURSOR_FORWARD : CURSOR_BACKWARD)); } +void RenderText::UpdateObscuredText() { + if (!obscured_) + return; + + const size_t obscured_text_length = + static_cast<size_t>(ui::UTF16IndexToOffset(text_, 0, text_.length())); + if (obscured_text_.length() != obscured_text_length) + obscured_text_.resize(obscured_text_length, kPasswordReplacementChar); +} + void RenderText::UpdateCachedBoundsAndOffset() { if (cached_bounds_and_offset_valid_) return; diff --git a/ui/gfx/render_text.h b/ui/gfx/render_text.h index 184cf56..3ba9f87 100644 --- a/ui/gfx/render_text.h +++ b/ui/gfx/render_text.h @@ -160,7 +160,7 @@ class UI_EXPORT RenderText { void set_default_style(const StyleRange& style) { default_style_ = style; } // In an obscured (password) field, all text is drawn as asterisks or bullets. - bool is_obscured() const { return obscured_; } + bool obscured() const { return obscured_; } void SetObscured(bool obscured); const Rect& display_rect() const { return display_rect_; } @@ -339,7 +339,13 @@ class UI_EXPORT RenderText { // These bounds are in local coordinates, but may be outside the visible // region if the text is longer than the textfield. Subsequent text, cursor, // or bounds changes may invalidate returned values. - virtual std::vector<Rect> GetSubstringBounds(ui::Range range) = 0; + virtual std::vector<Rect> GetSubstringBounds(const ui::Range& range) = 0; + + // Convert between indices into |text_| and indices into |obscured_text_|, + // which differ when the text is obscured. Regardless of whether or not the + // text is obscured, the character (code point) offsets always match. + virtual size_t TextIndexToLayoutIndex(size_t index) const = 0; + virtual size_t LayoutIndexToTextIndex(size_t index) const = 0; // 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. @@ -354,9 +360,8 @@ class UI_EXPORT RenderText { // Draw the text. virtual void DrawVisualText(Canvas* canvas) = 0; - // Like text() except that it returns asterisks or bullets if this is an - // obscured field. - string16 GetDisplayText() const; + // Returns the text used for layout, which may be |obscured_text_|. + const string16& GetLayoutText() const; // Apply composition style (underline) to composition range and selection // style (foreground) to selection range. @@ -400,7 +405,7 @@ class UI_EXPORT RenderText { FRIEND_TEST_ALL_PREFIXES(RenderTextTest, CustomDefaultStyle); FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ApplyStyleRange); FRIEND_TEST_ALL_PREFIXES(RenderTextTest, StyleRangesAdjust); - FRIEND_TEST_ALL_PREFIXES(RenderTextTest, PasswordCensorship); + FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ObscuredText); FRIEND_TEST_ALL_PREFIXES(RenderTextTest, GraphemePositions); FRIEND_TEST_ALL_PREFIXES(RenderTextTest, EdgeSelectionModels); FRIEND_TEST_ALL_PREFIXES(RenderTextTest, OriginForDrawing); @@ -412,6 +417,9 @@ class UI_EXPORT RenderText { // it is a NO-OP. void MoveCursorTo(size_t position, bool select); + // Updates |obscured_text_| if the text is obscured. + void UpdateObscuredText(); + // Update the cached bounds and display offset to ensure that the current // cursor is within the visible display area. void UpdateCachedBoundsAndOffset(); @@ -473,8 +481,10 @@ class UI_EXPORT RenderText { // The default text style. StyleRange default_style_; - // True if this is an obscured (password) field. + // A flag and the text to display for obscured (password) fields. + // Asterisks are used instead of the actual text glyphs when true. bool obscured_; + string16 obscured_text_; // Fade text head and/or tail, if text doesn't fit into |display_rect_|. bool fade_head_; diff --git a/ui/gfx/render_text_linux.cc b/ui/gfx/render_text_linux.cc index 372e72b..bd9724f 100644 --- a/ui/gfx/render_text_linux.cc +++ b/ui/gfx/render_text_linux.cc @@ -181,7 +181,7 @@ SelectionModel RenderTextLinux::AdjacentCharSelectionModel( SelectionModel RenderTextLinux::AdjacentWordSelectionModel( const SelectionModel& selection, VisualCursorDirection direction) { - if (is_obscured()) + if (obscured()) return EdgeSelectionModel(direction); base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); @@ -223,7 +223,7 @@ void RenderTextLinux::GetGlyphBounds(size_t index, *height = PANGO_PIXELS(pos.height); } -std::vector<Rect> RenderTextLinux::GetSubstringBounds(ui::Range range) { +std::vector<Rect> RenderTextLinux::GetSubstringBounds(const ui::Range& range) { DCHECK_LE(range.GetMax(), text().length()); if (range.is_empty()) @@ -235,6 +235,20 @@ std::vector<Rect> RenderTextLinux::GetSubstringBounds(ui::Range range) { return CalculateSubstringBounds(range); } +size_t RenderTextLinux::TextIndexToLayoutIndex(size_t index) const { + DCHECK(layout_); + const ptrdiff_t offset = ui::UTF16IndexToOffset(text(), 0, index); + const char* layout_pointer = g_utf8_offset_to_pointer(layout_text_, offset); + return (layout_pointer - layout_text_); +} + +size_t RenderTextLinux::LayoutIndexToTextIndex(size_t index) const { + DCHECK(layout_); + const char* layout_pointer = layout_text_ + index; + const long offset = g_utf8_pointer_to_offset(layout_text_, layout_pointer); + return ui::UTF16OffsetToIndex(text(), 0, offset); +} + bool RenderTextLinux::IsCursorablePosition(size_t position) { if (position == 0 && text().empty()) return true; @@ -281,7 +295,7 @@ void RenderTextLinux::EnsureLayout() { cairo_surface_destroy(surface); SetupPangoLayoutWithFontDescription(layout_, - GetDisplayText(), + GetLayoutText(), font_list().GetFontDescriptionString(), 0, GetTextDirection(), @@ -490,24 +504,6 @@ SelectionModel RenderTextLinux::LastSelectionModelInsideRun( return SelectionModel(caret, CURSOR_FORWARD); } -size_t RenderTextLinux::TextIndexToLayoutIndex(size_t text_index) const { - // If the text is obscured then |layout_text_| is not the same as |text()|, - // but whether or not the text is obscured, the character (code point) offset - // in |layout_text_| is the same as that in |text()|. - DCHECK(layout_); - ptrdiff_t offset = ui::UTF16IndexToOffset(text(), 0, text_index); - const char* layout_pointer = g_utf8_offset_to_pointer(layout_text_, offset); - return (layout_pointer - layout_text_); -} - -size_t RenderTextLinux::LayoutIndexToTextIndex(size_t layout_index) const { - // See |TextIndexToLayoutIndex()|. - DCHECK(layout_); - const char* layout_pointer = layout_text_ + layout_index; - long offset = g_utf8_pointer_to_offset(layout_text_, layout_pointer); - return ui::UTF16OffsetToIndex(text(), 0, offset); -} - std::vector<Rect> RenderTextLinux::CalculateSubstringBounds(ui::Range range) { int* ranges; int n_ranges; diff --git a/ui/gfx/render_text_linux.h b/ui/gfx/render_text_linux.h index 190f2dd..12f61ba 100644 --- a/ui/gfx/render_text_linux.h +++ b/ui/gfx/render_text_linux.h @@ -36,7 +36,9 @@ class RenderTextLinux : public RenderText { virtual void GetGlyphBounds(size_t index, ui::Range* xspan, int* height) OVERRIDE; - virtual std::vector<Rect> GetSubstringBounds(ui::Range range) OVERRIDE; + virtual std::vector<Rect> GetSubstringBounds(const ui::Range& range) OVERRIDE; + virtual size_t TextIndexToLayoutIndex(size_t index) const OVERRIDE; + virtual size_t LayoutIndexToTextIndex(size_t index) const OVERRIDE; virtual bool IsCursorablePosition(size_t position) OVERRIDE; virtual void ResetLayout() OVERRIDE; virtual void EnsureLayout() OVERRIDE; @@ -62,10 +64,6 @@ class RenderTextLinux : public RenderText { PangoAttribute* pango_attr, PangoAttrList* attrs); - // Convert between indices into text() and indices into |layout_text_|. - size_t TextIndexToLayoutIndex(size_t index) const; - size_t LayoutIndexToTextIndex(size_t index) const; - // Calculate the visual bounds containing the logical substring within the // given range. std::vector<Rect> CalculateSubstringBounds(ui::Range range); diff --git a/ui/gfx/render_text_mac.cc b/ui/gfx/render_text_mac.cc index 3396ceb..9ee069f 100644 --- a/ui/gfx/render_text_mac.cc +++ b/ui/gfx/render_text_mac.cc @@ -74,11 +74,21 @@ void RenderTextMac::GetGlyphBounds(size_t index, // TODO(asvitkine): Implement this. http://crbug.com/131618 } -std::vector<Rect> RenderTextMac::GetSubstringBounds(ui::Range range) { +std::vector<Rect> RenderTextMac::GetSubstringBounds(const ui::Range& range) { // TODO(asvitkine): Implement this. http://crbug.com/131618 return std::vector<Rect>(); } +size_t RenderTextMac::TextIndexToLayoutIndex(size_t index) const { + // TODO(asvitkine): Implement this. http://crbug.com/131618 + return index; +} + +size_t RenderTextMac::LayoutIndexToTextIndex(size_t index) const { + // TODO(asvitkine): Implement this. http://crbug.com/131618 + return index; +} + bool RenderTextMac::IsCursorablePosition(size_t position) { // TODO(asvitkine): Implement this. http://crbug.com/131618 return false; diff --git a/ui/gfx/render_text_mac.h b/ui/gfx/render_text_mac.h index 97ac971..3bbfae4 100644 --- a/ui/gfx/render_text_mac.h +++ b/ui/gfx/render_text_mac.h @@ -42,7 +42,9 @@ class RenderTextMac : public RenderText { virtual void GetGlyphBounds(size_t index, ui::Range* xspan, int* height) OVERRIDE; - virtual std::vector<Rect> GetSubstringBounds(ui::Range range) OVERRIDE; + virtual std::vector<Rect> GetSubstringBounds(const ui::Range& range) OVERRIDE; + virtual size_t TextIndexToLayoutIndex(size_t index) const OVERRIDE; + virtual size_t LayoutIndexToTextIndex(size_t index) const OVERRIDE; virtual bool IsCursorablePosition(size_t position) OVERRIDE; virtual void ResetLayout() OVERRIDE; virtual void EnsureLayout() OVERRIDE; diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc index 2680800..fa3a6e9 100644 --- a/ui/gfx/render_text_unittest.cc +++ b/ui/gfx/render_text_unittest.cc @@ -323,6 +323,7 @@ TEST_F(RenderTextTest, StyleRangesAdjust) { void TestVisualCursorMotionInObscuredField(RenderText* render_text, const string16& text, bool select) { + ASSERT_TRUE(render_text->obscured()); render_text->SetText(text); int len = text.length(); render_text->MoveCursor(LINE_BREAK, CURSOR_RIGHT, select); @@ -347,49 +348,70 @@ void TestVisualCursorMotionInObscuredField(RenderText* render_text, EXPECT_EQ(SelectionModel(0, CURSOR_BACKWARD), render_text->selection_model()); } -TEST_F(RenderTextTest, PasswordCensorship) { +TEST_F(RenderTextTest, ObscuredText) { const string16 seuss = ASCIIToUTF16("hop on pop"); const string16 no_seuss = ASCIIToUTF16("**********"); scoped_ptr<RenderText> render_text(RenderText::CreateInstance()); - // GetObscuredText returns asterisks when the obscured bit is set. + // GetLayoutText() returns asterisks when the obscured bit is set. render_text->SetText(seuss); render_text->SetObscured(true); EXPECT_EQ(seuss, render_text->text()); - EXPECT_EQ(no_seuss, render_text->GetDisplayText()); + EXPECT_EQ(no_seuss, render_text->GetLayoutText()); render_text->SetObscured(false); EXPECT_EQ(seuss, render_text->text()); - EXPECT_EQ(seuss, render_text->GetDisplayText()); + EXPECT_EQ(seuss, render_text->GetLayoutText()); -// TODO(benrg): No Windows implementation yet. -#if !defined(OS_WIN) render_text->SetObscured(true); // Surrogate pairs are counted as one code point. const char16 invalid_surrogates[] = {0xDC00, 0xD800, 0}; render_text->SetText(invalid_surrogates); - EXPECT_EQ(ASCIIToUTF16("**"), render_text->GetDisplayText()); + EXPECT_EQ(ASCIIToUTF16("**"), render_text->GetLayoutText()); const char16 valid_surrogates[] = {0xD800, 0xDC00, 0}; render_text->SetText(valid_surrogates); - EXPECT_EQ(ASCIIToUTF16("*"), render_text->GetDisplayText()); + EXPECT_EQ(ASCIIToUTF16("*"), render_text->GetLayoutText()); EXPECT_EQ(0U, render_text->cursor_position()); render_text->MoveCursor(CHARACTER_BREAK, CURSOR_RIGHT, false); EXPECT_EQ(2U, render_text->cursor_position()); - // Cursoring is independent of the underlying characters when the text is - // obscured. + // Test index conversion and cursor validity with a valid surrogate pair. + EXPECT_EQ(0U, render_text->TextIndexToLayoutIndex(0U)); + EXPECT_EQ(1U, render_text->TextIndexToLayoutIndex(1U)); + EXPECT_EQ(1U, render_text->TextIndexToLayoutIndex(2U)); + EXPECT_EQ(0U, render_text->LayoutIndexToTextIndex(0U)); + EXPECT_EQ(2U, render_text->LayoutIndexToTextIndex(1U)); + EXPECT_TRUE(render_text->IsCursorablePosition(0U)); + EXPECT_FALSE(render_text->IsCursorablePosition(1U)); + EXPECT_TRUE(render_text->IsCursorablePosition(2U)); + + // 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); + for (int x = -1; x <= 20; ++x) { + SelectionModel selection = render_text->FindCursorPosition(Point(x, 0)); + EXPECT_TRUE(selection.caret_pos() == 0U || selection.caret_pos() == 2U); + } + + // GetGlyphBounds() should yield the entire string bounds for text index 0. + int height = 0; + ui::Range bounds; + render_text->GetGlyphBounds(0U, &bounds, &height); + EXPECT_EQ(render_text->GetStringSize().width(), + static_cast<int>(bounds.length())); + + // Cursoring is independent of underlying characters when text is obscured. const wchar_t* const texts[] = { - L"hop on pop", // word boundaries - L"ab \x5D0\x5D1" L"12", // bidi embedding level of 2 - L"\x5D0\x5D1" L"12", // RTL paragraph direction on Linux - L"\x5D0\x5D1" // pure RTL + kWeak, kLtr, kLtrRtl, kLtrRtlLtr, kRtl, kRtlLtr, kRtlLtrRtl, + L"hop on pop", // Check LTR word boundaries. + L"\x05d0\x05d1 \x05d0\x05d2 \x05d1\x05d2", // Check RTL word boundaries. }; - for (size_t i = 0; i < ARRAYSIZE_UNSAFE(texts); ++i) { + for (size_t i = 0; i < arraysize(texts); ++i) { string16 text = WideToUTF16(texts[i]); TestVisualCursorMotionInObscuredField(render_text.get(), text, false); TestVisualCursorMotionInObscuredField(render_text.get(), text, true); } -#endif // !defined(OS_WIN) } TEST_F(RenderTextTest, GetTextDirection) { diff --git a/ui/gfx/render_text_win.cc b/ui/gfx/render_text_win.cc index cd9c074..0b72ab6 100644 --- a/ui/gfx/render_text_win.cc +++ b/ui/gfx/render_text_win.cc @@ -12,6 +12,7 @@ #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "base/win/windows_version.h" +#include "ui/base/text/utf16_indexing.h" #include "ui/gfx/canvas.h" #include "ui/gfx/font_fallback_win.h" #include "ui/gfx/font_smoothing_win.h" @@ -222,7 +223,7 @@ SelectionModel RenderTextWin::FindCursorPosition(const Point& point) { DCHECK(SUCCEEDED(hr)); DCHECK_GE(trailing, 0); position += run->range.start(); - size_t cursor = position + trailing; + const size_t cursor = LayoutIndexToTextIndex(position + trailing); DCHECK_LE(cursor, text().length()); return SelectionModel(cursor, trailing ? CURSOR_BACKWARD : CURSOR_FORWARD); } @@ -231,8 +232,11 @@ std::vector<RenderText::FontSpan> RenderTextWin::GetFontSpansForTesting() { EnsureLayout(); std::vector<RenderText::FontSpan> spans; - for (size_t i = 0; i < runs_.size(); ++i) - spans.push_back(RenderText::FontSpan(runs_[i]->font, runs_[i]->range)); + for (size_t i = 0; i < runs_.size(); ++i) { + spans.push_back(RenderText::FontSpan(runs_[i]->font, + ui::Range(LayoutIndexToTextIndex(runs_[i]->range.start()), + LayoutIndexToTextIndex(runs_[i]->range.end())))); + } return spans; } @@ -258,12 +262,12 @@ SelectionModel RenderTextWin::AdjacentCharSelectionModel( bool forward_motion = run->script_analysis.fRTL == (direction == CURSOR_LEFT); if (forward_motion) { - if (caret < run->range.end()) { + if (caret < LayoutIndexToTextIndex(run->range.end())) { caret = IndexOfAdjacentGrapheme(caret, CURSOR_FORWARD); return SelectionModel(caret, CURSOR_BACKWARD); } } else { - if (caret > run->range.start()) { + if (caret > LayoutIndexToTextIndex(run->range.start())) { caret = IndexOfAdjacentGrapheme(caret, CURSOR_BACKWARD); return SelectionModel(caret, CURSOR_FORWARD); } @@ -284,6 +288,9 @@ SelectionModel RenderTextWin::AdjacentCharSelectionModel( SelectionModel RenderTextWin::AdjacentWordSelectionModel( const SelectionModel& selection, VisualCursorDirection direction) { + if (obscured()) + return EdgeSelectionModel(direction); + base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD); bool success = iter.Init(); DCHECK(success); @@ -338,35 +345,37 @@ void RenderTextWin::SetSelectionModel(const SelectionModel& model) { void RenderTextWin::GetGlyphBounds(size_t index, ui::Range* xspan, int* height) { - size_t run_index = + const size_t run_index = GetRunContainingCaret(SelectionModel(index, CURSOR_FORWARD)); DCHECK_LT(run_index, runs_.size()); internal::TextRun* run = runs_[run_index]; - xspan->set_start(GetGlyphXBoundary(run, index, false)); - xspan->set_end(GetGlyphXBoundary(run, index, true)); + const size_t layout_index = TextIndexToLayoutIndex(index); + xspan->set_start(GetGlyphXBoundary(run, layout_index, false)); + xspan->set_end(GetGlyphXBoundary(run, layout_index, true)); *height = run->font.GetHeight(); } -std::vector<Rect> RenderTextWin::GetSubstringBounds(ui::Range range) { +std::vector<Rect> RenderTextWin::GetSubstringBounds(const ui::Range& range) { DCHECK(!needs_layout_); DCHECK(ui::Range(0, text().length()).Contains(range)); - Point display_offset(GetUpdatedDisplayOffset()); - HRESULT hr = 0; + ui::Range layout_range(TextIndexToLayoutIndex(range.start()), + TextIndexToLayoutIndex(range.end())); + DCHECK(ui::Range(0, GetLayoutText().length()).Contains(layout_range)); std::vector<Rect> bounds; - if (range.is_empty()) + if (layout_range.is_empty()) return bounds; // Add a Rect for each run/selection intersection. // TODO(msw): The bounds should probably not always be leading the range ends. for (size_t i = 0; i < runs_.size(); ++i) { internal::TextRun* run = runs_[visual_to_logical_[i]]; - ui::Range intersection = run->range.Intersect(range); + ui::Range intersection = run->range.Intersect(layout_range); if (intersection.IsValid()) { DCHECK(!intersection.is_reversed()); - ui::Range range(GetGlyphXBoundary(run, intersection.start(), false), - GetGlyphXBoundary(run, intersection.end(), false)); - Rect rect(range.GetMin(), 0, range.length(), run->font.GetHeight()); + ui::Range range_x(GetGlyphXBoundary(run, intersection.start(), false), + GetGlyphXBoundary(run, intersection.end(), false)); + Rect rect(range_x.GetMin(), 0, range_x.length(), run->font.GetHeight()); // Center the rect vertically in the display area. rect.Offset(0, (display_rect().height() - rect.height()) / 2); rect.set_origin(ToViewPoint(rect.origin())); @@ -381,22 +390,44 @@ std::vector<Rect> RenderTextWin::GetSubstringBounds(ui::Range range) { return bounds; } +size_t RenderTextWin::TextIndexToLayoutIndex(size_t index) const { + if (!obscured()) + return index; + + DCHECK_LE(index, text().length()); + const ptrdiff_t offset = ui::UTF16IndexToOffset(text(), 0, index); + DCHECK_GE(offset, 0); + DCHECK_LE(static_cast<size_t>(offset), GetLayoutText().length()); + return static_cast<size_t>(offset); +} + +size_t RenderTextWin::LayoutIndexToTextIndex(size_t index) const { + if (!obscured()) + return index; + + DCHECK_LE(index, GetLayoutText().length()); + const size_t text_index = ui::UTF16OffsetToIndex(text(), 0, index); + DCHECK_LE(text_index, text().length()); + return text_index; +} + bool RenderTextWin::IsCursorablePosition(size_t position) { if (position == 0 || position == text().length()) return true; EnsureLayout(); - size_t run_index = + const size_t run_index = GetRunContainingCaret(SelectionModel(position, CURSOR_FORWARD)); if (run_index >= runs_.size()) return false; internal::TextRun* run = runs_[run_index]; - size_t start = run->range.start(); - if (position == start) + const size_t start = run->range.start(); + const size_t layout_position = TextIndexToLayoutIndex(position); + if (layout_position == start) return true; - return run->logical_clusters[position - start] != - run->logical_clusters[position - start - 1]; + return run->logical_clusters[layout_position - start] != + run->logical_clusters[layout_position - start - 1]; } void RenderTextWin::ResetLayout() { @@ -483,18 +514,16 @@ void RenderTextWin::ItemizeLogicalText() { if (text().empty()) return; - const wchar_t* raw_text = text().c_str(); - const int text_length = text().length(); - HRESULT hr = E_OUTOFMEMORY; int script_items_count = 0; std::vector<SCRIPT_ITEM> script_items; + const int text_length = GetLayoutText().length(); for (size_t n = kGuessItems; hr == E_OUTOFMEMORY && n < kMaxItems; n *= 2) { // Derive the array of Uniscribe script items from the logical text. // ScriptItemize always adds a terminal array item so that the length of the // last item can be derived from the terminal SCRIPT_ITEM::iCharPos. script_items.resize(n); - hr = ScriptItemize(raw_text, + hr = ScriptItemize(GetLayoutText().c_str(), text_length, n - 1, &script_control_, @@ -527,8 +556,8 @@ void RenderTextWin::ItemizeLogicalText() { run->script_analysis = script_item->a; // Find the range end and advance the structures as needed. - int script_item_end = (script_item + 1)->iCharPos; - int style_range_end = style->range.end(); + const int script_item_end = (script_item + 1)->iCharPos; + const int style_range_end = TextIndexToLayoutIndex(style->range.end()); run_break = std::min(script_item_end, style_range_end); if (script_item_end <= style_range_end) script_item++; @@ -599,7 +628,7 @@ void RenderTextWin::LayoutVisualText() { void RenderTextWin::LayoutTextRun(internal::TextRun* run) { const size_t run_length = run->range.length(); - const wchar_t* run_text = &(text()[run->range.start()]); + const wchar_t* run_text = &(GetLayoutText()[run->range.start()]); Font original_font = run->font; LinkedFontsIterator fonts(original_font); bool tried_cached_font = false; @@ -709,7 +738,7 @@ HRESULT RenderTextWin::ShapeTextRunWithFont(internal::TextRun* run, HRESULT hr = E_OUTOFMEMORY; const size_t run_length = run->range.length(); - const wchar_t* run_text = &(text()[run->range.start()]); + const wchar_t* run_text = &(GetLayoutText()[run->range.start()]); // Max glyph guess: http://msdn.microsoft.com/en-us/library/dd368564.aspx size_t max_glyphs = static_cast<size_t>(1.5 * run_length + 16); while (hr == E_OUTOFMEMORY && max_glyphs < kMaxGlyphs) { @@ -738,7 +767,7 @@ int RenderTextWin::CountCharsWithMissingGlyphs(internal::TextRun* run) const { properties.cBytes = sizeof(properties); ScriptGetFontProperties(cached_hdc_, &run->script_cache, &properties); - const wchar_t* run_text = &(text()[run->range.start()]); + const wchar_t* run_text = &(GetLayoutText()[run->range.start()]); for (size_t char_index = 0; char_index < run->range.length(); ++char_index) { const int glyph_index = run->logical_clusters[char_index]; DCHECK_GE(glyph_index, 0); @@ -767,11 +796,11 @@ int RenderTextWin::CountCharsWithMissingGlyphs(internal::TextRun* run) const { size_t RenderTextWin::GetRunContainingCaret(const SelectionModel& caret) const { DCHECK(!needs_layout_); - size_t position = caret.caret_pos(); + size_t layout_position = TextIndexToLayoutIndex(caret.caret_pos()); LogicalCursorDirection affinity = caret.caret_affinity(); size_t run = 0; for (; run < runs_.size(); ++run) - if (RangeContainsCaret(runs_[run]->range, position, affinity)) + if (RangeContainsCaret(runs_[run]->range, layout_position, affinity)) break; return run; } @@ -789,14 +818,16 @@ size_t RenderTextWin::GetRunContainingPoint(const Point& point) const { SelectionModel RenderTextWin::FirstSelectionModelInsideRun( const internal::TextRun* run) { - size_t cursor = IndexOfAdjacentGrapheme(run->range.start(), CURSOR_FORWARD); - return SelectionModel(cursor, CURSOR_BACKWARD); + size_t position = LayoutIndexToTextIndex(run->range.start()); + position = IndexOfAdjacentGrapheme(position, CURSOR_FORWARD); + return SelectionModel(position, CURSOR_BACKWARD); } SelectionModel RenderTextWin::LastSelectionModelInsideRun( const internal::TextRun* run) { - size_t caret = IndexOfAdjacentGrapheme(run->range.end(), CURSOR_BACKWARD); - return SelectionModel(caret, CURSOR_FORWARD); + size_t position = LayoutIndexToTextIndex(run->range.end()); + position = IndexOfAdjacentGrapheme(position, CURSOR_BACKWARD); + return SelectionModel(position, CURSOR_FORWARD); } RenderText* RenderText::CreateInstance() { diff --git a/ui/gfx/render_text_win.h b/ui/gfx/render_text_win.h index a33dbe2..df33b64 100644 --- a/ui/gfx/render_text_win.h +++ b/ui/gfx/render_text_win.h @@ -83,7 +83,9 @@ class RenderTextWin : public RenderText { virtual void GetGlyphBounds(size_t index, ui::Range* xspan, int* height) OVERRIDE; - virtual std::vector<Rect> GetSubstringBounds(ui::Range range) OVERRIDE; + virtual std::vector<Rect> GetSubstringBounds(const ui::Range& range) OVERRIDE; + virtual size_t TextIndexToLayoutIndex(size_t index) const OVERRIDE; + virtual size_t LayoutIndexToTextIndex(size_t index) const OVERRIDE; virtual bool IsCursorablePosition(size_t position) OVERRIDE; virtual void ResetLayout() OVERRIDE; virtual void EnsureLayout() OVERRIDE; |