Index: ui/gfx/render_text.cc |
diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc |
index 50d29e83fe14585dc6002bc9b2a502b49d5c0b31..89acc9401ab7f9b736fd34e7af0c6558c7defc82 100644 |
--- a/ui/gfx/render_text.cc |
+++ b/ui/gfx/render_text.cc |
@@ -13,6 +13,7 @@ |
#include "base/stl_util.h" |
#include "base/strings/string_util.h" |
#include "base/strings/utf_string_conversions.h" |
+#include "base/trace_event/trace_event.h" |
#include "third_party/icu/source/common/unicode/rbbi.h" |
#include "third_party/icu/source/common/unicode/utf16.h" |
#include "third_party/skia/include/core/SkTypeface.h" |
@@ -444,7 +445,7 @@ void RenderText::SetText(const base::string16& text) { |
text_direction_ = base::i18n::UNKNOWN_DIRECTION; |
obscured_reveal_index_ = -1; |
- UpdateLayoutText(); |
+ OnTextAttributeChanged(); |
} |
void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) { |
@@ -463,7 +464,7 @@ void RenderText::SetFontList(const FontList& font_list) { |
SetStyle(UNDERLINE, (font_style & gfx::Font::UNDERLINE) != 0); |
baseline_ = kInvalidBaseline; |
cached_bounds_and_offset_valid_ = false; |
- ResetLayout(); |
+ OnLayoutTextAttributeChanged(false); |
} |
void RenderText::SetCursorEnabled(bool cursor_enabled) { |
@@ -481,7 +482,7 @@ void RenderText::SetObscured(bool obscured) { |
obscured_ = obscured; |
obscured_reveal_index_ = -1; |
cached_bounds_and_offset_valid_ = false; |
- UpdateLayoutText(); |
+ OnTextAttributeChanged(); |
} |
} |
@@ -491,13 +492,7 @@ void RenderText::SetObscuredRevealIndex(int index) { |
obscured_reveal_index_ = index; |
cached_bounds_and_offset_valid_ = false; |
- UpdateLayoutText(); |
-} |
- |
-void RenderText::SetReplaceNewlineCharsWithSymbols(bool replace) { |
- replace_newline_chars_with_symbols_ = replace; |
- cached_bounds_and_offset_valid_ = false; |
- UpdateLayoutText(); |
+ OnTextAttributeChanged(); |
} |
void RenderText::SetMultiline(bool multiline) { |
@@ -505,6 +500,7 @@ void RenderText::SetMultiline(bool multiline) { |
multiline_ = multiline; |
cached_bounds_and_offset_valid_ = false; |
lines_.clear(); |
+ OnDisplayTextAttributeChanged(); |
} |
} |
@@ -514,13 +510,14 @@ void RenderText::SetMinLineHeight(int line_height) { |
min_line_height_ = line_height; |
cached_bounds_and_offset_valid_ = false; |
lines_.clear(); |
+ OnDisplayTextAttributeChanged(); |
} |
void RenderText::SetElideBehavior(ElideBehavior elide_behavior) { |
// TODO(skanuj) : Add a test for triggering layout change. |
if (elide_behavior_ != elide_behavior) { |
elide_behavior_ = elide_behavior; |
- UpdateLayoutText(); |
+ OnDisplayTextAttributeChanged(); |
} |
} |
@@ -530,8 +527,10 @@ void RenderText::SetDisplayRect(const Rect& r) { |
baseline_ = kInvalidBaseline; |
cached_bounds_and_offset_valid_ = false; |
lines_.clear(); |
- if (elide_behavior_ != NO_ELIDE) |
- UpdateLayoutText(); |
+ if (elide_behavior_ != NO_ELIDE && |
+ elide_behavior_ != FADE_TAIL) { |
+ OnDisplayTextAttributeChanged(); |
+ } |
} |
} |
@@ -657,7 +656,11 @@ void RenderText::SetCompositionRange(const Range& composition_range) { |
Range(0, text_.length()).Contains(composition_range)); |
composition_range_.set_end(composition_range.end()); |
composition_range_.set_start(composition_range.start()); |
- ResetLayout(); |
+ // TODO(oshima|msw): Altering composition underlines shouldn't |
+ // require layout changes. It's currently necessary because |
+ // RenderTextHarfBuzz paints text decorations by run, and |
+ // RenderTextMac applies all styles during layout. |
+ OnLayoutTextAttributeChanged(false); |
} |
void RenderText::SetColor(SkColor value) { |
@@ -672,7 +675,9 @@ void RenderText::SetStyle(TextStyle style, bool value) { |
styles_[style].SetValue(value); |
cached_bounds_and_offset_valid_ = false; |
- ResetLayout(); |
+ // TODO(oshima|msw): Not all style change requires layout changes. |
+ // Consider optimizing based on the type of change. |
+ OnLayoutTextAttributeChanged(false); |
} |
void RenderText::ApplyStyle(TextStyle style, bool value, const Range& range) { |
@@ -684,7 +689,9 @@ void RenderText::ApplyStyle(TextStyle style, bool value, const Range& range) { |
styles_[style].ApplyValue(value, Range(start, end)); |
cached_bounds_and_offset_valid_ = false; |
- ResetLayout(); |
+ // TODO(oshima|msw): Not all style change requires layout changes. |
+ // Consider optimizing based on the type of change. |
+ OnLayoutTextAttributeChanged(false); |
} |
bool RenderText::GetStyle(TextStyle style) const { |
@@ -699,38 +706,15 @@ void RenderText::SetDirectionalityMode(DirectionalityMode mode) { |
directionality_mode_ = mode; |
text_direction_ = base::i18n::UNKNOWN_DIRECTION; |
cached_bounds_and_offset_valid_ = false; |
- ResetLayout(); |
+ OnLayoutTextAttributeChanged(false); |
} |
-base::i18n::TextDirection RenderText::GetTextDirection() { |
- if (text_direction_ == base::i18n::UNKNOWN_DIRECTION) { |
- switch (directionality_mode_) { |
- case DIRECTIONALITY_FROM_TEXT: |
- // Derive the direction from the display text, which differs from text() |
- // in the case of obscured (password) textfields. |
- text_direction_ = |
- base::i18n::GetFirstStrongCharacterDirection(GetLayoutText()); |
- break; |
- case DIRECTIONALITY_FROM_UI: |
- text_direction_ = base::i18n::IsRTL() ? base::i18n::RIGHT_TO_LEFT : |
- base::i18n::LEFT_TO_RIGHT; |
- break; |
- case DIRECTIONALITY_FORCE_LTR: |
- text_direction_ = base::i18n::LEFT_TO_RIGHT; |
- break; |
- case DIRECTIONALITY_FORCE_RTL: |
- text_direction_ = base::i18n::RIGHT_TO_LEFT; |
- break; |
- default: |
- NOTREACHED(); |
- } |
- } |
- |
- return text_direction_; |
+base::i18n::TextDirection RenderText::GetDisplayTextDirection() { |
+ return GetTextDirection(GetDisplayText()); |
} |
VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() { |
- return GetTextDirection() == base::i18n::LEFT_TO_RIGHT ? |
+ return GetDisplayTextDirection() == base::i18n::LEFT_TO_RIGHT ? |
CURSOR_RIGHT : CURSOR_LEFT; |
} |
@@ -787,7 +771,7 @@ void RenderText::DrawCursor(Canvas* canvas, const SelectionModel& position) { |
bool RenderText::IsValidLogicalIndex(size_t index) { |
// Check that the index is at a valid code point (not mid-surrgate-pair) and |
- // that it's not truncated from the layout text (its glyph may be shown). |
+ // that it's not truncated from the display text (its glyph may be shown). |
// |
// Indices within truncated text are disallowed so users can easily interact |
// with the underlying truncated text using the ellipsis as a proxy. This lets |
@@ -818,8 +802,10 @@ Rect RenderText::GetCursorBounds(const SelectionModel& caret, |
if (caret_pos == (caret_affinity == CURSOR_BACKWARD ? 0 : text().length())) { |
// The caret is attached to the boundary. Always return a 1-dip width caret, |
// since there is nothing to overtype. |
- if ((GetTextDirection() == base::i18n::RIGHT_TO_LEFT) == (caret_pos == 0)) |
+ if ((GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT) |
+ == (caret_pos == 0)) { |
x = size.width(); |
+ } |
} else { |
size_t grapheme_start = (caret_affinity == CURSOR_FORWARD) ? |
caret_pos : IndexOfAdjacentGrapheme(caret_pos, CURSOR_BACKWARD); |
@@ -931,7 +917,7 @@ RenderText::RenderText() |
obscured_reveal_index_(-1), |
truncate_length_(0), |
elide_behavior_(NO_ELIDE), |
- replace_newline_chars_with_symbols_(true), |
+ text_elided_(false), |
min_line_height_(0), |
multiline_(false), |
background_is_transparent_(false), |
@@ -967,15 +953,36 @@ void RenderText::SetSelectionModel(const SelectionModel& model) { |
cached_bounds_and_offset_valid_ = false; |
} |
-const base::string16& RenderText::GetLayoutText() const { |
- return layout_text_; |
+void RenderText::UpdateDisplayText(float text_width) { |
+ // TODO(oshima): Consider support eliding for multi-line text. |
+ // This requires max_line support first. |
+ if (multiline_ || |
+ elide_behavior() == NO_ELIDE || |
+ elide_behavior() == FADE_TAIL || |
+ text_width < display_rect_.width() || |
+ layout_text_.empty()) { |
+ text_elided_ = false; |
+ display_text_.clear(); |
+ return; |
+ } |
+ |
+ // This doesn't trim styles so ellipsis may get rendered as a different |
+ // style than the preceding text. See crbug.com/327850. |
+ display_text_.assign(Elide(layout_text_, |
+ text_width, |
+ static_cast<float>(display_rect_.width()), |
+ elide_behavior_)); |
+ |
+ text_elided_ = display_text_ != layout_text_; |
+ if (!text_elided_) |
+ display_text_.clear(); |
} |
const BreakList<size_t>& RenderText::GetLineBreaks() { |
if (line_breaks_.max() != 0) |
return line_breaks_; |
- const base::string16& layout_text = GetLayoutText(); |
+ const base::string16& layout_text = GetDisplayText(); |
const size_t text_length = layout_text.length(); |
line_breaks_.SetValue(0); |
line_breaks_.SetMax(text_length); |
@@ -1081,8 +1088,8 @@ std::vector<Rect> RenderText::TextBoundsToViewBounds(const Range& x) { |
HorizontalAlignment RenderText::GetCurrentHorizontalAlignment() { |
if (horizontal_alignment_ != ALIGN_TO_HEAD) |
return horizontal_alignment_; |
- return GetTextDirection() == base::i18n::RIGHT_TO_LEFT ? ALIGN_RIGHT |
- : ALIGN_LEFT; |
+ return GetDisplayTextDirection() == base::i18n::RIGHT_TO_LEFT ? |
+ ALIGN_RIGHT : ALIGN_LEFT; |
} |
Vector2d RenderText::GetAlignmentOffset(size_t line_number) { |
@@ -1111,7 +1118,7 @@ Vector2d RenderText::GetAlignmentOffset(size_t line_number) { |
lines_.back().size.height(); |
offset.set_y((display_rect_.height() - text_height) / 2); |
} else { |
- offset.set_y(GetBaseline() - GetLayoutTextBaseline()); |
+ offset.set_y(GetBaseline() - GetDisplayTextBaseline()); |
} |
return offset; |
@@ -1156,6 +1163,34 @@ void RenderText::ApplyTextShadows(internal::SkiaTextRenderer* renderer) { |
renderer->SetDrawLooper(looper.get()); |
} |
+base::i18n::TextDirection RenderText::GetTextDirection( |
+ const base::string16& text) { |
+ if (text_direction_ == base::i18n::UNKNOWN_DIRECTION) { |
+ switch (directionality_mode_) { |
+ case DIRECTIONALITY_FROM_TEXT: |
+ // Derive the direction from the display text, which differs from text() |
+ // in the case of obscured (password) textfields. |
+ text_direction_ = |
+ base::i18n::GetFirstStrongCharacterDirection(text); |
+ break; |
+ case DIRECTIONALITY_FROM_UI: |
+ text_direction_ = base::i18n::IsRTL() ? base::i18n::RIGHT_TO_LEFT : |
+ base::i18n::LEFT_TO_RIGHT; |
+ break; |
+ case DIRECTIONALITY_FORCE_LTR: |
+ text_direction_ = base::i18n::LEFT_TO_RIGHT; |
+ break; |
+ case DIRECTIONALITY_FORCE_RTL: |
+ text_direction_ = base::i18n::RIGHT_TO_LEFT; |
+ break; |
+ default: |
+ NOTREACHED(); |
+ } |
+ } |
+ |
+ return text_direction_; |
+} |
+ |
// static |
bool RenderText::RangeContainsCaret(const Range& range, |
size_t caret_pos, |
@@ -1174,8 +1209,9 @@ void RenderText::MoveCursorTo(size_t position, bool select) { |
(cursor == 0) ? CURSOR_FORWARD : CURSOR_BACKWARD)); |
} |
-void RenderText::UpdateLayoutText() { |
+void RenderText::OnTextAttributeChanged() { |
layout_text_.clear(); |
+ display_text_.clear(); |
line_breaks_.SetMax(0); |
if (obscured_) { |
@@ -1223,34 +1259,26 @@ void RenderText::UpdateLayoutText() { |
layout_text_.assign(text.substr(0, iter.getIndex()) + kEllipsisUTF16); |
} |
} |
- |
- if (elide_behavior_ != NO_ELIDE && |
- elide_behavior_ != FADE_TAIL && |
- !layout_text_.empty() && |
- GetContentWidth() > display_rect_.width()) { |
- // This doesn't trim styles so ellipsis may get rendered as a different |
- // style than the preceding text. See crbug.com/327850. |
- layout_text_.assign(Elide(layout_text_, |
- static_cast<float>(display_rect_.width()), |
- elide_behavior_)); |
- } |
- |
- // Replace the newline character with a newline symbol in single line mode. |
static const base::char16 kNewline[] = { '\n', 0 }; |
static const base::char16 kNewlineSymbol[] = { 0x2424, 0 }; |
- if (!multiline_ && replace_newline_chars_with_symbols_) |
+ if (!multiline_) |
base::ReplaceChars(layout_text_, kNewline, kNewlineSymbol, &layout_text_); |
- ResetLayout(); |
+ OnLayoutTextAttributeChanged(true); |
} |
base::string16 RenderText::Elide(const base::string16& text, |
+ float text_width, |
float available_width, |
ElideBehavior behavior) { |
if (available_width <= 0 || text.empty()) |
return base::string16(); |
if (behavior == ELIDE_EMAIL) |
return ElideEmail(text, available_width); |
+ if (text_width > 0 && text_width < available_width) |
+ return text; |
+ |
+ TRACE_EVENT0("ui", "RenderText::Elide"); |
// Create a RenderText copy with attributes that affect the rendering width. |
scoped_ptr<RenderText> render_text = CreateInstanceOfSameType(); |
@@ -1260,26 +1288,31 @@ base::string16 RenderText::Elide(const base::string16& text, |
render_text->set_truncate_length(truncate_length_); |
render_text->styles_ = styles_; |
render_text->colors_ = colors_; |
- render_text->SetText(text); |
- if (render_text->GetContentWidthF() <= available_width) |
+ if (text_width == 0) { |
+ render_text->SetText(text); |
+ text_width = render_text->GetContentWidthF(); |
+ } |
+ if (text_width <= available_width) |
return text; |
const base::string16 ellipsis = base::string16(kEllipsisUTF16); |
const bool insert_ellipsis = (behavior != TRUNCATE); |
const bool elide_in_middle = (behavior == ELIDE_MIDDLE); |
const bool elide_at_beginning = (behavior == ELIDE_HEAD); |
- StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning); |
- render_text->SetText(ellipsis); |
- const float ellipsis_width = render_text->GetContentWidthF(); |
+ if (insert_ellipsis) { |
+ render_text->SetText(ellipsis); |
+ const float ellipsis_width = render_text->GetContentWidthF(); |
+ if (ellipsis_width > available_width) |
+ return base::string16(); |
+ } |
- if (insert_ellipsis && (ellipsis_width > available_width)) |
- return base::string16(); |
+ StringSlicer slicer(text, ellipsis, elide_in_middle, elide_at_beginning); |
// Use binary search to compute the elided text. |
size_t lo = 0; |
size_t hi = text.length() - 1; |
- const base::i18n::TextDirection text_direction = GetTextDirection(); |
+ const base::i18n::TextDirection text_direction = GetTextDirection(text); |
for (size_t guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) { |
// Restore colors. They will be truncated to size by SetText. |
render_text->colors_ = colors_; |
@@ -1384,7 +1417,7 @@ base::string16 RenderText::ElideEmail(const base::string16& email, |
std::min<float>(available_domain_width, |
std::max<float>(available_width - full_username_width, |
available_width / 2)); |
- domain = Elide(domain, desired_domain_width, ELIDE_MIDDLE); |
+ domain = Elide(domain, 0, desired_domain_width, ELIDE_MIDDLE); |
// Failing to elide the domain such that at least one character remains |
// (other than the ellipsis itself) remains: return a single ellipsis. |
if (domain.length() <= 1U) |
@@ -1395,7 +1428,7 @@ base::string16 RenderText::ElideEmail(const base::string16& email, |
// is guaranteed to fit with at least one character remaining given all the |
// precautions taken earlier). |
available_width -= GetStringWidthF(domain, font_list()); |
- username = Elide(username, available_width, ELIDE_TAIL); |
+ username = Elide(username, 0, available_width, ELIDE_TAIL); |
return username + kAtSignUTF16 + domain; |
} |
@@ -1426,9 +1459,8 @@ void RenderText::UpdateCachedBoundsAndOffset() { |
} |
void RenderText::DrawSelection(Canvas* canvas) { |
- const std::vector<Rect> sel = GetSubstringBounds(selection()); |
- for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i) |
- canvas->FillRect(*i, selection_background_focused_color_); |
+ for (const Rect& s : GetSubstringBounds(selection())) |
+ canvas->FillRect(s, selection_background_focused_color_); |
} |
} // namespace gfx |