diff options
author | msw@chromium.org <msw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-10 21:40:37 +0000 |
---|---|---|
committer | msw@chromium.org <msw@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-10 21:40:37 +0000 |
commit | 8d93614f9306e6b53455b469b8acf98613b4d417 (patch) | |
tree | 6e78a7cec4c7dfadfbb02cedd114969a13a19682 | |
parent | 0a3fca5aa2c58975f85cc4d1181d98f328afa5f6 (diff) | |
download | chromium_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.cc | 2 | ||||
-rw-r--r-- | chrome/browser/ui/views/location_bar/add_to_app_launcher_view.cc | 2 | ||||
-rw-r--r-- | chrome/browser/ui/views/location_bar/content_setting_image_view.cc | 2 | ||||
-rw-r--r-- | chrome/browser/ui/views/location_bar/origin_chip_view.cc | 4 | ||||
-rw-r--r-- | chrome/browser/ui/views/omnibox/omnibox_result_view.cc | 2 | ||||
-rw-r--r-- | ui/gfx/render_text.cc | 145 | ||||
-rw-r--r-- | ui/gfx/render_text.h | 20 | ||||
-rw-r--r-- | ui/gfx/render_text_pango.cc | 9 | ||||
-rw-r--r-- | ui/gfx/render_text_unittest.cc | 12 | ||||
-rw-r--r-- | ui/gfx/text_constants.h | 3 | ||||
-rw-r--r-- | ui/gfx/text_elider.cc | 65 | ||||
-rw-r--r-- | ui/gfx/text_elider_unittest.cc | 20 | ||||
-rw-r--r-- | ui/views/controls/label.cc | 6 | ||||
-rw-r--r-- | ui/views/examples/label_example.cc | 6 | ||||
-rw-r--r-- | ui/views/examples/text_example.cc | 6 |
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); |