Chromium Code Reviews| 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 326 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 358 // Reset selection model. SetText should always followed by SetSelectionModel | 359 // Reset selection model. SetText should always followed by SetSelectionModel |
| 359 // or SetCursorPosition in upper layer. | 360 // or SetCursorPosition in upper layer. |
| 360 SetSelectionModel(SelectionModel()); | 361 SetSelectionModel(SelectionModel()); |
| 361 | 362 |
| 362 // Invalidate the cached text direction if it depends on the text contents. | 363 // Invalidate the cached text direction if it depends on the text contents. |
| 363 if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT) | 364 if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT) |
| 364 text_direction_ = base::i18n::UNKNOWN_DIRECTION; | 365 text_direction_ = base::i18n::UNKNOWN_DIRECTION; |
| 365 | 366 |
| 366 obscured_reveal_index_ = -1; | 367 obscured_reveal_index_ = -1; |
| 367 UpdateLayoutText(); | 368 UpdateLayoutText(); |
| 368 ResetLayout(); | |
| 369 } | 369 } |
| 370 | 370 |
| 371 void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) { | 371 void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) { |
| 372 if (horizontal_alignment_ != alignment) { | 372 if (horizontal_alignment_ != alignment) { |
| 373 horizontal_alignment_ = alignment; | 373 horizontal_alignment_ = alignment; |
| 374 display_offset_ = Vector2d(); | 374 display_offset_ = Vector2d(); |
| 375 cached_bounds_and_offset_valid_ = false; | 375 cached_bounds_and_offset_valid_ = false; |
| 376 } | 376 } |
| 377 } | 377 } |
| 378 | 378 |
| (...skipping 25 matching lines...) Expand all Loading... | |
| 404 insert_mode_ = !insert_mode_; | 404 insert_mode_ = !insert_mode_; |
| 405 cached_bounds_and_offset_valid_ = false; | 405 cached_bounds_and_offset_valid_ = false; |
| 406 } | 406 } |
| 407 | 407 |
| 408 void RenderText::SetObscured(bool obscured) { | 408 void RenderText::SetObscured(bool obscured) { |
| 409 if (obscured != obscured_) { | 409 if (obscured != obscured_) { |
| 410 obscured_ = obscured; | 410 obscured_ = obscured; |
| 411 obscured_reveal_index_ = -1; | 411 obscured_reveal_index_ = -1; |
| 412 cached_bounds_and_offset_valid_ = false; | 412 cached_bounds_and_offset_valid_ = false; |
| 413 UpdateLayoutText(); | 413 UpdateLayoutText(); |
| 414 ResetLayout(); | |
| 415 } | 414 } |
| 416 } | 415 } |
| 417 | 416 |
| 418 void RenderText::SetObscuredRevealIndex(int index) { | 417 void RenderText::SetObscuredRevealIndex(int index) { |
| 419 if (obscured_reveal_index_ == index) | 418 if (obscured_reveal_index_ == index) |
| 420 return; | 419 return; |
| 421 | 420 |
| 422 obscured_reveal_index_ = index; | 421 obscured_reveal_index_ = index; |
| 423 cached_bounds_and_offset_valid_ = false; | 422 cached_bounds_and_offset_valid_ = false; |
| 424 UpdateLayoutText(); | 423 UpdateLayoutText(); |
| 425 ResetLayout(); | |
| 426 } | 424 } |
| 427 | 425 |
| 428 void RenderText::SetMultiline(bool multiline) { | 426 void RenderText::SetMultiline(bool multiline) { |
| 429 if (multiline != multiline_) { | 427 if (multiline != multiline_) { |
| 430 multiline_ = multiline; | 428 multiline_ = multiline; |
| 431 cached_bounds_and_offset_valid_ = false; | 429 cached_bounds_and_offset_valid_ = false; |
| 432 lines_.clear(); | 430 lines_.clear(); |
| 433 } | 431 } |
| 434 } | 432 } |
| 435 | 433 |
| 434 void RenderText::SetElideBehavior(ElideBehavior elide_behavior) { | |
| 435 elide_behavior_ = elide_behavior; | |
| 436 if (elide_behavior_ != gfx::NO_ELIDE) | |
|
msw
2013/12/12 19:28:29
This should check if the elide behavior changed, a
Anuj
2013/12/13 01:23:50
Good point. Thanks.
| |
| 437 UpdateLayoutText(); | |
| 438 } | |
| 439 | |
| 436 void RenderText::SetDisplayRect(const Rect& r) { | 440 void RenderText::SetDisplayRect(const Rect& r) { |
| 437 display_rect_ = r; | 441 display_rect_ = r; |
| 438 baseline_ = kInvalidBaseline; | 442 baseline_ = kInvalidBaseline; |
| 439 cached_bounds_and_offset_valid_ = false; | 443 cached_bounds_and_offset_valid_ = false; |
| 440 lines_.clear(); | 444 lines_.clear(); |
| 441 } | 445 } |
| 442 | 446 |
| 443 void RenderText::SetCursorPosition(size_t position) { | 447 void RenderText::SetCursorPosition(size_t position) { |
| 444 MoveCursorTo(position, false); | 448 MoveCursorTo(position, false); |
| 445 } | 449 } |
| (...skipping 377 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 823 selection_color_(kDefaultColor), | 827 selection_color_(kDefaultColor), |
| 824 selection_background_focused_color_(kDefaultSelectionBackgroundColor), | 828 selection_background_focused_color_(kDefaultSelectionBackgroundColor), |
| 825 focused_(false), | 829 focused_(false), |
| 826 composition_range_(Range::InvalidRange()), | 830 composition_range_(Range::InvalidRange()), |
| 827 colors_(kDefaultColor), | 831 colors_(kDefaultColor), |
| 828 styles_(NUM_TEXT_STYLES), | 832 styles_(NUM_TEXT_STYLES), |
| 829 composition_and_selection_styles_applied_(false), | 833 composition_and_selection_styles_applied_(false), |
| 830 obscured_(false), | 834 obscured_(false), |
| 831 obscured_reveal_index_(-1), | 835 obscured_reveal_index_(-1), |
| 832 truncate_length_(0), | 836 truncate_length_(0), |
| 837 elide_behavior_(NO_ELIDE), | |
| 833 multiline_(false), | 838 multiline_(false), |
| 834 fade_head_(false), | 839 fade_head_(false), |
| 835 fade_tail_(false), | 840 fade_tail_(false), |
| 836 background_is_transparent_(false), | 841 background_is_transparent_(false), |
| 837 clip_to_display_rect_(true), | 842 clip_to_display_rect_(true), |
| 838 baseline_(kInvalidBaseline), | 843 baseline_(kInvalidBaseline), |
| 839 cached_bounds_and_offset_valid_(false) { | 844 cached_bounds_and_offset_valid_(false) { |
| 840 } | 845 } |
| 841 | 846 |
| 842 const Vector2d& RenderText::GetUpdatedDisplayOffset() { | 847 const Vector2d& RenderText::GetUpdatedDisplayOffset() { |
| (...skipping 259 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1102 U16_NEXT(text_.data(), end, text_.length(), unused_char); | 1107 U16_NEXT(text_.data(), end, text_.length(), unused_char); |
| 1103 | 1108 |
| 1104 // Gets the index in |layout_text_| to be replaced. | 1109 // Gets the index in |layout_text_| to be replaced. |
| 1105 const size_t cp_start = | 1110 const size_t cp_start = |
| 1106 static_cast<size_t>(gfx::UTF16IndexToOffset(text_, 0, start)); | 1111 static_cast<size_t>(gfx::UTF16IndexToOffset(text_, 0, start)); |
| 1107 if (layout_text_.length() > cp_start) | 1112 if (layout_text_.length() > cp_start) |
| 1108 layout_text_.replace(cp_start, 1, text_.substr(start, end - start)); | 1113 layout_text_.replace(cp_start, 1, text_.substr(start, end - start)); |
| 1109 } | 1114 } |
| 1110 } | 1115 } |
| 1111 | 1116 |
| 1112 const base::string16& text = obscured_ ? layout_text_ : text_; | 1117 const base::string16& text = GetLayoutText(); |
| 1113 if (truncate_length_ > 0 && truncate_length_ < text.length()) { | 1118 if (truncate_length_ > 0 && truncate_length_ < text.length()) { |
| 1114 // Truncate the text at a valid character break and append an ellipsis. | 1119 // Truncate the text at a valid character break and append an ellipsis. |
| 1115 icu::StringCharacterIterator iter(text.c_str()); | 1120 icu::StringCharacterIterator iter(text.c_str()); |
| 1116 iter.setIndex32(truncate_length_ - 1); | 1121 iter.setIndex32(truncate_length_ - 1); |
| 1117 layout_text_.assign(text.substr(0, iter.getIndex()) + gfx::kEllipsisUTF16); | 1122 layout_text_.assign(text.substr(0, iter.getIndex()) + gfx::kEllipsisUTF16); |
| 1118 } | 1123 } |
| 1124 | |
| 1125 int content_width = GetContentWidth(); | |
|
msw
2013/12/12 19:28:29
Inline this, it's value is only needed once, condi
Anuj
2013/12/13 01:23:50
Done.
| |
| 1126 if (elide_behavior_ != NO_ELIDE && display_rect_.width() > 0 && | |
| 1127 content_width > display_rect_.width()) { | |
| 1128 base::string16 elided_text = ElideText(GetLayoutText()); | |
| 1129 | |
| 1130 // This doesn't trim styles so ellipsis may get rendered as a different | |
| 1131 // style than the preceding text. See crbug.com/327850. | |
| 1132 layout_text_.assign(elided_text); | |
| 1133 } | |
| 1134 ResetLayout(); | |
| 1135 } | |
| 1136 | |
| 1137 // TODO(skanuj): Fix code duplication with ElideText in ui/gfx/text_elider.cc | |
| 1138 // See crbug.com/327846 | |
| 1139 base::string16 RenderText::ElideText(const base::string16& text) { | |
| 1140 if (text.empty()) | |
| 1141 return text; | |
| 1142 | |
| 1143 const bool insert_ellipsis = (elide_behavior_ != TRUNCATE_AT_END); | |
| 1144 // Create a copy render text. We copy the attributes which affect the | |
|
msw
2013/12/12 19:28:29
optional nit: // Create a RenderText copy with att
Anuj
2013/12/13 01:23:50
Done.
| |
| 1145 // rendering width. | |
| 1146 scoped_ptr<RenderText> render_text(CreateInstance()); | |
| 1147 render_text->SetFontList(font_list_); | |
| 1148 render_text->SetDirectionalityMode(directionality_mode_); | |
| 1149 render_text->SetCursorEnabled(cursor_enabled_); | |
| 1150 render_text->styles_ = styles_; | |
| 1151 render_text->colors_ = colors_; | |
| 1152 render_text->SetText(text); | |
| 1153 const float current_text_pixel_width = render_text->GetContentWidth(); | |
| 1154 | |
| 1155 const base::string16 ellipsis = base::string16(kEllipsisUTF16); | |
| 1156 const bool elide_in_middle = false; | |
| 1157 StringSlicer slicer(text, ellipsis, elide_in_middle); | |
| 1158 | |
| 1159 // Pango will return 0 width for absurdly long strings. Cut the string in | |
| 1160 // half and try again. | |
| 1161 // This is caused by an int overflow in Pango (specifically, in | |
| 1162 // pango_glyph_string_extents_range). It's actually more subtle than just | |
| 1163 // returning 0, since on super absurdly long strings, the int can wrap and | |
| 1164 // return positive numbers again. Detecting that is probably not worth it | |
| 1165 // (eliding way too much from a ridiculous string is probably still | |
| 1166 // ridiculous), but we should check other widths for bogus values as well. | |
| 1167 if (current_text_pixel_width <= 0 && !text.empty()) | |
| 1168 return ElideText(slicer.CutString(text.length() / 2, insert_ellipsis)); | |
| 1169 | |
| 1170 if (current_text_pixel_width <= display_rect_.width()) | |
| 1171 return text; | |
| 1172 | |
| 1173 if (insert_ellipsis && | |
| 1174 GetStringWidthF(ellipsis, font_list_) > display_rect_.width()) | |
| 1175 return base::string16(); | |
| 1176 | |
| 1177 // Use binary search to compute the elided text. | |
| 1178 size_t lo = 0; | |
| 1179 size_t hi = text.length() - 1; | |
| 1180 | |
| 1181 for (size_t guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) { | |
| 1182 // Restore styles and colors. They will be truncated to size by SetText. | |
| 1183 render_text->styles_ = styles_; | |
| 1184 render_text->colors_ = colors_; | |
| 1185 base::string16 new_text = slicer.CutString(guess, false); | |
| 1186 render_text->SetText(new_text); | |
|
msw
2013/12/12 19:28:29
Oh darn, I think you'll need to (conditionally?) r
Anuj
2013/12/13 01:23:50
No. The unconditional reapplying of original style
msw
2013/12/13 01:56:08
Gah, I just missed copying done above... Sorry for
Anuj
2013/12/13 02:23:45
Done.
| |
| 1187 | |
| 1188 // This has to be an additional step so that the ellipsis is rendered with | |
| 1189 // same style as trailing part of the text. | |
| 1190 if (insert_ellipsis) { | |
| 1191 | |
|
msw
2013/12/12 19:28:29
nit: remove this blank line.
Anuj
2013/12/13 01:23:50
Done.
| |
| 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 += 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(); | |
| 1119 } | 1228 } |
| 1120 | 1229 |
| 1121 void RenderText::UpdateCachedBoundsAndOffset() { | 1230 void RenderText::UpdateCachedBoundsAndOffset() { |
| 1122 if (cached_bounds_and_offset_valid_) | 1231 if (cached_bounds_and_offset_valid_) |
| 1123 return; | 1232 return; |
| 1124 | 1233 |
| 1125 // TODO(ckocagil): Add support for scrolling multiline text. | 1234 // TODO(ckocagil): Add support for scrolling multiline text. |
| 1126 | 1235 |
| 1127 // 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 |
| 1128 // 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 Loading... | |
| 1167 cursor_bounds_ += delta_offset; | 1276 cursor_bounds_ += delta_offset; |
| 1168 } | 1277 } |
| 1169 | 1278 |
| 1170 void RenderText::DrawSelection(Canvas* canvas) { | 1279 void RenderText::DrawSelection(Canvas* canvas) { |
| 1171 const std::vector<Rect> sel = GetSubstringBounds(selection()); | 1280 const std::vector<Rect> sel = GetSubstringBounds(selection()); |
| 1172 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) |
| 1173 canvas->FillRect(*i, selection_background_focused_color_); | 1282 canvas->FillRect(*i, selection_background_focused_color_); |
| 1174 } | 1283 } |
| 1175 | 1284 |
| 1176 } // namespace gfx | 1285 } // namespace gfx |
| OLD | NEW |