summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorszym@chromium.org <szym@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-16 22:34:14 +0000
committerszym@chromium.org <szym@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-12-16 22:34:14 +0000
commitdfb88abf7d2927d4adb134de2b8fb01a0c897fd0 (patch)
treea3ad3e689be2d4de7072897393bcc9fd502c4b5b
parent36a18e4205e3fed5dcceb5c261ed959686f87f95 (diff)
downloadchromium_src-dfb88abf7d2927d4adb134de2b8fb01a0c897fd0.zip
chromium_src-dfb88abf7d2927d4adb134de2b8fb01a0c897fd0.tar.gz
chromium_src-dfb88abf7d2927d4adb134de2b8fb01a0c897fd0.tar.bz2
Revert of https://codereview.chromium.org/107513011/
Reason for revert: Test fails on main waterfall. TBR=jshin@chromium.org,msw@chromium.org,pkasting@chromium.org,skanuj@chromium.org NOTREECHECKS=true NOTRY=true BUG=327833 Review URL: https://codereview.chromium.org/103053014 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@241050 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--base/i18n/rtl.cc15
-rw-r--r--base/i18n/rtl.h4
-rw-r--r--base/i18n/rtl_unittest.cc64
-rw-r--r--chrome/browser/ui/views/omnibox/omnibox_result_view.cc245
-rw-r--r--chrome/browser/ui/views/omnibox/omnibox_result_view.h16
-rw-r--r--ui/gfx/render_text.cc128
-rw-r--r--ui/gfx/render_text.h16
-rw-r--r--ui/gfx/render_text_unittest.cc86
-rw-r--r--ui/gfx/text_elider.cc128
-rw-r--r--ui/gfx/text_elider.h36
10 files changed, 299 insertions, 439 deletions
diff --git a/base/i18n/rtl.cc b/base/i18n/rtl.cc
index 851b036..d9818e8 100644
--- a/base/i18n/rtl.cc
+++ b/base/i18n/rtl.cc
@@ -163,21 +163,6 @@ TextDirection GetFirstStrongCharacterDirection(const string16& text) {
return LEFT_TO_RIGHT;
}
-TextDirection GetLastStrongCharacterDirection(const string16& text) {
- const UChar* string = text.c_str();
- size_t position = text.length();
- while (position > 0) {
- UChar32 character;
- size_t prev_position = position;
- U16_PREV(string, 0, prev_position, character);
- TextDirection direction = GetCharacterDirection(character);
- if (direction != UNKNOWN_DIRECTION)
- return direction;
- position = prev_position;
- }
- return LEFT_TO_RIGHT;
-}
-
TextDirection GetStringDirection(const string16& text) {
const UChar* string = text.c_str();
size_t length = text.length();
diff --git a/base/i18n/rtl.h b/base/i18n/rtl.h
index aa5f681..c80d2f8 100644
--- a/base/i18n/rtl.h
+++ b/base/i18n/rtl.h
@@ -64,7 +64,7 @@ BASE_I18N_EXPORT bool ICUIsRTL();
BASE_I18N_EXPORT TextDirection GetTextDirectionForLocale(
const char* locale_name);
-// Given the string in |text|, returns the directionality of the first or last
+// Given the string in |text|, returns the directionality of the first
// character with strong directionality in the string. If no character in the
// text has strong directionality, LEFT_TO_RIGHT is returned. The Bidi
// character types L, LRE, LRO, R, AL, RLE, and RLO are considered as strong
@@ -72,8 +72,6 @@ BASE_I18N_EXPORT TextDirection GetTextDirectionForLocale(
// for more information.
BASE_I18N_EXPORT TextDirection GetFirstStrongCharacterDirection(
const string16& text);
-BASE_I18N_EXPORT TextDirection GetLastStrongCharacterDirection(
- const string16& text);
// Given the string in |text|, returns LEFT_TO_RIGHT or RIGHT_TO_LEFT if all the
// strong directionality characters in the string are of the same
diff --git a/base/i18n/rtl_unittest.cc b/base/i18n/rtl_unittest.cc
index 8faaccf..58772b0 100644
--- a/base/i18n/rtl_unittest.cc
+++ b/base/i18n/rtl_unittest.cc
@@ -46,8 +46,6 @@ TEST_F(RTLTest, GetFirstStrongCharacterDirection) {
} cases[] = {
// Test pure LTR string.
{ L"foo bar", LEFT_TO_RIGHT },
- // Test pure RTL string.
- { L"\x05d0\x05d1\x05d2 \x05d3\x0d4\x05d5", RIGHT_TO_LEFT},
// Test bidi string in which the first character with strong directionality
// is a character with type L.
{ L"foo \x05d0 bar", LEFT_TO_RIGHT },
@@ -109,68 +107,6 @@ TEST_F(RTLTest, GetFirstStrongCharacterDirection) {
GetFirstStrongCharacterDirection(WideToUTF16(cases[i].text)));
}
-
-// Note that the cases with LRE, LRO, RLE and RLO are invalid for
-// GetLastStrongCharacterDirection because they should be followed by PDF
-// character.
-TEST_F(RTLTest, GetLastStrongCharacterDirection) {
- struct {
- const wchar_t* text;
- TextDirection direction;
- } cases[] = {
- // Test pure LTR string.
- { L"foo bar", LEFT_TO_RIGHT },
- // Test pure RTL string.
- { L"\x05d0\x05d1\x05d2 \x05d3\x0d4\x05d5", RIGHT_TO_LEFT},
- // Test bidi string in which the last character with strong directionality
- // is a character with type L.
- { L"foo \x05d0 bar", LEFT_TO_RIGHT },
- // Test bidi string in which the last character with strong directionality
- // is a character with type R.
- { L"\x05d0 foo bar \x05d3", RIGHT_TO_LEFT },
- // Test bidi string which ends with a character with weak directionality
- // and in which the last character with strong directionality is a
- // character with type L.
- { L"!foo \x05d0 bar!", LEFT_TO_RIGHT },
- // Test bidi string which ends with a character with weak directionality
- // and in which the last character with strong directionality is a
- // character with type R.
- { L",\x05d0 foo bar \x05d1,", RIGHT_TO_LEFT },
- // Test bidi string in which the last character with strong directionality
- // is a character with type AL.
- { L"\x0622 foo \x05d0 bar \x0622", RIGHT_TO_LEFT },
- // Test a string without strong directionality characters.
- { L",!.{}", LEFT_TO_RIGHT },
- // Test empty string.
- { L"", LEFT_TO_RIGHT },
- // Test characters in non-BMP (e.g. Phoenician letters. Please refer to
- // http://demo.icu-project.org/icu-bin/ubrowse?scr=151&b=10910 for more
- // information).
- {
-#if defined(WCHAR_T_IS_UTF32)
- L"abc 123" L" ! \x10910 !",
-#elif defined(WCHAR_T_IS_UTF16)
- L"abc 123" L" ! \xd802\xdd10 !",
-#else
-#error wchar_t should be either UTF-16 or UTF-32
-#endif
- RIGHT_TO_LEFT },
- {
-#if defined(WCHAR_T_IS_UTF32)
- L"abc 123" L" ! \x10401 !",
-#elif defined(WCHAR_T_IS_UTF16)
- L"abc 123" L" ! \xd801\xdc01 !",
-#else
-#error wchar_t should be either UTF-16 or UTF-32
-#endif
- LEFT_TO_RIGHT },
- };
-
- for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i)
- EXPECT_EQ(cases[i].direction,
- GetLastStrongCharacterDirection(WideToUTF16(cases[i].text)));
-}
-
TEST_F(RTLTest, GetStringDirection) {
struct {
const wchar_t* text;
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
index 8903375..9938b547 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
@@ -357,58 +357,213 @@ int OmniboxResultView::DrawString(
}
}
- scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateInstance());
- const size_t text_length = text.length();
- render_text->SetText(text);
- render_text->SetFontList(font_list_);
- render_text->SetMultiline(false);
- render_text->SetCursorEnabled(false);
- if (is_url)
- render_text->SetDirectionalityMode(gfx::DIRECTIONALITY_FORCE_LTR);
-
- // Apply classifications.
- for (size_t i = 0; i < classifications.size(); ++i) {
- const size_t text_start = classifications[i].offset;
- if (text_start >= text_length)
- break;
-
- const size_t text_end = (i < (classifications.size() - 1)) ?
- std::min(classifications[i + 1].offset, text_length) : text_length;
- const gfx::Range current_range(text_start, text_end);
-
- // Calculate style-related data.
- if (classifications[i].style & ACMatchClassification::MATCH)
- render_text->ApplyStyle(gfx::BOLD, true, current_range);
-
- ColorKind colorKind = TEXT;
- if (classifications[i].style & ACMatchClassification::URL)
- colorKind = URL;
- else if (force_dim ||
- (classifications[i].style & ACMatchClassification::DIM))
- colorKind = DIMMED_TEXT;
- render_text->ApplyColor(GetColor(GetState(), colorKind), current_range);
+ // Split the text into visual runs. We do this first so that we don't need to
+ // worry about whether our eliding might change the visual display in
+ // unintended ways, e.g. by removing directional markings or by adding an
+ // ellipsis that's not enclosed in appropriate markings.
+ base::i18n::BiDiLineIterator bidi_line;
+ if (!bidi_line.Open(text, base::i18n::IsRTL(), is_url))
+ return x;
+ const int num_runs = bidi_line.CountRuns();
+ ScopedVector<gfx::RenderText> render_texts;
+ Runs runs;
+ for (int run = 0; run < num_runs; ++run) {
+ int run_start_int = 0, run_length_int = 0;
+ // The index we pass to GetVisualRun corresponds to the position of the run
+ // in the displayed text. For example, the string "Google in HEBREW" (where
+ // HEBREW is text in the Hebrew language) has two runs: "Google in " which
+ // is an LTR run, and "HEBREW" which is an RTL run. In an LTR context, the
+ // run "Google in " has the index 0 (since it is the leftmost run
+ // displayed). In an RTL context, the same run has the index 1 because it
+ // is the rightmost run. This is why the order in which we traverse the
+ // runs is different depending on the locale direction.
+ const UBiDiDirection run_direction = bidi_line.GetVisualRun(
+ (base::i18n::IsRTL() && !is_url) ? (num_runs - run - 1) : run,
+ &run_start_int, &run_length_int);
+ DCHECK_GT(run_length_int, 0);
+ runs.push_back(RunData());
+ RunData* current_run = &runs.back();
+ current_run->run_start = run_start_int;
+ const size_t run_end = current_run->run_start + run_length_int;
+ current_run->visual_order = run;
+ current_run->is_rtl = !is_url && (run_direction == UBIDI_RTL);
+
+ // Compute classifications for this run.
+ for (size_t i = 0; i < classifications.size(); ++i) {
+ const size_t text_start =
+ std::max(classifications[i].offset, current_run->run_start);
+ if (text_start >= run_end)
+ break; // We're past the last classification in the run.
+
+ const size_t text_end = (i < (classifications.size() - 1)) ?
+ std::min(classifications[i + 1].offset, run_end) : run_end;
+ if (text_end <= current_run->run_start)
+ continue; // We haven't reached the first classification in the run.
+
+ render_texts.push_back(gfx::RenderText::CreateInstance());
+ gfx::RenderText* render_text = render_texts.back();
+ current_run->classifications.push_back(render_text);
+ render_text->SetText(text.substr(text_start, text_end - text_start));
+ render_text->SetFontList(font_list_);
+
+ // Calculate style-related data.
+ if (classifications[i].style & ACMatchClassification::MATCH)
+ render_text->SetStyle(gfx::BOLD, true);
+ const ResultViewState state = GetState();
+ if (classifications[i].style & ACMatchClassification::URL)
+ render_text->SetColor(GetColor(state, URL));
+ else if (classifications[i].style & ACMatchClassification::DIM)
+ render_text->SetColor(GetColor(state, DIMMED_TEXT));
+ else
+ render_text->SetColor(GetColor(state, force_dim ? DIMMED_TEXT : TEXT));
+
+ current_run->pixel_width += render_text->GetStringSize().width();
+ }
+ DCHECK(!current_run->classifications.empty());
}
-
+ DCHECK(!runs.empty());
+
+ // Sort into logical order so we can elide logically.
+ std::sort(runs.begin(), runs.end(), &SortRunsLogically);
+
+ // Now determine what to elide, if anything. Several subtle points:
+ // * Because we have the run data, we can get edge cases correct, like
+ // whether to place an ellipsis before or after the end of a run when the
+ // text needs to be elided at the run boundary.
+ // * The "or one before it" comments below refer to cases where an earlier
+ // classification fits completely, but leaves too little space for an
+ // ellipsis that turns out to be needed later. These cases are commented
+ // more completely in Elide().
int remaining_width = mirroring_context_->remaining_width(x);
+ for (Runs::iterator i(runs.begin()); i != runs.end(); ++i) {
+ if (i->pixel_width > remaining_width) {
+ // This run or one before it needs to be elided.
+ for (Classifications::iterator j(i->classifications.begin());
+ j != i->classifications.end(); ++j) {
+ const int width = (*j)->GetStringSize().width();
+ if (width > remaining_width) {
+ // This classification or one before it needs to be elided. Erase all
+ // further classifications and runs so Elide() can simply reverse-
+ // iterate over everything to find the specific classification to
+ // elide.
+ i->classifications.erase(++j, i->classifications.end());
+ runs.erase(++i, runs.end());
+ Elide(&runs, remaining_width);
+ break;
+ }
+ remaining_width -= width;
+ }
+ break;
+ }
+ remaining_width -= i->pixel_width;
+ }
- // No need to try anything if we can't even show a solitary character.
- if ((text_length == 1) &&
- (remaining_width < render_text->GetContentWidth())) {
- return x;
+ // Sort back into visual order so we can display the runs correctly.
+ std::sort(runs.begin(), runs.end(), &SortRunsVisually);
+
+ // Draw the runs.
+ for (Runs::iterator i(runs.begin()); i != runs.end(); ++i) {
+ const bool reverse_visible_order = (i->is_rtl != base::i18n::IsRTL());
+ if (reverse_visible_order)
+ std::reverse(i->classifications.begin(), i->classifications.end());
+ for (Classifications::const_iterator j(i->classifications.begin());
+ j != i->classifications.end(); ++j) {
+ const gfx::Size size = (*j)->GetStringSize();
+ // Align the text runs to a common baseline.
+ const gfx::Rect rect(
+ mirroring_context_->mirrored_left_coord(x, x + size.width()), y,
+ size.width(), height());
+ (*j)->SetDisplayRect(rect);
+ (*j)->Draw(canvas);
+ x += size.width();
+ }
}
- if (render_text->GetContentWidth() > remaining_width)
- render_text->SetElideBehavior(gfx::ELIDE_AT_END);
+ return x;
+}
- // Set the display rect to trigger eliding.
- render_text->SetDisplayRect(gfx::Rect(
- mirroring_context_->mirrored_left_coord(x, x + remaining_width), y,
- remaining_width, height()));
- render_text->set_clip_to_display_rect(true);
- render_text->Draw(canvas);
+void OmniboxResultView::Elide(Runs* runs, int remaining_width) const {
+ // The complexity of this function is due to edge cases like the following:
+ // We have 100 px of available space, an initial classification that takes 86
+ // px, and a font that has a 15 px wide ellipsis character. Now if the first
+ // classification is followed by several very narrow classifications (e.g. 3
+ // px wide each), we don't know whether we need to elide or not at the time we
+ // see the first classification -- it depends on how many subsequent
+ // classifications follow, and some of those may be in the next run (or
+ // several runs!). This is why instead we let our caller move forward until
+ // we know we definitely need to elide, and then in this function we move
+ // backward again until we find a string that we can successfully do the
+ // eliding on.
+ bool on_trailing_classification = true;
+ for (Runs::reverse_iterator i(runs->rbegin()); i != runs->rend(); ++i) {
+ for (Classifications::reverse_iterator j(i->classifications.rbegin());
+ j != i->classifications.rend(); ++j) {
+ if (!on_trailing_classification) {
+ // We also add this classification's width (sans ellipsis) back to the
+ // available width since we want to consider the available space we'll
+ // have when we draw this classification.
+ remaining_width += (*j)->GetStringSize().width();
+
+ // If we reached here, we couldn't fit an ellipsis in the space taken by
+ // the previous classifications we looped over (see comments at bottom
+ // of loop). Append one here to represent those elided portions.
+ (*j)->SetText((*j)->text() + kEllipsis);
+ }
+ on_trailing_classification = false;
+
+ // Can we fit at least an ellipsis?
+ gfx::Font font((*j)->GetStyle(gfx::BOLD) ?
+ (*j)->GetPrimaryFont().DeriveFont(0, gfx::Font::BOLD) :
+ (*j)->GetPrimaryFont());
+ base::string16 elided_text(
+ gfx::ElideText((*j)->text(), font, remaining_width,
+ gfx::ELIDE_AT_END));
+ Classifications::reverse_iterator prior(j + 1);
+ const bool on_leading_classification =
+ (prior == i->classifications.rend());
+ if (elided_text.empty() && (remaining_width >= ellipsis_width_) &&
+ on_leading_classification) {
+ // Edge case: This classification is bold, we can't fit a bold ellipsis
+ // but we can fit a normal one, and this is the first classification in
+ // the run. We should display a lone normal ellipsis, because appending
+ // one to the end of the previous run might put it in the wrong visual
+ // location (if the previous run is reversed from the normal visual
+ // order).
+ // NOTE: If this isn't the first classification in the run, we don't
+ // need to bother with this; see note below.
+ elided_text = kEllipsis;
+ }
+ if (!elided_text.empty()) {
+ // Success. Elide this classification and stop.
+ (*j)->SetText(elided_text);
+
+ // If we could only fit an ellipsis, then only make it bold if there was
+ // an immediate prior classification in this run that was also bold, or
+ // it will look orphaned.
+ if ((*j)->GetStyle(gfx::BOLD) && (elided_text.length() == 1) &&
+ (on_leading_classification || !(*prior)->GetStyle(gfx::BOLD)))
+ (*j)->SetStyle(gfx::BOLD, false);
+
+ // Erase any other classifications that come after the elided one.
+ i->classifications.erase(j.base(), i->classifications.end());
+ runs->erase(i.base(), runs->end());
+ return;
+ }
+
+ // We couldn't fit an ellipsis. Move back one classification,
+ // append an ellipsis, and try again.
+ // NOTE: In the edge case that a bold ellipsis doesn't fit but a
+ // normal one would, and we reach here, then there is a previous
+ // classification in this run, and so either:
+ // * It's normal, and will be able to draw successfully with the
+ // ellipsis we'll append to it, or
+ // * It is also bold, in which case we don't want to fall back
+ // to a normal ellipsis anyway (see comment above).
+ }
+ }
- // Need to call GetContentWidth again as the SetDisplayRect may modify it.
- return x + render_text->GetContentWidth();
+ // We couldn't draw anything.
+ runs->clear();
}
void OmniboxResultView::Layout() {
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.h b/chrome/browser/ui/views/omnibox/omnibox_result_view.h
index 886e1d5..07167bf 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.h
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.h
@@ -111,6 +111,22 @@ class OmniboxResultView : public views::View,
gfx::ImageSkia GetIcon() const;
const gfx::ImageSkia* GetKeywordIcon() const;
+ // Elides |runs| to fit in |remaining_width|. The runs in |runs| should be in
+ // logical order.
+ //
+ // When we need to elide a run, the ellipsis will be placed at the end of that
+ // run. This means that if we elide a run whose visual direction is opposite
+ // that of the drawing context, the ellipsis will not be at the "end" of the
+ // drawn string. For example, if in an LTR context we have the LTR run
+ // "LTR_STRING" and the RTL run "RTL_STRING", the unelided text would be drawn
+ // like:
+ // LTR_STRING GNIRTS_LTR
+ // If we need to elide the RTL run, then it will be drawn like:
+ // LTR_STRING ...RTS_LTR
+ // Instead of:
+ // LTR_STRING RTS_LTR...
+ void Elide(Runs* runs, int remaining_width) const;
+
// views::View:
virtual void Layout() OVERRIDE;
virtual void OnBoundsChanged(const gfx::Rect& previous_bounds) OVERRIDE;
diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc
index 23f6740..e8e3436 100644
--- a/ui/gfx/render_text.cc
+++ b/ui/gfx/render_text.cc
@@ -19,7 +19,6 @@
#include "ui/gfx/skia_util.h"
#include "ui/gfx/text_constants.h"
#include "ui/gfx/text_elider.h"
-#include "ui/gfx/text_utils.h"
#include "ui/gfx/utf16_indexing.h"
namespace gfx {
@@ -366,6 +365,7 @@ void RenderText::SetText(const base::string16& text) {
obscured_reveal_index_ = -1;
UpdateLayoutText();
+ ResetLayout();
}
void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) {
@@ -411,6 +411,7 @@ void RenderText::SetObscured(bool obscured) {
obscured_reveal_index_ = -1;
cached_bounds_and_offset_valid_ = false;
UpdateLayoutText();
+ ResetLayout();
}
}
@@ -421,6 +422,7 @@ void RenderText::SetObscuredRevealIndex(int index) {
obscured_reveal_index_ = index;
cached_bounds_and_offset_valid_ = false;
UpdateLayoutText();
+ ResetLayout();
}
void RenderText::SetMultiline(bool multiline) {
@@ -431,23 +433,11 @@ void RenderText::SetMultiline(bool multiline) {
}
}
-void RenderText::SetElideBehavior(ElideBehavior elide_behavior) {
- // TODO(skanuj) : Add a test for triggering layout change.
- if (elide_behavior_ != elide_behavior) {
- elide_behavior_ = elide_behavior;
- UpdateLayoutText();
- }
-}
-
void RenderText::SetDisplayRect(const Rect& r) {
- if (r != display_rect_) {
- display_rect_ = r;
- baseline_ = kInvalidBaseline;
- cached_bounds_and_offset_valid_ = false;
- lines_.clear();
- if (elide_behavior_ != gfx::NO_ELIDE)
- UpdateLayoutText();
- }
+ display_rect_ = r;
+ baseline_ = kInvalidBaseline;
+ cached_bounds_and_offset_valid_ = false;
+ lines_.clear();
}
void RenderText::SetCursorPosition(size_t position) {
@@ -840,7 +830,6 @@ RenderText::RenderText()
obscured_(false),
obscured_reveal_index_(-1),
truncate_length_(0),
- elide_behavior_(NO_ELIDE),
multiline_(false),
fade_head_(false),
fade_tail_(false),
@@ -1120,114 +1109,13 @@ void RenderText::UpdateLayoutText() {
}
}
- const base::string16& text = GetLayoutText();
+ const base::string16& text = obscured_ ? layout_text_ : text_;
if (truncate_length_ > 0 && truncate_length_ < text.length()) {
// Truncate the text at a valid character break and append an ellipsis.
icu::StringCharacterIterator iter(text.c_str());
iter.setIndex32(truncate_length_ - 1);
layout_text_.assign(text.substr(0, iter.getIndex()) + gfx::kEllipsisUTF16);
}
-
- if (elide_behavior_ != NO_ELIDE && display_rect_.width() > 0 &&
- GetContentWidth() > display_rect_.width()) {
- base::string16 elided_text = ElideText(GetLayoutText());
-
- // This doesn't trim styles so ellipsis may get rendered as a different
- // style than the preceding text. See crbug.com/327850.
- layout_text_.assign(elided_text);
- }
- ResetLayout();
-}
-
-// TODO(skanuj): Fix code duplication with ElideText in ui/gfx/text_elider.cc
-// See crbug.com/327846
-base::string16 RenderText::ElideText(const base::string16& text) {
- if (text.empty())
- return text;
-
- const bool insert_ellipsis = (elide_behavior_ != TRUNCATE_AT_END);
- // Create a RenderText copy with attributes that affect the rendering width.
- scoped_ptr<RenderText> render_text(CreateInstance());
- render_text->SetFontList(font_list_);
- render_text->SetDirectionalityMode(directionality_mode_);
- render_text->SetCursorEnabled(cursor_enabled_);
- render_text->styles_ = styles_;
- render_text->colors_ = colors_;
- render_text->SetText(text);
- const float current_text_pixel_width = render_text->GetContentWidth();
-
- const base::string16 ellipsis = base::string16(kEllipsisUTF16);
- const bool elide_in_middle = false;
- StringSlicer slicer(text, ellipsis, elide_in_middle);
-
- // Pango will return 0 width for absurdly long strings. Cut the string in
- // half and try again.
- // This is caused by an int overflow in Pango (specifically, in
- // pango_glyph_string_extents_range). It's actually more subtle than just
- // returning 0, since on super absurdly long strings, the int can wrap and
- // return positive numbers again. Detecting that is probably not worth it
- // (eliding way too much from a ridiculous string is probably still
- // ridiculous), but we should check other widths for bogus values as well.
- if (current_text_pixel_width <= 0 && !text.empty())
- return ElideText(slicer.CutString(text.length() / 2, insert_ellipsis));
-
- if (current_text_pixel_width <= display_rect_.width())
- return text;
-
- if (insert_ellipsis &&
- GetStringWidthF(ellipsis, font_list_) > display_rect_.width())
- return base::string16();
-
- // Use binary search to compute the elided text.
- size_t lo = 0;
- size_t hi = text.length() - 1;
-
- for (size_t guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) {
- // Restore styles and colors. They will be truncated to size by SetText.
- render_text->styles_ = styles_;
- render_text->colors_ = colors_;
- base::string16 new_text = slicer.CutString(guess, false);
- render_text->SetText(new_text);
-
- // This has to be an additional step so that the ellipsis is rendered with
- // same style as trailing part of the text.
- if (insert_ellipsis) {
- // When ellipsis follows text whose directionality is not the same as that
- // of the whole text, it will be rendered with the directionality of the
- // whole text. Since we want ellipsis to indicate continuation of the
- // preceding text, we force the directionality of ellipsis to be same as
- // the preceding text using LTR or RTL markers.
- base::i18n::TextDirection leading_text_direction =
- base::i18n::GetFirstStrongCharacterDirection(new_text);
- base::i18n::TextDirection trailing_text_direction =
- base::i18n::GetLastStrongCharacterDirection(new_text);
- new_text += ellipsis;
- if (trailing_text_direction != leading_text_direction) {
- if (trailing_text_direction == base::i18n::LEFT_TO_RIGHT)
- new_text += base::i18n::kLeftToRightMark;
- else
- new_text += base::i18n::kRightToLeftMark;
- }
- render_text->SetText(new_text);
- }
-
- // We check the width of the whole desired string at once to ensure we
- // handle kerning/ligatures/etc. correctly.
- const int guess_width = render_text->GetContentWidth();
- if (guess_width == display_rect_.width())
- break;
- if (guess_width > display_rect_.width()) {
- hi = guess - 1;
- // Move back if we are on loop terminating condition, and guess is wider
- // than available.
- if (hi < lo)
- lo = hi;
- } else {
- lo = guess + 1;
- }
- }
-
- return render_text->text();
}
void RenderText::UpdateCachedBoundsAndOffset() {
diff --git a/ui/gfx/render_text.h b/ui/gfx/render_text.h
index 308b673..8696d69 100644
--- a/ui/gfx/render_text.h
+++ b/ui/gfx/render_text.h
@@ -27,7 +27,6 @@
#include "ui/gfx/shadow_value.h"
#include "ui/gfx/size_f.h"
#include "ui/gfx/text_constants.h"
-#include "ui/gfx/text_elider.h"
#include "ui/gfx/vector2d.h"
class SkCanvas;
@@ -228,12 +227,6 @@ class GFX_EXPORT RenderText {
// WARNING: Only use this for system limits, it lacks complex text support.
void set_truncate_length(size_t length) { truncate_length_ = length; }
- // Elides the text to fit in |display_rect| according to the specified
- // |elide_behavior|. |ELIDE_IN_MIDDLE| is not supported.
- // If both truncate and elide are specified, the shorter of the two will be
- // applicable.
- void SetElideBehavior(ElideBehavior elide_behavior);
-
const Rect& display_rect() const { return display_rect_; }
void SetDisplayRect(const Rect& r);
@@ -542,8 +535,6 @@ class GFX_EXPORT RenderText {
FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ApplyColorAndStyle);
FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ObscuredText);
FRIEND_TEST_ALL_PREFIXES(RenderTextTest, RevealObscuredText);
- FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ElidedText);
- FRIEND_TEST_ALL_PREFIXES(RenderTextTest, ElidedObscuredText);
FRIEND_TEST_ALL_PREFIXES(RenderTextTest, TruncatedText);
FRIEND_TEST_ALL_PREFIXES(RenderTextTest, TruncatedObscuredText);
FRIEND_TEST_ALL_PREFIXES(RenderTextTest, GraphemePositions);
@@ -565,10 +556,6 @@ class GFX_EXPORT RenderText {
// Updates |layout_text_| if the text is obscured or truncated.
void UpdateLayoutText();
- // Elides |text| to fit in the |display_rect_| with given |elide_behavior_|.
- // See ElideText in ui/gfx/text_elider.cc for reference.
- base::string16 ElideText(const base::string16& text);
-
// Update the cached bounds and display offset to ensure that the current
// cursor is within the visible display area.
void UpdateCachedBoundsAndOffset();
@@ -641,9 +628,6 @@ class GFX_EXPORT RenderText {
// The maximum length of text to display, 0 forgoes a hard limit.
size_t truncate_length_;
- // The behavior for eliding or truncating.
- ElideBehavior elide_behavior_;
-
// The obscured and/or truncated text that will be displayed.
base::string16 layout_text_;
diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc
index aa28b24..c8c50d4 100644
--- a/ui/gfx/render_text_unittest.cc
+++ b/ui/gfx/render_text_unittest.cc
@@ -401,92 +401,6 @@ TEST_F(RenderTextTest, RevealObscuredText) {
EXPECT_EQ(valid_expect_5_and_6, render_text->GetLayoutText());
}
-TEST_F(RenderTextTest, ElidedText) {
- // TODO(skanuj) : Add more test cases for following
- // - RenderText styles.
- // - Cross interaction of truncate, elide and obscure.
- // - ElideText tests from text_elider.cc.
- struct {
- const wchar_t* text;
- const wchar_t* layout_text;
- } cases[] = {
- // Strings shorter than the elision width should be laid out in full.
- { L"" , L"" },
- { kWeak , kWeak },
- { kLtr , kLtr },
- { kLtrRtl , kLtrRtl },
- { kLtrRtlLtr , kLtrRtlLtr },
- { kRtl , kRtl },
- { kRtlLtr , kRtlLtr },
- { kRtlLtrRtl , kRtlLtrRtl },
- // Strings as long as the elision width should be laid out in full.
- { L"012ab", L"012ab" },
- // Long strings should be elided with an ellipsis appended at the end.
- { L"012" L"abc" , L"012a\x2026" },
- { L"012" L"ab" L"\x5d0\x5d1" , L"012a\x2026" },
- { L"012" L"a" L"\x5d1" L"b" , L"012a\x2026" },
- // No RLM marker added as digits (012) have weak directionality.
- { L"01" L"\x5d0\x5d1\x5d2" L"ab" , L"01\x5d0\x5d1\x2026" },
- // RLM marker added as "ab" have strong LTR directionality.
- { L"ab" L"\x5d0\x5d1\x5d2" L"ab" , L"ab\x5d0\x5d1\x2026\x200f" },
- // Complex script is not handled. In this example, the "\x0915\x093f" is a
- // compound glyph, but only half of it is elided.
- { L"0123\x0915\x093f" L"abcd" , L"0123\x0915\x2026" },
- // Surrogate pairs should be elided reasonably enough.
- { L"0\x05e9\x05bc\x05c1\x05b8" , L"0\x05e9\x05bc\x05c1\x05b8" },
- { L"0\x05e9\x05bc\x05c1\x05b8" L"ab" , L"0\x05e9\x05bc\x2026" },
- { L"01\x05e9\x05bc\x05c1\x05b8" L"ab" , L"01\x05e9\x2026" },
- { L"012\x05e9\x05bc\x05c1\x05b8" L"ab" , L"012\x2026" },
- { L"012\xF0\x9D\x84\x9E" , L"012\xF0\x2026" },
- };
-
- scoped_ptr<RenderText> expected_render_text(RenderText::CreateInstance());
- expected_render_text->SetFontList(FontList("serif, Sans serif, 12px"));
- expected_render_text->SetDisplayRect(gfx::Rect(0, 0, 9999, 100));
-
- scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
- render_text->SetFontList(FontList("serif, Sans serif, 12px"));
- render_text->SetElideBehavior(gfx::ELIDE_AT_END);
- for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); i++) {
- expected_render_text->SetText(WideToUTF16(cases[i].layout_text));
- int expected_width = expected_render_text->GetContentWidth();
- render_text->SetText(WideToUTF16(cases[i].text));
-#if defined(OS_WIN)
- // TODO(skanuj): It seems in some cases (on Windows XP), the original text
- // is shorter than the elided text. Those cases should be modified by
- // adding extra chars in the end. Currently this problem happens for the
- // following test case:
- // { L"012\xF0\x9D\x84\x9E" , L"012\xF0\x2026" },
- int original_width = render_text->GetContentWidth();
- if ((base::win::GetVersion() < base::win::VERSION_VISTA) &&
- (expected_width >= original_width))
- continue;
-#endif
- render_text->SetDisplayRect(gfx::Rect(0, 0, expected_width, 100));
- EXPECT_EQ(WideToUTF16(cases[i].text), render_text->text());
- EXPECT_EQ(WideToUTF16(cases[i].layout_text), render_text->GetLayoutText())
- << "->For case " << i << ": " << cases[i].text << "\n";
- expected_render_text->SetText(base::string16());
- }
-}
-
-TEST_F(RenderTextTest, ElidedObscuredText) {
- scoped_ptr<RenderText> expected_render_text(RenderText::CreateInstance());
- expected_render_text->SetFontList(FontList("serif, Sans serif, 12px"));
- expected_render_text->SetDisplayRect(gfx::Rect(0, 0, 9999, 100));
- expected_render_text->SetText(WideToUTF16(L"**\x2026"));
-
- scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
- render_text->SetFontList(FontList("serif, Sans serif, 12px"));
- render_text->SetElideBehavior(gfx::ELIDE_AT_END);
- render_text->SetDisplayRect(
- gfx::Rect(0, 0, expected_render_text->GetContentWidth(), 100));
- render_text->SetObscured(true);
- render_text->SetText(WideToUTF16(L"abcdef"));
- EXPECT_EQ(WideToUTF16(L"abcdef"), render_text->text());
- EXPECT_EQ(WideToUTF16(L"**\x2026"), render_text->GetLayoutText());
-}
-
TEST_F(RenderTextTest, TruncatedText) {
struct {
const wchar_t* text;
diff --git a/ui/gfx/text_elider.cc b/ui/gfx/text_elider.cc
index c58a96d..5b94696 100644
--- a/ui/gfx/text_elider.cc
+++ b/ui/gfx/text_elider.cc
@@ -39,6 +39,68 @@ const base::char16 kForwardSlash = '/';
namespace {
+// Helper class to split + elide text, while respecting UTF16 surrogate pairs.
+class StringSlicer {
+ public:
+ StringSlicer(const base::string16& text,
+ const base::string16& ellipsis,
+ bool elide_in_middle)
+ : text_(text),
+ ellipsis_(ellipsis),
+ elide_in_middle_(elide_in_middle) {
+ }
+
+ // Cuts |text_| to be |length| characters long. If |elide_in_middle_| is true,
+ // the middle of the string is removed to leave equal-length pieces from the
+ // beginning and end of the string; otherwise, the end of the string is
+ // removed and only the beginning remains. If |insert_ellipsis| is true,
+ // then an ellipsis character will be inserted at the cut point.
+ base::string16 CutString(size_t length, bool insert_ellipsis) {
+ const base::string16 ellipsis_text = insert_ellipsis ? ellipsis_
+ : base::string16();
+
+ if (!elide_in_middle_)
+ return text_.substr(0, FindValidBoundaryBefore(length)) + ellipsis_text;
+
+ // We put the extra character, if any, before the cut.
+ const size_t half_length = length / 2;
+ const size_t prefix_length = FindValidBoundaryBefore(length - half_length);
+ const size_t suffix_start_guess = text_.length() - half_length;
+ const size_t suffix_start = FindValidBoundaryAfter(suffix_start_guess);
+ const size_t suffix_length =
+ half_length - (suffix_start_guess - suffix_start);
+ return text_.substr(0, prefix_length) + ellipsis_text +
+ text_.substr(suffix_start, suffix_length);
+ }
+
+ private:
+ // Returns a valid cut boundary at or before |index|.
+ size_t FindValidBoundaryBefore(size_t index) const {
+ DCHECK_LE(index, text_.length());
+ if (index != text_.length())
+ U16_SET_CP_START(text_.data(), 0, index);
+ return index;
+ }
+
+ // Returns a valid cut boundary at or after |index|.
+ size_t FindValidBoundaryAfter(size_t index) const {
+ DCHECK_LE(index, text_.length());
+ if (index != text_.length())
+ U16_SET_CP_LIMIT(text_.data(), 0, index, text_.length());
+ return index;
+ }
+
+ // The text to be sliced.
+ const base::string16& text_;
+
+ // Ellipsis string to use.
+ const base::string16& ellipsis_;
+
+ // If true, the middle of the string will be elided.
+ bool elide_in_middle_;
+
+ DISALLOW_COPY_AND_ASSIGN(StringSlicer);
+};
// Build a path from the first |num_components| elements in |path_elements|.
// Prepends |path_prefix|, appends |filename|, inserts ellipsis if appropriate.
@@ -88,46 +150,6 @@ base::string16 ElideComponentizedPath(
} // namespace
-StringSlicer::StringSlicer(const base::string16& text,
- const base::string16& ellipsis,
- bool elide_in_middle)
- : text_(text),
- ellipsis_(ellipsis),
- elide_in_middle_(elide_in_middle) {
-}
-
-base::string16 StringSlicer::CutString(size_t length, bool insert_ellipsis) {
- const base::string16 ellipsis_text = insert_ellipsis ? ellipsis_
- : base::string16();
-
- if (!elide_in_middle_)
- return text_.substr(0, FindValidBoundaryBefore(length)) + ellipsis_text;
-
- // We put the extra character, if any, before the cut.
- const size_t half_length = length / 2;
- const size_t prefix_length = FindValidBoundaryBefore(length - half_length);
- const size_t suffix_start_guess = text_.length() - half_length;
- const size_t suffix_start = FindValidBoundaryAfter(suffix_start_guess);
- const size_t suffix_length =
- half_length - (suffix_start_guess - suffix_start);
- return text_.substr(0, prefix_length) + ellipsis_text +
- text_.substr(suffix_start, suffix_length);
-}
-
-size_t StringSlicer::FindValidBoundaryBefore(size_t index) const {
- DCHECK_LE(index, text_.length());
- if (index != text_.length())
- U16_SET_CP_START(text_.data(), 0, index);
- return index;
-}
-
-size_t StringSlicer::FindValidBoundaryAfter(size_t index) const {
- DCHECK_LE(index, text_.length());
- if (index != text_.length())
- U16_SET_CP_LIMIT(text_.data(), 0, index, text_.length());
- return index;
-}
-
base::string16 ElideEmail(const base::string16& email,
const FontList& font_list,
float available_pixel_width) {
@@ -455,8 +477,7 @@ base::string16 ElideText(const base::string16& text,
// (eliding way too much from a ridiculous string is probably still
// ridiculous), but we should check other widths for bogus values as well.
if (current_text_pixel_width <= 0 && !text.empty()) {
- const base::string16 cut =
- slicer.CutString(text.length() / 2, insert_ellipsis);
+ const base::string16 cut = slicer.CutString(text.length() / 2, false);
return ElideText(cut, font_list, available_pixel_width, elide_behavior);
}
@@ -472,23 +493,20 @@ base::string16 ElideText(const base::string16& text,
size_t hi = text.length() - 1;
size_t guess;
for (guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) {
- // We check the width of the whole desired string at once to ensure we
+ // We check the length of the whole desired string at once to ensure we
// handle kerning/ligatures/etc. correctly.
- // TODO(skanuj) : Handle directionality of ellipsis based on adjacent
- // characters. See crbug.com/327963.
const base::string16 cut = slicer.CutString(guess, insert_ellipsis);
- const float guess_width = GetStringWidthF(cut, font_list);
- if (guess_width == available_pixel_width)
- break;
- if (guess_width > available_pixel_width) {
+ const float guess_length = GetStringWidthF(cut, font_list);
+ // Check again that we didn't hit a Pango width overflow. If so, cut the
+ // current string in half and start over.
+ if (guess_length <= 0) {
+ return ElideText(slicer.CutString(guess / 2, false),
+ font_list, available_pixel_width, elide_behavior);
+ }
+ if (guess_length > available_pixel_width)
hi = guess - 1;
- // Move back if we are on loop terminating condition, and guess is wider
- // than available.
- if (hi < lo)
- lo = hi;
- } else {
+ else
lo = guess + 1;
- }
}
return slicer.CutString(guess, insert_ellipsis);
diff --git a/ui/gfx/text_elider.h b/ui/gfx/text_elider.h
index eef1f8e..4eaca82 100644
--- a/ui/gfx/text_elider.h
+++ b/ui/gfx/text_elider.h
@@ -29,38 +29,6 @@ class FontList;
GFX_EXPORT extern const char kEllipsis[];
GFX_EXPORT extern const base::char16 kEllipsisUTF16[];
-
-// Helper class to split + elide text, while respecting UTF16 surrogate pairs.
-class StringSlicer {
- public:
- StringSlicer(const base::string16& text,
- const base::string16& ellipsis,
- bool elide_in_middle);
-
- // Cuts |text_| to be |length| characters long. If |elide_in_middle_| is true,
- // the middle of the string is removed to leave equal-length pieces from the
- // beginning and end of the string; otherwise, the end of the string is
- // removed and only the beginning remains. If |insert_ellipsis| is true,
- // then an ellipsis character will be inserted at the cut point.
- base::string16 CutString(size_t length, bool insert_ellipsis);
-
- private:
- // Returns a valid cut boundary at or before/after |index|.
- size_t FindValidBoundaryBefore(size_t index) const;
- size_t FindValidBoundaryAfter(size_t index) const;
-
- // The text to be sliced.
- const base::string16& text_;
-
- // Ellipsis string to use.
- const base::string16& ellipsis_;
-
- // If true, the middle of the string will be elided.
- bool elide_in_middle_;
-
- DISALLOW_COPY_AND_ASSIGN(StringSlicer);
-};
-
// Elides a well-formed email address (e.g. username@domain.com) to fit into
// |available_pixel_width| using the specified |font_list|.
// This function guarantees that the string returned will contain at least one
@@ -100,9 +68,7 @@ enum ElideBehavior {
// Add ellipsis in the middle of the string.
ELIDE_IN_MIDDLE,
// Truncate the end of the string.
- TRUNCATE_AT_END,
- // No eliding of the string.
- NO_ELIDE
+ TRUNCATE_AT_END
};
// Elides |text| to fit in |available_pixel_width| according to the specified