| 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, 0 }; |
| 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; |
| 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(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(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 |