Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(4)

Side by Side Diff: ui/gfx/text_elider.cc

Issue 354963003: Move gfx::ElideText functionality to RenderText. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Reorder RenderText::Elide impl to match decl. Created 6 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« ui/gfx/render_text.h ('K') | « ui/gfx/render_text.cc ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 // 4 //
5 // This file implements utility functions for eliding and formatting UI text. 5 // This file implements utility functions for eliding and formatting UI text.
6 // 6 //
7 // Note that several of the functions declared in text_elider.h are implemented 7 // Note that several of the functions declared in text_elider.h are implemented
8 // in this file using helper classes in an unnamed namespace. 8 // in this file using helper classes in an unnamed namespace.
9 9
10 #include "ui/gfx/text_elider.h" 10 #include "ui/gfx/text_elider.h"
11 11
12 #include <string> 12 #include <string>
13 #include <vector> 13 #include <vector>
14 14
15 #include "base/files/file_path.h" 15 #include "base/files/file_path.h"
16 #include "base/i18n/break_iterator.h" 16 #include "base/i18n/break_iterator.h"
17 #include "base/i18n/char_iterator.h" 17 #include "base/i18n/char_iterator.h"
18 #include "base/i18n/rtl.h" 18 #include "base/i18n/rtl.h"
19 #include "base/memory/scoped_ptr.h" 19 #include "base/memory/scoped_ptr.h"
20 #include "base/strings/string_split.h" 20 #include "base/strings/string_split.h"
21 #include "base/strings/string_util.h" 21 #include "base/strings/string_util.h"
22 #include "base/strings/sys_string_conversions.h" 22 #include "base/strings/sys_string_conversions.h"
23 #include "base/strings/utf_string_conversions.h" 23 #include "base/strings/utf_string_conversions.h"
24 #include "third_party/icu/source/common/unicode/rbbi.h" 24 #include "third_party/icu/source/common/unicode/rbbi.h"
25 #include "third_party/icu/source/common/unicode/uloc.h" 25 #include "third_party/icu/source/common/unicode/uloc.h"
26 #include "ui/gfx/font_list.h" 26 #include "ui/gfx/font_list.h"
27 #include "ui/gfx/render_text.h"
27 #include "ui/gfx/text_utils.h" 28 #include "ui/gfx/text_utils.h"
28 29
29 using base::ASCIIToUTF16; 30 using base::ASCIIToUTF16;
30 using base::UTF8ToUTF16; 31 using base::UTF8ToUTF16;
31 using base::WideToUTF16; 32 using base::WideToUTF16;
32 33
33 namespace gfx { 34 namespace gfx {
34 35
35 namespace {
36
37 // Elides a well-formed email address (e.g. username@domain.com) to fit into
38 // |available_pixel_width| using the specified |font_list|.
39 // This function guarantees that the string returned will contain at least one
40 // character, other than the ellipses, on either side of the '@'. If it is
41 // impossible to achieve these requirements: only an ellipsis will be returned.
42 // If possible: this elides only the username portion of the |email|. Otherwise,
43 // the domain is elided in the middle so that it splits the available width
44 // equally with the elided username (should the username be short enough that it
45 // doesn't need half the available width: the elided domain will occupy that
46 // extra width).
47 base::string16 ElideEmail(const base::string16& email,
48 const FontList& font_list,
49 float available_pixel_width) {
50 if (GetStringWidthF(email, font_list) <= available_pixel_width)
51 return email;
52
53 // Split the email into its local-part (username) and domain-part. The email
54 // spec technically allows for @ symbols in the local-part (username) of the
55 // email under some special requirements. It is guaranteed that there is no @
56 // symbol in the domain part of the email however so splitting at the last @
57 // symbol is safe.
58 const size_t split_index = email.find_last_of('@');
59 DCHECK_NE(split_index, base::string16::npos);
60 base::string16 username = email.substr(0, split_index);
61 base::string16 domain = email.substr(split_index + 1);
62 DCHECK(!username.empty());
63 DCHECK(!domain.empty());
64
65 // Subtract the @ symbol from the available width as it is mandatory.
66 const base::string16 kAtSignUTF16 = ASCIIToUTF16("@");
67 available_pixel_width -= GetStringWidthF(kAtSignUTF16, font_list);
68
69 // Check whether eliding the domain is necessary: if eliding the username
70 // is sufficient, the domain will not be elided.
71 const float full_username_width = GetStringWidthF(username, font_list);
72 const float available_domain_width =
73 available_pixel_width -
74 std::min(full_username_width,
75 GetStringWidthF(username.substr(0, 1) + kEllipsisUTF16,
76 font_list));
77 if (GetStringWidthF(domain, font_list) > available_domain_width) {
78 // Elide the domain so that it only takes half of the available width.
79 // Should the username not need all the width available in its half, the
80 // domain will occupy the leftover width.
81 // If |desired_domain_width| is greater than |available_domain_width|: the
82 // minimal username elision allowed by the specifications will not fit; thus
83 // |desired_domain_width| must be <= |available_domain_width| at all cost.
84 const float desired_domain_width =
85 std::min(available_domain_width,
86 std::max(available_pixel_width - full_username_width,
87 available_pixel_width / 2));
88 domain = ElideText(domain, font_list, desired_domain_width, ELIDE_MIDDLE);
89 // Failing to elide the domain such that at least one character remains
90 // (other than the ellipsis itself) remains: return a single ellipsis.
91 if (domain.length() <= 1U)
92 return base::string16(kEllipsisUTF16);
93 }
94
95 // Fit the username in the remaining width (at this point the elided username
96 // is guaranteed to fit with at least one character remaining given all the
97 // precautions taken earlier).
98 available_pixel_width -= GetStringWidthF(domain, font_list);
99 username = ElideText(username, font_list, available_pixel_width, ELIDE_TAIL);
100 return username + kAtSignUTF16 + domain;
101 }
102
103 } // namespace
104
105 // U+2026 in utf8 36 // U+2026 in utf8
106 const char kEllipsis[] = "\xE2\x80\xA6"; 37 const char kEllipsis[] = "\xE2\x80\xA6";
107 const base::char16 kEllipsisUTF16[] = { 0x2026, 0 }; 38 const base::char16 kEllipsisUTF16[] = { 0x2026, 0 };
108 const base::char16 kForwardSlash = '/'; 39 const base::char16 kForwardSlash = '/';
109 40
110 StringSlicer::StringSlicer(const base::string16& text, 41 StringSlicer::StringSlicer(const base::string16& text,
111 const base::string16& ellipsis, 42 const base::string16& ellipsis,
112 bool elide_in_middle, 43 bool elide_in_middle,
113 bool elide_at_beginning) 44 bool elide_at_beginning)
114 : text_(text), 45 : text_(text),
(...skipping 84 matching lines...) Expand 10 before | Expand all | Expand 10 after
199 ElideText(rootname, font_list, available_root_width, ELIDE_TAIL); 130 ElideText(rootname, font_list, available_root_width, ELIDE_TAIL);
200 elided_name += extension; 131 elided_name += extension;
201 return base::i18n::GetDisplayStringInLTRDirectionality(elided_name); 132 return base::i18n::GetDisplayStringInLTRDirectionality(elided_name);
202 } 133 }
203 134
204 base::string16 ElideText(const base::string16& text, 135 base::string16 ElideText(const base::string16& text,
205 const FontList& font_list, 136 const FontList& font_list,
206 float available_pixel_width, 137 float available_pixel_width,
207 ElideBehavior behavior) { 138 ElideBehavior behavior) {
208 DCHECK_NE(behavior, FADE_TAIL); 139 DCHECK_NE(behavior, FADE_TAIL);
209 if (text.empty() || behavior == FADE_TAIL) 140 scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
210 return text; 141 render_text->SetCursorEnabled(false);
211 if (behavior == ELIDE_EMAIL) 142 render_text->SetFontList(font_list);
212 return ElideEmail(text, font_list, available_pixel_width); 143 return render_text->Elide(text, available_pixel_width, behavior);
213
214 const float current_text_pixel_width = GetStringWidthF(text, font_list);
215 const bool elide_in_middle = (behavior == ELIDE_MIDDLE);
216 const bool elide_at_beginning = (behavior == ELIDE_HEAD);
217 const bool insert_ellipsis = (behavior != TRUNCATE);
218 const base::string16 ellipsis = base::string16(kEllipsisUTF16);
219 StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning);
220
221 // Pango will return 0 width for absurdly long strings. Cut the string in
222 // half and try again.
223 // This is caused by an int overflow in Pango (specifically, in
224 // pango_glyph_string_extents_range). It's actually more subtle than just
225 // returning 0, since on super absurdly long strings, the int can wrap and
226 // return positive numbers again. Detecting that is probably not worth it
227 // (eliding way too much from a ridiculous string is probably still
228 // ridiculous), but we should check other widths for bogus values as well.
229 if (current_text_pixel_width <= 0) {
230 const base::string16 cut =
231 slicer.CutString(text.length() / 2, insert_ellipsis);
232 return ElideText(cut, font_list, available_pixel_width, behavior);
233 }
234
235 if (current_text_pixel_width <= available_pixel_width)
236 return text;
237
238 if (insert_ellipsis &&
239 GetStringWidthF(ellipsis, font_list) > available_pixel_width)
240 return base::string16();
241
242 // Use binary search to compute the elided text.
243 size_t lo = 0;
244 size_t hi = text.length() - 1;
245 size_t guess;
246 for (guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) {
247 // We check the width of the whole desired string at once to ensure we
248 // handle kerning/ligatures/etc. correctly.
249 // TODO(skanuj) : Handle directionality of ellipsis based on adjacent
250 // characters. See crbug.com/327963.
251 const base::string16 cut = slicer.CutString(guess, insert_ellipsis);
252 const float guess_width = GetStringWidthF(cut, font_list);
253 if (guess_width == available_pixel_width)
254 break;
255 if (guess_width > available_pixel_width) {
256 hi = guess - 1;
257 // Move back if we are on loop terminating condition, and guess is wider
258 // than available.
259 if (hi < lo)
260 lo = hi;
261 } else {
262 lo = guess + 1;
263 }
264 }
265
266 return slicer.CutString(guess, insert_ellipsis);
267 } 144 }
268 145
269 bool ElideString(const base::string16& input, 146 bool ElideString(const base::string16& input,
270 int max_len, 147 int max_len,
271 base::string16* output) { 148 base::string16* output) {
272 DCHECK_GE(max_len, 0); 149 DCHECK_GE(max_len, 0);
273 if (static_cast<int>(input.length()) <= max_len) { 150 if (static_cast<int>(input.length()) <= max_len) {
274 output->assign(input); 151 output->assign(input);
275 return false; 152 return false;
276 } 153 }
(...skipping 539 matching lines...) Expand 10 before | Expand all | Expand 10 after
816 index = char_iterator.getIndex(); 693 index = char_iterator.getIndex();
817 } else { 694 } else {
818 // String has leading whitespace, return the elide string. 695 // String has leading whitespace, return the elide string.
819 return kElideString; 696 return kElideString;
820 } 697 }
821 } 698 }
822 return string.substr(0, index) + kElideString; 699 return string.substr(0, index) + kElideString;
823 } 700 }
824 701
825 } // namespace gfx 702 } // namespace gfx
OLDNEW
« ui/gfx/render_text.h ('K') | « ui/gfx/render_text.cc ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698