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

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: Reorder RenderText::Elide impl to match decl. 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
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 830 matching lines...) Expand 10 before | Expand all | Expand 10 after
866 } 865 }
867 866
868 SelectionModel RenderText::GetSelectionModelForSelectionStart() { 867 SelectionModel RenderText::GetSelectionModelForSelectionStart() {
869 const Range& sel = selection(); 868 const Range& sel = selection();
870 if (sel.is_empty()) 869 if (sel.is_empty())
871 return selection_model_; 870 return selection_model_;
872 return SelectionModel(sel.start(), 871 return SelectionModel(sel.start(),
873 sel.is_reversed() ? CURSOR_BACKWARD : CURSOR_FORWARD); 872 sel.is_reversed() ? CURSOR_BACKWARD : CURSOR_FORWARD);
874 } 873 }
875 874
875 base::string16 RenderText::Elide(const base::string16& text,
876 float available_width,
877 ElideBehavior behavior) {
878 if (text.empty() || behavior == FADE_TAIL)
879 return text;
880 if (available_width <= 0)
881 return base::string16();
882 if (behavior == ELIDE_EMAIL)
883 return ElideEmail(text, available_width);
884
885 // Create a RenderText copy with attributes that affect the rendering width.
886 scoped_ptr<RenderText> render_text(CreateInstance());
887 render_text->SetFontList(font_list_);
888 render_text->SetDirectionalityMode(directionality_mode_);
889 render_text->SetCursorEnabled(cursor_enabled_);
890 render_text->styles_ = styles_;
891 render_text->colors_ = colors_;
892 render_text->SetText(text);
893 const int current_text_pixel_width = render_text->GetContentWidth();
894
895 const base::string16 ellipsis = base::string16(kEllipsisUTF16);
896 const bool insert_ellipsis = (behavior != TRUNCATE);
897 const bool elide_in_middle = (behavior == ELIDE_MIDDLE);
898 const bool elide_at_beginning = (behavior == ELIDE_HEAD);
899 StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning);
900
901 // Pango will return 0 width for absurdly long strings. Cut the string in
902 // half and try again.
903 // This is caused by an int overflow in Pango (specifically, in
904 // pango_glyph_string_extents_range). It's actually more subtle than just
905 // returning 0, since on super absurdly long strings, the int can wrap and
906 // return positive numbers again. Detecting that is probably not worth it
907 // (eliding way too much from a ridiculous string is probably still
908 // ridiculous), but we should check other widths for bogus values as well.
909 if (current_text_pixel_width <= 0 && !text.empty())
910 return Elide(slicer.CutString(text.length() / 2, insert_ellipsis),
911 available_width, behavior);
912
913 if (current_text_pixel_width <= available_width)
914 return text;
915
916 render_text->SetText(ellipsis);
917 const int ellipsis_width = render_text->GetContentWidth();
918
919 if (insert_ellipsis && (ellipsis_width > available_width))
920 return base::string16();
921
922 // Use binary search to compute the elided text.
923 size_t lo = 0;
924 size_t hi = text.length() - 1;
925 const base::i18n::TextDirection text_direction = GetTextDirection();
926 for (size_t guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) {
927 // Restore styles and colors. They will be truncated to size by SetText.
928 render_text->styles_ = styles_;
929 render_text->colors_ = colors_;
930 base::string16 new_text =
931 slicer.CutString(guess, insert_ellipsis && behavior != ELIDE_TAIL);
932 render_text->SetText(new_text);
933
934 // This has to be an additional step so that the ellipsis is rendered with
935 // same style as trailing part of the text.
936 if (insert_ellipsis && behavior == ELIDE_TAIL) {
937 // When ellipsis follows text whose directionality is not the same as that
938 // of the whole text, it will be rendered with the directionality of the
939 // whole text. Since we want ellipsis to indicate continuation of the
940 // preceding text, we force the directionality of ellipsis to be same as
941 // the preceding text using LTR or RTL markers.
942 base::i18n::TextDirection trailing_text_direction =
943 base::i18n::GetLastStrongCharacterDirection(new_text);
944 new_text.append(ellipsis);
945 if (trailing_text_direction != text_direction) {
946 if (trailing_text_direction == base::i18n::LEFT_TO_RIGHT)
947 new_text += base::i18n::kLeftToRightMark;
948 else
949 new_text += base::i18n::kRightToLeftMark;
950 }
951 render_text->SetText(new_text);
952 }
953
954 // We check the width of the whole desired string at once to ensure we
955 // handle kerning/ligatures/etc. correctly.
956 const int guess_width = render_text->GetContentWidth();
957 if (guess_width == available_width)
958 break;
959 if (guess_width > available_width) {
960 hi = guess - 1;
961 // Move back if we are on loop terminating condition, and guess is wider
962 // than available.
963 if (hi < lo)
964 lo = hi;
965 } else {
966 lo = guess + 1;
967 }
968 }
969
970 return render_text->text();
971 }
972
876 RenderText::RenderText() 973 RenderText::RenderText()
877 : horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT), 974 : horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT),
878 directionality_mode_(DIRECTIONALITY_FROM_TEXT), 975 directionality_mode_(DIRECTIONALITY_FROM_TEXT),
879 text_direction_(base::i18n::UNKNOWN_DIRECTION), 976 text_direction_(base::i18n::UNKNOWN_DIRECTION),
880 cursor_enabled_(true), 977 cursor_enabled_(true),
881 cursor_visible_(false), 978 cursor_visible_(false),
882 insert_mode_(true), 979 insert_mode_(true),
883 cursor_color_(kDefaultColor), 980 cursor_color_(kDefaultColor),
884 selection_color_(kDefaultColor), 981 selection_color_(kDefaultColor),
885 selection_background_focused_color_(kDefaultSelectionBackgroundColor), 982 selection_background_focused_color_(kDefaultSelectionBackgroundColor),
(...skipping 276 matching lines...) Expand 10 before | Expand all | Expand 10 after
1162 icu::StringCharacterIterator iter(text.c_str()); 1259 icu::StringCharacterIterator iter(text.c_str());
1163 iter.setIndex32(truncate_length_ - 1); 1260 iter.setIndex32(truncate_length_ - 1);
1164 layout_text_.assign(text.substr(0, iter.getIndex()) + kEllipsisUTF16); 1261 layout_text_.assign(text.substr(0, iter.getIndex()) + kEllipsisUTF16);
1165 } 1262 }
1166 1263
1167 if (elide_behavior_ != TRUNCATE && elide_behavior_ != FADE_TAIL && 1264 if (elide_behavior_ != TRUNCATE && elide_behavior_ != FADE_TAIL &&
1168 display_rect_.width() > 0 && !layout_text_.empty() && 1265 display_rect_.width() > 0 && !layout_text_.empty() &&
1169 GetContentWidth() > display_rect_.width()) { 1266 GetContentWidth() > display_rect_.width()) {
1170 // This doesn't trim styles so ellipsis may get rendered as a different 1267 // This doesn't trim styles so ellipsis may get rendered as a different
1171 // style than the preceding text. See crbug.com/327850. 1268 // style than the preceding text. See crbug.com/327850.
1172 layout_text_.assign(ElideText(layout_text_)); 1269 layout_text_.assign(
1270 Elide(layout_text_, display_rect_.width(), elide_behavior_));
1173 } 1271 }
1174 1272
1175 ResetLayout(); 1273 ResetLayout();
1176 } 1274 }
1177 1275
1178 // TODO(skanuj): Fix code duplication with ElideText in ui/gfx/text_elider.cc 1276 base::string16 RenderText::ElideEmail(const base::string16& email,
1179 // See crbug.com/327846 1277 float available_width) {
1180 base::string16 RenderText::ElideText(const base::string16& text) { 1278 // The returned string will have at least one character besides the ellipses
1181 const bool insert_ellipsis = (elide_behavior_ != TRUNCATE); 1279 // on either side of '@'; if that's impossible a single ellipsis is returned.
1182 // Create a RenderText copy with attributes that affect the rendering width. 1280 // If possible, only the username is elided. Otherwise, the domain is elided
1183 scoped_ptr<RenderText> render_text(CreateInstance()); 1281 // in the middle, splitting available width equally with the elided username.
1184 render_text->SetFontList(font_list_); 1282 // If the username is short enough that it doesn't need half the available
1185 render_text->SetDirectionalityMode(directionality_mode_); 1283 // width, the elided domain will occupy that extra width.
1186 render_text->SetCursorEnabled(cursor_enabled_);
1187 1284
1188 render_text->styles_ = styles_; 1285 // Split the email into its local-part (username) and domain-part. The email
1189 render_text->colors_ = colors_; 1286 // spec allows for @ symbols in the username under some special requirements,
1190 render_text->SetText(text); 1287 // but not in the domain part, so splitting at the last @ symbol is safe.
1191 const int current_text_pixel_width = render_text->GetContentWidth(); 1288 const size_t split_index = email.find_last_of('@');
1289 DCHECK_NE(split_index, base::string16::npos);
1290 base::string16 username = email.substr(0, split_index);
1291 base::string16 domain = email.substr(split_index + 1);
1292 DCHECK(!username.empty());
1293 DCHECK(!domain.empty());
1192 1294
1193 const base::string16 ellipsis = base::string16(kEllipsisUTF16); 1295 // Subtract the @ symbol from the available width as it is mandatory.
1194 const bool elide_in_middle = false; 1296 const base::string16 kAtSignUTF16 = base::ASCIIToUTF16("@");
1195 const bool elide_at_beginning = false; 1297 available_width -= GetStringWidthF(kAtSignUTF16, font_list());
1196 StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning);
1197 1298
1198 // Pango will return 0 width for absurdly long strings. Cut the string in 1299 // Check whether eliding the domain is necessary: if eliding the username
1199 // half and try again. 1300 // is sufficient, the domain will not be elided.
1200 // This is caused by an int overflow in Pango (specifically, in 1301 const float full_username_width = GetStringWidthF(username, font_list());
1201 // pango_glyph_string_extents_range). It's actually more subtle than just 1302 const float available_domain_width = available_width -
1202 // returning 0, since on super absurdly long strings, the int can wrap and 1303 std::min(full_username_width,
1203 // return positive numbers again. Detecting that is probably not worth it 1304 GetStringWidthF(username.substr(0, 1) + kEllipsisUTF16, font_list()));
1204 // (eliding way too much from a ridiculous string is probably still 1305 if (GetStringWidthF(domain, font_list()) > available_domain_width) {
1205 // ridiculous), but we should check other widths for bogus values as well. 1306 // Elide the domain so that it only takes half of the available width.
1206 if (current_text_pixel_width <= 0 && !text.empty()) 1307 // Should the username not need all the width available in its half, the
1207 return ElideText(slicer.CutString(text.length() / 2, insert_ellipsis)); 1308 // domain will occupy the leftover width.
1208 1309 // If |desired_domain_width| is greater than |available_domain_width|: the
1209 if (current_text_pixel_width <= display_rect_.width()) 1310 // minimal username elision allowed by the specifications will not fit; thus
1210 return text; 1311 // |desired_domain_width| must be <= |available_domain_width| at all cost.
1211 1312 const float desired_domain_width =
1212 render_text->SetText(base::string16()); 1313 std::min<float>(available_domain_width,
1213 render_text->SetText(ellipsis); 1314 std::max<float>(available_width - full_username_width,
1214 const int ellipsis_width = render_text->GetContentWidth(); 1315 available_width / 2));
1215 1316 domain = Elide(domain, desired_domain_width, ELIDE_MIDDLE);
1216 if (insert_ellipsis && (ellipsis_width >= display_rect_.width())) 1317 // Failing to elide the domain such that at least one character remains
1217 return base::string16(); 1318 // (other than the ellipsis itself) remains: return a single ellipsis.
1218 1319 if (domain.length() <= 1U)
1219 // Use binary search to compute the elided text. 1320 return base::string16(kEllipsisUTF16);
1220 size_t lo = 0;
1221 size_t hi = text.length() - 1;
1222 const base::i18n::TextDirection text_direction = GetTextDirection();
1223 for (size_t guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) {
1224 // Restore styles and colors. They will be truncated to size by SetText.
1225 render_text->styles_ = styles_;
1226 render_text->colors_ = colors_;
1227 base::string16 new_text = slicer.CutString(guess, false);
1228 render_text->SetText(new_text);
1229
1230 // This has to be an additional step so that the ellipsis is rendered with
1231 // same style as trailing part of the text.
1232 if (insert_ellipsis) {
1233 // When ellipsis follows text whose directionality is not the same as that
1234 // of the whole text, it will be rendered with the directionality of the
1235 // whole text. Since we want ellipsis to indicate continuation of the
1236 // preceding text, we force the directionality of ellipsis to be same as
1237 // the preceding text using LTR or RTL markers.
1238 base::i18n::TextDirection trailing_text_direction =
1239 base::i18n::GetLastStrongCharacterDirection(new_text);
1240 new_text.append(ellipsis);
1241 if (trailing_text_direction != text_direction) {
1242 if (trailing_text_direction == base::i18n::LEFT_TO_RIGHT)
1243 new_text += base::i18n::kLeftToRightMark;
1244 else
1245 new_text += base::i18n::kRightToLeftMark;
1246 }
1247 render_text->SetText(new_text);
1248 }
1249
1250 // We check the width of the whole desired string at once to ensure we
1251 // handle kerning/ligatures/etc. correctly.
1252 const int guess_width = render_text->GetContentWidth();
1253 if (guess_width == display_rect_.width())
1254 break;
1255 if (guess_width > display_rect_.width()) {
1256 hi = guess - 1;
1257 // Move back if we are on loop terminating condition, and guess is wider
1258 // than available.
1259 if (hi < lo)
1260 lo = hi;
1261 } else {
1262 lo = guess + 1;
1263 }
1264 } 1321 }
1265 1322
1266 return render_text->text(); 1323 // Fit the username in the remaining width (at this point the elided username
1324 // is guaranteed to fit with at least one character remaining given all the
1325 // precautions taken earlier).
1326 available_width -= GetStringWidthF(domain, font_list());
1327 username = Elide(username, available_width, ELIDE_TAIL);
1328 return username + kAtSignUTF16 + domain;
1267 } 1329 }
1268 1330
1269 void RenderText::UpdateCachedBoundsAndOffset() { 1331 void RenderText::UpdateCachedBoundsAndOffset() {
1270 if (cached_bounds_and_offset_valid_) 1332 if (cached_bounds_and_offset_valid_)
1271 return; 1333 return;
1272 1334
1273 // TODO(ckocagil): Add support for scrolling multiline text. 1335 // TODO(ckocagil): Add support for scrolling multiline text.
1274 1336
1275 // First, set the valid flag true to calculate the current cursor bounds using 1337 // First, set the valid flag true to calculate the current cursor bounds using
1276 // the stale |display_offset_|. Applying |delta_offset| at the end of this 1338 // the stale |display_offset_|. Applying |delta_offset| at the end of this
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after
1316 cursor_bounds_ += delta_offset; 1378 cursor_bounds_ += delta_offset;
1317 } 1379 }
1318 1380
1319 void RenderText::DrawSelection(Canvas* canvas) { 1381 void RenderText::DrawSelection(Canvas* canvas) {
1320 const std::vector<Rect> sel = GetSubstringBounds(selection()); 1382 const std::vector<Rect> sel = GetSubstringBounds(selection());
1321 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i) 1383 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
1322 canvas->FillRect(*i, selection_background_focused_color_); 1384 canvas->FillRect(*i, selection_background_focused_color_);
1323 } 1385 }
1324 1386
1325 } // namespace gfx 1387 } // namespace gfx
OLDNEW
« ui/gfx/render_text.h ('K') | « ui/gfx/render_text.h ('k') | ui/gfx/text_elider.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698