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

Side by Side Diff: ui/base/text/text_elider.cc

Issue 17745005: Clamp RenderTextWin layout length to 10,000 code points. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Resolve string formatting; you just can't please some compilers. Created 7 years, 5 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
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/base/text/text_elider.h" 10 #include "ui/base/text/text_elider.h"
(...skipping 15 matching lines...) Expand all
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;
Alexei Svitkine (slow) 2013/06/28 15:37:02 Consider making it: const char16 kEllipsisUTF16[]
msw 2013/06/28 16:31:23 Done.
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
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;
Alexei Svitkine (slow) 2013/06/28 15:37:02 You can use kEllipisisUTF16 if you change it to a
msw 2013/06/28 16:31:23 I'll pass; I hit test failures trying to change mo
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
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(1, 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
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
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(1, 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
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
OLDNEW
« no previous file with comments | « ui/base/text/text_elider.h ('k') | ui/gfx/render_text.h » ('j') | ui/gfx/render_text_win.cc » ('J')

Powered by Google App Engine
This is Rietveld 408576698