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

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

Issue 112063003: Implement eliding/truncating at end in RenderText (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Addressed comments and added unit tests 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
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 "third_party/icu/source/common/unicode/rbbi.h" 13 #include "third_party/icu/source/common/unicode/rbbi.h"
14 #include "third_party/icu/source/common/unicode/utf16.h" 14 #include "third_party/icu/source/common/unicode/utf16.h"
15 #include "third_party/skia/include/core/SkTypeface.h" 15 #include "third_party/skia/include/core/SkTypeface.h"
16 #include "third_party/skia/include/effects/SkGradientShader.h" 16 #include "third_party/skia/include/effects/SkGradientShader.h"
17 #include "ui/gfx/canvas.h" 17 #include "ui/gfx/canvas.h"
18 #include "ui/gfx/insets.h" 18 #include "ui/gfx/insets.h"
19 #include "ui/gfx/skia_util.h" 19 #include "ui/gfx/skia_util.h"
20 #include "ui/gfx/text_constants.h" 20 #include "ui/gfx/text_constants.h"
21 #include "ui/gfx/text_elider.h" 21 #include "ui/gfx/text_elider.h"
22 #include "ui/gfx/text_utils.h"
22 #include "ui/gfx/utf16_indexing.h" 23 #include "ui/gfx/utf16_indexing.h"
23 24
24 namespace gfx { 25 namespace gfx {
25 26
26 namespace { 27 namespace {
27 28
28 // All chars are replaced by this char when the password style is set. 29 // 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, '*' 30 // 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). 31 // that's available in the font (find_invisible_char() in gtkentry.c).
31 const base::char16 kPasswordReplacementChar = '*'; 32 const base::char16 kPasswordReplacementChar = '*';
(...skipping 326 matching lines...) Expand 10 before | Expand all | Expand 10 after
358 // Reset selection model. SetText should always followed by SetSelectionModel 359 // Reset selection model. SetText should always followed by SetSelectionModel
359 // or SetCursorPosition in upper layer. 360 // or SetCursorPosition in upper layer.
360 SetSelectionModel(SelectionModel()); 361 SetSelectionModel(SelectionModel());
361 362
362 // Invalidate the cached text direction if it depends on the text contents. 363 // Invalidate the cached text direction if it depends on the text contents.
363 if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT) 364 if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT)
364 text_direction_ = base::i18n::UNKNOWN_DIRECTION; 365 text_direction_ = base::i18n::UNKNOWN_DIRECTION;
365 366
366 obscured_reveal_index_ = -1; 367 obscured_reveal_index_ = -1;
367 UpdateLayoutText(); 368 UpdateLayoutText();
368 ResetLayout();
369 } 369 }
370 370
371 void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) { 371 void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) {
372 if (horizontal_alignment_ != alignment) { 372 if (horizontal_alignment_ != alignment) {
373 horizontal_alignment_ = alignment; 373 horizontal_alignment_ = alignment;
374 display_offset_ = Vector2d(); 374 display_offset_ = Vector2d();
375 cached_bounds_and_offset_valid_ = false; 375 cached_bounds_and_offset_valid_ = false;
376 } 376 }
377 } 377 }
378 378
(...skipping 25 matching lines...) Expand all
404 insert_mode_ = !insert_mode_; 404 insert_mode_ = !insert_mode_;
405 cached_bounds_and_offset_valid_ = false; 405 cached_bounds_and_offset_valid_ = false;
406 } 406 }
407 407
408 void RenderText::SetObscured(bool obscured) { 408 void RenderText::SetObscured(bool obscured) {
409 if (obscured != obscured_) { 409 if (obscured != obscured_) {
410 obscured_ = obscured; 410 obscured_ = obscured;
411 obscured_reveal_index_ = -1; 411 obscured_reveal_index_ = -1;
412 cached_bounds_and_offset_valid_ = false; 412 cached_bounds_and_offset_valid_ = false;
413 UpdateLayoutText(); 413 UpdateLayoutText();
414 ResetLayout();
415 } 414 }
416 } 415 }
417 416
418 void RenderText::SetObscuredRevealIndex(int index) { 417 void RenderText::SetObscuredRevealIndex(int index) {
419 if (obscured_reveal_index_ == index) 418 if (obscured_reveal_index_ == index)
420 return; 419 return;
421 420
422 obscured_reveal_index_ = index; 421 obscured_reveal_index_ = index;
423 cached_bounds_and_offset_valid_ = false; 422 cached_bounds_and_offset_valid_ = false;
424 UpdateLayoutText(); 423 UpdateLayoutText();
425 ResetLayout();
426 } 424 }
427 425
428 void RenderText::SetMultiline(bool multiline) { 426 void RenderText::SetMultiline(bool multiline) {
429 if (multiline != multiline_) { 427 if (multiline != multiline_) {
430 multiline_ = multiline; 428 multiline_ = multiline;
431 cached_bounds_and_offset_valid_ = false; 429 cached_bounds_and_offset_valid_ = false;
432 lines_.clear(); 430 lines_.clear();
433 } 431 }
434 } 432 }
435 433
434 void RenderText::SetElideBehavior(ElideBehavior elide_behavior) {
435 elide_behavior_ = elide_behavior;
436 if (elide_behavior_ != gfx::NO_ELIDE)
msw 2013/12/12 19:28:29 This should check if the elide behavior changed, a
Anuj 2013/12/13 01:23:50 Good point. Thanks.
437 UpdateLayoutText();
438 }
439
436 void RenderText::SetDisplayRect(const Rect& r) { 440 void RenderText::SetDisplayRect(const Rect& r) {
437 display_rect_ = r; 441 display_rect_ = r;
438 baseline_ = kInvalidBaseline; 442 baseline_ = kInvalidBaseline;
439 cached_bounds_and_offset_valid_ = false; 443 cached_bounds_and_offset_valid_ = false;
440 lines_.clear(); 444 lines_.clear();
441 } 445 }
442 446
443 void RenderText::SetCursorPosition(size_t position) { 447 void RenderText::SetCursorPosition(size_t position) {
444 MoveCursorTo(position, false); 448 MoveCursorTo(position, false);
445 } 449 }
(...skipping 377 matching lines...) Expand 10 before | Expand all | Expand 10 after
823 selection_color_(kDefaultColor), 827 selection_color_(kDefaultColor),
824 selection_background_focused_color_(kDefaultSelectionBackgroundColor), 828 selection_background_focused_color_(kDefaultSelectionBackgroundColor),
825 focused_(false), 829 focused_(false),
826 composition_range_(Range::InvalidRange()), 830 composition_range_(Range::InvalidRange()),
827 colors_(kDefaultColor), 831 colors_(kDefaultColor),
828 styles_(NUM_TEXT_STYLES), 832 styles_(NUM_TEXT_STYLES),
829 composition_and_selection_styles_applied_(false), 833 composition_and_selection_styles_applied_(false),
830 obscured_(false), 834 obscured_(false),
831 obscured_reveal_index_(-1), 835 obscured_reveal_index_(-1),
832 truncate_length_(0), 836 truncate_length_(0),
837 elide_behavior_(NO_ELIDE),
833 multiline_(false), 838 multiline_(false),
834 fade_head_(false), 839 fade_head_(false),
835 fade_tail_(false), 840 fade_tail_(false),
836 background_is_transparent_(false), 841 background_is_transparent_(false),
837 clip_to_display_rect_(true), 842 clip_to_display_rect_(true),
838 baseline_(kInvalidBaseline), 843 baseline_(kInvalidBaseline),
839 cached_bounds_and_offset_valid_(false) { 844 cached_bounds_and_offset_valid_(false) {
840 } 845 }
841 846
842 const Vector2d& RenderText::GetUpdatedDisplayOffset() { 847 const Vector2d& RenderText::GetUpdatedDisplayOffset() {
(...skipping 259 matching lines...) Expand 10 before | Expand all | Expand 10 after
1102 U16_NEXT(text_.data(), end, text_.length(), unused_char); 1107 U16_NEXT(text_.data(), end, text_.length(), unused_char);
1103 1108
1104 // Gets the index in |layout_text_| to be replaced. 1109 // Gets the index in |layout_text_| to be replaced.
1105 const size_t cp_start = 1110 const size_t cp_start =
1106 static_cast<size_t>(gfx::UTF16IndexToOffset(text_, 0, start)); 1111 static_cast<size_t>(gfx::UTF16IndexToOffset(text_, 0, start));
1107 if (layout_text_.length() > cp_start) 1112 if (layout_text_.length() > cp_start)
1108 layout_text_.replace(cp_start, 1, text_.substr(start, end - start)); 1113 layout_text_.replace(cp_start, 1, text_.substr(start, end - start));
1109 } 1114 }
1110 } 1115 }
1111 1116
1112 const base::string16& text = obscured_ ? layout_text_ : text_; 1117 const base::string16& text = GetLayoutText();
1113 if (truncate_length_ > 0 && truncate_length_ < text.length()) { 1118 if (truncate_length_ > 0 && truncate_length_ < text.length()) {
1114 // Truncate the text at a valid character break and append an ellipsis. 1119 // Truncate the text at a valid character break and append an ellipsis.
1115 icu::StringCharacterIterator iter(text.c_str()); 1120 icu::StringCharacterIterator iter(text.c_str());
1116 iter.setIndex32(truncate_length_ - 1); 1121 iter.setIndex32(truncate_length_ - 1);
1117 layout_text_.assign(text.substr(0, iter.getIndex()) + gfx::kEllipsisUTF16); 1122 layout_text_.assign(text.substr(0, iter.getIndex()) + gfx::kEllipsisUTF16);
1118 } 1123 }
1124
1125 int content_width = GetContentWidth();
msw 2013/12/12 19:28:29 Inline this, it's value is only needed once, condi
Anuj 2013/12/13 01:23:50 Done.
1126 if (elide_behavior_ != NO_ELIDE && display_rect_.width() > 0 &&
1127 content_width > display_rect_.width()) {
1128 base::string16 elided_text = ElideText(GetLayoutText());
1129
1130 // This doesn't trim styles so ellipsis may get rendered as a different
1131 // style than the preceding text. See crbug.com/327850.
1132 layout_text_.assign(elided_text);
1133 }
1134 ResetLayout();
1135 }
1136
1137 // TODO(skanuj): Fix code duplication with ElideText in ui/gfx/text_elider.cc
1138 // See crbug.com/327846
1139 base::string16 RenderText::ElideText(const base::string16& text) {
1140 if (text.empty())
1141 return text;
1142
1143 const bool insert_ellipsis = (elide_behavior_ != TRUNCATE_AT_END);
1144 // Create a copy render text. We copy the attributes which affect the
msw 2013/12/12 19:28:29 optional nit: // Create a RenderText copy with att
Anuj 2013/12/13 01:23:50 Done.
1145 // rendering width.
1146 scoped_ptr<RenderText> render_text(CreateInstance());
1147 render_text->SetFontList(font_list_);
1148 render_text->SetDirectionalityMode(directionality_mode_);
1149 render_text->SetCursorEnabled(cursor_enabled_);
1150 render_text->styles_ = styles_;
1151 render_text->colors_ = colors_;
1152 render_text->SetText(text);
1153 const float current_text_pixel_width = render_text->GetContentWidth();
1154
1155 const base::string16 ellipsis = base::string16(kEllipsisUTF16);
1156 const bool elide_in_middle = false;
1157 StringSlicer slicer(text, ellipsis, elide_in_middle);
1158
1159 // Pango will return 0 width for absurdly long strings. Cut the string in
1160 // half and try again.
1161 // This is caused by an int overflow in Pango (specifically, in
1162 // pango_glyph_string_extents_range). It's actually more subtle than just
1163 // returning 0, since on super absurdly long strings, the int can wrap and
1164 // return positive numbers again. Detecting that is probably not worth it
1165 // (eliding way too much from a ridiculous string is probably still
1166 // ridiculous), but we should check other widths for bogus values as well.
1167 if (current_text_pixel_width <= 0 && !text.empty())
1168 return ElideText(slicer.CutString(text.length() / 2, insert_ellipsis));
1169
1170 if (current_text_pixel_width <= display_rect_.width())
1171 return text;
1172
1173 if (insert_ellipsis &&
1174 GetStringWidthF(ellipsis, font_list_) > display_rect_.width())
1175 return base::string16();
1176
1177 // Use binary search to compute the elided text.
1178 size_t lo = 0;
1179 size_t hi = text.length() - 1;
1180
1181 for (size_t guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) {
1182 // Restore styles and colors. They will be truncated to size by SetText.
1183 render_text->styles_ = styles_;
1184 render_text->colors_ = colors_;
1185 base::string16 new_text = slicer.CutString(guess, false);
1186 render_text->SetText(new_text);
msw 2013/12/12 19:28:29 Oh darn, I think you'll need to (conditionally?) r
Anuj 2013/12/13 01:23:50 No. The unconditional reapplying of original style
msw 2013/12/13 01:56:08 Gah, I just missed copying done above... Sorry for
Anuj 2013/12/13 02:23:45 Done.
1187
1188 // This has to be an additional step so that the ellipsis is rendered with
1189 // same style as trailing part of the text.
1190 if (insert_ellipsis) {
1191
msw 2013/12/12 19:28:29 nit: remove this blank line.
Anuj 2013/12/13 01:23:50 Done.
1192 // When ellipsis follows text whose directionality is not the same as that
1193 // of the whole text, it will be rendered with the directionality of the
1194 // whole text. Since we want ellipsis to indicate continuation of the
1195 // preceding text, we force the directionality of ellipsis to be same as
1196 // the preceding text using LTR or RTL markers.
1197 base::i18n::TextDirection leading_text_direction =
1198 base::i18n::GetFirstStrongCharacterDirection(new_text);
1199 base::i18n::TextDirection trailing_text_direction =
1200 base::i18n::GetLastStrongCharacterDirection(new_text);
1201 new_text += ellipsis;
1202 if (trailing_text_direction != leading_text_direction) {
1203 if (trailing_text_direction == base::i18n::LEFT_TO_RIGHT)
1204 new_text += base::i18n::kLeftToRightMark;
1205 else
1206 new_text += base::i18n::kRightToLeftMark;
1207 }
1208 render_text->SetText(new_text);
1209 }
1210
1211 // We check the width of the whole desired string at once to ensure we
1212 // handle kerning/ligatures/etc. correctly.
1213 const int guess_width = render_text->GetContentWidth();
1214 if (guess_width == display_rect_.width())
1215 break;
1216 if (guess_width > display_rect_.width()) {
1217 hi = guess - 1;
1218 // Move back if we are on loop terminating condition, and guess is wider
1219 // than available.
1220 if (hi < lo)
1221 lo = hi;
1222 } else {
1223 lo = guess + 1;
1224 }
1225 }
1226
1227 return render_text->text();
1119 } 1228 }
1120 1229
1121 void RenderText::UpdateCachedBoundsAndOffset() { 1230 void RenderText::UpdateCachedBoundsAndOffset() {
1122 if (cached_bounds_and_offset_valid_) 1231 if (cached_bounds_and_offset_valid_)
1123 return; 1232 return;
1124 1233
1125 // TODO(ckocagil): Add support for scrolling multiline text. 1234 // TODO(ckocagil): Add support for scrolling multiline text.
1126 1235
1127 // First, set the valid flag true to calculate the current cursor bounds using 1236 // First, set the valid flag true to calculate the current cursor bounds using
1128 // the stale |display_offset_|. Applying |delta_offset| at the end of this 1237 // the stale |display_offset_|. Applying |delta_offset| at the end of this
(...skipping 38 matching lines...) Expand 10 before | Expand all | Expand 10 after
1167 cursor_bounds_ += delta_offset; 1276 cursor_bounds_ += delta_offset;
1168 } 1277 }
1169 1278
1170 void RenderText::DrawSelection(Canvas* canvas) { 1279 void RenderText::DrawSelection(Canvas* canvas) {
1171 const std::vector<Rect> sel = GetSubstringBounds(selection()); 1280 const std::vector<Rect> sel = GetSubstringBounds(selection());
1172 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i) 1281 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
1173 canvas->FillRect(*i, selection_background_focused_color_); 1282 canvas->FillRect(*i, selection_background_focused_color_);
1174 } 1283 }
1175 1284
1176 } // namespace gfx 1285 } // namespace gfx
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698