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

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: 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 394 matching lines...) Expand 10 before | Expand all | Expand 10 after
426 } 427 }
427 428
428 void RenderText::SetMultiline(bool multiline) { 429 void RenderText::SetMultiline(bool multiline) {
429 if (multiline != multiline_) { 430 if (multiline != multiline_) {
430 multiline_ = multiline; 431 multiline_ = multiline;
431 cached_bounds_and_offset_valid_ = false; 432 cached_bounds_and_offset_valid_ = false;
432 lines_.clear(); 433 lines_.clear();
433 } 434 }
434 } 435 }
435 436
437 void RenderText::Elide(float available_pixel_width,
438 ElideBehavior elide_behavior) {
439 available_pixel_width_ = available_pixel_width;
440 elide_behavior_ = elide_behavior;
441 UpdateLayoutText();
442 ResetLayout();
msw 2013/12/11 08:10:14 nit: Would you mind adding a ResetLayout() call to
Anuj 2013/12/12 08:08:41 Done.
msw 2013/12/12 19:28:29 Thanks!
Anuj 2013/12/13 01:23:50 Done.
443 }
444
436 void RenderText::SetDisplayRect(const Rect& r) { 445 void RenderText::SetDisplayRect(const Rect& r) {
437 display_rect_ = r; 446 display_rect_ = r;
438 baseline_ = kInvalidBaseline; 447 baseline_ = kInvalidBaseline;
439 cached_bounds_and_offset_valid_ = false; 448 cached_bounds_and_offset_valid_ = false;
440 lines_.clear(); 449 lines_.clear();
441 } 450 }
442 451
443 void RenderText::SetCursorPosition(size_t position) { 452 void RenderText::SetCursorPosition(size_t position) {
444 MoveCursorTo(position, false); 453 MoveCursorTo(position, false);
445 } 454 }
(...skipping 377 matching lines...) Expand 10 before | Expand all | Expand 10 after
823 selection_color_(kDefaultColor), 832 selection_color_(kDefaultColor),
824 selection_background_focused_color_(kDefaultSelectionBackgroundColor), 833 selection_background_focused_color_(kDefaultSelectionBackgroundColor),
825 focused_(false), 834 focused_(false),
826 composition_range_(Range::InvalidRange()), 835 composition_range_(Range::InvalidRange()),
827 colors_(kDefaultColor), 836 colors_(kDefaultColor),
828 styles_(NUM_TEXT_STYLES), 837 styles_(NUM_TEXT_STYLES),
829 composition_and_selection_styles_applied_(false), 838 composition_and_selection_styles_applied_(false),
830 obscured_(false), 839 obscured_(false),
831 obscured_reveal_index_(-1), 840 obscured_reveal_index_(-1),
832 truncate_length_(0), 841 truncate_length_(0),
842 available_pixel_width_(0.0),
843 elide_behavior_(NO_ELISION),
833 multiline_(false), 844 multiline_(false),
834 fade_head_(false), 845 fade_head_(false),
835 fade_tail_(false), 846 fade_tail_(false),
836 background_is_transparent_(false), 847 background_is_transparent_(false),
837 clip_to_display_rect_(true), 848 clip_to_display_rect_(true),
838 baseline_(kInvalidBaseline), 849 baseline_(kInvalidBaseline),
839 cached_bounds_and_offset_valid_(false) { 850 cached_bounds_and_offset_valid_(false) {
840 } 851 }
841 852
842 const Vector2d& RenderText::GetUpdatedDisplayOffset() { 853 const Vector2d& RenderText::GetUpdatedDisplayOffset() {
(...skipping 266 matching lines...) Expand 10 before | Expand all | Expand 10 after
1109 } 1120 }
1110 } 1121 }
1111 1122
1112 const base::string16& text = obscured_ ? layout_text_ : text_; 1123 const base::string16& text = obscured_ ? layout_text_ : text_;
1113 if (truncate_length_ > 0 && truncate_length_ < text.length()) { 1124 if (truncate_length_ > 0 && truncate_length_ < text.length()) {
1114 // Truncate the text at a valid character break and append an ellipsis. 1125 // Truncate the text at a valid character break and append an ellipsis.
1115 icu::StringCharacterIterator iter(text.c_str()); 1126 icu::StringCharacterIterator iter(text.c_str());
1116 iter.setIndex32(truncate_length_ - 1); 1127 iter.setIndex32(truncate_length_ - 1);
1117 layout_text_.assign(text.substr(0, iter.getIndex()) + gfx::kEllipsisUTF16); 1128 layout_text_.assign(text.substr(0, iter.getIndex()) + gfx::kEllipsisUTF16);
1118 } 1129 }
1130
1131 int content_width = GetContentWidth();
1132 if (elide_behavior_ != NO_ELISION && available_pixel_width_ > 0 &&
1133 content_width > available_pixel_width_) {
1134 base::string16 elided_text = ElideText(text);
msw 2013/12/11 08:10:14 This disregards the obscuring and truncation done
Anuj 2013/12/12 08:08:41 Done.
1135
1136 // This doesn't trim styles so ellipsis may get rendered as a different
msw 2013/12/11 08:10:14 I suppose it'll be tough to temporarily apply styl
Anuj 2013/12/12 08:08:41 Filed crbug.com/327850
1137 // style than the preceding text.
1138 // DISCUSS WITH REVIEWER.
1139 layout_text_.assign(elided_text);
1140 }
1141 }
1142
1143 base::string16 RenderText::ElideText(const base::string16& text) {
msw 2013/12/11 08:10:14 This is quite similar to ElideText from text_elide
Anuj 2013/12/12 08:08:41 Done.
1144 if (text.empty())
1145 return text;
1146
1147 const float current_text_pixel_width = GetStringSizeF().width();
msw 2013/12/11 08:10:14 GetStringSizeF will actually EnsureLayout and calc
Anuj 2013/12/12 08:08:41 I can't forgo the argument because of the recursiv
msw 2013/12/12 19:28:29 Hmm, nice!
Anuj 2013/12/13 01:23:50 Done.
1148 const bool elide_in_middle = false;
1149 const bool insert_ellipsis = (elide_behavior_ != TRUNCATE_AT_END);
1150
1151 const base::string16 ellipsis = base::string16(kEllipsisUTF16);
1152 StringSlicer slicer(text, ellipsis, elide_in_middle);
1153
1154 // Pango will return 0 width for absurdly long strings. Cut the string in
1155 // half and try again.
1156 // This is caused by an int overflow in Pango (specifically, in
1157 // pango_glyph_string_extents_range). It's actually more subtle than just
1158 // returning 0, since on super absurdly long strings, the int can wrap and
1159 // return positive numbers again. Detecting that is probably not worth it
1160 // (eliding way too much from a ridiculous string is probably still
1161 // ridiculous), but we should check other widths for bogus values as well.
1162 if (current_text_pixel_width <= 0 && !text.empty()) {
1163 const base::string16 cut = slicer.CutString(text.length() / 2, false);
msw 2013/12/11 08:10:14 Shouldn't this actually insert an ellipsis if |ins
Anuj 2013/12/12 08:08:41 Done.
1164 return ElideText(cut);
1165 }
1166
1167 if (current_text_pixel_width <= available_pixel_width_)
1168 return text;
1169
1170 if (insert_ellipsis &&
1171 GetStringWidthF(ellipsis, font_list_) > available_pixel_width_)
1172 return base::string16();
1173
1174 // Use binary search to compute the elided text.
1175 size_t lo = 0;
1176 size_t hi = text.length() - 1;
1177 size_t guess;
msw 2013/12/11 08:10:14 nit: declare guess in the for loop's control.
Anuj 2013/12/12 08:08:41 Done.
1178
1179 // Create a copy render text. We copy the attributes which affect the
1180 // rendering width.
1181 scoped_ptr<RenderText> render_text(CreateInstance());
1182 render_text->SetFontList(font_list_);
1183 render_text->SetDirectionalityMode(directionality_mode_);
1184 render_text->styles_ = styles_;
1185 render_text->colors_ = colors_;
1186 render_text->SetText(text);
1187 for (guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) {
1188 // Restore styles and colors. They will be truncated to size by SetText.
1189 render_text->styles_ = styles_;
1190 render_text->colors_ = colors_;
1191 base::string16 newtext = slicer.CutString(guess, false);
msw 2013/12/11 08:10:14 nit: use the unix_hacker naming convention |new_te
Anuj 2013/12/12 08:08:41 Done.
1192 render_text->SetText(newtext);
1193
1194 // This has to be an additional step so that the ellipsis is rendered with
1195 // same style as trailing part of the text.
1196 if (insert_ellipsis) {
1197 newtext += ellipsis;
1198
1199 // When ellipsis follows text whose directionality is not the same as that
1200 // of the whole text, it will be rendered with the directionality of the
1201 // whole text. Since we want ellipsis to indicate continuation of the
1202 // preceding text, we force the directionality of ellipsis to be same as
1203 // the preceding text using LTR or RTL markers.
1204 base::i18n::TextDirection trailing_text_direction =
1205 base::i18n::GetLastStrongCharacterDirection(newtext);
msw 2013/12/11 08:10:14 nit: indent two more spaces.
Anuj 2013/12/12 08:08:41 Done.
1206 if (trailing_text_direction != render_text->GetTextDirection()) {
1207 if (trailing_text_direction == base::i18n::LEFT_TO_RIGHT)
1208 newtext += base::i18n::kLeftToRightMark;
msw 2013/12/11 08:10:14 I think you should use WrapStringWithLTRFormatting
Anuj 2013/12/11 18:41:32 See implicit markers here - http://www.iamcal.com/
msw 2013/12/11 19:39:57 Cool, using that mark seems correct, thanks for th
Anuj 2013/12/12 08:08:41 I suggested an extra param because existing users
msw 2013/12/12 19:28:29 I suggest making the default behavior add the dire
Anuj 2013/12/13 01:23:50 Adding a TODO, because ellipsis in middle also nee
1209 else
1210 newtext += base::i18n::kRightToLeftMark;
1211 }
1212 render_text->SetText(newtext);
1213 }
1214
1215 // We check the length of the whole desired string at once to ensure we
msw 2013/12/11 08:10:14 nit: s/length/width/, s/guess_length/guess_width/;
Anuj 2013/12/12 08:08:41 Done.
1216 // handle kerning/ligatures/etc. correctly.
1217 const float guess_length = render_text->GetStringSizeF().width();
1218 // Check again that we didn't hit a Pango width overflow. If so, cut the
msw 2013/12/11 08:10:14 This shouldn't be necessary here or in the text_el
Anuj 2013/12/12 08:08:41 I thought adding ellipsis can cause that behavior.
1219 // current string in half and start over.
1220 if (guess_length <= 0) {
msw 2013/12/11 08:10:14 nit: remove unnecessary curly braces.
Anuj 2013/12/12 08:08:41 Done.
1221 return ElideText(slicer.CutString(guess / 2, false));
1222 }
1223 if (guess_length > available_pixel_width_) {
msw 2013/12/11 08:10:14 Shouldn't this break if guess_length == available_
Anuj 2013/12/12 08:08:41 Done.
1224 // Move back if we are on loop terminating condition, and guess is longer
1225 // than available.
1226 if (hi == lo)
msw 2013/12/11 08:10:14 Sorry, I'm failing to understand how this can happ
Anuj 2013/12/11 18:41:32 I had a scenario where the following happened avai
msw 2013/12/11 19:39:57 Yeah, I worked through a few conceptual examples a
Anuj 2013/12/12 08:08:41 I got a new interview question ;)
msw 2013/12/12 19:28:29 I thought binary searches were supposed to be easy
Anuj 2013/12/13 01:23:50 Done.
1227 lo = guess - 1;
1228 hi = guess - 1;
1229 } else {
1230 lo = guess + 1;
1231 }
1232 }
1233
1234 return render_text->text();
1119 } 1235 }
1120 1236
1121 void RenderText::UpdateCachedBoundsAndOffset() { 1237 void RenderText::UpdateCachedBoundsAndOffset() {
1122 if (cached_bounds_and_offset_valid_) 1238 if (cached_bounds_and_offset_valid_)
1123 return; 1239 return;
1124 1240
1125 // TODO(ckocagil): Add support for scrolling multiline text. 1241 // TODO(ckocagil): Add support for scrolling multiline text.
1126 1242
1127 // First, set the valid flag true to calculate the current cursor bounds using 1243 // 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 1244 // 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; 1283 cursor_bounds_ += delta_offset;
1168 } 1284 }
1169 1285
1170 void RenderText::DrawSelection(Canvas* canvas) { 1286 void RenderText::DrawSelection(Canvas* canvas) {
1171 const std::vector<Rect> sel = GetSubstringBounds(selection()); 1287 const std::vector<Rect> sel = GetSubstringBounds(selection());
1172 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i) 1288 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
1173 canvas->FillRect(*i, selection_background_focused_color_); 1289 canvas->FillRect(*i, selection_background_focused_color_);
1174 } 1290 }
1175 1291
1176 } // namespace gfx 1292 } // namespace gfx
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698