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

Side by Side Diff: ui/gfx/text_elider.cc

Issue 143463006: Remove net dependency from ui/gfx (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: add gurl include to elide_url_unittest.cc Created 6 years, 10 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
« no previous file with comments | « ui/gfx/text_elider.h ('k') | ui/gfx/text_elider_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 "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
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
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
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
OLDNEW
« no previous file with comments | « ui/gfx/text_elider.h ('k') | ui/gfx/text_elider_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698