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

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: Fixed merge issue 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 17 matching lines...) Expand all
401 insert_mode_ = !insert_mode_; 402 insert_mode_ = !insert_mode_;
402 cached_bounds_and_offset_valid_ = false; 403 cached_bounds_and_offset_valid_ = false;
403 } 404 }
404 405
405 void RenderText::SetObscured(bool obscured) { 406 void RenderText::SetObscured(bool obscured) {
406 if (obscured != obscured_) { 407 if (obscured != obscured_) {
407 obscured_ = obscured; 408 obscured_ = obscured;
408 obscured_reveal_index_ = -1; 409 obscured_reveal_index_ = -1;
409 cached_bounds_and_offset_valid_ = false; 410 cached_bounds_and_offset_valid_ = false;
410 UpdateLayoutText(); 411 UpdateLayoutText();
411 ResetLayout();
412 } 412 }
413 } 413 }
414 414
415 void RenderText::SetObscuredRevealIndex(int index) { 415 void RenderText::SetObscuredRevealIndex(int index) {
416 if (obscured_reveal_index_ == index) 416 if (obscured_reveal_index_ == index)
417 return; 417 return;
418 418
419 obscured_reveal_index_ = index; 419 obscured_reveal_index_ = index;
420 cached_bounds_and_offset_valid_ = false; 420 cached_bounds_and_offset_valid_ = false;
421 UpdateLayoutText(); 421 UpdateLayoutText();
422 ResetLayout();
423 } 422 }
424 423
425 void RenderText::SetMultiline(bool multiline) { 424 void RenderText::SetMultiline(bool multiline) {
426 if (multiline != multiline_) { 425 if (multiline != multiline_) {
427 multiline_ = multiline; 426 multiline_ = multiline;
428 cached_bounds_and_offset_valid_ = false; 427 cached_bounds_and_offset_valid_ = false;
429 lines_.clear(); 428 lines_.clear();
430 } 429 }
431 } 430 }
432 431
432 void RenderText::SetElideBehavior(ElideBehavior elide_behavior) {
433 // TODO(skanuj) : Add a test for triggering layout change.
434 if (elide_behavior_ != elide_behavior) {
435 elide_behavior_ = elide_behavior;
436 UpdateLayoutText();
437 }
438 }
439
433 void RenderText::SetDisplayRect(const Rect& r) { 440 void RenderText::SetDisplayRect(const Rect& r) {
434 display_rect_ = r; 441 if (r != display_rect_) {
435 baseline_ = kInvalidBaseline; 442 display_rect_ = r;
436 cached_bounds_and_offset_valid_ = false; 443 baseline_ = kInvalidBaseline;
437 lines_.clear(); 444 cached_bounds_and_offset_valid_ = false;
445 lines_.clear();
446 if (elide_behavior_ != gfx::NO_ELIDE)
447 UpdateLayoutText();
448 }
438 } 449 }
439 450
440 void RenderText::SetCursorPosition(size_t position) { 451 void RenderText::SetCursorPosition(size_t position) {
441 MoveCursorTo(position, false); 452 MoveCursorTo(position, false);
442 } 453 }
443 454
444 void RenderText::MoveCursor(BreakType break_type, 455 void RenderText::MoveCursor(BreakType break_type,
445 VisualCursorDirection direction, 456 VisualCursorDirection direction,
446 bool select) { 457 bool select) {
447 SelectionModel position(cursor_position(), selection_model_.caret_affinity()); 458 SelectionModel position(cursor_position(), selection_model_.caret_affinity());
(...skipping 372 matching lines...) Expand 10 before | Expand all | Expand 10 after
820 selection_color_(kDefaultColor), 831 selection_color_(kDefaultColor),
821 selection_background_focused_color_(kDefaultSelectionBackgroundColor), 832 selection_background_focused_color_(kDefaultSelectionBackgroundColor),
822 focused_(false), 833 focused_(false),
823 composition_range_(Range::InvalidRange()), 834 composition_range_(Range::InvalidRange()),
824 colors_(kDefaultColor), 835 colors_(kDefaultColor),
825 styles_(NUM_TEXT_STYLES), 836 styles_(NUM_TEXT_STYLES),
826 composition_and_selection_styles_applied_(false), 837 composition_and_selection_styles_applied_(false),
827 obscured_(false), 838 obscured_(false),
828 obscured_reveal_index_(-1), 839 obscured_reveal_index_(-1),
829 truncate_length_(0), 840 truncate_length_(0),
841 elide_behavior_(NO_ELIDE),
830 multiline_(false), 842 multiline_(false),
831 fade_head_(false), 843 fade_head_(false),
832 fade_tail_(false), 844 fade_tail_(false),
833 background_is_transparent_(false), 845 background_is_transparent_(false),
834 clip_to_display_rect_(true), 846 clip_to_display_rect_(true),
835 baseline_(kInvalidBaseline), 847 baseline_(kInvalidBaseline),
836 cached_bounds_and_offset_valid_(false) { 848 cached_bounds_and_offset_valid_(false) {
837 } 849 }
838 850
839 const Vector2d& RenderText::GetUpdatedDisplayOffset() { 851 const Vector2d& RenderText::GetUpdatedDisplayOffset() {
(...skipping 258 matching lines...) Expand 10 before | Expand all | Expand 10 after
1098 U16_NEXT(text_.data(), end, text_.length(), unused_char); 1110 U16_NEXT(text_.data(), end, text_.length(), unused_char);
1099 1111
1100 // Gets the index in |layout_text_| to be replaced. 1112 // Gets the index in |layout_text_| to be replaced.
1101 const size_t cp_start = 1113 const size_t cp_start =
1102 static_cast<size_t>(gfx::UTF16IndexToOffset(text_, 0, start)); 1114 static_cast<size_t>(gfx::UTF16IndexToOffset(text_, 0, start));
1103 if (layout_text_.length() > cp_start) 1115 if (layout_text_.length() > cp_start)
1104 layout_text_.replace(cp_start, 1, text_.substr(start, end - start)); 1116 layout_text_.replace(cp_start, 1, text_.substr(start, end - start));
1105 } 1117 }
1106 } 1118 }
1107 1119
1108 const base::string16& text = obscured_ ? layout_text_ : text_; 1120 const base::string16& text = GetLayoutText();
1109 if (truncate_length_ > 0 && truncate_length_ < text.length()) { 1121 if (truncate_length_ > 0 && truncate_length_ < text.length()) {
1110 // Truncate the text at a valid character break and append an ellipsis. 1122 // Truncate the text at a valid character break and append an ellipsis.
1111 icu::StringCharacterIterator iter(text.c_str()); 1123 icu::StringCharacterIterator iter(text.c_str());
1112 iter.setIndex32(truncate_length_ - 1); 1124 iter.setIndex32(truncate_length_ - 1);
1113 layout_text_.assign(text.substr(0, iter.getIndex()) + gfx::kEllipsisUTF16); 1125 layout_text_.assign(text.substr(0, iter.getIndex()) + gfx::kEllipsisUTF16);
1114 } 1126 }
1127
1128 if (elide_behavior_ != NO_ELIDE && display_rect_.width() > 0 &&
1129 !GetLayoutText().empty() && GetContentWidth() > display_rect_.width()) {
1130 base::string16 elided_text = ElideText(GetLayoutText());
1131
1132 // This doesn't trim styles so ellipsis may get rendered as a different
1133 // style than the preceding text. See crbug.com/327850.
1134 layout_text_.assign(elided_text);
1135 }
1136 ResetLayout();
1137 }
1138
1139 // TODO(skanuj): Fix code duplication with ElideText in ui/gfx/text_elider.cc
1140 // See crbug.com/327846
1141 base::string16 RenderText::ElideText(const base::string16& text) {
1142 const bool insert_ellipsis = (elide_behavior_ != TRUNCATE_AT_END);
1143 // Create a RenderText copy with attributes that affect the rendering width.
1144 scoped_ptr<RenderText> render_text(CreateInstance());
1145 render_text->SetFontList(font_list_);
1146 render_text->SetDirectionalityMode(directionality_mode_);
1147 render_text->SetCursorEnabled(cursor_enabled_);
1148
1149 render_text->styles_ = styles_;
1150 render_text->colors_ = colors_;
1151 render_text->SetText(text);
1152 const int current_text_pixel_width = render_text->GetContentWidth();
1153
1154 const base::string16 ellipsis = base::string16(gfx::kEllipsisUTF16);
1155 const bool elide_in_middle = false;
1156 StringSlicer slicer(text, ellipsis, elide_in_middle);
1157
1158 // Pango will return 0 width for absurdly long strings. Cut the string in
1159 // half and try again.
1160 // This is caused by an int overflow in Pango (specifically, in
1161 // pango_glyph_string_extents_range). It's actually more subtle than just
1162 // returning 0, since on super absurdly long strings, the int can wrap and
1163 // return positive numbers again. Detecting that is probably not worth it
1164 // (eliding way too much from a ridiculous string is probably still
1165 // ridiculous), but we should check other widths for bogus values as well.
1166 if (current_text_pixel_width <= 0 && !text.empty())
1167 return ElideText(slicer.CutString(text.length() / 2, insert_ellipsis));
1168
1169 if (current_text_pixel_width <= display_rect_.width())
1170 return text;
1171
1172 render_text->SetText(base::string16());
1173 render_text->SetText(ellipsis);
1174 const int ellipsis_width = render_text->GetContentWidth();
1175
1176 if (insert_ellipsis && (ellipsis_width >= display_rect_.width()))
1177 return base::string16();
1178
1179 // Use binary search to compute the elided text.
1180 size_t lo = 0;
1181 size_t hi = text.length() - 1;
1182 for (size_t guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) {
1183 // Restore styles and colors. They will be truncated to size by SetText.
1184 render_text->styles_ = styles_;
1185 render_text->colors_ = colors_;
1186 base::string16 new_text = slicer.CutString(guess, false);
1187 render_text->SetText(new_text);
1188
1189 // This has to be an additional step so that the ellipsis is rendered with
1190 // same style as trailing part of the text.
1191 if (insert_ellipsis) {
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.append(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();
1115 } 1228 }
1116 1229
1117 void RenderText::UpdateCachedBoundsAndOffset() { 1230 void RenderText::UpdateCachedBoundsAndOffset() {
1118 if (cached_bounds_and_offset_valid_) 1231 if (cached_bounds_and_offset_valid_)
1119 return; 1232 return;
1120 1233
1121 // TODO(ckocagil): Add support for scrolling multiline text. 1234 // TODO(ckocagil): Add support for scrolling multiline text.
1122 1235
1123 // 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
1124 // 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
1163 cursor_bounds_ += delta_offset; 1276 cursor_bounds_ += delta_offset;
1164 } 1277 }
1165 1278
1166 void RenderText::DrawSelection(Canvas* canvas) { 1279 void RenderText::DrawSelection(Canvas* canvas) {
1167 const std::vector<Rect> sel = GetSubstringBounds(selection()); 1280 const std::vector<Rect> sel = GetSubstringBounds(selection());
1168 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)
1169 canvas->FillRect(*i, selection_background_focused_color_); 1282 canvas->FillRect(*i, selection_background_focused_color_);
1170 } 1283 }
1171 1284
1172 } // namespace gfx 1285 } // 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