| 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 "net/base/escape.h" | |
| 25 #include "net/base/net_util.h" | |
| 26 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" | |
| 27 #include "third_party/icu/source/common/unicode/rbbi.h" | 24 #include "third_party/icu/source/common/unicode/rbbi.h" |
| 28 #include "third_party/icu/source/common/unicode/uloc.h" | 25 #include "third_party/icu/source/common/unicode/uloc.h" |
| 29 #include "ui/gfx/font_list.h" | 26 #include "ui/gfx/font_list.h" |
| 30 #include "ui/gfx/text_utils.h" | 27 #include "ui/gfx/text_utils.h" |
| 31 #include "url/gurl.h" | |
| 32 | 28 |
| 33 using base::ASCIIToUTF16; | 29 using base::ASCIIToUTF16; |
| 34 using base::UTF8ToUTF16; | 30 using base::UTF8ToUTF16; |
| 35 using base::WideToUTF16; | 31 using base::WideToUTF16; |
| 36 | 32 |
| 37 namespace gfx { | 33 namespace gfx { |
| 38 | 34 |
| 39 // U+2026 in utf8 | 35 // U+2026 in utf8 |
| 40 const char kEllipsis[] = "\xE2\x80\xA6"; | 36 const char kEllipsis[] = "\xE2\x80\xA6"; |
| 41 const base::char16 kEllipsisUTF16[] = { 0x2026, 0 }; | 37 const base::char16 kEllipsisUTF16[] = { 0x2026, 0 }; |
| 42 const base::char16 kForwardSlash = '/'; | 38 const base::char16 kForwardSlash = '/'; |
| 43 | 39 |
| 44 namespace { | |
| 45 | |
| 46 | |
| 47 // Build a path from the first |num_components| elements in |path_elements|. | |
| 48 // Prepends |path_prefix|, appends |filename|, inserts ellipsis if appropriate. | |
| 49 base::string16 BuildPathFromComponents( | |
| 50 const base::string16& path_prefix, | |
| 51 const std::vector<base::string16>& path_elements, | |
| 52 const base::string16& filename, | |
| 53 size_t num_components) { | |
| 54 // Add the initial elements of the path. | |
| 55 base::string16 path = path_prefix; | |
| 56 | |
| 57 // Build path from first |num_components| elements. | |
| 58 for (size_t j = 0; j < num_components; ++j) | |
| 59 path += path_elements[j] + kForwardSlash; | |
| 60 | |
| 61 // Add |filename|, ellipsis if necessary. | |
| 62 if (num_components != (path_elements.size() - 1)) | |
| 63 path += base::string16(kEllipsisUTF16) + kForwardSlash; | |
| 64 path += filename; | |
| 65 | |
| 66 return path; | |
| 67 } | |
| 68 | |
| 69 // Takes a prefix (Domain, or Domain+subdomain) and a collection of path | |
| 70 // components and elides if possible. Returns a string containing the longest | |
| 71 // possible elided path, or an empty string if elision is not possible. | |
| 72 base::string16 ElideComponentizedPath( | |
| 73 const base::string16& url_path_prefix, | |
| 74 const std::vector<base::string16>& url_path_elements, | |
| 75 const base::string16& url_filename, | |
| 76 const base::string16& url_query, | |
| 77 const FontList& font_list, | |
| 78 float available_pixel_width) { | |
| 79 const size_t url_path_number_of_elements = url_path_elements.size(); | |
| 80 | |
| 81 CHECK(url_path_number_of_elements); | |
| 82 for (size_t i = url_path_number_of_elements - 1; i > 0; --i) { | |
| 83 base::string16 elided_path = BuildPathFromComponents(url_path_prefix, | |
| 84 url_path_elements, url_filename, i); | |
| 85 if (available_pixel_width >= GetStringWidthF(elided_path, font_list)) | |
| 86 return ElideText(elided_path + url_query, font_list, | |
| 87 available_pixel_width, ELIDE_AT_END); | |
| 88 } | |
| 89 | |
| 90 return base::string16(); | |
| 91 } | |
| 92 | |
| 93 } // namespace | |
| 94 | |
| 95 StringSlicer::StringSlicer(const base::string16& text, | 40 StringSlicer::StringSlicer(const base::string16& text, |
| 96 const base::string16& ellipsis, | 41 const base::string16& ellipsis, |
| 97 bool elide_in_middle) | 42 bool elide_in_middle) |
| 98 : text_(text), | 43 : text_(text), |
| 99 ellipsis_(ellipsis), | 44 ellipsis_(ellipsis), |
| 100 elide_in_middle_(elide_in_middle) { | 45 elide_in_middle_(elide_in_middle) { |
| 101 } | 46 } |
| 102 | 47 |
| 103 base::string16 StringSlicer::CutString(size_t length, bool insert_ellipsis) { | 48 base::string16 StringSlicer::CutString(size_t length, bool insert_ellipsis) { |
| 104 const base::string16 ellipsis_text = insert_ellipsis ? ellipsis_ | 49 const base::string16 ellipsis_text = insert_ellipsis ? ellipsis_ |
| (...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 184 // Fit the username in the remaining width (at this point the elided username | 129 // Fit the username in the remaining width (at this point the elided username |
| 185 // is guaranteed to fit with at least one character remaining given all the | 130 // is guaranteed to fit with at least one character remaining given all the |
| 186 // precautions taken earlier). | 131 // precautions taken earlier). |
| 187 available_pixel_width -= GetStringWidthF(domain, font_list); | 132 available_pixel_width -= GetStringWidthF(domain, font_list); |
| 188 username = ElideText(username, font_list, available_pixel_width, | 133 username = ElideText(username, font_list, available_pixel_width, |
| 189 ELIDE_AT_END); | 134 ELIDE_AT_END); |
| 190 | 135 |
| 191 return username + kAtSignUTF16 + domain; | 136 return username + kAtSignUTF16 + domain; |
| 192 } | 137 } |
| 193 | 138 |
| 194 // TODO(pkasting): http://crbug.com/77883 This whole function gets | |
| 195 // kerning/ligatures/etc. issues potentially wrong by assuming that the width of | |
| 196 // a rendered string is always the sum of the widths of its substrings. Also I | |
| 197 // suspect it could be made simpler. | |
| 198 base::string16 ElideUrl(const GURL& url, | |
| 199 const FontList& font_list, | |
| 200 float available_pixel_width, | |
| 201 const std::string& languages) { | |
| 202 // Get a formatted string and corresponding parsing of the url. | |
| 203 url_parse::Parsed parsed; | |
| 204 const base::string16 url_string = | |
| 205 net::FormatUrl(url, languages, net::kFormatUrlOmitAll, | |
| 206 net::UnescapeRule::SPACES, &parsed, NULL, NULL); | |
| 207 if (available_pixel_width <= 0) | |
| 208 return url_string; | |
| 209 | |
| 210 // If non-standard, return plain eliding. | |
| 211 if (!url.IsStandard()) | |
| 212 return ElideText(url_string, font_list, available_pixel_width, | |
| 213 ELIDE_AT_END); | |
| 214 | |
| 215 // Now start eliding url_string to fit within available pixel width. | |
| 216 // Fist pass - check to see whether entire url_string fits. | |
| 217 const float pixel_width_url_string = GetStringWidthF(url_string, font_list); | |
| 218 if (available_pixel_width >= pixel_width_url_string) | |
| 219 return url_string; | |
| 220 | |
| 221 // Get the path substring, including query and reference. | |
| 222 const size_t path_start_index = parsed.path.begin; | |
| 223 const size_t path_len = parsed.path.len; | |
| 224 base::string16 url_path_query_etc = url_string.substr(path_start_index); | |
| 225 base::string16 url_path = url_string.substr(path_start_index, path_len); | |
| 226 | |
| 227 // Return general elided text if url minus the query fits. | |
| 228 const base::string16 url_minus_query = | |
| 229 url_string.substr(0, path_start_index + path_len); | |
| 230 if (available_pixel_width >= GetStringWidthF(url_minus_query, font_list)) | |
| 231 return ElideText(url_string, font_list, available_pixel_width, | |
| 232 ELIDE_AT_END); | |
| 233 | |
| 234 // Get Host. | |
| 235 base::string16 url_host = UTF8ToUTF16(url.host()); | |
| 236 | |
| 237 // Get domain and registry information from the URL. | |
| 238 base::string16 url_domain = UTF8ToUTF16( | |
| 239 net::registry_controlled_domains::GetDomainAndRegistry( | |
| 240 url, net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES)); | |
| 241 if (url_domain.empty()) | |
| 242 url_domain = url_host; | |
| 243 | |
| 244 // Add port if required. | |
| 245 if (!url.port().empty()) { | |
| 246 url_host += UTF8ToUTF16(":" + url.port()); | |
| 247 url_domain += UTF8ToUTF16(":" + url.port()); | |
| 248 } | |
| 249 | |
| 250 // Get sub domain. | |
| 251 base::string16 url_subdomain; | |
| 252 const size_t domain_start_index = url_host.find(url_domain); | |
| 253 if (domain_start_index != base::string16::npos) | |
| 254 url_subdomain = url_host.substr(0, domain_start_index); | |
| 255 const base::string16 kWwwPrefix = UTF8ToUTF16("www."); | |
| 256 if ((url_subdomain == kWwwPrefix || url_subdomain.empty() || | |
| 257 url.SchemeIsFile())) { | |
| 258 url_subdomain.clear(); | |
| 259 } | |
| 260 | |
| 261 // If this is a file type, the path is now defined as everything after ":". | |
| 262 // For example, "C:/aa/aa/bb", the path is "/aa/bb/cc". Interesting, the | |
| 263 // domain is now C: - this is a nice hack for eliding to work pleasantly. | |
| 264 if (url.SchemeIsFile()) { | |
| 265 // Split the path string using ":" | |
| 266 std::vector<base::string16> file_path_split; | |
| 267 base::SplitString(url_path, ':', &file_path_split); | |
| 268 if (file_path_split.size() > 1) { // File is of type "file:///C:/.." | |
| 269 url_host.clear(); | |
| 270 url_domain.clear(); | |
| 271 url_subdomain.clear(); | |
| 272 | |
| 273 const base::string16 kColon = UTF8ToUTF16(":"); | |
| 274 url_host = url_domain = file_path_split.at(0).substr(1) + kColon; | |
| 275 url_path_query_etc = url_path = file_path_split.at(1); | |
| 276 } | |
| 277 } | |
| 278 | |
| 279 // Second Pass - remove scheme - the rest fits. | |
| 280 const float pixel_width_url_host = GetStringWidthF(url_host, font_list); | |
| 281 const float pixel_width_url_path = GetStringWidthF(url_path_query_etc, | |
| 282 font_list); | |
| 283 if (available_pixel_width >= | |
| 284 pixel_width_url_host + pixel_width_url_path) | |
| 285 return url_host + url_path_query_etc; | |
| 286 | |
| 287 // Third Pass: Subdomain, domain and entire path fits. | |
| 288 const float pixel_width_url_domain = GetStringWidthF(url_domain, font_list); | |
| 289 const float pixel_width_url_subdomain = | |
| 290 GetStringWidthF(url_subdomain, font_list); | |
| 291 if (available_pixel_width >= | |
| 292 pixel_width_url_subdomain + pixel_width_url_domain + | |
| 293 pixel_width_url_path) | |
| 294 return url_subdomain + url_domain + url_path_query_etc; | |
| 295 | |
| 296 // Query element. | |
| 297 base::string16 url_query; | |
| 298 const float kPixelWidthDotsTrailer = GetStringWidthF( | |
| 299 base::string16(kEllipsisUTF16), font_list); | |
| 300 if (parsed.query.is_nonempty()) { | |
| 301 url_query = UTF8ToUTF16("?") + url_string.substr(parsed.query.begin); | |
| 302 if (available_pixel_width >= | |
| 303 (pixel_width_url_subdomain + pixel_width_url_domain + | |
| 304 pixel_width_url_path - GetStringWidthF(url_query, font_list))) { | |
| 305 return ElideText(url_subdomain + url_domain + url_path_query_etc, | |
| 306 font_list, available_pixel_width, ELIDE_AT_END); | |
| 307 } | |
| 308 } | |
| 309 | |
| 310 // Parse url_path using '/'. | |
| 311 std::vector<base::string16> url_path_elements; | |
| 312 base::SplitString(url_path, kForwardSlash, &url_path_elements); | |
| 313 | |
| 314 // Get filename - note that for a path ending with / | |
| 315 // such as www.google.com/intl/ads/, the file name is ads/. | |
| 316 size_t url_path_number_of_elements = url_path_elements.size(); | |
| 317 DCHECK(url_path_number_of_elements != 0); | |
| 318 base::string16 url_filename; | |
| 319 if ((url_path_elements.at(url_path_number_of_elements - 1)).length() > 0) { | |
| 320 url_filename = *(url_path_elements.end() - 1); | |
| 321 } else if (url_path_number_of_elements > 1) { // Path ends with a '/'. | |
| 322 url_filename = url_path_elements.at(url_path_number_of_elements - 2) + | |
| 323 kForwardSlash; | |
| 324 url_path_number_of_elements--; | |
| 325 } | |
| 326 DCHECK(url_path_number_of_elements != 0); | |
| 327 | |
| 328 const size_t kMaxNumberOfUrlPathElementsAllowed = 1024; | |
| 329 if (url_path_number_of_elements <= 1 || | |
| 330 url_path_number_of_elements > kMaxNumberOfUrlPathElementsAllowed) { | |
| 331 // No path to elide, or too long of a path (could overflow in loop below) | |
| 332 // Just elide this as a text string. | |
| 333 return ElideText(url_subdomain + url_domain + url_path_query_etc, font_list, | |
| 334 available_pixel_width, ELIDE_AT_END); | |
| 335 } | |
| 336 | |
| 337 // Start eliding the path and replacing elements by ".../". | |
| 338 const base::string16 kEllipsisAndSlash = | |
| 339 base::string16(kEllipsisUTF16) + kForwardSlash; | |
| 340 const float pixel_width_ellipsis_slash = | |
| 341 GetStringWidthF(kEllipsisAndSlash, font_list); | |
| 342 | |
| 343 // Check with both subdomain and domain. | |
| 344 base::string16 elided_path = | |
| 345 ElideComponentizedPath(url_subdomain + url_domain, url_path_elements, | |
| 346 url_filename, url_query, font_list, | |
| 347 available_pixel_width); | |
| 348 if (!elided_path.empty()) | |
| 349 return elided_path; | |
| 350 | |
| 351 // Check with only domain. | |
| 352 // If a subdomain is present, add an ellipsis before domain. | |
| 353 // This is added only if the subdomain pixel width is larger than | |
| 354 // the pixel width of kEllipsis. Otherwise, subdomain remains, | |
| 355 // which means that this case has been resolved earlier. | |
| 356 base::string16 url_elided_domain = url_subdomain + url_domain; | |
| 357 if (pixel_width_url_subdomain > kPixelWidthDotsTrailer) { | |
| 358 if (!url_subdomain.empty()) | |
| 359 url_elided_domain = kEllipsisAndSlash[0] + url_domain; | |
| 360 else | |
| 361 url_elided_domain = url_domain; | |
| 362 | |
| 363 elided_path = ElideComponentizedPath(url_elided_domain, url_path_elements, | |
| 364 url_filename, url_query, font_list, | |
| 365 available_pixel_width); | |
| 366 | |
| 367 if (!elided_path.empty()) | |
| 368 return elided_path; | |
| 369 } | |
| 370 | |
| 371 // Return elided domain/.../filename anyway. | |
| 372 base::string16 final_elided_url_string(url_elided_domain); | |
| 373 const float url_elided_domain_width = GetStringWidthF(url_elided_domain, | |
| 374 font_list); | |
| 375 | |
| 376 // A hack to prevent trailing ".../...". | |
| 377 if ((available_pixel_width - url_elided_domain_width) > | |
| 378 pixel_width_ellipsis_slash + kPixelWidthDotsTrailer + | |
| 379 GetStringWidthF(ASCIIToUTF16("UV"), font_list)) { | |
| 380 final_elided_url_string += BuildPathFromComponents(base::string16(), | |
| 381 url_path_elements, url_filename, 1); | |
| 382 } else { | |
| 383 final_elided_url_string += url_path; | |
| 384 } | |
| 385 | |
| 386 return ElideText(final_elided_url_string, font_list, available_pixel_width, | |
| 387 ELIDE_AT_END); | |
| 388 } | |
| 389 | |
| 390 base::string16 ElideFilename(const base::FilePath& filename, | 139 base::string16 ElideFilename(const base::FilePath& filename, |
| 391 const FontList& font_list, | 140 const FontList& font_list, |
| 392 float available_pixel_width) { | 141 float available_pixel_width) { |
| 393 #if defined(OS_WIN) | 142 #if defined(OS_WIN) |
| 394 base::string16 filename_utf16 = filename.value(); | 143 base::string16 filename_utf16 = filename.value(); |
| 395 base::string16 extension = filename.Extension(); | 144 base::string16 extension = filename.Extension(); |
| 396 base::string16 rootname = filename.BaseName().RemoveExtension().value(); | 145 base::string16 rootname = filename.BaseName().RemoveExtension().value(); |
| 397 #elif defined(OS_POSIX) | 146 #elif defined(OS_POSIX) |
| 398 base::string16 filename_utf16 = WideToUTF16(base::SysNativeMBToWide( | 147 base::string16 filename_utf16 = WideToUTF16(base::SysNativeMBToWide( |
| 399 filename.value())); | 148 filename.value())); |
| (...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 491 if (hi < lo) | 240 if (hi < lo) |
| 492 lo = hi; | 241 lo = hi; |
| 493 } else { | 242 } else { |
| 494 lo = guess + 1; | 243 lo = guess + 1; |
| 495 } | 244 } |
| 496 } | 245 } |
| 497 | 246 |
| 498 return slicer.CutString(guess, insert_ellipsis); | 247 return slicer.CutString(guess, insert_ellipsis); |
| 499 } | 248 } |
| 500 | 249 |
| 501 SortedDisplayURL::SortedDisplayURL(const GURL& url, | |
| 502 const std::string& languages) { | |
| 503 net::AppendFormattedHost(url, languages, &sort_host_); | |
| 504 base::string16 host_minus_www = net::StripWWW(sort_host_); | |
| 505 url_parse::Parsed parsed; | |
| 506 display_url_ = | |
| 507 net::FormatUrl(url, languages, net::kFormatUrlOmitAll, | |
| 508 net::UnescapeRule::SPACES, &parsed, &prefix_end_, NULL); | |
| 509 if (sort_host_.length() > host_minus_www.length()) { | |
| 510 prefix_end_ += sort_host_.length() - host_minus_www.length(); | |
| 511 sort_host_.swap(host_minus_www); | |
| 512 } | |
| 513 } | |
| 514 | |
| 515 SortedDisplayURL::SortedDisplayURL() : prefix_end_(0) { | |
| 516 } | |
| 517 | |
| 518 SortedDisplayURL::~SortedDisplayURL() { | |
| 519 } | |
| 520 | |
| 521 int SortedDisplayURL::Compare(const SortedDisplayURL& other, | |
| 522 icu::Collator* collator) const { | |
| 523 // Compare on hosts first. The host won't contain 'www.'. | |
| 524 UErrorCode compare_status = U_ZERO_ERROR; | |
| 525 UCollationResult host_compare_result = collator->compare( | |
| 526 static_cast<const UChar*>(sort_host_.c_str()), | |
| 527 static_cast<int>(sort_host_.length()), | |
| 528 static_cast<const UChar*>(other.sort_host_.c_str()), | |
| 529 static_cast<int>(other.sort_host_.length()), | |
| 530 compare_status); | |
| 531 DCHECK(U_SUCCESS(compare_status)); | |
| 532 if (host_compare_result != 0) | |
| 533 return host_compare_result; | |
| 534 | |
| 535 // Hosts match, compare on the portion of the url after the host. | |
| 536 base::string16 path = this->AfterHost(); | |
| 537 base::string16 o_path = other.AfterHost(); | |
| 538 compare_status = U_ZERO_ERROR; | |
| 539 UCollationResult path_compare_result = collator->compare( | |
| 540 static_cast<const UChar*>(path.c_str()), | |
| 541 static_cast<int>(path.length()), | |
| 542 static_cast<const UChar*>(o_path.c_str()), | |
| 543 static_cast<int>(o_path.length()), | |
| 544 compare_status); | |
| 545 DCHECK(U_SUCCESS(compare_status)); | |
| 546 if (path_compare_result != 0) | |
| 547 return path_compare_result; | |
| 548 | |
| 549 // Hosts and paths match, compare on the complete url. This'll push the www. | |
| 550 // ones to the end. | |
| 551 compare_status = U_ZERO_ERROR; | |
| 552 UCollationResult display_url_compare_result = collator->compare( | |
| 553 static_cast<const UChar*>(display_url_.c_str()), | |
| 554 static_cast<int>(display_url_.length()), | |
| 555 static_cast<const UChar*>(other.display_url_.c_str()), | |
| 556 static_cast<int>(other.display_url_.length()), | |
| 557 compare_status); | |
| 558 DCHECK(U_SUCCESS(compare_status)); | |
| 559 return display_url_compare_result; | |
| 560 } | |
| 561 | |
| 562 base::string16 SortedDisplayURL::AfterHost() const { | |
| 563 const size_t slash_index = display_url_.find(sort_host_, prefix_end_); | |
| 564 if (slash_index == base::string16::npos) { | |
| 565 NOTREACHED(); | |
| 566 return base::string16(); | |
| 567 } | |
| 568 return display_url_.substr(slash_index + sort_host_.length()); | |
| 569 } | |
| 570 | |
| 571 bool ElideString(const base::string16& input, int max_len, | 250 bool ElideString(const base::string16& input, int max_len, |
| 572 base::string16* output) { | 251 base::string16* output) { |
| 573 DCHECK_GE(max_len, 0); | 252 DCHECK_GE(max_len, 0); |
| 574 if (static_cast<int>(input.length()) <= max_len) { | 253 if (static_cast<int>(input.length()) <= max_len) { |
| 575 output->assign(input); | 254 output->assign(input); |
| 576 return false; | 255 return false; |
| 577 } | 256 } |
| 578 | 257 |
| 579 switch (max_len) { | 258 switch (max_len) { |
| 580 case 0: | 259 case 0: |
| (...skipping 536 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1117 index = char_iterator.getIndex(); | 796 index = char_iterator.getIndex(); |
| 1118 } else { | 797 } else { |
| 1119 // String has leading whitespace, return the elide string. | 798 // String has leading whitespace, return the elide string. |
| 1120 return kElideString; | 799 return kElideString; |
| 1121 } | 800 } |
| 1122 } | 801 } |
| 1123 return string.substr(0, index) + kElideString; | 802 return string.substr(0, index) + kElideString; |
| 1124 } | 803 } |
| 1125 | 804 |
| 1126 } // namespace gfx | 805 } // namespace gfx |
| OLD | NEW |