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/base/text/text_elider.h" | 10 #include "ui/base/text/text_elider.h" |
(...skipping 15 matching lines...) Expand all Loading... | |
26 #include "net/base/net_util.h" | 26 #include "net/base/net_util.h" |
27 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" | 27 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
28 #include "third_party/icu/public/common/unicode/rbbi.h" | 28 #include "third_party/icu/public/common/unicode/rbbi.h" |
29 #include "third_party/icu/public/common/unicode/uloc.h" | 29 #include "third_party/icu/public/common/unicode/uloc.h" |
30 #include "ui/gfx/font.h" | 30 #include "ui/gfx/font.h" |
31 | 31 |
32 namespace ui { | 32 namespace ui { |
33 | 33 |
34 // U+2026 in utf8 | 34 // U+2026 in utf8 |
35 const char kEllipsis[] = "\xE2\x80\xA6"; | 35 const char kEllipsis[] = "\xE2\x80\xA6"; |
36 const char16 kEllipsisUTF16 = 0x2026; | |
Alexei Svitkine (slow)
2013/06/28 15:37:02
Consider making it:
const char16 kEllipsisUTF16[]
msw
2013/06/28 16:31:23
Done.
| |
36 const char16 kForwardSlash = '/'; | 37 const char16 kForwardSlash = '/'; |
37 | 38 |
38 namespace { | 39 namespace { |
39 | 40 |
40 // Helper class to split + elide text, while respecting UTF16 surrogate pairs. | 41 // Helper class to split + elide text, while respecting UTF16 surrogate pairs. |
41 class StringSlicer { | 42 class StringSlicer { |
42 public: | 43 public: |
43 StringSlicer(const string16& text, | 44 StringSlicer(const string16& text, |
44 const string16& ellipsis, | 45 const string16& ellipsis, |
45 bool elide_in_middle) | 46 bool elide_in_middle) |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
98 | 99 |
99 DISALLOW_COPY_AND_ASSIGN(StringSlicer); | 100 DISALLOW_COPY_AND_ASSIGN(StringSlicer); |
100 }; | 101 }; |
101 | 102 |
102 // Build a path from the first |num_components| elements in |path_elements|. | 103 // Build a path from the first |num_components| elements in |path_elements|. |
103 // Prepends |path_prefix|, appends |filename|, inserts ellipsis if appropriate. | 104 // Prepends |path_prefix|, appends |filename|, inserts ellipsis if appropriate. |
104 string16 BuildPathFromComponents(const string16& path_prefix, | 105 string16 BuildPathFromComponents(const string16& path_prefix, |
105 const std::vector<string16>& path_elements, | 106 const std::vector<string16>& path_elements, |
106 const string16& filename, | 107 const string16& filename, |
107 size_t num_components) { | 108 size_t num_components) { |
108 const string16 kEllipsisAndSlash = UTF8ToUTF16(kEllipsis) + kForwardSlash; | |
109 | |
110 // Add the initial elements of the path. | 109 // Add the initial elements of the path. |
111 string16 path = path_prefix; | 110 string16 path = path_prefix; |
112 | 111 |
113 // Build path from first |num_components| elements. | 112 // Build path from first |num_components| elements. |
114 for (size_t j = 0; j < num_components; ++j) | 113 for (size_t j = 0; j < num_components; ++j) |
115 path += path_elements[j] + kForwardSlash; | 114 path += path_elements[j] + kForwardSlash; |
116 | 115 |
117 // Add |filename|, ellipsis if necessary. | 116 // Add |filename|, ellipsis if necessary. |
118 if (num_components != (path_elements.size() - 1)) | 117 if (num_components != (path_elements.size() - 1)) |
119 path += kEllipsisAndSlash; | 118 path += UTF8ToUTF16(kEllipsis) + kForwardSlash; |
Alexei Svitkine (slow)
2013/06/28 15:37:02
You can use kEllipisisUTF16 if you change it to a
msw
2013/06/28 16:31:23
I'll pass; I hit test failures trying to change mo
| |
120 path += filename; | 119 path += filename; |
121 | 120 |
122 return path; | 121 return path; |
123 } | 122 } |
124 | 123 |
125 // Takes a prefix (Domain, or Domain+subdomain) and a collection of path | 124 // Takes a prefix (Domain, or Domain+subdomain) and a collection of path |
126 // components and elides if possible. Returns a string containing the longest | 125 // components and elides if possible. Returns a string containing the longest |
127 // possible elided path, or an empty string if elision is not possible. | 126 // possible elided path, or an empty string if elision is not possible. |
128 string16 ElideComponentizedPath(const string16& url_path_prefix, | 127 string16 ElideComponentizedPath(const string16& url_path_prefix, |
129 const std::vector<string16>& url_path_elements, | 128 const std::vector<string16>& url_path_elements, |
130 const string16& url_filename, | 129 const string16& url_filename, |
131 const string16& url_query, | 130 const string16& url_query, |
132 const gfx::Font& font, | 131 const gfx::Font& font, |
133 int available_pixel_width) { | 132 int available_pixel_width) { |
134 const size_t url_path_number_of_elements = url_path_elements.size(); | 133 const size_t url_path_number_of_elements = url_path_elements.size(); |
135 | 134 |
136 const string16 kEllipsisAndSlash = UTF8ToUTF16(kEllipsis) + kForwardSlash; | |
137 | |
138 CHECK(url_path_number_of_elements); | 135 CHECK(url_path_number_of_elements); |
139 for (size_t i = url_path_number_of_elements - 1; i > 0; --i) { | 136 for (size_t i = url_path_number_of_elements - 1; i > 0; --i) { |
140 string16 elided_path = BuildPathFromComponents(url_path_prefix, | 137 string16 elided_path = BuildPathFromComponents(url_path_prefix, |
141 url_path_elements, url_filename, i); | 138 url_path_elements, url_filename, i); |
142 if (available_pixel_width >= font.GetStringWidth(elided_path)) | 139 if (available_pixel_width >= font.GetStringWidth(elided_path)) |
143 return ElideText(elided_path + url_query, | 140 return ElideText(elided_path + url_query, |
144 font, available_pixel_width, ELIDE_AT_END); | 141 font, available_pixel_width, ELIDE_AT_END); |
145 } | 142 } |
146 | 143 |
147 return string16(); | 144 return string16(); |
(...skipping 12 matching lines...) Expand all Loading... | |
160 // email under some special requirements. It is guaranteed that there is no @ | 157 // email under some special requirements. It is guaranteed that there is no @ |
161 // symbol in the domain part of the email however so splitting at the last @ | 158 // symbol in the domain part of the email however so splitting at the last @ |
162 // symbol is safe. | 159 // symbol is safe. |
163 const size_t split_index = email.find_last_of('@'); | 160 const size_t split_index = email.find_last_of('@'); |
164 DCHECK_NE(split_index, string16::npos); | 161 DCHECK_NE(split_index, string16::npos); |
165 string16 username = email.substr(0, split_index); | 162 string16 username = email.substr(0, split_index); |
166 string16 domain = email.substr(split_index + 1); | 163 string16 domain = email.substr(split_index + 1); |
167 DCHECK(!username.empty()); | 164 DCHECK(!username.empty()); |
168 DCHECK(!domain.empty()); | 165 DCHECK(!domain.empty()); |
169 | 166 |
170 const string16 kEllipsisUTF16 = UTF8ToUTF16(kEllipsis); | |
171 | |
172 // Subtract the @ symbol from the available width as it is mandatory. | 167 // Subtract the @ symbol from the available width as it is mandatory. |
173 const string16 kAtSignUTF16 = ASCIIToUTF16("@"); | 168 const string16 kAtSignUTF16 = ASCIIToUTF16("@"); |
174 available_pixel_width -= font.GetStringWidth(kAtSignUTF16); | 169 available_pixel_width -= font.GetStringWidth(kAtSignUTF16); |
175 | 170 |
176 // Check whether eliding the domain is necessary: if eliding the username | 171 // Check whether eliding the domain is necessary: if eliding the username |
177 // is sufficient, the domain will not be elided. | 172 // is sufficient, the domain will not be elided. |
178 const int full_username_width = font.GetStringWidth(username); | 173 const int full_username_width = font.GetStringWidth(username); |
179 const int available_domain_width = | 174 const int available_domain_width = |
180 available_pixel_width - | 175 available_pixel_width - |
181 std::min(full_username_width, | 176 std::min(full_username_width, |
182 font.GetStringWidth(username.substr(0, 1) + kEllipsisUTF16)); | 177 font.GetStringWidth(username.substr(0, 1) + kEllipsisUTF16)); |
183 if (font.GetStringWidth(domain) > available_domain_width) { | 178 if (font.GetStringWidth(domain) > available_domain_width) { |
184 // Elide the domain so that it only takes half of the available width. | 179 // Elide the domain so that it only takes half of the available width. |
185 // Should the username not need all the width available in its half, the | 180 // Should the username not need all the width available in its half, the |
186 // domain will occupy the leftover width. | 181 // domain will occupy the leftover width. |
187 // If |desired_domain_width| is greater than |available_domain_width|: the | 182 // If |desired_domain_width| is greater than |available_domain_width|: the |
188 // minimal username elision allowed by the specifications will not fit; thus | 183 // minimal username elision allowed by the specifications will not fit; thus |
189 // |desired_domain_width| must be <= |available_domain_width| at all cost. | 184 // |desired_domain_width| must be <= |available_domain_width| at all cost. |
190 const int desired_domain_width = | 185 const int desired_domain_width = |
191 std::min(available_domain_width, | 186 std::min(available_domain_width, |
192 std::max(available_pixel_width - full_username_width, | 187 std::max(available_pixel_width - full_username_width, |
193 available_pixel_width / 2)); | 188 available_pixel_width / 2)); |
194 domain = ElideText(domain, font, desired_domain_width, ELIDE_IN_MIDDLE); | 189 domain = ElideText(domain, font, desired_domain_width, ELIDE_IN_MIDDLE); |
195 // Failing to elide the domain such that at least one character remains | 190 // Failing to elide the domain such that at least one character remains |
196 // (other than the ellipsis itself) remains: return a single ellipsis. | 191 // (other than the ellipsis itself) remains: return a single ellipsis. |
197 if (domain.length() <= 1U) | 192 if (domain.length() <= 1U) |
198 return kEllipsisUTF16; | 193 return string16(1, kEllipsisUTF16); |
199 } | 194 } |
200 | 195 |
201 // Fit the username in the remaining width (at this point the elided username | 196 // Fit the username in the remaining width (at this point the elided username |
202 // is guaranteed to fit with at least one character remaining given all the | 197 // is guaranteed to fit with at least one character remaining given all the |
203 // precautions taken earlier). | 198 // precautions taken earlier). |
204 username = ElideText(username, | 199 username = ElideText(username, |
205 font, | 200 font, |
206 available_pixel_width - font.GetStringWidth(domain), | 201 available_pixel_width - font.GetStringWidth(domain), |
207 ELIDE_AT_END); | 202 ELIDE_AT_END); |
208 | 203 |
(...skipping 151 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
360 if (!elided_path.empty()) | 355 if (!elided_path.empty()) |
361 return elided_path; | 356 return elided_path; |
362 | 357 |
363 // Check with only domain. | 358 // Check with only domain. |
364 // If a subdomain is present, add an ellipsis before domain. | 359 // If a subdomain is present, add an ellipsis before domain. |
365 // This is added only if the subdomain pixel width is larger than | 360 // This is added only if the subdomain pixel width is larger than |
366 // the pixel width of kEllipsis. Otherwise, subdomain remains, | 361 // the pixel width of kEllipsis. Otherwise, subdomain remains, |
367 // which means that this case has been resolved earlier. | 362 // which means that this case has been resolved earlier. |
368 string16 url_elided_domain = url_subdomain + url_domain; | 363 string16 url_elided_domain = url_subdomain + url_domain; |
369 if (pixel_width_url_subdomain > kPixelWidthDotsTrailer) { | 364 if (pixel_width_url_subdomain > kPixelWidthDotsTrailer) { |
370 if (!url_subdomain.empty()) { | 365 if (!url_subdomain.empty()) |
371 url_elided_domain = kEllipsisAndSlash[0] + url_domain; | 366 url_elided_domain = kEllipsisAndSlash[0] + url_domain; |
372 } else { | 367 else |
373 url_elided_domain = url_domain; | 368 url_elided_domain = url_domain; |
374 } | |
375 | 369 |
376 elided_path = ElideComponentizedPath(url_elided_domain, url_path_elements, | 370 elided_path = ElideComponentizedPath(url_elided_domain, url_path_elements, |
377 url_filename, url_query, font, | 371 url_filename, url_query, font, |
378 available_pixel_width); | 372 available_pixel_width); |
379 | 373 |
380 if (!elided_path.empty()) | 374 if (!elided_path.empty()) |
381 return elided_path; | 375 return elided_path; |
382 } | 376 } |
383 | 377 |
384 // Return elided domain/.../filename anyway. | 378 // Return elided domain/.../filename anyway. |
(...skipping 63 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
448 return base::i18n::GetDisplayStringInLTRDirectionality(elided_name); | 442 return base::i18n::GetDisplayStringInLTRDirectionality(elided_name); |
449 } | 443 } |
450 | 444 |
451 string16 ElideText(const string16& text, | 445 string16 ElideText(const string16& text, |
452 const gfx::Font& font, | 446 const gfx::Font& font, |
453 int available_pixel_width, | 447 int available_pixel_width, |
454 ElideBehavior elide_behavior) { | 448 ElideBehavior elide_behavior) { |
455 if (text.empty()) | 449 if (text.empty()) |
456 return text; | 450 return text; |
457 | 451 |
458 const string16 kEllipsisUTF16 = UTF8ToUTF16(kEllipsis); | |
459 | |
460 const int current_text_pixel_width = font.GetStringWidth(text); | 452 const int current_text_pixel_width = font.GetStringWidth(text); |
461 const bool elide_in_middle = (elide_behavior == ELIDE_IN_MIDDLE); | 453 const bool elide_in_middle = (elide_behavior == ELIDE_IN_MIDDLE); |
462 const bool insert_ellipsis = (elide_behavior != TRUNCATE_AT_END); | 454 const bool insert_ellipsis = (elide_behavior != TRUNCATE_AT_END); |
463 | 455 |
464 StringSlicer slicer(text, kEllipsisUTF16, elide_in_middle); | 456 const string16 ellipsis = string16(1, kEllipsisUTF16); |
457 StringSlicer slicer(text, ellipsis, elide_in_middle); | |
465 | 458 |
466 // Pango will return 0 width for absurdly long strings. Cut the string in | 459 // Pango will return 0 width for absurdly long strings. Cut the string in |
467 // half and try again. | 460 // half and try again. |
468 // This is caused by an int overflow in Pango (specifically, in | 461 // This is caused by an int overflow in Pango (specifically, in |
469 // pango_glyph_string_extents_range). It's actually more subtle than just | 462 // pango_glyph_string_extents_range). It's actually more subtle than just |
470 // returning 0, since on super absurdly long strings, the int can wrap and | 463 // returning 0, since on super absurdly long strings, the int can wrap and |
471 // return positive numbers again. Detecting that is probably not worth it | 464 // return positive numbers again. Detecting that is probably not worth it |
472 // (eliding way too much from a ridiculous string is probably still | 465 // (eliding way too much from a ridiculous string is probably still |
473 // ridiculous), but we should check other widths for bogus values as well. | 466 // ridiculous), but we should check other widths for bogus values as well. |
474 if (current_text_pixel_width <= 0 && !text.empty()) { | 467 if (current_text_pixel_width <= 0 && !text.empty()) { |
475 const string16 cut = slicer.CutString(text.length() / 2, false); | 468 const string16 cut = slicer.CutString(text.length() / 2, false); |
476 return ElideText(cut, font, available_pixel_width, elide_behavior); | 469 return ElideText(cut, font, available_pixel_width, elide_behavior); |
477 } | 470 } |
478 | 471 |
479 if (current_text_pixel_width <= available_pixel_width) | 472 if (current_text_pixel_width <= available_pixel_width) |
480 return text; | 473 return text; |
481 | 474 |
482 if (insert_ellipsis && | 475 if (insert_ellipsis && font.GetStringWidth(ellipsis) > available_pixel_width) |
483 font.GetStringWidth(kEllipsisUTF16) > available_pixel_width) | |
484 return string16(); | 476 return string16(); |
485 | 477 |
486 // Use binary search to compute the elided text. | 478 // Use binary search to compute the elided text. |
487 size_t lo = 0; | 479 size_t lo = 0; |
488 size_t hi = text.length() - 1; | 480 size_t hi = text.length() - 1; |
489 size_t guess; | 481 size_t guess; |
490 for (guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) { | 482 for (guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) { |
491 // We check the length of the whole desired string at once to ensure we | 483 // We check the length of the whole desired string at once to ensure we |
492 // handle kerning/ligatures/etc. correctly. | 484 // handle kerning/ligatures/etc. correctly. |
493 const string16 cut = slicer.CutString(guess, insert_ellipsis); | 485 const string16 cut = slicer.CutString(guess, insert_ellipsis); |
(...skipping 634 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1128 index = char_iterator.getIndex(); | 1120 index = char_iterator.getIndex(); |
1129 } else { | 1121 } else { |
1130 // String has leading whitespace, return the elide string. | 1122 // String has leading whitespace, return the elide string. |
1131 return kElideString; | 1123 return kElideString; |
1132 } | 1124 } |
1133 } | 1125 } |
1134 return string.substr(0, index) + kElideString; | 1126 return string.substr(0, index) + kElideString; |
1135 } | 1127 } |
1136 | 1128 |
1137 } // namespace ui | 1129 } // namespace ui |
OLD | NEW |