Index: ui/gfx/render_text.cc |
diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc |
index c7871ee7a05ed4e1da574e2d6ffb1d6ca03e147a..7ed4edfe514ac023cf7af9ee0fd2e1774517dee4 100644 |
--- a/ui/gfx/render_text.cc |
+++ b/ui/gfx/render_text.cc |
@@ -22,7 +22,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" |
@@ -873,6 +872,104 @@ SelectionModel RenderText::GetSelectionModelForSelectionStart() { |
sel.is_reversed() ? CURSOR_BACKWARD : CURSOR_FORWARD); |
} |
+base::string16 RenderText::Elide(const base::string16& text, |
+ float available_width, |
+ ElideBehavior behavior) { |
+ if (text.empty() || behavior == FADE_TAIL) |
+ return text; |
+ if (available_width <= 0) |
+ 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->styles_ = styles_; |
+ render_text->colors_ = colors_; |
+ render_text->SetText(text); |
+ const int current_text_pixel_width = render_text->GetContentWidth(); |
+ |
+ const base::string16 ellipsis = base::string16(kEllipsisUTF16); |
+ 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 Elide(slicer.CutString(text.length() / 2, insert_ellipsis), |
+ available_width, behavior); |
+ |
+ if (current_text_pixel_width <= available_width) |
+ return text; |
+ |
+ render_text->SetText(ellipsis); |
+ const int ellipsis_width = render_text->GetContentWidth(); |
+ |
+ if (insert_ellipsis && (ellipsis_width > available_width)) |
+ return base::string16(); |
+ |
+ // Use binary search to compute the elided text. |
+ size_t lo = 0; |
+ size_t hi = text.length() - 1; |
+ const base::i18n::TextDirection text_direction = GetTextDirection(); |
+ 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, 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 && 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 |
+ // preceding text, we force the directionality of ellipsis to be same as |
+ // the preceding text using LTR or RTL markers. |
+ base::i18n::TextDirection trailing_text_direction = |
+ base::i18n::GetLastStrongCharacterDirection(new_text); |
+ new_text.append(ellipsis); |
+ if (trailing_text_direction != 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 == available_width) |
+ break; |
+ if (guess_width > available_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(); |
+} |
+ |
RenderText::RenderText() |
: horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT), |
directionality_mode_(DIRECTIONALITY_FROM_TEXT), |
@@ -1169,101 +1266,66 @@ void RenderText::UpdateLayoutText() { |
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_)); |
} |
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); |
- // 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 int current_text_pixel_width = render_text->GetContentWidth(); |
- |
- const base::string16 ellipsis = base::string16(kEllipsisUTF16); |
- const bool elide_in_middle = false; |
- const bool elide_at_beginning = false; |
- 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(); |
- |
- if (insert_ellipsis && (ellipsis_width >= display_rect_.width())) |
- return base::string16(); |
- |
- // Use binary search to compute the elided text. |
- size_t lo = 0; |
- size_t hi = text.length() - 1; |
- const base::i18n::TextDirection text_direction = GetTextDirection(); |
- 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 trailing_text_direction = |
- base::i18n::GetLastStrongCharacterDirection(new_text); |
- new_text.append(ellipsis); |
- if (trailing_text_direction != 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; |
- } |
+base::string16 RenderText::ElideEmail(const base::string16& email, |
+ float available_width) { |
+ // The returned string will have at least one character besides the ellipses |
+ // 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); |
} |
- return render_text->text(); |
+ // 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() { |