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

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

Issue 119813002: Implement eliding/truncating at end in RenderText (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Addressed comments from msw #1 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
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 #include "ui/gfx/render_text.h" 5 #include "ui/gfx/render_text.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <climits> 8 #include <climits>
9 9
10 #include "base/i18n/break_iterator.h" 10 #include "base/i18n/break_iterator.h"
11 #include "base/logging.h" 11 #include "base/logging.h"
12 #include "base/stl_util.h" 12 #include "base/stl_util.h"
13 #include "base/strings/utf_string_conversions.h"
13 #include "third_party/icu/source/common/unicode/rbbi.h" 14 #include "third_party/icu/source/common/unicode/rbbi.h"
14 #include "third_party/icu/source/common/unicode/utf16.h" 15 #include "third_party/icu/source/common/unicode/utf16.h"
15 #include "third_party/skia/include/core/SkTypeface.h" 16 #include "third_party/skia/include/core/SkTypeface.h"
16 #include "third_party/skia/include/effects/SkGradientShader.h" 17 #include "third_party/skia/include/effects/SkGradientShader.h"
17 #include "ui/gfx/canvas.h" 18 #include "ui/gfx/canvas.h"
18 #include "ui/gfx/insets.h" 19 #include "ui/gfx/insets.h"
19 #include "ui/gfx/skia_util.h" 20 #include "ui/gfx/skia_util.h"
20 #include "ui/gfx/text_constants.h" 21 #include "ui/gfx/text_constants.h"
21 #include "ui/gfx/text_elider.h" 22 #include "ui/gfx/text_elider.h"
23 #include "ui/gfx/text_utils.h"
22 #include "ui/gfx/utf16_indexing.h" 24 #include "ui/gfx/utf16_indexing.h"
23 25
24 namespace gfx { 26 namespace gfx {
25 27
26 namespace { 28 namespace {
27 29
28 // All chars are replaced by this char when the password style is set. 30 // All chars are replaced by this char when the password style is set.
29 // TODO(benrg): GTK uses the first of U+25CF, U+2022, U+2731, U+273A, '*' 31 // TODO(benrg): GTK uses the first of U+25CF, U+2022, U+2731, U+273A, '*'
30 // that's available in the font (find_invisible_char() in gtkentry.c). 32 // that's available in the font (find_invisible_char() in gtkentry.c).
31 const base::char16 kPasswordReplacementChar = '*'; 33 const base::char16 kPasswordReplacementChar = '*';
(...skipping 331 matching lines...) Expand 10 before | Expand all | Expand 10 after
363 // Reset selection model. SetText should always followed by SetSelectionModel 365 // Reset selection model. SetText should always followed by SetSelectionModel
364 // or SetCursorPosition in upper layer. 366 // or SetCursorPosition in upper layer.
365 SetSelectionModel(SelectionModel()); 367 SetSelectionModel(SelectionModel());
366 368
367 // Invalidate the cached text direction if it depends on the text contents. 369 // Invalidate the cached text direction if it depends on the text contents.
368 if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT) 370 if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT)
369 text_direction_ = base::i18n::UNKNOWN_DIRECTION; 371 text_direction_ = base::i18n::UNKNOWN_DIRECTION;
370 372
371 obscured_reveal_index_ = -1; 373 obscured_reveal_index_ = -1;
372 UpdateLayoutText(); 374 UpdateLayoutText();
373 ResetLayout();
374 } 375 }
375 376
376 void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) { 377 void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) {
377 if (horizontal_alignment_ != alignment) { 378 if (horizontal_alignment_ != alignment) {
378 horizontal_alignment_ = alignment; 379 horizontal_alignment_ = alignment;
379 display_offset_ = Vector2d(); 380 display_offset_ = Vector2d();
380 cached_bounds_and_offset_valid_ = false; 381 cached_bounds_and_offset_valid_ = false;
381 } 382 }
382 } 383 }
383 384
(...skipping 25 matching lines...) Expand all
409 insert_mode_ = !insert_mode_; 410 insert_mode_ = !insert_mode_;
410 cached_bounds_and_offset_valid_ = false; 411 cached_bounds_and_offset_valid_ = false;
411 } 412 }
412 413
413 void RenderText::SetObscured(bool obscured) { 414 void RenderText::SetObscured(bool obscured) {
414 if (obscured != obscured_) { 415 if (obscured != obscured_) {
415 obscured_ = obscured; 416 obscured_ = obscured;
416 obscured_reveal_index_ = -1; 417 obscured_reveal_index_ = -1;
417 cached_bounds_and_offset_valid_ = false; 418 cached_bounds_and_offset_valid_ = false;
418 UpdateLayoutText(); 419 UpdateLayoutText();
419 ResetLayout();
420 } 420 }
421 } 421 }
422 422
423 void RenderText::SetObscuredRevealIndex(int index) { 423 void RenderText::SetObscuredRevealIndex(int index) {
424 if (obscured_reveal_index_ == index) 424 if (obscured_reveal_index_ == index)
425 return; 425 return;
426 426
427 obscured_reveal_index_ = index; 427 obscured_reveal_index_ = index;
428 cached_bounds_and_offset_valid_ = false; 428 cached_bounds_and_offset_valid_ = false;
429 UpdateLayoutText(); 429 UpdateLayoutText();
430 ResetLayout();
431 } 430 }
432 431
433 void RenderText::SetMultiline(bool multiline) { 432 void RenderText::SetMultiline(bool multiline) {
434 if (multiline != multiline_) { 433 if (multiline != multiline_) {
435 multiline_ = multiline; 434 multiline_ = multiline;
436 cached_bounds_and_offset_valid_ = false; 435 cached_bounds_and_offset_valid_ = false;
437 lines_.clear(); 436 lines_.clear();
438 } 437 }
439 } 438 }
440 439
440 void RenderText::SetElideBehavior(ElideBehavior elide_behavior) {
441 // TODO(skanuj) : Add a test for triggering layout change.
442 if (elide_behavior_ != elide_behavior) {
443 elide_behavior_ = elide_behavior;
444 UpdateLayoutText();
445 }
446 }
447
441 void RenderText::SetDisplayRect(const Rect& r) { 448 void RenderText::SetDisplayRect(const Rect& r) {
442 display_rect_ = r; 449 if (r != display_rect_) {
443 baseline_ = kInvalidBaseline; 450 display_rect_ = r;
444 cached_bounds_and_offset_valid_ = false; 451 baseline_ = kInvalidBaseline;
445 lines_.clear(); 452 cached_bounds_and_offset_valid_ = false;
453 lines_.clear();
454 if (elide_behavior_ != gfx::NO_ELIDE)
455 UpdateLayoutText();
456 }
446 } 457 }
447 458
448 void RenderText::SetCursorPosition(size_t position) { 459 void RenderText::SetCursorPosition(size_t position) {
449 MoveCursorTo(position, false); 460 MoveCursorTo(position, false);
450 } 461 }
451 462
452 void RenderText::MoveCursor(BreakType break_type, 463 void RenderText::MoveCursor(BreakType break_type,
453 VisualCursorDirection direction, 464 VisualCursorDirection direction,
454 bool select) { 465 bool select) {
455 SelectionModel position(cursor_position(), selection_model_.caret_affinity()); 466 SelectionModel position(cursor_position(), selection_model_.caret_affinity());
(...skipping 372 matching lines...) Expand 10 before | Expand all | Expand 10 after
828 selection_color_(kDefaultColor), 839 selection_color_(kDefaultColor),
829 selection_background_focused_color_(kDefaultSelectionBackgroundColor), 840 selection_background_focused_color_(kDefaultSelectionBackgroundColor),
830 focused_(false), 841 focused_(false),
831 composition_range_(Range::InvalidRange()), 842 composition_range_(Range::InvalidRange()),
832 colors_(kDefaultColor), 843 colors_(kDefaultColor),
833 styles_(NUM_TEXT_STYLES), 844 styles_(NUM_TEXT_STYLES),
834 composition_and_selection_styles_applied_(false), 845 composition_and_selection_styles_applied_(false),
835 obscured_(false), 846 obscured_(false),
836 obscured_reveal_index_(-1), 847 obscured_reveal_index_(-1),
837 truncate_length_(0), 848 truncate_length_(0),
849 elide_behavior_(NO_ELIDE),
838 multiline_(false), 850 multiline_(false),
839 fade_head_(false), 851 fade_head_(false),
840 fade_tail_(false), 852 fade_tail_(false),
841 background_is_transparent_(false), 853 background_is_transparent_(false),
842 clip_to_display_rect_(true), 854 clip_to_display_rect_(true),
843 baseline_(kInvalidBaseline), 855 baseline_(kInvalidBaseline),
844 cached_bounds_and_offset_valid_(false) { 856 cached_bounds_and_offset_valid_(false) {
845 } 857 }
846 858
847 const Vector2d& RenderText::GetUpdatedDisplayOffset() { 859 const Vector2d& RenderText::GetUpdatedDisplayOffset() {
(...skipping 259 matching lines...) Expand 10 before | Expand all | Expand 10 after
1107 U16_NEXT(text_.data(), end, text_.length(), unused_char); 1119 U16_NEXT(text_.data(), end, text_.length(), unused_char);
1108 1120
1109 // Gets the index in |layout_text_| to be replaced. 1121 // Gets the index in |layout_text_| to be replaced.
1110 const size_t cp_start = 1122 const size_t cp_start =
1111 static_cast<size_t>(gfx::UTF16IndexToOffset(text_, 0, start)); 1123 static_cast<size_t>(gfx::UTF16IndexToOffset(text_, 0, start));
1112 if (layout_text_.length() > cp_start) 1124 if (layout_text_.length() > cp_start)
1113 layout_text_.replace(cp_start, 1, text_.substr(start, end - start)); 1125 layout_text_.replace(cp_start, 1, text_.substr(start, end - start));
1114 } 1126 }
1115 } 1127 }
1116 1128
1117 const base::string16& text = obscured_ ? layout_text_ : text_; 1129 const base::string16& text = GetLayoutText();
1118 if (truncate_length_ > 0 && truncate_length_ < text.length()) { 1130 if (truncate_length_ > 0 && truncate_length_ < text.length()) {
1119 // Truncate the text at a valid character break and append an ellipsis. 1131 // Truncate the text at a valid character break and append an ellipsis.
1120 icu::StringCharacterIterator iter(text.c_str()); 1132 icu::StringCharacterIterator iter(text.c_str());
1121 iter.setIndex32(truncate_length_ - 1); 1133 iter.setIndex32(truncate_length_ - 1);
1122 layout_text_.assign(text.substr(0, iter.getIndex()) + gfx::kEllipsisUTF16); 1134 layout_text_.assign(text.substr(0, iter.getIndex()) + gfx::kEllipsisUTF16);
1123 } 1135 }
1136
1137 if (elide_behavior_ != NO_ELIDE && display_rect_.width() > 0 &&
1138 GetContentWidth() > display_rect_.width()) {
1139 base::string16 elided_text = ElideText(GetLayoutText());
1140
1141 // This doesn't trim styles so ellipsis may get rendered as a different
1142 // style than the preceding text. See crbug.com/327850.
1143 layout_text_.assign(elided_text);
1144 }
1145 ResetLayout();
1146 }
1147
1148 // TODO(skanuj): Fix code duplication with ElideText in ui/gfx/text_elider.cc
1149 // See crbug.com/327846
1150 base::string16 RenderText::ElideText(const base::string16& text) {
1151 if (text.empty())
1152 return text;
1153
1154 const bool insert_ellipsis = (elide_behavior_ != TRUNCATE_AT_END);
1155 // Create a RenderText copy with attributes that affect the rendering width.
1156 scoped_ptr<RenderText> render_text(CreateInstance());
1157 render_text->SetFontList(font_list_);
1158 render_text->SetDirectionalityMode(directionality_mode_);
1159 render_text->SetCursorEnabled(cursor_enabled_);
1160
1161 render_text->styles_ = styles_;
1162 render_text->colors_ = colors_;
1163 render_text->SetText(text);
1164 const int current_text_pixel_width = render_text->GetContentWidth();
1165
1166 const base::string16 ellipsis = base::string16(gfx::kEllipsisUTF16);
1167 const bool elide_in_middle = false;
1168 StringSlicer slicer(text, ellipsis, elide_in_middle);
1169
1170 // Pango will return 0 width for absurdly long strings. Cut the string in
1171 // half and try again.
1172 // This is caused by an int overflow in Pango (specifically, in
1173 // pango_glyph_string_extents_range). It's actually more subtle than just
1174 // returning 0, since on super absurdly long strings, the int can wrap and
1175 // return positive numbers again. Detecting that is probably not worth it
1176 // (eliding way too much from a ridiculous string is probably still
1177 // ridiculous), but we should check other widths for bogus values as well.
1178 if (current_text_pixel_width <= 0 && !text.empty())
1179 return ElideText(slicer.CutString(text.length() / 2, insert_ellipsis));
1180
1181 if (current_text_pixel_width <= display_rect_.width())
1182 return text;
1183
1184 render_text->SetText(base::string16());
1185 render_text->SetText(ellipsis);
1186 const int ellipsis_width = render_text->GetContentWidth();
1187
1188 if (insert_ellipsis && (ellipsis_width >= display_rect_.width()))
1189 return base::string16();
1190
1191 // Use binary search to compute the elided text.
1192 size_t lo = 0;
1193 size_t hi = text.length() - 1;
1194 for (size_t guess = (lo + hi) / 2; (lo <= hi); guess = (lo + hi) / 2) {
msw 2013/12/30 23:18:00 nit: remove the parens around lo <= hi
Anuj 2013/12/31 00:04:03 Done.
1195 // This won't happen because of the check against ellipsis width.
1196 if (guess < 0)
msw 2013/12/30 23:18:00 Guess will *never* be <0, it's unsigned... remove
Anuj 2013/12/31 00:04:03 Done.
1197 return base::string16();
1198
1199 if (guess >= text.length())
msw 2013/12/30 23:18:00 Perhaps my previous question was unclear. Under wh
Anuj 2013/12/31 00:04:03 Done.
1200 return text;
1201
1202 // Restore styles and colors. They will be truncated to size by SetText.
1203 render_text->styles_ = styles_;
1204 render_text->colors_ = colors_;
1205 base::string16 new_text = slicer.CutString(guess, false);
1206 render_text->SetText(new_text);
1207
1208 // This has to be an additional step so that the ellipsis is rendered with
1209 // same style as trailing part of the text.
1210 if (insert_ellipsis) {
1211 // When ellipsis follows text whose directionality is not the same as that
1212 // of the whole text, it will be rendered with the directionality of the
1213 // whole text. Since we want ellipsis to indicate continuation of the
1214 // preceding text, we force the directionality of ellipsis to be same as
1215 // the preceding text using LTR or RTL markers.
1216 base::i18n::TextDirection leading_text_direction =
1217 base::i18n::GetFirstStrongCharacterDirection(new_text);
1218 base::i18n::TextDirection trailing_text_direction =
1219 base::i18n::GetLastStrongCharacterDirection(new_text);
1220 new_text.append(ellipsis);
1221 if (trailing_text_direction != leading_text_direction) {
1222 if (trailing_text_direction == base::i18n::LEFT_TO_RIGHT)
1223 new_text += base::i18n::kLeftToRightMark;
1224 else
1225 new_text += base::i18n::kRightToLeftMark;
1226 }
1227 render_text->SetText(new_text);
1228 }
1229
1230 // We check the width of the whole desired string at once to ensure we
1231 // handle kerning/ligatures/etc. correctly.
1232 const int guess_width = render_text->GetContentWidth();
1233 if (guess_width == display_rect_.width())
1234 break;
1235 if (guess_width > display_rect_.width()) {
1236 hi = guess - 1;
1237 // Move back if we are on loop terminating condition, and guess is wider
1238 // than available.
1239 if (hi < lo)
1240 lo = hi;
1241 } else {
1242 lo = guess + 1;
1243 }
1244 }
1245
1246 return render_text->text();
1124 } 1247 }
1125 1248
1126 void RenderText::UpdateCachedBoundsAndOffset() { 1249 void RenderText::UpdateCachedBoundsAndOffset() {
1127 if (cached_bounds_and_offset_valid_) 1250 if (cached_bounds_and_offset_valid_)
1128 return; 1251 return;
1129 1252
1130 // TODO(ckocagil): Add support for scrolling multiline text. 1253 // TODO(ckocagil): Add support for scrolling multiline text.
1131 1254
1132 // First, set the valid flag true to calculate the current cursor bounds using 1255 // First, set the valid flag true to calculate the current cursor bounds using
1133 // the stale |display_offset_|. Applying |delta_offset| at the end of this 1256 // the stale |display_offset_|. Applying |delta_offset| at the end of this
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
1172 cursor_bounds_ += delta_offset; 1295 cursor_bounds_ += delta_offset;
1173 } 1296 }
1174 1297
1175 void RenderText::DrawSelection(Canvas* canvas) { 1298 void RenderText::DrawSelection(Canvas* canvas) {
1176 const std::vector<Rect> sel = GetSubstringBounds(selection()); 1299 const std::vector<Rect> sel = GetSubstringBounds(selection());
1177 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i) 1300 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
1178 canvas->FillRect(*i, selection_background_focused_color_); 1301 canvas->FillRect(*i, selection_background_focused_color_);
1179 } 1302 }
1180 1303
1181 } // namespace gfx 1304 } // namespace gfx
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698