| 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() {
|
|
|