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

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

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

Powered by Google App Engine
This is Rietveld 408576698