OLD | NEW |
---|---|
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 Loading... | |
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 |
OLD | NEW |