summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormsw@chromium.org <msw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-10 21:40:37 +0000
committermsw@chromium.org <msw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2014-07-10 21:40:37 +0000
commit8d93614f9306e6b53455b469b8acf98613b4d417 (patch)
tree6e78a7cec4c7dfadfbb02cedd114969a13a19682
parent0a3fca5aa2c58975f85cc4d1181d98f328afa5f6 (diff)
downloadchromium_src-8d93614f9306e6b53455b469b8acf98613b4d417.zip
chromium_src-8d93614f9306e6b53455b469b8acf98613b4d417.tar.gz
chromium_src-8d93614f9306e6b53455b469b8acf98613b4d417.tar.bz2
Move gfx::ElideText functionality to RenderText.
This is a prerequisite for http://crrev.com/23228004 (RenderText must elide correctly for direct Label use) Use RenderText in gfx::ElideText on Win, Linux, Mac. (old impl still needed for iOS and Android, for now) Support additional eliding types in RenderText. (matches behavior of gfx::ElideText, see TextEliderTest) (still fixes the directionality of trailing ellipses) (respect head and middle eliding when truncating) Disambiguate gfx::NO_ELIDE from gfx::TRUNCATE. Make the ElideEmail helper a private RenderText function. Disable tests and no-op gfx::ElideText on iOS/Android. Improve ElideTextSurrogatePairs perf: 7561 ms -> 3196 ms. TODO: Fix RenderText::ElideEmail GetStringWidthF calls. TODO: Support eliding filenames, like gfx::ElideFilename. BUG=249938,327846,240037,125348,338784 R=asvitkine@chromium.org,sky@chromium.org TEST=No observable text eliding behavior changes. Review URL: https://codereview.chromium.org/354963003 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@282433 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.cc2
-rw-r--r--chrome/browser/ui/views/location_bar/add_to_app_launcher_view.cc2
-rw-r--r--chrome/browser/ui/views/location_bar/content_setting_image_view.cc2
-rw-r--r--chrome/browser/ui/views/location_bar/origin_chip_view.cc4
-rw-r--r--chrome/browser/ui/views/omnibox/omnibox_result_view.cc2
-rw-r--r--ui/gfx/render_text.cc145
-rw-r--r--ui/gfx/render_text.h20
-rw-r--r--ui/gfx/render_text_pango.cc9
-rw-r--r--ui/gfx/render_text_unittest.cc12
-rw-r--r--ui/gfx/text_constants.h3
-rw-r--r--ui/gfx/text_elider.cc65
-rw-r--r--ui/gfx/text_elider_unittest.cc20
-rw-r--r--ui/views/controls/label.cc6
-rw-r--r--ui/views/examples/label_example.cc6
-rw-r--r--ui/views/examples/text_example.cc6
15 files changed, 189 insertions, 115 deletions
diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.cc
index ceab84f..f6eddec 100644
--- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.cc
+++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_permissions_panel.cc
@@ -121,7 +121,7 @@ void AppInfoPermissionsPanel::CreateActivePermissionsControl() {
active_permissions_heading_ = CreateHeading(l10n_util::GetStringUTF16(
IDS_APPLICATION_INFO_ACTIVE_PERMISSIONS_TEXT));
active_permissions_list_ =
- CreateBulletedListView(permission_strings, true, gfx::TRUNCATE);
+ CreateBulletedListView(permission_strings, true, gfx::NO_ELIDE);
}
}
diff --git a/chrome/browser/ui/views/location_bar/add_to_app_launcher_view.cc b/chrome/browser/ui/views/location_bar/add_to_app_launcher_view.cc
index a9bc376..2d9753f 100644
--- a/chrome/browser/ui/views/location_bar/add_to_app_launcher_view.cc
+++ b/chrome/browser/ui/views/location_bar/add_to_app_launcher_view.cc
@@ -86,7 +86,7 @@ AddToAppLauncherView::AddToAppLauncherView(LocationBarView* parent,
parent_background_color,
SkColorGetA(background_image_color)));
text_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
- text_label_->SetElideBehavior(gfx::TRUNCATE);
+ text_label_->SetElideBehavior(gfx::NO_ELIDE);
text_label_->SetText(base::UTF8ToUTF16(
l10n_util::GetStringUTF8(IDS_ADD_TO_APP_LIST_NOTIFICATION_TEXT)));
AddChildView(text_label_);
diff --git a/chrome/browser/ui/views/location_bar/content_setting_image_view.cc b/chrome/browser/ui/views/location_bar/content_setting_image_view.cc
index 1947e88..2f7459b 100644
--- a/chrome/browser/ui/views/location_bar/content_setting_image_view.cc
+++ b/chrome/browser/ui/views/location_bar/content_setting_image_view.cc
@@ -75,7 +75,7 @@ ContentSettingImageView::ContentSettingImageView(
parent_background_color,
SkColorGetA(background_image_color)));
text_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
- text_label_->SetElideBehavior(gfx::TRUNCATE);
+ text_label_->SetElideBehavior(gfx::NO_ELIDE);
AddChildView(text_label_);
slide_animator_.SetSlideDuration(kAnimationDurationMS);
diff --git a/chrome/browser/ui/views/location_bar/origin_chip_view.cc b/chrome/browser/ui/views/location_bar/origin_chip_view.cc
index 1f22a32..a7c3596 100644
--- a/chrome/browser/ui/views/location_bar/origin_chip_view.cc
+++ b/chrome/browser/ui/views/location_bar/origin_chip_view.cc
@@ -166,12 +166,12 @@ OriginChipView::OriginChipView(LocationBarView* location_bar_view,
ev_label_ = new views::Label(base::string16(), GetFontList());
ev_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
- ev_label_->SetElideBehavior(gfx::TRUNCATE);
+ ev_label_->SetElideBehavior(gfx::NO_ELIDE);
AddChildView(ev_label_);
host_label_ = new views::Label(base::string16(), GetFontList());
host_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
- host_label_->SetElideBehavior(gfx::TRUNCATE);
+ host_label_->SetElideBehavior(gfx::NO_ELIDE);
AddChildView(host_label_);
fade_in_animation_.SetTweenType(gfx::Tween::LINEAR_OUT_SLOW_IN);
diff --git a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
index 0288ed8..555f88b 100644
--- a/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
+++ b/chrome/browser/ui/views/omnibox/omnibox_result_view.cc
@@ -29,7 +29,6 @@
#include "ui/gfx/image/image.h"
#include "ui/gfx/range/range.h"
#include "ui/gfx/render_text.h"
-#include "ui/gfx/text_elider.h"
#include "ui/gfx/text_utils.h"
#include "ui/native_theme/native_theme.h"
@@ -338,6 +337,7 @@ int OmniboxResultView::DrawRenderText(
scoped_ptr<gfx::RenderText> OmniboxResultView::CreateRenderText(
const base::string16& text) const {
scoped_ptr<gfx::RenderText> render_text(gfx::RenderText::CreateInstance());
+ render_text->SetDisplayRect(gfx::Rect(gfx::Size(INT_MAX, 0)));
render_text->SetCursorEnabled(false);
render_text->SetElideBehavior(gfx::ELIDE_TAIL);
render_text->SetFontList(font_list_);
diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc
index fb7364b..7736356 100644
--- a/ui/gfx/render_text.cc
+++ b/ui/gfx/render_text.cc
@@ -23,7 +23,6 @@
#include "ui/gfx/scoped_canvas.h"
#include "ui/gfx/skia_util.h"
#include "ui/gfx/switches.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"
@@ -514,7 +513,7 @@ void RenderText::SetDisplayRect(const Rect& r) {
baseline_ = kInvalidBaseline;
cached_bounds_and_offset_valid_ = false;
lines_.clear();
- if (elide_behavior_ != TRUNCATE)
+ if (elide_behavior_ != NO_ELIDE)
UpdateLayoutText();
}
}
@@ -746,8 +745,8 @@ SizeF RenderText::GetStringSizeF() {
return SizeF(size.width(), size.height());
}
-int RenderText::GetContentWidth() {
- return GetStringSize().width() + (cursor_enabled_ ? 1 : 0);
+float RenderText::GetContentWidth() {
+ return GetStringSizeF().width() + (cursor_enabled_ ? 1 : 0);
}
int RenderText::GetBaseline() {
@@ -927,7 +926,7 @@ RenderText::RenderText()
obscured_(false),
obscured_reveal_index_(-1),
truncate_length_(0),
- elide_behavior_(TRUNCATE),
+ elide_behavior_(NO_ELIDE),
multiline_(false),
background_is_transparent_(false),
clip_to_display_rect_(true),
@@ -1191,16 +1190,30 @@ void RenderText::UpdateLayoutText() {
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()) + kEllipsisUTF16);
+ // Respect ELIDE_HEAD and ELIDE_MIDDLE preferences during truncation.
+ if (elide_behavior_ == ELIDE_HEAD) {
+ iter.setIndex32(text.length() - truncate_length_ + 1);
+ layout_text_.assign(kEllipsisUTF16 + text.substr(iter.getIndex()));
+ } else if (elide_behavior_ == ELIDE_MIDDLE) {
+ iter.setIndex32(truncate_length_ / 2);
+ const size_t ellipsis_start = iter.getIndex();
+ iter.setIndex32(text.length() - (truncate_length_ / 2));
+ const size_t ellipsis_end = iter.getIndex();
+ DCHECK_LE(ellipsis_start, ellipsis_end);
+ layout_text_.assign(text.substr(0, ellipsis_start) + kEllipsisUTF16 +
+ text.substr(ellipsis_end));
+ } else {
+ iter.setIndex32(truncate_length_ - 1);
+ layout_text_.assign(text.substr(0, iter.getIndex()) + kEllipsisUTF16);
+ }
}
- if (elide_behavior_ != TRUNCATE && elide_behavior_ != FADE_TAIL &&
- display_rect_.width() > 0 && !layout_text_.empty() &&
- GetContentWidth() > display_rect_.width()) {
+ if (elide_behavior_ != NO_ELIDE && elide_behavior_ != FADE_TAIL &&
+ !layout_text_.empty() && GetContentWidth() > display_rect_.width()) {
// 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(ElideText(layout_text_));
+ layout_text_.assign(
+ Elide(layout_text_, display_rect_.width(), elide_behavior_));
}
// Replace the newline character with a newline symbol in single line mode.
@@ -1212,45 +1225,36 @@ void RenderText::UpdateLayoutText() {
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) {
- const bool insert_ellipsis = (elide_behavior_ != TRUNCATE);
+base::string16 RenderText::Elide(const base::string16& text,
+ float available_width,
+ ElideBehavior behavior) {
+ if (available_width <= 0 || text.empty())
+ return base::string16();
+ if (behavior == ELIDE_EMAIL)
+ return ElideEmail(text, available_width);
+
// 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->set_truncate_length(truncate_length_);
render_text->styles_ = styles_;
render_text->colors_ = colors_;
render_text->SetText(text);
- const int current_text_pixel_width = render_text->GetContentWidth();
+ if (render_text->GetContentWidth() <= available_width)
+ return text;
const base::string16 ellipsis = base::string16(kEllipsisUTF16);
- const bool elide_in_middle = false;
- const bool elide_at_beginning = false;
+ const bool insert_ellipsis = (behavior != TRUNCATE);
+ const bool elide_in_middle = (behavior == ELIDE_MIDDLE);
+ const bool elide_at_beginning = (behavior == ELIDE_HEAD);
StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning);
- // 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;
-
- render_text->SetText(base::string16());
render_text->SetText(ellipsis);
- const int ellipsis_width = render_text->GetContentWidth();
+ const float ellipsis_width = render_text->GetContentWidth();
- if (insert_ellipsis && (ellipsis_width >= display_rect_.width()))
+ if (insert_ellipsis && (ellipsis_width > available_width))
return base::string16();
// Use binary search to compute the elided text.
@@ -1261,12 +1265,13 @@ base::string16 RenderText::ElideText(const base::string16& text) {
// 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);
+ base::string16 new_text =
+ slicer.CutString(guess, insert_ellipsis && behavior != ELIDE_TAIL);
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) {
+ if (insert_ellipsis && behavior == ELIDE_TAIL) {
// 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
@@ -1286,13 +1291,12 @@ base::string16 RenderText::ElideText(const base::string16& 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())
+ const float guess_width = render_text->GetContentWidth();
+ if (guess_width == available_width)
break;
- if (guess_width > display_rect_.width()) {
+ if (guess_width > available_width) {
hi = guess - 1;
- // Move back if we are on loop terminating condition, and guess is wider
- // than available.
+ // Move back on the loop terminating condition when the guess is too wide.
if (hi < lo)
lo = hi;
} else {
@@ -1303,6 +1307,61 @@ base::string16 RenderText::ElideText(const base::string16& text) {
return render_text->text();
}
+base::string16 RenderText::ElideEmail(const base::string16& email,
+ float available_width) {
+ // The returned string will have at least one character besides the ellipsis
+ // on either side of '@'; if that's impossible, a single ellipsis is returned.
+ // If possible, only the username is elided. Otherwise, the domain is elided
+ // in the middle, splitting available width equally with the elided username.
+ // If the username is short enough that it doesn't need half the available
+ // width, the elided domain will occupy that extra width.
+
+ // Split the email into its local-part (username) and domain-part. The email
+ // spec allows for @ symbols in the username under some special requirements,
+ // but not in the domain part, so splitting at the last @ symbol is safe.
+ const size_t split_index = email.find_last_of('@');
+ DCHECK_NE(split_index, base::string16::npos);
+ base::string16 username = email.substr(0, split_index);
+ base::string16 domain = email.substr(split_index + 1);
+ DCHECK(!username.empty());
+ DCHECK(!domain.empty());
+
+ // Subtract the @ symbol from the available width as it is mandatory.
+ const base::string16 kAtSignUTF16 = base::ASCIIToUTF16("@");
+ available_width -= GetStringWidthF(kAtSignUTF16, font_list());
+
+ // Check whether eliding the domain is necessary: if eliding the username
+ // is sufficient, the domain will not be elided.
+ const float full_username_width = GetStringWidthF(username, font_list());
+ const float available_domain_width = available_width -
+ std::min(full_username_width,
+ GetStringWidthF(username.substr(0, 1) + kEllipsisUTF16, font_list()));
+ if (GetStringWidthF(domain, font_list()) > available_domain_width) {
+ // Elide the domain so that it only takes half of the available width.
+ // Should the username not need all the width available in its half, the
+ // domain will occupy the leftover width.
+ // If |desired_domain_width| is greater than |available_domain_width|: the
+ // minimal username elision allowed by the specifications will not fit; thus
+ // |desired_domain_width| must be <= |available_domain_width| at all cost.
+ const float desired_domain_width =
+ std::min<float>(available_domain_width,
+ std::max<float>(available_width - full_username_width,
+ available_width / 2));
+ domain = Elide(domain, desired_domain_width, ELIDE_MIDDLE);
+ // Failing to elide the domain such that at least one character remains
+ // (other than the ellipsis itself) remains: return a single ellipsis.
+ if (domain.length() <= 1U)
+ return base::string16(kEllipsisUTF16);
+ }
+
+ // Fit the username in the remaining width (at this point the elided username
+ // is guaranteed to fit with at least one character remaining given all the
+ // precautions taken earlier).
+ available_width -= GetStringWidthF(domain, font_list());
+ username = Elide(username, available_width, ELIDE_TAIL);
+ return username + kAtSignUTF16 + domain;
+}
+
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 6258c8b..afd62ea 100644
--- a/ui/gfx/render_text.h
+++ b/ui/gfx/render_text.h
@@ -29,7 +29,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;
@@ -254,11 +253,12 @@ 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_MIDDLE| is not supported. If a truncate length and
- // an elide mode are specified, the shorter of the two will be applicable.
+ // The layout text will be elided to fit |display_rect| using this behavior.
+ // The layout text may be shortened further by the truncate length.
void SetElideBehavior(ElideBehavior elide_behavior);
+ const base::string16& layout_text() const { return layout_text_; }
+
const Rect& display_rect() const { return display_rect_; }
void SetDisplayRect(const Rect& r);
@@ -355,7 +355,7 @@ class GFX_EXPORT RenderText {
// Returns the width of the content (which is the wrapped width in multiline
// mode). Reserves room for the cursor if |cursor_enabled_| is true.
- int GetContentWidth();
+ float GetContentWidth();
// Returns the common baseline of the text. The return value is the vertical
// offset from the top of |display_rect_| to the text baseline, in pixels.
@@ -581,9 +581,13 @@ 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);
+ // Elides |text| as needed to fit in the |available_width| using |behavior|.
+ base::string16 Elide(const base::string16& text,
+ float available_width,
+ ElideBehavior behavior);
+
+ // Elides |email| as needed to fit the |available_width|.
+ base::string16 ElideEmail(const base::string16& email, float available_width);
// Update the cached bounds and display offset to ensure that the current
// cursor is within the visible display area.
diff --git a/ui/gfx/render_text_pango.cc b/ui/gfx/render_text_pango.cc
index 9db3995..b3a84fa 100644
--- a/ui/gfx/render_text_pango.cc
+++ b/ui/gfx/render_text_pango.cc
@@ -82,6 +82,15 @@ Size RenderTextPango::GetStringSize() {
EnsureLayout();
int width = 0, height = 0;
pango_layout_get_pixel_size(layout_, &width, &height);
+
+ // Pango returns 0 widths for very long strings (of 0x40000 chars or more).
+ // This is caused by an int overflow in pango_glyph_string_extents_range.
+ // Absurdly long strings may even report non-zero garbage values for width;
+ // while detecting that isn't worthwhile, this handles the 0 width cases.
+ const long kAbsurdLength = 100000;
+ if (width == 0 && g_utf8_strlen(layout_text_, -1) > kAbsurdLength)
+ width = font_list().GetExpectedTextWidth(g_utf8_strlen(layout_text_, -1));
+
// Keep a consistent height between this particular string's PangoLayout and
// potentially larger text supported by the FontList.
// For example, if a text field contains a Japanese character, which is
diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc
index 3cd3b98..3fc5aa3 100644
--- a/ui/gfx/render_text_unittest.cc
+++ b/ui/gfx/render_text_unittest.cc
@@ -1267,6 +1267,18 @@ TEST_F(RenderTextTest, StringSizeSanity) {
EXPECT_GT(string_size.height(), 0);
}
+TEST_F(RenderTextTest, StringSizeLongStrings) {
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ Size previous_string_size;
+ for (size_t length = 10; length < 1000000; length *= 10) {
+ render_text->SetText(base::string16(length, 'a'));
+ const Size string_size = render_text->GetStringSize();
+ EXPECT_GT(string_size.width(), previous_string_size.width());
+ EXPECT_GT(string_size.height(), 0);
+ previous_string_size = string_size;
+ }
+}
+
// TODO(asvitkine): This test fails because PlatformFontMac uses point font
// sizes instead of pixel sizes like other implementations.
#if !defined(OS_MACOSX)
diff --git a/ui/gfx/text_constants.h b/ui/gfx/text_constants.h
index 47c91cf..82f2a3b 100644
--- a/ui/gfx/text_constants.h
+++ b/ui/gfx/text_constants.h
@@ -44,7 +44,8 @@ enum TextStyle {
// Elision behaviors of text that exceeds constrained dimensions.
enum ElideBehavior {
- TRUNCATE = 0, // Do not elide or fade; the text may be truncated at the end.
+ NO_ELIDE = 0, // Do not modify the text, it may overflow its available bounds.
+ TRUNCATE, // Do not elide or fade, just truncate at the end of the string.
ELIDE_HEAD, // Add an ellipsis at the start of the string.
ELIDE_MIDDLE, // Add an ellipsis in the middle of the string.
ELIDE_TAIL, // Add an ellipsis at the end of the string.
diff --git a/ui/gfx/text_elider.cc b/ui/gfx/text_elider.cc
index 8c07bde..56ed501b 100644
--- a/ui/gfx/text_elider.cc
+++ b/ui/gfx/text_elider.cc
@@ -24,6 +24,7 @@
#include "third_party/icu/source/common/unicode/rbbi.h"
#include "third_party/icu/source/common/unicode/uloc.h"
#include "ui/gfx/font_list.h"
+#include "ui/gfx/render_text.h"
#include "ui/gfx/text_utils.h"
using base::ASCIIToUTF16;
@@ -34,16 +35,13 @@ namespace gfx {
namespace {
-// 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
-// character, other than the ellipses, on either side of the '@'. If it is
-// impossible to achieve these requirements: only an ellipsis will be returned.
-// If possible: this elides only the username portion of the |email|. Otherwise,
-// the domain is elided in the middle so that it splits the available width
-// equally with the elided username (should the username be short enough that it
-// doesn't need half the available width: the elided domain will occupy that
-// extra width).
+#if defined(OS_ANDROID) || defined(OS_IOS)
+// The returned string will have at least one character besides the ellipsis
+// on either side of '@'; if that's impossible, a single ellipsis is returned.
+// If possible, only the username is elided. Otherwise, the domain is elided
+// in the middle, splitting available width equally with the elided username.
+// If the username is short enough that it doesn't need half the available
+// width, the elided domain will occupy that extra width.
base::string16 ElideEmail(const base::string16& email,
const FontList& font_list,
float available_pixel_width) {
@@ -51,10 +49,8 @@ base::string16 ElideEmail(const base::string16& email,
return email;
// Split the email into its local-part (username) and domain-part. The email
- // spec technically allows for @ symbols in the local-part (username) of the
- // email under some special requirements. It is guaranteed that there is no @
- // symbol in the domain part of the email however so splitting at the last @
- // symbol is safe.
+ // spec allows for @ symbols in the username under some special requirements,
+ // but not in the domain part, so splitting at the last @ symbol is safe.
const size_t split_index = email.find_last_of('@');
DCHECK_NE(split_index, base::string16::npos);
base::string16 username = email.substr(0, split_index);
@@ -99,6 +95,7 @@ base::string16 ElideEmail(const base::string16& email,
username = ElideText(username, font_list, available_pixel_width, ELIDE_TAIL);
return username + kAtSignUTF16 + domain;
}
+#endif
} // namespace
@@ -205,36 +202,34 @@ base::string16 ElideText(const base::string16& text,
const FontList& font_list,
float available_pixel_width,
ElideBehavior behavior) {
+#if !defined(OS_ANDROID) && !defined(OS_IOS)
+ DCHECK_NE(behavior, FADE_TAIL);
+ scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
+ render_text->SetCursorEnabled(false);
+ // Do not bother accurately sizing strings over 5000 characters here, for
+ // performance purposes. This matches the behavior of Canvas::SizeStringFloat.
+ render_text->set_truncate_length(5000);
+ render_text->SetFontList(font_list);
+ available_pixel_width = std::ceil(available_pixel_width);
+ render_text->SetDisplayRect(gfx::Rect(gfx::Size(available_pixel_width, 1)));
+ render_text->SetElideBehavior(behavior);
+ render_text->SetText(text);
+ return render_text->layout_text();
+#else
DCHECK_NE(behavior, FADE_TAIL);
- if (text.empty() || behavior == FADE_TAIL)
+ if (text.empty() || behavior == FADE_TAIL || behavior == NO_ELIDE ||
+ GetStringWidthF(text, font_list) <= available_pixel_width) {
return text;
+ }
if (behavior == ELIDE_EMAIL)
return ElideEmail(text, font_list, available_pixel_width);
- const float current_text_pixel_width = GetStringWidthF(text, font_list);
const bool elide_in_middle = (behavior == ELIDE_MIDDLE);
const bool elide_at_beginning = (behavior == ELIDE_HEAD);
const bool insert_ellipsis = (behavior != TRUNCATE);
const base::string16 ellipsis = base::string16(kEllipsisUTF16);
StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning);
- // 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) {
- const base::string16 cut =
- slicer.CutString(text.length() / 2, insert_ellipsis);
- return ElideText(cut, font_list, available_pixel_width, behavior);
- }
-
- if (current_text_pixel_width <= available_pixel_width)
- return text;
-
if (insert_ellipsis &&
GetStringWidthF(ellipsis, font_list) > available_pixel_width)
return base::string16();
@@ -254,8 +249,7 @@ base::string16 ElideText(const base::string16& text,
break;
if (guess_width > available_pixel_width) {
hi = guess - 1;
- // Move back if we are on loop terminating condition, and guess is wider
- // than available.
+ // Move back on the loop terminating condition when the guess is too wide.
if (hi < lo)
lo = hi;
} else {
@@ -264,6 +258,7 @@ base::string16 ElideText(const base::string16& text,
}
return slicer.CutString(guess, insert_ellipsis);
+#endif
}
bool ElideString(const base::string16& input,
diff --git a/ui/gfx/text_elider_unittest.cc b/ui/gfx/text_elider_unittest.cc
index 0b6bcad..92c47ce 100644
--- a/ui/gfx/text_elider_unittest.cc
+++ b/ui/gfx/text_elider_unittest.cc
@@ -302,17 +302,12 @@ TEST(TextEliderTest, MAYBE_ElideTextEllipsisFront) {
static void CheckSurrogatePairs(const base::string16& text,
base::char16 first_char,
base::char16 second_char) {
- size_t index = text.find_first_of(first_char);
- while (index != base::string16::npos) {
- EXPECT_LT(index, text.length() - 1);
- EXPECT_EQ(second_char, text[index + 1]);
- index = text.find_first_of(first_char, index + 1);
- }
- index = text.find_first_of(second_char);
- while (index != base::string16::npos) {
- EXPECT_GT(index, 0U);
- EXPECT_EQ(first_char, text[index - 1]);
- index = text.find_first_of(second_char, index + 1);
+ for (size_t index = 0; index < text.length(); ++index) {
+ EXPECT_NE(second_char, text[index]);
+ if (text[index] == first_char) {
+ ASSERT_LT(++index, text.length());
+ EXPECT_EQ(second_char, text[index]);
+ }
}
}
@@ -327,8 +322,7 @@ TEST(TextEliderTest, MAYBE_ElideTextSurrogatePairs) {
// The below is 'MUSICAL SYMBOL G CLEF', which is represented in UTF-16 as
// two characters forming a surrogate pair 0x0001D11E.
const std::string kSurrogate = "\xF0\x9D\x84\x9E";
- const base::string16 kTestString =
- UTF8ToUTF16(kSurrogate + "ab" + kSurrogate + kSurrogate + "cd");
+ const base::string16 kTestString = UTF8ToUTF16(kSurrogate + "x" + kSurrogate);
const float kTestStringWidth = GetStringWidthF(kTestString, font_list);
const base::char16 kSurrogateFirstChar = kTestString[0];
const base::char16 kSurrogateSecondChar = kTestString[1];
diff --git a/ui/views/controls/label.cc b/ui/views/controls/label.cc
index 648a0e6..368f1d8 100644
--- a/ui/views/controls/label.cc
+++ b/ui/views/controls/label.cc
@@ -137,7 +137,7 @@ void Label::SetLineHeight(int height) {
void Label::SetMultiLine(bool multi_line) {
DCHECK(!multi_line || (elide_behavior_ == gfx::ELIDE_TAIL ||
- elide_behavior_ == gfx::TRUNCATE));
+ elide_behavior_ == gfx::NO_ELIDE));
if (multi_line != is_multi_line_) {
is_multi_line_ = multi_line;
ResetCachedSize();
@@ -164,7 +164,7 @@ void Label::SetAllowCharacterBreak(bool allow_character_break) {
void Label::SetElideBehavior(gfx::ElideBehavior elide_behavior) {
DCHECK(!is_multi_line_ || (elide_behavior_ == gfx::ELIDE_TAIL ||
- elide_behavior_ == gfx::TRUNCATE));
+ elide_behavior_ == gfx::NO_ELIDE));
if (elide_behavior != elide_behavior_) {
elide_behavior_ = elide_behavior;
ResetCachedSize();
@@ -496,7 +496,7 @@ void Label::CalculateDrawStringParams(base::string16* paint_text,
int* flags) const {
DCHECK(paint_text && text_bounds && flags);
- const bool forbid_ellipsis = elide_behavior_ == gfx::TRUNCATE ||
+ const bool forbid_ellipsis = elide_behavior_ == gfx::NO_ELIDE ||
elide_behavior_ == gfx::FADE_TAIL;
if (is_multi_line_ || forbid_ellipsis) {
*paint_text = layout_text();
diff --git a/ui/views/examples/label_example.cc b/ui/views/examples/label_example.cc
index 6521ded..0ffa746 100644
--- a/ui/views/examples/label_example.cc
+++ b/ui/views/examples/label_example.cc
@@ -25,8 +25,8 @@ namespace examples {
namespace {
-const char* kElideBehaviors[] = { "Truncate", "Elide Head", "Elide Middle",
- "Elide Tail", "Elide Email", "Fade Tail" };
+const char* kElideBehaviors[] = { "No Elide", "Truncate", "Elide Head",
+ "Elide Middle", "Elide Tail", "Elide Email", "Fade Tail" };
const char* kAlignments[] = { "Left", "Center", "Right", "Head" };
// A Label with a clamped preferred width to demonstrate eliding or wrapping.
@@ -190,7 +190,7 @@ void LabelExample::AddCustomLabel(View* container) {
layout->StartRow(0, 2);
custom_label_ = new PreferredSizeLabel();
custom_label_->SetHorizontalAlignment(gfx::ALIGN_LEFT);
- custom_label_->SetElideBehavior(gfx::TRUNCATE);
+ custom_label_->SetElideBehavior(gfx::NO_ELIDE);
custom_label_->SetText(textfield_->text());
layout->AddView(custom_label_);
diff --git a/ui/views/examples/text_example.cc b/ui/views/examples/text_example.cc
index 2682e73..3176485 100644
--- a/ui/views/examples/text_example.cc
+++ b/ui/views/examples/text_example.cc
@@ -46,7 +46,7 @@ const wchar_t kRightToLeftText[] =
L"\x5e9\x5dc\x5d5\x5dd \x5d4\x5e2\x5d5\x5dc\x5dd!";
const char* kTextExamples[] = { "Short", "Long", "Ampersands", "RTL Hebrew", };
-const char* kElideBehaviors[] = { "Elide", "Truncate", "Fade", };
+const char* kElideBehaviors[] = { "Elide", "No Elide", "Fade", };
const char* kPrefixOptions[] = { "Default", "Show", "Hide", };
const char* kHorizontalAligments[] = { "Default", "Left", "Center", "Right", };
@@ -67,7 +67,7 @@ class TextExample::TextExampleView : public View {
: text_(base::ASCIIToUTF16(kShortText)),
flags_(0),
halo_(false),
- elide_(gfx::TRUNCATE) {
+ elide_(gfx::NO_ELIDE) {
}
virtual void OnPaint(gfx::Canvas* canvas) OVERRIDE {
@@ -239,7 +239,7 @@ void TextExample::OnPerformAction(Combobox* combobox) {
text_view_->set_elide(gfx::ELIDE_TAIL);
break;
case 1:
- text_view_->set_elide(gfx::TRUNCATE);
+ text_view_->set_elide(gfx::NO_ELIDE);
break;
case 2:
text_view_->set_elide(gfx::FADE_TAIL);