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" |
(...skipping 25 matching lines...) Expand all Loading... |
36 | 36 |
37 namespace gfx { | 37 namespace gfx { |
38 | 38 |
39 // U+2026 in utf8 | 39 // U+2026 in utf8 |
40 const char kEllipsis[] = "\xE2\x80\xA6"; | 40 const char kEllipsis[] = "\xE2\x80\xA6"; |
41 const base::char16 kEllipsisUTF16[] = { 0x2026, 0 }; | 41 const base::char16 kEllipsisUTF16[] = { 0x2026, 0 }; |
42 const base::char16 kForwardSlash = '/'; | 42 const base::char16 kForwardSlash = '/'; |
43 | 43 |
44 namespace { | 44 namespace { |
45 | 45 |
46 // Helper class to split + elide text, while respecting UTF16 surrogate pairs. | |
47 class StringSlicer { | |
48 public: | |
49 StringSlicer(const base::string16& text, | |
50 const base::string16& ellipsis, | |
51 bool elide_in_middle) | |
52 : text_(text), | |
53 ellipsis_(ellipsis), | |
54 elide_in_middle_(elide_in_middle) { | |
55 } | |
56 | |
57 // Cuts |text_| to be |length| characters long. If |elide_in_middle_| is true, | |
58 // the middle of the string is removed to leave equal-length pieces from the | |
59 // beginning and end of the string; otherwise, the end of the string is | |
60 // removed and only the beginning remains. If |insert_ellipsis| is true, | |
61 // then an ellipsis character will be inserted at the cut point. | |
62 base::string16 CutString(size_t length, bool insert_ellipsis) { | |
63 const base::string16 ellipsis_text = insert_ellipsis ? ellipsis_ | |
64 : base::string16(); | |
65 | |
66 if (!elide_in_middle_) | |
67 return text_.substr(0, FindValidBoundaryBefore(length)) + ellipsis_text; | |
68 | |
69 // We put the extra character, if any, before the cut. | |
70 const size_t half_length = length / 2; | |
71 const size_t prefix_length = FindValidBoundaryBefore(length - half_length); | |
72 const size_t suffix_start_guess = text_.length() - half_length; | |
73 const size_t suffix_start = FindValidBoundaryAfter(suffix_start_guess); | |
74 const size_t suffix_length = | |
75 half_length - (suffix_start_guess - suffix_start); | |
76 return text_.substr(0, prefix_length) + ellipsis_text + | |
77 text_.substr(suffix_start, suffix_length); | |
78 } | |
79 | |
80 private: | |
81 // Returns a valid cut boundary at or before |index|. | |
82 size_t FindValidBoundaryBefore(size_t index) const { | |
83 DCHECK_LE(index, text_.length()); | |
84 if (index != text_.length()) | |
85 U16_SET_CP_START(text_.data(), 0, index); | |
86 return index; | |
87 } | |
88 | |
89 // Returns a valid cut boundary at or after |index|. | |
90 size_t FindValidBoundaryAfter(size_t index) const { | |
91 DCHECK_LE(index, text_.length()); | |
92 if (index != text_.length()) | |
93 U16_SET_CP_LIMIT(text_.data(), 0, index, text_.length()); | |
94 return index; | |
95 } | |
96 | |
97 // The text to be sliced. | |
98 const base::string16& text_; | |
99 | |
100 // Ellipsis string to use. | |
101 const base::string16& ellipsis_; | |
102 | |
103 // If true, the middle of the string will be elided. | |
104 bool elide_in_middle_; | |
105 | |
106 DISALLOW_COPY_AND_ASSIGN(StringSlicer); | |
107 }; | |
108 | 46 |
109 // Build a path from the first |num_components| elements in |path_elements|. | 47 // Build a path from the first |num_components| elements in |path_elements|. |
110 // Prepends |path_prefix|, appends |filename|, inserts ellipsis if appropriate. | 48 // Prepends |path_prefix|, appends |filename|, inserts ellipsis if appropriate. |
111 base::string16 BuildPathFromComponents( | 49 base::string16 BuildPathFromComponents( |
112 const base::string16& path_prefix, | 50 const base::string16& path_prefix, |
113 const std::vector<base::string16>& path_elements, | 51 const std::vector<base::string16>& path_elements, |
114 const base::string16& filename, | 52 const base::string16& filename, |
115 size_t num_components) { | 53 size_t num_components) { |
116 // Add the initial elements of the path. | 54 // Add the initial elements of the path. |
117 base::string16 path = path_prefix; | 55 base::string16 path = path_prefix; |
(...skipping 29 matching lines...) Expand all Loading... |
147 if (available_pixel_width >= GetStringWidthF(elided_path, font_list)) | 85 if (available_pixel_width >= GetStringWidthF(elided_path, font_list)) |
148 return ElideText(elided_path + url_query, font_list, | 86 return ElideText(elided_path + url_query, font_list, |
149 available_pixel_width, ELIDE_AT_END); | 87 available_pixel_width, ELIDE_AT_END); |
150 } | 88 } |
151 | 89 |
152 return base::string16(); | 90 return base::string16(); |
153 } | 91 } |
154 | 92 |
155 } // namespace | 93 } // namespace |
156 | 94 |
| 95 StringSlicer::StringSlicer(const base::string16& text, |
| 96 const base::string16& ellipsis, |
| 97 bool elide_in_middle) |
| 98 : text_(text), |
| 99 ellipsis_(ellipsis), |
| 100 elide_in_middle_(elide_in_middle) { |
| 101 } |
| 102 |
| 103 base::string16 StringSlicer::CutString(size_t length, bool insert_ellipsis) { |
| 104 const base::string16 ellipsis_text = insert_ellipsis ? ellipsis_ |
| 105 : base::string16(); |
| 106 |
| 107 if (!elide_in_middle_) |
| 108 return text_.substr(0, FindValidBoundaryBefore(length)) + ellipsis_text; |
| 109 |
| 110 // We put the extra character, if any, before the cut. |
| 111 const size_t half_length = length / 2; |
| 112 const size_t prefix_length = FindValidBoundaryBefore(length - half_length); |
| 113 const size_t suffix_start_guess = text_.length() - half_length; |
| 114 const size_t suffix_start = FindValidBoundaryAfter(suffix_start_guess); |
| 115 const size_t suffix_length = |
| 116 half_length - (suffix_start_guess - suffix_start); |
| 117 return text_.substr(0, prefix_length) + ellipsis_text + |
| 118 text_.substr(suffix_start, suffix_length); |
| 119 } |
| 120 |
| 121 size_t StringSlicer::FindValidBoundaryBefore(size_t index) const { |
| 122 DCHECK_LE(index, text_.length()); |
| 123 if (index != text_.length()) |
| 124 U16_SET_CP_START(text_.data(), 0, index); |
| 125 return index; |
| 126 } |
| 127 |
| 128 size_t StringSlicer::FindValidBoundaryAfter(size_t index) const { |
| 129 DCHECK_LE(index, text_.length()); |
| 130 if (index != text_.length()) |
| 131 U16_SET_CP_LIMIT(text_.data(), 0, index, text_.length()); |
| 132 return index; |
| 133 } |
| 134 |
157 base::string16 ElideEmail(const base::string16& email, | 135 base::string16 ElideEmail(const base::string16& email, |
158 const FontList& font_list, | 136 const FontList& font_list, |
159 float available_pixel_width) { | 137 float available_pixel_width) { |
160 if (GetStringWidthF(email, font_list) <= available_pixel_width) | 138 if (GetStringWidthF(email, font_list) <= available_pixel_width) |
161 return email; | 139 return email; |
162 | 140 |
163 // Split the email into its local-part (username) and domain-part. The email | 141 // Split the email into its local-part (username) and domain-part. The email |
164 // spec technically allows for @ symbols in the local-part (username) of the | 142 // spec technically allows for @ symbols in the local-part (username) of the |
165 // email under some special requirements. It is guaranteed that there is no @ | 143 // email under some special requirements. It is guaranteed that there is no @ |
166 // symbol in the domain part of the email however so splitting at the last @ | 144 // symbol in the domain part of the email however so splitting at the last @ |
(...skipping 307 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
474 | 452 |
475 // Pango will return 0 width for absurdly long strings. Cut the string in | 453 // Pango will return 0 width for absurdly long strings. Cut the string in |
476 // half and try again. | 454 // half and try again. |
477 // This is caused by an int overflow in Pango (specifically, in | 455 // This is caused by an int overflow in Pango (specifically, in |
478 // pango_glyph_string_extents_range). It's actually more subtle than just | 456 // pango_glyph_string_extents_range). It's actually more subtle than just |
479 // returning 0, since on super absurdly long strings, the int can wrap and | 457 // returning 0, since on super absurdly long strings, the int can wrap and |
480 // return positive numbers again. Detecting that is probably not worth it | 458 // return positive numbers again. Detecting that is probably not worth it |
481 // (eliding way too much from a ridiculous string is probably still | 459 // (eliding way too much from a ridiculous string is probably still |
482 // ridiculous), but we should check other widths for bogus values as well. | 460 // ridiculous), but we should check other widths for bogus values as well. |
483 if (current_text_pixel_width <= 0 && !text.empty()) { | 461 if (current_text_pixel_width <= 0 && !text.empty()) { |
484 const base::string16 cut = slicer.CutString(text.length() / 2, false); | 462 const base::string16 cut = |
| 463 slicer.CutString(text.length() / 2, insert_ellipsis); |
485 return ElideText(cut, font_list, available_pixel_width, elide_behavior); | 464 return ElideText(cut, font_list, available_pixel_width, elide_behavior); |
486 } | 465 } |
487 | 466 |
488 if (current_text_pixel_width <= available_pixel_width) | 467 if (current_text_pixel_width <= available_pixel_width) |
489 return text; | 468 return text; |
490 | 469 |
491 if (insert_ellipsis && | 470 if (insert_ellipsis && |
492 GetStringWidthF(ellipsis, font_list) > available_pixel_width) | 471 GetStringWidthF(ellipsis, font_list) > available_pixel_width) |
493 return base::string16(); | 472 return base::string16(); |
494 | 473 |
495 // Use binary search to compute the elided text. | 474 // Use binary search to compute the elided text. |
496 size_t lo = 0; | 475 size_t lo = 0; |
497 size_t hi = text.length() - 1; | 476 size_t hi = text.length() - 1; |
498 size_t guess; | 477 size_t guess; |
499 for (guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) { | 478 for (guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) { |
500 // We check the length of the whole desired string at once to ensure we | 479 // We check the width of the whole desired string at once to ensure we |
501 // handle kerning/ligatures/etc. correctly. | 480 // handle kerning/ligatures/etc. correctly. |
| 481 // TODO(skanuj) : Handle directionality of ellipsis based on adjacent |
| 482 // characters. See crbug.com/327963. |
502 const base::string16 cut = slicer.CutString(guess, insert_ellipsis); | 483 const base::string16 cut = slicer.CutString(guess, insert_ellipsis); |
503 const float guess_length = GetStringWidthF(cut, font_list); | 484 const float guess_width = GetStringWidthF(cut, font_list); |
504 // Check again that we didn't hit a Pango width overflow. If so, cut the | 485 if (guess_width == available_pixel_width) |
505 // current string in half and start over. | 486 break; |
506 if (guess_length <= 0) { | 487 if (guess_width > available_pixel_width) { |
507 return ElideText(slicer.CutString(guess / 2, false), | 488 hi = guess - 1; |
508 font_list, available_pixel_width, elide_behavior); | 489 // Move back if we are on loop terminating condition, and guess is wider |
| 490 // than available. |
| 491 if (hi < lo) |
| 492 lo = hi; |
| 493 } else { |
| 494 lo = guess + 1; |
509 } | 495 } |
510 if (guess_length > available_pixel_width) | |
511 hi = guess - 1; | |
512 else | |
513 lo = guess + 1; | |
514 } | 496 } |
515 | 497 |
516 return slicer.CutString(guess, insert_ellipsis); | 498 return slicer.CutString(guess, insert_ellipsis); |
517 } | 499 } |
518 | 500 |
519 SortedDisplayURL::SortedDisplayURL(const GURL& url, | 501 SortedDisplayURL::SortedDisplayURL(const GURL& url, |
520 const std::string& languages) { | 502 const std::string& languages) { |
521 net::AppendFormattedHost(url, languages, &sort_host_); | 503 net::AppendFormattedHost(url, languages, &sort_host_); |
522 base::string16 host_minus_www = net::StripWWW(sort_host_); | 504 base::string16 host_minus_www = net::StripWWW(sort_host_); |
523 url_parse::Parsed parsed; | 505 url_parse::Parsed parsed; |
(...skipping 611 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1135 index = char_iterator.getIndex(); | 1117 index = char_iterator.getIndex(); |
1136 } else { | 1118 } else { |
1137 // String has leading whitespace, return the elide string. | 1119 // String has leading whitespace, return the elide string. |
1138 return kElideString; | 1120 return kElideString; |
1139 } | 1121 } |
1140 } | 1122 } |
1141 return string.substr(0, index) + kElideString; | 1123 return string.substr(0, index) + kElideString; |
1142 } | 1124 } |
1143 | 1125 |
1144 } // namespace gfx | 1126 } // namespace gfx |
OLD | NEW |