OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 |
OLD | NEW |