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 "base/strings/utf_string_conversions.h" |
13 #include "third_party/icu/source/common/unicode/rbbi.h" | 14 #include "third_party/icu/source/common/unicode/rbbi.h" |
14 #include "third_party/icu/source/common/unicode/utf16.h" | 15 #include "third_party/icu/source/common/unicode/utf16.h" |
15 #include "third_party/skia/include/core/SkTypeface.h" | 16 #include "third_party/skia/include/core/SkTypeface.h" |
16 #include "third_party/skia/include/effects/SkGradientShader.h" | 17 #include "third_party/skia/include/effects/SkGradientShader.h" |
17 #include "ui/gfx/canvas.h" | 18 #include "ui/gfx/canvas.h" |
18 #include "ui/gfx/insets.h" | 19 #include "ui/gfx/insets.h" |
19 #include "ui/gfx/skia_util.h" | 20 #include "ui/gfx/skia_util.h" |
20 #include "ui/gfx/text_constants.h" | 21 #include "ui/gfx/text_constants.h" |
21 #include "ui/gfx/text_elider.h" | 22 #include "ui/gfx/text_elider.h" |
| 23 #include "ui/gfx/text_utils.h" |
22 #include "ui/gfx/utf16_indexing.h" | 24 #include "ui/gfx/utf16_indexing.h" |
23 | 25 |
24 namespace gfx { | 26 namespace gfx { |
25 | 27 |
26 namespace { | 28 namespace { |
27 | 29 |
28 // All chars are replaced by this char when the password style is set. | 30 // 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, '*' | 31 // 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). | 32 // that's available in the font (find_invisible_char() in gtkentry.c). |
31 const base::char16 kPasswordReplacementChar = '*'; | 33 const base::char16 kPasswordReplacementChar = '*'; |
(...skipping 331 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
363 // Reset selection model. SetText should always followed by SetSelectionModel | 365 // Reset selection model. SetText should always followed by SetSelectionModel |
364 // or SetCursorPosition in upper layer. | 366 // or SetCursorPosition in upper layer. |
365 SetSelectionModel(SelectionModel()); | 367 SetSelectionModel(SelectionModel()); |
366 | 368 |
367 // Invalidate the cached text direction if it depends on the text contents. | 369 // Invalidate the cached text direction if it depends on the text contents. |
368 if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT) | 370 if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT) |
369 text_direction_ = base::i18n::UNKNOWN_DIRECTION; | 371 text_direction_ = base::i18n::UNKNOWN_DIRECTION; |
370 | 372 |
371 obscured_reveal_index_ = -1; | 373 obscured_reveal_index_ = -1; |
372 UpdateLayoutText(); | 374 UpdateLayoutText(); |
373 ResetLayout(); | |
374 } | 375 } |
375 | 376 |
376 void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) { | 377 void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) { |
377 if (horizontal_alignment_ != alignment) { | 378 if (horizontal_alignment_ != alignment) { |
378 horizontal_alignment_ = alignment; | 379 horizontal_alignment_ = alignment; |
379 display_offset_ = Vector2d(); | 380 display_offset_ = Vector2d(); |
380 cached_bounds_and_offset_valid_ = false; | 381 cached_bounds_and_offset_valid_ = false; |
381 } | 382 } |
382 } | 383 } |
383 | 384 |
(...skipping 17 matching lines...) Expand all Loading... |
401 insert_mode_ = !insert_mode_; | 402 insert_mode_ = !insert_mode_; |
402 cached_bounds_and_offset_valid_ = false; | 403 cached_bounds_and_offset_valid_ = false; |
403 } | 404 } |
404 | 405 |
405 void RenderText::SetObscured(bool obscured) { | 406 void RenderText::SetObscured(bool obscured) { |
406 if (obscured != obscured_) { | 407 if (obscured != obscured_) { |
407 obscured_ = obscured; | 408 obscured_ = obscured; |
408 obscured_reveal_index_ = -1; | 409 obscured_reveal_index_ = -1; |
409 cached_bounds_and_offset_valid_ = false; | 410 cached_bounds_and_offset_valid_ = false; |
410 UpdateLayoutText(); | 411 UpdateLayoutText(); |
411 ResetLayout(); | |
412 } | 412 } |
413 } | 413 } |
414 | 414 |
415 void RenderText::SetObscuredRevealIndex(int index) { | 415 void RenderText::SetObscuredRevealIndex(int index) { |
416 if (obscured_reveal_index_ == index) | 416 if (obscured_reveal_index_ == index) |
417 return; | 417 return; |
418 | 418 |
419 obscured_reveal_index_ = index; | 419 obscured_reveal_index_ = index; |
420 cached_bounds_and_offset_valid_ = false; | 420 cached_bounds_and_offset_valid_ = false; |
421 UpdateLayoutText(); | 421 UpdateLayoutText(); |
422 ResetLayout(); | |
423 } | 422 } |
424 | 423 |
425 void RenderText::SetMultiline(bool multiline) { | 424 void RenderText::SetMultiline(bool multiline) { |
426 if (multiline != multiline_) { | 425 if (multiline != multiline_) { |
427 multiline_ = multiline; | 426 multiline_ = multiline; |
428 cached_bounds_and_offset_valid_ = false; | 427 cached_bounds_and_offset_valid_ = false; |
429 lines_.clear(); | 428 lines_.clear(); |
430 } | 429 } |
431 } | 430 } |
432 | 431 |
| 432 void RenderText::SetElideBehavior(ElideBehavior elide_behavior) { |
| 433 // TODO(skanuj) : Add a test for triggering layout change. |
| 434 if (elide_behavior_ != elide_behavior) { |
| 435 elide_behavior_ = elide_behavior; |
| 436 UpdateLayoutText(); |
| 437 } |
| 438 } |
| 439 |
433 void RenderText::SetDisplayRect(const Rect& r) { | 440 void RenderText::SetDisplayRect(const Rect& r) { |
434 display_rect_ = r; | 441 if (r != display_rect_) { |
435 baseline_ = kInvalidBaseline; | 442 display_rect_ = r; |
436 cached_bounds_and_offset_valid_ = false; | 443 baseline_ = kInvalidBaseline; |
437 lines_.clear(); | 444 cached_bounds_and_offset_valid_ = false; |
| 445 lines_.clear(); |
| 446 if (elide_behavior_ != gfx::NO_ELIDE) |
| 447 UpdateLayoutText(); |
| 448 } |
438 } | 449 } |
439 | 450 |
440 void RenderText::SetCursorPosition(size_t position) { | 451 void RenderText::SetCursorPosition(size_t position) { |
441 MoveCursorTo(position, false); | 452 MoveCursorTo(position, false); |
442 } | 453 } |
443 | 454 |
444 void RenderText::MoveCursor(BreakType break_type, | 455 void RenderText::MoveCursor(BreakType break_type, |
445 VisualCursorDirection direction, | 456 VisualCursorDirection direction, |
446 bool select) { | 457 bool select) { |
447 SelectionModel position(cursor_position(), selection_model_.caret_affinity()); | 458 SelectionModel position(cursor_position(), selection_model_.caret_affinity()); |
(...skipping 372 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
820 selection_color_(kDefaultColor), | 831 selection_color_(kDefaultColor), |
821 selection_background_focused_color_(kDefaultSelectionBackgroundColor), | 832 selection_background_focused_color_(kDefaultSelectionBackgroundColor), |
822 focused_(false), | 833 focused_(false), |
823 composition_range_(Range::InvalidRange()), | 834 composition_range_(Range::InvalidRange()), |
824 colors_(kDefaultColor), | 835 colors_(kDefaultColor), |
825 styles_(NUM_TEXT_STYLES), | 836 styles_(NUM_TEXT_STYLES), |
826 composition_and_selection_styles_applied_(false), | 837 composition_and_selection_styles_applied_(false), |
827 obscured_(false), | 838 obscured_(false), |
828 obscured_reveal_index_(-1), | 839 obscured_reveal_index_(-1), |
829 truncate_length_(0), | 840 truncate_length_(0), |
| 841 elide_behavior_(NO_ELIDE), |
830 multiline_(false), | 842 multiline_(false), |
831 fade_head_(false), | 843 fade_head_(false), |
832 fade_tail_(false), | 844 fade_tail_(false), |
833 background_is_transparent_(false), | 845 background_is_transparent_(false), |
834 clip_to_display_rect_(true), | 846 clip_to_display_rect_(true), |
835 baseline_(kInvalidBaseline), | 847 baseline_(kInvalidBaseline), |
836 cached_bounds_and_offset_valid_(false) { | 848 cached_bounds_and_offset_valid_(false) { |
837 } | 849 } |
838 | 850 |
839 const Vector2d& RenderText::GetUpdatedDisplayOffset() { | 851 const Vector2d& RenderText::GetUpdatedDisplayOffset() { |
(...skipping 258 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1098 U16_NEXT(text_.data(), end, text_.length(), unused_char); | 1110 U16_NEXT(text_.data(), end, text_.length(), unused_char); |
1099 | 1111 |
1100 // Gets the index in |layout_text_| to be replaced. | 1112 // Gets the index in |layout_text_| to be replaced. |
1101 const size_t cp_start = | 1113 const size_t cp_start = |
1102 static_cast<size_t>(gfx::UTF16IndexToOffset(text_, 0, start)); | 1114 static_cast<size_t>(gfx::UTF16IndexToOffset(text_, 0, start)); |
1103 if (layout_text_.length() > cp_start) | 1115 if (layout_text_.length() > cp_start) |
1104 layout_text_.replace(cp_start, 1, text_.substr(start, end - start)); | 1116 layout_text_.replace(cp_start, 1, text_.substr(start, end - start)); |
1105 } | 1117 } |
1106 } | 1118 } |
1107 | 1119 |
1108 const base::string16& text = obscured_ ? layout_text_ : text_; | 1120 const base::string16& text = GetLayoutText(); |
1109 if (truncate_length_ > 0 && truncate_length_ < text.length()) { | 1121 if (truncate_length_ > 0 && truncate_length_ < text.length()) { |
1110 // Truncate the text at a valid character break and append an ellipsis. | 1122 // Truncate the text at a valid character break and append an ellipsis. |
1111 icu::StringCharacterIterator iter(text.c_str()); | 1123 icu::StringCharacterIterator iter(text.c_str()); |
1112 iter.setIndex32(truncate_length_ - 1); | 1124 iter.setIndex32(truncate_length_ - 1); |
1113 layout_text_.assign(text.substr(0, iter.getIndex()) + gfx::kEllipsisUTF16); | 1125 layout_text_.assign(text.substr(0, iter.getIndex()) + gfx::kEllipsisUTF16); |
1114 } | 1126 } |
| 1127 |
| 1128 if (elide_behavior_ != NO_ELIDE && display_rect_.width() > 0 && |
| 1129 !GetLayoutText().empty() && GetContentWidth() > display_rect_.width()) { |
| 1130 base::string16 elided_text = ElideText(GetLayoutText()); |
| 1131 |
| 1132 // This doesn't trim styles so ellipsis may get rendered as a different |
| 1133 // style than the preceding text. See crbug.com/327850. |
| 1134 layout_text_.assign(elided_text); |
| 1135 } |
| 1136 ResetLayout(); |
| 1137 } |
| 1138 |
| 1139 // TODO(skanuj): Fix code duplication with ElideText in ui/gfx/text_elider.cc |
| 1140 // See crbug.com/327846 |
| 1141 base::string16 RenderText::ElideText(const base::string16& text) { |
| 1142 const bool insert_ellipsis = (elide_behavior_ != TRUNCATE_AT_END); |
| 1143 // Create a RenderText copy with attributes that affect the rendering width. |
| 1144 scoped_ptr<RenderText> render_text(CreateInstance()); |
| 1145 render_text->SetFontList(font_list_); |
| 1146 render_text->SetDirectionalityMode(directionality_mode_); |
| 1147 render_text->SetCursorEnabled(cursor_enabled_); |
| 1148 |
| 1149 render_text->styles_ = styles_; |
| 1150 render_text->colors_ = colors_; |
| 1151 render_text->SetText(text); |
| 1152 const int current_text_pixel_width = render_text->GetContentWidth(); |
| 1153 |
| 1154 const base::string16 ellipsis = base::string16(gfx::kEllipsisUTF16); |
| 1155 const bool elide_in_middle = false; |
| 1156 StringSlicer slicer(text, ellipsis, elide_in_middle); |
| 1157 |
| 1158 // Pango will return 0 width for absurdly long strings. Cut the string in |
| 1159 // half and try again. |
| 1160 // This is caused by an int overflow in Pango (specifically, in |
| 1161 // pango_glyph_string_extents_range). It's actually more subtle than just |
| 1162 // returning 0, since on super absurdly long strings, the int can wrap and |
| 1163 // return positive numbers again. Detecting that is probably not worth it |
| 1164 // (eliding way too much from a ridiculous string is probably still |
| 1165 // ridiculous), but we should check other widths for bogus values as well. |
| 1166 if (current_text_pixel_width <= 0 && !text.empty()) |
| 1167 return ElideText(slicer.CutString(text.length() / 2, insert_ellipsis)); |
| 1168 |
| 1169 if (current_text_pixel_width <= display_rect_.width()) |
| 1170 return text; |
| 1171 |
| 1172 render_text->SetText(base::string16()); |
| 1173 render_text->SetText(ellipsis); |
| 1174 const int ellipsis_width = render_text->GetContentWidth(); |
| 1175 |
| 1176 if (insert_ellipsis && (ellipsis_width >= display_rect_.width())) |
| 1177 return base::string16(); |
| 1178 |
| 1179 // Use binary search to compute the elided text. |
| 1180 size_t lo = 0; |
| 1181 size_t hi = text.length() - 1; |
| 1182 for (size_t guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) { |
| 1183 // Restore styles and colors. They will be truncated to size by SetText. |
| 1184 render_text->styles_ = styles_; |
| 1185 render_text->colors_ = colors_; |
| 1186 base::string16 new_text = slicer.CutString(guess, false); |
| 1187 render_text->SetText(new_text); |
| 1188 |
| 1189 // This has to be an additional step so that the ellipsis is rendered with |
| 1190 // same style as trailing part of the text. |
| 1191 if (insert_ellipsis) { |
| 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.append(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(); |
1115 } | 1228 } |
1116 | 1229 |
1117 void RenderText::UpdateCachedBoundsAndOffset() { | 1230 void RenderText::UpdateCachedBoundsAndOffset() { |
1118 if (cached_bounds_and_offset_valid_) | 1231 if (cached_bounds_and_offset_valid_) |
1119 return; | 1232 return; |
1120 | 1233 |
1121 // TODO(ckocagil): Add support for scrolling multiline text. | 1234 // TODO(ckocagil): Add support for scrolling multiline text. |
1122 | 1235 |
1123 // 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 |
1124 // 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... |
1163 cursor_bounds_ += delta_offset; | 1276 cursor_bounds_ += delta_offset; |
1164 } | 1277 } |
1165 | 1278 |
1166 void RenderText::DrawSelection(Canvas* canvas) { | 1279 void RenderText::DrawSelection(Canvas* canvas) { |
1167 const std::vector<Rect> sel = GetSubstringBounds(selection()); | 1280 const std::vector<Rect> sel = GetSubstringBounds(selection()); |
1168 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) |
1169 canvas->FillRect(*i, selection_background_focused_color_); | 1282 canvas->FillRect(*i, selection_background_focused_color_); |
1170 } | 1283 } |
1171 | 1284 |
1172 } // namespace gfx | 1285 } // namespace gfx |
OLD | NEW |