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

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: Exclude unreachable code by platform. 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
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 namespace {
36 37
37 // Elides a well-formed email address (e.g. username@domain.com) to fit into 38 #if !defined(OS_WIN) && !defined(OS_LINUX)
38 // |available_pixel_width| using the specified |font_list|. 39 // The returned string will have at least one character besides the ellipsis
39 // This function guarantees that the string returned will contain at least one 40 // on either side of '@'; if that's impossible, a single ellipsis is returned.
40 // character, other than the ellipses, on either side of the '@'. If it is 41 // If possible, only the username is elided. Otherwise, the domain is elided
41 // impossible to achieve these requirements: only an ellipsis will be returned. 42 // in the middle, splitting available width equally with the elided username.
42 // If possible: this elides only the username portion of the |email|. Otherwise, 43 // If the username is short enough that it doesn't need half the available
43 // the domain is elided in the middle so that it splits the available width 44 // width, the elided domain will occupy that extra 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, 45 base::string16 ElideEmail(const base::string16& email,
48 const FontList& font_list, 46 const FontList& font_list,
49 float available_pixel_width) { 47 float available_pixel_width) {
50 if (GetStringWidthF(email, font_list) <= available_pixel_width) 48 if (GetStringWidthF(email, font_list) <= available_pixel_width)
51 return email; 49 return email;
52 50
53 // Split the email into its local-part (username) and domain-part. The email 51 // 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 52 // spec allows for @ symbols in the username under some special requirements,
55 // email under some special requirements. It is guaranteed that there is no @ 53 // but not in the domain part, so splitting at the last @ symbol is safe.
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('@'); 54 const size_t split_index = email.find_last_of('@');
59 DCHECK_NE(split_index, base::string16::npos); 55 DCHECK_NE(split_index, base::string16::npos);
60 base::string16 username = email.substr(0, split_index); 56 base::string16 username = email.substr(0, split_index);
61 base::string16 domain = email.substr(split_index + 1); 57 base::string16 domain = email.substr(split_index + 1);
62 DCHECK(!username.empty()); 58 DCHECK(!username.empty());
63 DCHECK(!domain.empty()); 59 DCHECK(!domain.empty());
64 60
65 // Subtract the @ symbol from the available width as it is mandatory. 61 // Subtract the @ symbol from the available width as it is mandatory.
66 const base::string16 kAtSignUTF16 = ASCIIToUTF16("@"); 62 const base::string16 kAtSignUTF16 = ASCIIToUTF16("@");
67 available_pixel_width -= GetStringWidthF(kAtSignUTF16, font_list); 63 available_pixel_width -= GetStringWidthF(kAtSignUTF16, font_list);
(...skipping 24 matching lines...) Expand all
92 return base::string16(kEllipsisUTF16); 88 return base::string16(kEllipsisUTF16);
93 } 89 }
94 90
95 // Fit the username in the remaining width (at this point the elided username 91 // 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 92 // is guaranteed to fit with at least one character remaining given all the
97 // precautions taken earlier). 93 // precautions taken earlier).
98 available_pixel_width -= GetStringWidthF(domain, font_list); 94 available_pixel_width -= GetStringWidthF(domain, font_list);
99 username = ElideText(username, font_list, available_pixel_width, ELIDE_TAIL); 95 username = ElideText(username, font_list, available_pixel_width, ELIDE_TAIL);
100 return username + kAtSignUTF16 + domain; 96 return username + kAtSignUTF16 + domain;
101 } 97 }
98 #endif
102 99
103 } // namespace 100 } // namespace
104 101
105 // U+2026 in utf8 102 // U+2026 in utf8
106 const char kEllipsis[] = "\xE2\x80\xA6"; 103 const char kEllipsis[] = "\xE2\x80\xA6";
107 const base::char16 kEllipsisUTF16[] = { 0x2026, 0 }; 104 const base::char16 kEllipsisUTF16[] = { 0x2026, 0 };
108 const base::char16 kForwardSlash = '/'; 105 const base::char16 kForwardSlash = '/';
109 106
110 StringSlicer::StringSlicer(const base::string16& text, 107 StringSlicer::StringSlicer(const base::string16& text,
111 const base::string16& ellipsis, 108 const base::string16& ellipsis,
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after
198 base::string16 elided_name = 195 base::string16 elided_name =
199 ElideText(rootname, font_list, available_root_width, ELIDE_TAIL); 196 ElideText(rootname, font_list, available_root_width, ELIDE_TAIL);
200 elided_name += extension; 197 elided_name += extension;
201 return base::i18n::GetDisplayStringInLTRDirectionality(elided_name); 198 return base::i18n::GetDisplayStringInLTRDirectionality(elided_name);
202 } 199 }
203 200
204 base::string16 ElideText(const base::string16& text, 201 base::string16 ElideText(const base::string16& text,
205 const FontList& font_list, 202 const FontList& font_list,
206 float available_pixel_width, 203 float available_pixel_width,
207 ElideBehavior behavior) { 204 ElideBehavior behavior) {
205 #if defined(OS_WIN) || defined(OS_LINUX)
Alexei Svitkine (slow) 2014/07/03 20:12:34 I understand why it wouldn't work on platforms tha
msw 2014/07/08 19:07:36 RenderText doesn't support RectF values for the di
208 DCHECK_NE(behavior, FADE_TAIL); 206 DCHECK_NE(behavior, FADE_TAIL);
209 if (text.empty() || behavior == FADE_TAIL) 207 scoped_ptr<RenderText> render_text(RenderText::CreateInstance());
208 render_text->SetCursorEnabled(false);
209 // Do not bother accurately sizing strings over 5000 characters here, for
210 // performance purposes. This matches the behavior of Canvas::SizeStringFloat.
211 render_text->set_truncate_length(5000);
212 render_text->SetFontList(font_list);
213 render_text->SetDisplayRect(gfx::Rect(gfx::Size(available_pixel_width, 1)));
214 render_text->SetElideBehavior(behavior);
215 render_text->SetText(text);
216 return render_text->layout_text();
217 #else
218 DCHECK_NE(behavior, FADE_TAIL);
219 if (text.empty() || behavior == FADE_TAIL || behavior == NO_ELIDE ||
220 GetStringWidthF(text, font_list) <= available_pixel_width)
Alexei Svitkine (slow) 2014/07/03 20:12:34 Nit: {}'s
msw 2014/07/08 19:07:36 Done.
210 return text; 221 return text;
211 if (behavior == ELIDE_EMAIL) 222 if (behavior == ELIDE_EMAIL)
212 return ElideEmail(text, font_list, available_pixel_width); 223 return ElideEmail(text, font_list, available_pixel_width);
213 224
214 const float current_text_pixel_width = GetStringWidthF(text, font_list);
215 const bool elide_in_middle = (behavior == ELIDE_MIDDLE); 225 const bool elide_in_middle = (behavior == ELIDE_MIDDLE);
216 const bool elide_at_beginning = (behavior == ELIDE_HEAD); 226 const bool elide_at_beginning = (behavior == ELIDE_HEAD);
217 const bool insert_ellipsis = (behavior != TRUNCATE); 227 const bool insert_ellipsis = (behavior != TRUNCATE);
218 const base::string16 ellipsis = base::string16(kEllipsisUTF16); 228 const base::string16 ellipsis = base::string16(kEllipsisUTF16);
219 StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning); 229 StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning);
220 230
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 && 231 if (insert_ellipsis &&
239 GetStringWidthF(ellipsis, font_list) > available_pixel_width) 232 GetStringWidthF(ellipsis, font_list) > available_pixel_width)
240 return base::string16(); 233 return base::string16();
241 234
242 // Use binary search to compute the elided text. 235 // Use binary search to compute the elided text.
243 size_t lo = 0; 236 size_t lo = 0;
244 size_t hi = text.length() - 1; 237 size_t hi = text.length() - 1;
245 size_t guess; 238 size_t guess;
246 for (guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) { 239 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 240 // We check the width of the whole desired string at once to ensure we
248 // handle kerning/ligatures/etc. correctly. 241 // handle kerning/ligatures/etc. correctly.
249 // TODO(skanuj) : Handle directionality of ellipsis based on adjacent 242 // TODO(skanuj) : Handle directionality of ellipsis based on adjacent
250 // characters. See crbug.com/327963. 243 // characters. See crbug.com/327963.
251 const base::string16 cut = slicer.CutString(guess, insert_ellipsis); 244 const base::string16 cut = slicer.CutString(guess, insert_ellipsis);
252 const float guess_width = GetStringWidthF(cut, font_list); 245 const float guess_width = GetStringWidthF(cut, font_list);
253 if (guess_width == available_pixel_width) 246 if (guess_width == available_pixel_width)
254 break; 247 break;
255 if (guess_width > available_pixel_width) { 248 if (guess_width > available_pixel_width) {
256 hi = guess - 1; 249 hi = guess - 1;
257 // Move back if we are on loop terminating condition, and guess is wider 250 // Move back on the loop terminating condition when the guess is too wide.
258 // than available.
259 if (hi < lo) 251 if (hi < lo)
260 lo = hi; 252 lo = hi;
261 } else { 253 } else {
262 lo = guess + 1; 254 lo = guess + 1;
263 } 255 }
264 } 256 }
265 257
266 return slicer.CutString(guess, insert_ellipsis); 258 return slicer.CutString(guess, insert_ellipsis);
259 #endif
267 } 260 }
268 261
269 bool ElideString(const base::string16& input, 262 bool ElideString(const base::string16& input,
270 int max_len, 263 int max_len,
271 base::string16* output) { 264 base::string16* output) {
272 DCHECK_GE(max_len, 0); 265 DCHECK_GE(max_len, 0);
273 if (static_cast<int>(input.length()) <= max_len) { 266 if (static_cast<int>(input.length()) <= max_len) {
274 output->assign(input); 267 output->assign(input);
275 return false; 268 return false;
276 } 269 }
(...skipping 539 matching lines...) Expand 10 before | Expand all | Expand 10 after
816 index = char_iterator.getIndex(); 809 index = char_iterator.getIndex();
817 } else { 810 } else {
818 // String has leading whitespace, return the elide string. 811 // String has leading whitespace, return the elide string.
819 return kElideString; 812 return kElideString;
820 } 813 }
821 } 814 }
822 return string.substr(0, index) + kElideString; 815 return string.substr(0, index) + kElideString;
823 } 816 }
824 817
825 } // namespace gfx 818 } // namespace gfx
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698