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

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

Issue 119813002: Implement eliding/truncating at end in RenderText (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fixed merge issue Created 6 years, 11 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') | no next file » | 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"
(...skipping 25 matching lines...) Expand all
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
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
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
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
OLDNEW
« no previous file with comments | « ui/gfx/text_elider.h ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698