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

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 #2 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/render_text.h ('k') | ui/gfx/render_text_unittest.cc » ('j') | 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 #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 base::string16 text = GetLayoutText();
msw 2013/12/31 00:24:52 This won't make a string copy will it? Consider ju
Anuj 2013/12/31 00:42:03 Done.
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 text = GetLayoutText();
1138 if (elide_behavior_ != NO_ELIDE && display_rect_.width() > 0 &&
1139 !text.empty() && GetContentWidth() > display_rect_.width()) {
1140 base::string16 elided_text = ElideText(text);
1141
1142 // This doesn't trim styles so ellipsis may get rendered as a different
1143 // style than the preceding text. See crbug.com/327850.
1144 layout_text_.assign(elided_text);
1145 }
1146 ResetLayout();
1147 }
1148
1149 // TODO(skanuj): Fix code duplication with ElideText in ui/gfx/text_elider.cc
1150 // See crbug.com/327846
1151 base::string16 RenderText::ElideText(const base::string16& text) {
1152 const bool insert_ellipsis = (elide_behavior_ != TRUNCATE_AT_END);
1153 // Create a RenderText copy with attributes that affect the rendering width.
1154 scoped_ptr<RenderText> render_text(CreateInstance());
1155 render_text->SetFontList(font_list_);
1156 render_text->SetDirectionalityMode(directionality_mode_);
1157 render_text->SetCursorEnabled(cursor_enabled_);
1158
1159 render_text->styles_ = styles_;
1160 render_text->colors_ = colors_;
1161 render_text->SetText(text);
1162 const int current_text_pixel_width = render_text->GetContentWidth();
1163
1164 const base::string16 ellipsis = base::string16(gfx::kEllipsisUTF16);
1165 const bool elide_in_middle = false;
1166 StringSlicer slicer(text, ellipsis, elide_in_middle);
1167
1168 // Pango will return 0 width for absurdly long strings. Cut the string in
1169 // half and try again.
1170 // This is caused by an int overflow in Pango (specifically, in
1171 // pango_glyph_string_extents_range). It's actually more subtle than just
1172 // returning 0, since on super absurdly long strings, the int can wrap and
1173 // return positive numbers again. Detecting that is probably not worth it
1174 // (eliding way too much from a ridiculous string is probably still
1175 // ridiculous), but we should check other widths for bogus values as well.
1176 if (current_text_pixel_width <= 0 && !text.empty())
1177 return ElideText(slicer.CutString(text.length() / 2, insert_ellipsis));
1178
1179 if (current_text_pixel_width <= display_rect_.width())
1180 return text;
1181
1182 render_text->SetText(base::string16());
1183 render_text->SetText(ellipsis);
1184 const int ellipsis_width = render_text->GetContentWidth();
1185
1186 if (insert_ellipsis && (ellipsis_width >= display_rect_.width()))
1187 return base::string16();
1188
1189 // Use binary search to compute the elided text.
1190 size_t lo = 0;
1191 size_t hi = text.length() - 1;
1192 for (size_t guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) {
1193 // Restore styles and colors. They will be truncated to size by SetText.
1194 render_text->styles_ = styles_;
1195 render_text->colors_ = colors_;
1196 base::string16 new_text = slicer.CutString(guess, false);
1197 render_text->SetText(new_text);
1198
1199 // This has to be an additional step so that the ellipsis is rendered with
1200 // same style as trailing part of the text.
1201 if (insert_ellipsis) {
1202 // When ellipsis follows text whose directionality is not the same as that
1203 // of the whole text, it will be rendered with the directionality of the
1204 // whole text. Since we want ellipsis to indicate continuation of the
1205 // preceding text, we force the directionality of ellipsis to be same as
1206 // the preceding text using LTR or RTL markers.
1207 base::i18n::TextDirection leading_text_direction =
1208 base::i18n::GetFirstStrongCharacterDirection(new_text);
1209 base::i18n::TextDirection trailing_text_direction =
1210 base::i18n::GetLastStrongCharacterDirection(new_text);
1211 new_text.append(ellipsis);
1212 if (trailing_text_direction != leading_text_direction) {
1213 if (trailing_text_direction == base::i18n::LEFT_TO_RIGHT)
1214 new_text += base::i18n::kLeftToRightMark;
1215 else
1216 new_text += base::i18n::kRightToLeftMark;
1217 }
1218 render_text->SetText(new_text);
1219 }
1220
1221 // We check the width of the whole desired string at once to ensure we
1222 // handle kerning/ligatures/etc. correctly.
1223 const int guess_width = render_text->GetContentWidth();
1224 if (guess_width == display_rect_.width())
1225 break;
1226 if (guess_width > display_rect_.width()) {
1227 hi = guess - 1;
1228 // Move back if we are on loop terminating condition, and guess is wider
1229 // than available.
1230 if (hi < lo)
1231 lo = hi;
1232 } else {
1233 lo = guess + 1;
1234 }
1235 }
1236
1237 return render_text->text();
1124 } 1238 }
1125 1239
1126 void RenderText::UpdateCachedBoundsAndOffset() { 1240 void RenderText::UpdateCachedBoundsAndOffset() {
1127 if (cached_bounds_and_offset_valid_) 1241 if (cached_bounds_and_offset_valid_)
1128 return; 1242 return;
1129 1243
1130 // TODO(ckocagil): Add support for scrolling multiline text. 1244 // TODO(ckocagil): Add support for scrolling multiline text.
1131 1245
1132 // First, set the valid flag true to calculate the current cursor bounds using 1246 // 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 1247 // 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; 1286 cursor_bounds_ += delta_offset;
1173 } 1287 }
1174 1288
1175 void RenderText::DrawSelection(Canvas* canvas) { 1289 void RenderText::DrawSelection(Canvas* canvas) {
1176 const std::vector<Rect> sel = GetSubstringBounds(selection()); 1290 const std::vector<Rect> sel = GetSubstringBounds(selection());
1177 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i) 1291 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
1178 canvas->FillRect(*i, selection_background_focused_color_); 1292 canvas->FillRect(*i, selection_background_focused_color_);
1179 } 1293 }
1180 1294
1181 } // namespace gfx 1295 } // namespace gfx
OLDNEW
« no previous file with comments | « ui/gfx/render_text.h ('k') | ui/gfx/render_text_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698