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

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

Issue 354963003: Move gfx::ElideText functionality to RenderText. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Fix the initial width of OmniboxResultView's elided RenderTexts. Created 6 years, 5 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_pango.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/command_line.h" 10 #include "base/command_line.h"
11 #include "base/i18n/break_iterator.h" 11 #include "base/i18n/break_iterator.h"
12 #include "base/logging.h" 12 #include "base/logging.h"
13 #include "base/stl_util.h" 13 #include "base/stl_util.h"
14 #include "base/strings/utf_string_conversions.h" 14 #include "base/strings/utf_string_conversions.h"
15 #include "third_party/icu/source/common/unicode/rbbi.h" 15 #include "third_party/icu/source/common/unicode/rbbi.h"
16 #include "third_party/icu/source/common/unicode/utf16.h" 16 #include "third_party/icu/source/common/unicode/utf16.h"
17 #include "third_party/skia/include/core/SkTypeface.h" 17 #include "third_party/skia/include/core/SkTypeface.h"
18 #include "third_party/skia/include/effects/SkGradientShader.h" 18 #include "third_party/skia/include/effects/SkGradientShader.h"
19 #include "ui/gfx/canvas.h" 19 #include "ui/gfx/canvas.h"
20 #include "ui/gfx/insets.h" 20 #include "ui/gfx/insets.h"
21 #include "ui/gfx/render_text_harfbuzz.h" 21 #include "ui/gfx/render_text_harfbuzz.h"
22 #include "ui/gfx/scoped_canvas.h" 22 #include "ui/gfx/scoped_canvas.h"
23 #include "ui/gfx/skia_util.h" 23 #include "ui/gfx/skia_util.h"
24 #include "ui/gfx/switches.h" 24 #include "ui/gfx/switches.h"
25 #include "ui/gfx/text_constants.h"
26 #include "ui/gfx/text_elider.h" 25 #include "ui/gfx/text_elider.h"
27 #include "ui/gfx/text_utils.h" 26 #include "ui/gfx/text_utils.h"
28 #include "ui/gfx/utf16_indexing.h" 27 #include "ui/gfx/utf16_indexing.h"
29 28
30 namespace gfx { 29 namespace gfx {
31 30
32 namespace { 31 namespace {
33 32
34 // All chars are replaced by this char when the password style is set. 33 // All chars are replaced by this char when the password style is set.
35 // TODO(benrg): GTK uses the first of U+25CF, U+2022, U+2731, U+273A, '*' 34 // TODO(benrg): GTK uses the first of U+25CF, U+2022, U+2731, U+273A, '*'
(...skipping 470 matching lines...) Expand 10 before | Expand all | Expand 10 after
506 UpdateLayoutText(); 505 UpdateLayoutText();
507 } 506 }
508 } 507 }
509 508
510 void RenderText::SetDisplayRect(const Rect& r) { 509 void RenderText::SetDisplayRect(const Rect& r) {
511 if (r != display_rect_) { 510 if (r != display_rect_) {
512 display_rect_ = r; 511 display_rect_ = r;
513 baseline_ = kInvalidBaseline; 512 baseline_ = kInvalidBaseline;
514 cached_bounds_and_offset_valid_ = false; 513 cached_bounds_and_offset_valid_ = false;
515 lines_.clear(); 514 lines_.clear();
516 if (elide_behavior_ != TRUNCATE) 515 if (elide_behavior_ != NO_ELIDE)
517 UpdateLayoutText(); 516 UpdateLayoutText();
518 } 517 }
519 } 518 }
520 519
521 void RenderText::SetCursorPosition(size_t position) { 520 void RenderText::SetCursorPosition(size_t position) {
522 MoveCursorTo(position, false); 521 MoveCursorTo(position, false);
523 } 522 }
524 523
525 void RenderText::MoveCursor(BreakType break_type, 524 void RenderText::MoveCursor(BreakType break_type,
526 VisualCursorDirection direction, 525 VisualCursorDirection direction,
(...skipping 211 matching lines...) Expand 10 before | Expand all | Expand 10 after
738 VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() { 737 VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() {
739 return GetTextDirection() == base::i18n::LEFT_TO_RIGHT ? 738 return GetTextDirection() == base::i18n::LEFT_TO_RIGHT ?
740 CURSOR_RIGHT : CURSOR_LEFT; 739 CURSOR_RIGHT : CURSOR_LEFT;
741 } 740 }
742 741
743 SizeF RenderText::GetStringSizeF() { 742 SizeF RenderText::GetStringSizeF() {
744 const Size size = GetStringSize(); 743 const Size size = GetStringSize();
745 return SizeF(size.width(), size.height()); 744 return SizeF(size.width(), size.height());
746 } 745 }
747 746
748 int RenderText::GetContentWidth() { 747 float RenderText::GetContentWidth() {
749 return GetStringSize().width() + (cursor_enabled_ ? 1 : 0); 748 return GetStringSizeF().width() + (cursor_enabled_ ? 1 : 0);
750 } 749 }
751 750
752 int RenderText::GetBaseline() { 751 int RenderText::GetBaseline() {
753 if (baseline_ == kInvalidBaseline) 752 if (baseline_ == kInvalidBaseline)
754 baseline_ = DetermineBaselineCenteringText(display_rect(), font_list()); 753 baseline_ = DetermineBaselineCenteringText(display_rect(), font_list());
755 DCHECK_NE(kInvalidBaseline, baseline_); 754 DCHECK_NE(kInvalidBaseline, baseline_);
756 return baseline_; 755 return baseline_;
757 } 756 }
758 757
759 void RenderText::Draw(Canvas* canvas) { 758 void RenderText::Draw(Canvas* canvas) {
(...skipping 123 matching lines...) Expand 10 before | Expand all | Expand 10 after
883 selection_color_(kDefaultColor), 882 selection_color_(kDefaultColor),
884 selection_background_focused_color_(kDefaultSelectionBackgroundColor), 883 selection_background_focused_color_(kDefaultSelectionBackgroundColor),
885 focused_(false), 884 focused_(false),
886 composition_range_(Range::InvalidRange()), 885 composition_range_(Range::InvalidRange()),
887 colors_(kDefaultColor), 886 colors_(kDefaultColor),
888 styles_(NUM_TEXT_STYLES), 887 styles_(NUM_TEXT_STYLES),
889 composition_and_selection_styles_applied_(false), 888 composition_and_selection_styles_applied_(false),
890 obscured_(false), 889 obscured_(false),
891 obscured_reveal_index_(-1), 890 obscured_reveal_index_(-1),
892 truncate_length_(0), 891 truncate_length_(0),
893 elide_behavior_(TRUNCATE), 892 elide_behavior_(NO_ELIDE),
894 multiline_(false), 893 multiline_(false),
895 background_is_transparent_(false), 894 background_is_transparent_(false),
896 clip_to_display_rect_(true), 895 clip_to_display_rect_(true),
897 baseline_(kInvalidBaseline), 896 baseline_(kInvalidBaseline),
898 cached_bounds_and_offset_valid_(false) { 897 cached_bounds_and_offset_valid_(false) {
899 } 898 }
900 899
901 const Vector2d& RenderText::GetUpdatedDisplayOffset() { 900 const Vector2d& RenderText::GetUpdatedDisplayOffset() {
902 UpdateCachedBoundsAndOffset(); 901 UpdateCachedBoundsAndOffset();
903 return display_offset_; 902 return display_offset_;
(...skipping 248 matching lines...) Expand 10 before | Expand all | Expand 10 after
1152 layout_text_.replace(cp_start, 1, text_.substr(start, end - start)); 1151 layout_text_.replace(cp_start, 1, text_.substr(start, end - start));
1153 } 1152 }
1154 } else { 1153 } else {
1155 layout_text_ = text_; 1154 layout_text_ = text_;
1156 } 1155 }
1157 1156
1158 const base::string16& text = layout_text_; 1157 const base::string16& text = layout_text_;
1159 if (truncate_length_ > 0 && truncate_length_ < text.length()) { 1158 if (truncate_length_ > 0 && truncate_length_ < text.length()) {
1160 // Truncate the text at a valid character break and append an ellipsis. 1159 // Truncate the text at a valid character break and append an ellipsis.
1161 icu::StringCharacterIterator iter(text.c_str()); 1160 icu::StringCharacterIterator iter(text.c_str());
1162 iter.setIndex32(truncate_length_ - 1); 1161 // Respect ELIDE_HEAD and ELIDE_MIDDLE preferences during truncation.
1163 layout_text_.assign(text.substr(0, iter.getIndex()) + kEllipsisUTF16); 1162 if (elide_behavior_ == ELIDE_HEAD) {
1163 iter.setIndex32(text.length() - truncate_length_ + 1);
sky 2014/07/09 18:19:38 Is it possible for truncate_length_ to equal text.
msw 2014/07/09 19:16:08 The condition at line 1158 ensures |truncate_lengt
1164 layout_text_.assign(kEllipsisUTF16 + text.substr(iter.getIndex()));
1165 } else if (elide_behavior_ == ELIDE_MIDDLE) {
1166 iter.setIndex32(truncate_length_ / 2);
1167 const size_t ellipsis_start = iter.getIndex();
1168 iter.setIndex32(text.length() - (truncate_length_ / 2));
1169 const size_t ellipsis_end = iter.getIndex();
1170 layout_text_.assign(text.substr(0, ellipsis_start) + kEllipsisUTF16 +
1171 text.substr(ellipsis_end));
sky 2014/07/09 18:19:38 Might it be possible for ellipsis_start to be > el
msw 2014/07/09 19:16:08 Afaict, that would only happen if |truncate_length
1172 } else {
1173 iter.setIndex32(truncate_length_ - 1);
1174 layout_text_.assign(text.substr(0, iter.getIndex()) + kEllipsisUTF16);
1175 }
1164 } 1176 }
1165 1177
1166 if (elide_behavior_ != TRUNCATE && elide_behavior_ != FADE_TAIL && 1178 if (elide_behavior_ != NO_ELIDE && elide_behavior_ != FADE_TAIL &&
1167 display_rect_.width() > 0 && !layout_text_.empty() && 1179 !layout_text_.empty() && GetContentWidth() > display_rect_.width()) {
1168 GetContentWidth() > display_rect_.width()) {
1169 // This doesn't trim styles so ellipsis may get rendered as a different 1180 // This doesn't trim styles so ellipsis may get rendered as a different
1170 // style than the preceding text. See crbug.com/327850. 1181 // style than the preceding text. See crbug.com/327850.
1171 layout_text_.assign(ElideText(layout_text_)); 1182 layout_text_.assign(
1183 Elide(layout_text_, display_rect_.width(), elide_behavior_));
1172 } 1184 }
1173 1185
1174 ResetLayout(); 1186 ResetLayout();
1175 } 1187 }
1176 1188
1177 // TODO(skanuj): Fix code duplication with ElideText in ui/gfx/text_elider.cc 1189 base::string16 RenderText::Elide(const base::string16& text,
1178 // See crbug.com/327846 1190 float available_width,
1179 base::string16 RenderText::ElideText(const base::string16& text) { 1191 ElideBehavior behavior) {
1180 const bool insert_ellipsis = (elide_behavior_ != TRUNCATE); 1192 if (available_width <= 0 || text.empty())
1193 return base::string16();
1194 if (behavior == ELIDE_EMAIL)
1195 return ElideEmail(text, available_width);
1196
1181 // Create a RenderText copy with attributes that affect the rendering width. 1197 // Create a RenderText copy with attributes that affect the rendering width.
1182 scoped_ptr<RenderText> render_text(CreateInstance()); 1198 scoped_ptr<RenderText> render_text(CreateInstance());
1183 render_text->SetFontList(font_list_); 1199 render_text->SetFontList(font_list_);
1184 render_text->SetDirectionalityMode(directionality_mode_); 1200 render_text->SetDirectionalityMode(directionality_mode_);
1185 render_text->SetCursorEnabled(cursor_enabled_); 1201 render_text->SetCursorEnabled(cursor_enabled_);
1186 1202 render_text->set_truncate_length(truncate_length_);
1187 render_text->styles_ = styles_; 1203 render_text->styles_ = styles_;
1188 render_text->colors_ = colors_; 1204 render_text->colors_ = colors_;
1189 render_text->SetText(text); 1205 render_text->SetText(text);
1190 const int current_text_pixel_width = render_text->GetContentWidth(); 1206 if (render_text->GetContentWidth() <= available_width)
1207 return text;
1191 1208
1192 const base::string16 ellipsis = base::string16(kEllipsisUTF16); 1209 const base::string16 ellipsis = base::string16(kEllipsisUTF16);
1193 const bool elide_in_middle = false; 1210 const bool insert_ellipsis = (behavior != TRUNCATE);
1194 const bool elide_at_beginning = false; 1211 const bool elide_in_middle = (behavior == ELIDE_MIDDLE);
1212 const bool elide_at_beginning = (behavior == ELIDE_HEAD);
1195 StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning); 1213 StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning);
1196 1214
1197 // Pango will return 0 width for absurdly long strings. Cut the string in 1215 render_text->SetText(ellipsis);
1198 // half and try again. 1216 const float ellipsis_width = render_text->GetContentWidth();
1199 // This is caused by an int overflow in Pango (specifically, in
1200 // pango_glyph_string_extents_range). It's actually more subtle than just
1201 // returning 0, since on super absurdly long strings, the int can wrap and
1202 // return positive numbers again. Detecting that is probably not worth it
1203 // (eliding way too much from a ridiculous string is probably still
1204 // ridiculous), but we should check other widths for bogus values as well.
1205 if (current_text_pixel_width <= 0 && !text.empty())
1206 return ElideText(slicer.CutString(text.length() / 2, insert_ellipsis));
1207 1217
1208 if (current_text_pixel_width <= display_rect_.width()) 1218 if (insert_ellipsis && (ellipsis_width > available_width))
1209 return text;
1210
1211 render_text->SetText(base::string16());
1212 render_text->SetText(ellipsis);
1213 const int ellipsis_width = render_text->GetContentWidth();
1214
1215 if (insert_ellipsis && (ellipsis_width >= display_rect_.width()))
1216 return base::string16(); 1219 return base::string16();
1217 1220
1218 // Use binary search to compute the elided text. 1221 // Use binary search to compute the elided text.
1219 size_t lo = 0; 1222 size_t lo = 0;
1220 size_t hi = text.length() - 1; 1223 size_t hi = text.length() - 1;
1221 const base::i18n::TextDirection text_direction = GetTextDirection(); 1224 const base::i18n::TextDirection text_direction = GetTextDirection();
1222 for (size_t guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) { 1225 for (size_t guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) {
1223 // Restore styles and colors. They will be truncated to size by SetText. 1226 // Restore styles and colors. They will be truncated to size by SetText.
1224 render_text->styles_ = styles_; 1227 render_text->styles_ = styles_;
1225 render_text->colors_ = colors_; 1228 render_text->colors_ = colors_;
1226 base::string16 new_text = slicer.CutString(guess, false); 1229 base::string16 new_text =
1230 slicer.CutString(guess, insert_ellipsis && behavior != ELIDE_TAIL);
1227 render_text->SetText(new_text); 1231 render_text->SetText(new_text);
1228 1232
1229 // This has to be an additional step so that the ellipsis is rendered with 1233 // This has to be an additional step so that the ellipsis is rendered with
1230 // same style as trailing part of the text. 1234 // same style as trailing part of the text.
1231 if (insert_ellipsis) { 1235 if (insert_ellipsis && behavior == ELIDE_TAIL) {
1232 // When ellipsis follows text whose directionality is not the same as that 1236 // When ellipsis follows text whose directionality is not the same as that
1233 // of the whole text, it will be rendered with the directionality of the 1237 // of the whole text, it will be rendered with the directionality of the
1234 // whole text. Since we want ellipsis to indicate continuation of the 1238 // whole text. Since we want ellipsis to indicate continuation of the
1235 // preceding text, we force the directionality of ellipsis to be same as 1239 // preceding text, we force the directionality of ellipsis to be same as
1236 // the preceding text using LTR or RTL markers. 1240 // the preceding text using LTR or RTL markers.
1237 base::i18n::TextDirection trailing_text_direction = 1241 base::i18n::TextDirection trailing_text_direction =
1238 base::i18n::GetLastStrongCharacterDirection(new_text); 1242 base::i18n::GetLastStrongCharacterDirection(new_text);
1239 new_text.append(ellipsis); 1243 new_text.append(ellipsis);
1240 if (trailing_text_direction != text_direction) { 1244 if (trailing_text_direction != text_direction) {
1241 if (trailing_text_direction == base::i18n::LEFT_TO_RIGHT) 1245 if (trailing_text_direction == base::i18n::LEFT_TO_RIGHT)
1242 new_text += base::i18n::kLeftToRightMark; 1246 new_text += base::i18n::kLeftToRightMark;
1243 else 1247 else
1244 new_text += base::i18n::kRightToLeftMark; 1248 new_text += base::i18n::kRightToLeftMark;
1245 } 1249 }
1246 render_text->SetText(new_text); 1250 render_text->SetText(new_text);
1247 } 1251 }
1248 1252
1249 // We check the width of the whole desired string at once to ensure we 1253 // We check the width of the whole desired string at once to ensure we
1250 // handle kerning/ligatures/etc. correctly. 1254 // handle kerning/ligatures/etc. correctly.
1251 const int guess_width = render_text->GetContentWidth(); 1255 const float guess_width = render_text->GetContentWidth();
1252 if (guess_width == display_rect_.width()) 1256 if (guess_width == available_width)
1253 break; 1257 break;
1254 if (guess_width > display_rect_.width()) { 1258 if (guess_width > available_width) {
1255 hi = guess - 1; 1259 hi = guess - 1;
1256 // Move back if we are on loop terminating condition, and guess is wider 1260 // Move back on the loop terminating condition when the guess is too wide.
1257 // than available.
1258 if (hi < lo) 1261 if (hi < lo)
1259 lo = hi; 1262 lo = hi;
1260 } else { 1263 } else {
1261 lo = guess + 1; 1264 lo = guess + 1;
1262 } 1265 }
1263 } 1266 }
1264 1267
1265 return render_text->text(); 1268 return render_text->text();
1266 } 1269 }
1267 1270
1271 base::string16 RenderText::ElideEmail(const base::string16& email,
1272 float available_width) {
1273 // The returned string will have at least one character besides the ellipsis
1274 // on either side of '@'; if that's impossible, a single ellipsis is returned.
1275 // If possible, only the username is elided. Otherwise, the domain is elided
1276 // in the middle, splitting available width equally with the elided username.
1277 // If the username is short enough that it doesn't need half the available
1278 // width, the elided domain will occupy that extra width.
1279
1280 // Split the email into its local-part (username) and domain-part. The email
1281 // spec allows for @ symbols in the username under some special requirements,
1282 // but not in the domain part, so splitting at the last @ symbol is safe.
1283 const size_t split_index = email.find_last_of('@');
1284 DCHECK_NE(split_index, base::string16::npos);
1285 base::string16 username = email.substr(0, split_index);
1286 base::string16 domain = email.substr(split_index + 1);
1287 DCHECK(!username.empty());
1288 DCHECK(!domain.empty());
1289
1290 // Subtract the @ symbol from the available width as it is mandatory.
1291 const base::string16 kAtSignUTF16 = base::ASCIIToUTF16("@");
1292 available_width -= GetStringWidthF(kAtSignUTF16, font_list());
1293
1294 // Check whether eliding the domain is necessary: if eliding the username
1295 // is sufficient, the domain will not be elided.
1296 const float full_username_width = GetStringWidthF(username, font_list());
1297 const float available_domain_width = available_width -
1298 std::min(full_username_width,
1299 GetStringWidthF(username.substr(0, 1) + kEllipsisUTF16, font_list()));
1300 if (GetStringWidthF(domain, font_list()) > available_domain_width) {
1301 // Elide the domain so that it only takes half of the available width.
1302 // Should the username not need all the width available in its half, the
1303 // domain will occupy the leftover width.
1304 // If |desired_domain_width| is greater than |available_domain_width|: the
1305 // minimal username elision allowed by the specifications will not fit; thus
1306 // |desired_domain_width| must be <= |available_domain_width| at all cost.
1307 const float desired_domain_width =
1308 std::min<float>(available_domain_width,
1309 std::max<float>(available_width - full_username_width,
1310 available_width / 2));
1311 domain = Elide(domain, desired_domain_width, ELIDE_MIDDLE);
1312 // Failing to elide the domain such that at least one character remains
1313 // (other than the ellipsis itself) remains: return a single ellipsis.
1314 if (domain.length() <= 1U)
1315 return base::string16(kEllipsisUTF16);
1316 }
1317
1318 // Fit the username in the remaining width (at this point the elided username
1319 // is guaranteed to fit with at least one character remaining given all the
1320 // precautions taken earlier).
1321 available_width -= GetStringWidthF(domain, font_list());
1322 username = Elide(username, available_width, ELIDE_TAIL);
1323 return username + kAtSignUTF16 + domain;
1324 }
1325
1268 void RenderText::UpdateCachedBoundsAndOffset() { 1326 void RenderText::UpdateCachedBoundsAndOffset() {
1269 if (cached_bounds_and_offset_valid_) 1327 if (cached_bounds_and_offset_valid_)
1270 return; 1328 return;
1271 1329
1272 // TODO(ckocagil): Add support for scrolling multiline text. 1330 // TODO(ckocagil): Add support for scrolling multiline text.
1273 1331
1274 // First, set the valid flag true to calculate the current cursor bounds using 1332 // First, set the valid flag true to calculate the current cursor bounds using
1275 // the stale |display_offset_|. Applying |delta_offset| at the end of this 1333 // the stale |display_offset_|. Applying |delta_offset| at the end of this
1276 // function will set |cursor_bounds_| and |display_offset_| to correct values. 1334 // function will set |cursor_bounds_| and |display_offset_| to correct values.
1277 cached_bounds_and_offset_valid_ = true; 1335 cached_bounds_and_offset_valid_ = true;
(...skipping 37 matching lines...) Expand 10 before | Expand all | Expand 10 after
1315 cursor_bounds_ += delta_offset; 1373 cursor_bounds_ += delta_offset;
1316 } 1374 }
1317 1375
1318 void RenderText::DrawSelection(Canvas* canvas) { 1376 void RenderText::DrawSelection(Canvas* canvas) {
1319 const std::vector<Rect> sel = GetSubstringBounds(selection()); 1377 const std::vector<Rect> sel = GetSubstringBounds(selection());
1320 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i) 1378 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
1321 canvas->FillRect(*i, selection_background_focused_color_); 1379 canvas->FillRect(*i, selection_background_focused_color_);
1322 } 1380 }
1323 1381
1324 } // namespace gfx 1382 } // namespace gfx
OLDNEW
« no previous file with comments | « ui/gfx/render_text.h ('k') | ui/gfx/render_text_pango.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698