| 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
|
|
|