Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / ui / gfx / render_text.cc
index 2a65660..e7144f8 100644 (file)
@@ -7,9 +7,11 @@
 #include <algorithm>
 #include <climits>
 
+#include "base/command_line.h"
 #include "base/i18n/break_iterator.h"
 #include "base/logging.h"
 #include "base/stl_util.h"
+#include "base/strings/string_util.h"
 #include "base/strings/utf_string_conversions.h"
 #include "third_party/icu/source/common/unicode/rbbi.h"
 #include "third_party/icu/source/common/unicode/utf16.h"
 #include "third_party/skia/include/effects/SkGradientShader.h"
 #include "ui/gfx/canvas.h"
 #include "ui/gfx/insets.h"
+#include "ui/gfx/render_text_harfbuzz.h"
+#include "ui/gfx/scoped_canvas.h"
 #include "ui/gfx/skia_util.h"
-#include "ui/gfx/text_constants.h"
+#include "ui/gfx/switches.h"
 #include "ui/gfx/text_elider.h"
 #include "ui/gfx/text_utils.h"
 #include "ui/gfx/utf16_indexing.h"
@@ -74,11 +78,11 @@ int DetermineBaselineCenteringText(const Rect& display_rect,
   return baseline + std::max(min_shift, std::min(max_shift, baseline_shift));
 }
 
-// Converts |gfx::Font::FontStyle| flags to |SkTypeface::Style| flags.
+// Converts |Font::FontStyle| flags to |SkTypeface::Style| flags.
 SkTypeface::Style ConvertFontStyleToSkiaTypefaceStyle(int font_style) {
   int skia_style = SkTypeface::kNormal;
-  skia_style |= (font_style & gfx::Font::BOLD) ? SkTypeface::kBold : 0;
-  skia_style |= (font_style & gfx::Font::ITALIC) ? SkTypeface::kItalic : 0;
+  skia_style |= (font_style & Font::BOLD) ? SkTypeface::kBold : 0;
+  skia_style |= (font_style & Font::ITALIC) ? SkTypeface::kItalic : 0;
   return static_cast<SkTypeface::Style>(skia_style);
 }
 
@@ -151,6 +155,19 @@ skia::RefPtr<SkShader> CreateFadeShader(const Rect& text_rect,
                                      colors.size(), SkShader::kClamp_TileMode));
 }
 
+// Converts a FontRenderParams::Hinting value to the corresponding
+// SkPaint::Hinting value.
+SkPaint::Hinting FontRenderParamsHintingToSkPaintHinting(
+    FontRenderParams::Hinting params_hinting) {
+  switch (params_hinting) {
+    case FontRenderParams::HINTING_NONE:   return SkPaint::kNo_Hinting;
+    case FontRenderParams::HINTING_SLIGHT: return SkPaint::kSlight_Hinting;
+    case FontRenderParams::HINTING_MEDIUM: return SkPaint::kNormal_Hinting;
+    case FontRenderParams::HINTING_FULL:   return SkPaint::kFull_Hinting;
+  }
+  return SkPaint::kNo_Hinting;
+}
+
 }  // namespace
 
 namespace internal {
@@ -160,7 +177,8 @@ namespace internal {
 const SkScalar kUnderlineMetricsNotSet = -1.0f;
 
 SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas)
-    : canvas_skia_(canvas->sk_canvas()),
+    : canvas_(canvas),
+      canvas_skia_(canvas->sk_canvas()),
       started_drawing_(false),
       underline_thickness_(kUnderlineMetricsNotSet),
       underline_position_(0.0f) {
@@ -193,16 +211,14 @@ void SkiaTextRenderer::SetDrawLooper(SkDrawLooper* draw_looper) {
   paint_.setLooper(draw_looper);
 }
 
-void SkiaTextRenderer::SetFontSmoothingSettings(bool antialiasing,
-                                                bool subpixel_rendering,
-                                                bool subpixel_positioning) {
-  paint_.setAntiAlias(antialiasing);
-  paint_.setLCDRenderText(subpixel_rendering);
-  paint_.setSubpixelText(subpixel_positioning);
-}
-
-void SkiaTextRenderer::SetFontHinting(SkPaint::Hinting hinting) {
-  paint_.setHinting(hinting);
+void SkiaTextRenderer::SetFontRenderParams(const FontRenderParams& params,
+                                           bool background_is_transparent) {
+  paint_.setAntiAlias(params.antialiasing);
+  paint_.setLCDRenderText(!background_is_transparent &&
+      params.subpixel_rendering != FontRenderParams::SUBPIXEL_RENDERING_NONE);
+  paint_.setSubpixelText(params.subpixel_positioning);
+  paint_.setAutohinted(params.autohinter);
+  paint_.setHinting(FontRenderParamsHintingToSkPaintHinting(params.hinting));
 }
 
 void SkiaTextRenderer::SetTypeface(SkTypeface* typeface) {
@@ -217,17 +233,14 @@ void SkiaTextRenderer::SetFontFamilyWithStyle(const std::string& family,
                                               int style) {
   DCHECK(!family.empty());
 
-  SkTypeface::Style skia_style = ConvertFontStyleToSkiaTypefaceStyle(style);
-  skia::RefPtr<SkTypeface> typeface =
-      skia::AdoptRef(SkTypeface::CreateFromName(family.c_str(), skia_style));
+  skia::RefPtr<SkTypeface> typeface = CreateSkiaTypeface(family.c_str(), style);
   if (typeface) {
     // |paint_| adds its own ref. So don't |release()| it from the ref ptr here.
     SetTypeface(typeface.get());
 
     // Enable fake bold text if bold style is needed but new typeface does not
     // have it.
-    paint_.setFakeBoldText((skia_style & SkTypeface::kBold) &&
-                           !typeface->isBold());
+    paint_.setFakeBoldText((style & Font::BOLD) && !typeface->isBold());
   }
 }
 
@@ -279,8 +292,20 @@ void SkiaTextRenderer::DrawDecorations(int x, int y, int width, bool underline,
     DrawUnderline(x, y, width);
   if (strike)
     DrawStrike(x, y, width);
-  if (diagonal_strike)
-    DrawDiagonalStrike(x, y, width);
+  if (diagonal_strike) {
+    if (!diagonal_)
+      diagonal_.reset(new DiagonalStrike(canvas_, Point(x, y), paint_));
+    diagonal_->AddPiece(width, paint_.getColor());
+  } else if (diagonal_) {
+    EndDiagonalStrike();
+  }
+}
+
+void SkiaTextRenderer::EndDiagonalStrike() {
+  if (diagonal_) {
+    diagonal_->Draw();
+    diagonal_.reset();
+  }
 }
 
 void SkiaTextRenderer::DrawUnderline(int x, int y, int width) {
@@ -302,15 +327,55 @@ void SkiaTextRenderer::DrawStrike(int x, int y, int width) const {
   canvas_skia_->drawRect(r, paint_);
 }
 
-void SkiaTextRenderer::DrawDiagonalStrike(int x, int y, int width) const {
+SkiaTextRenderer::DiagonalStrike::DiagonalStrike(Canvas* canvas,
+                                                 Point start,
+                                                 const SkPaint& paint)
+    : canvas_(canvas),
+      start_(start),
+      paint_(paint),
+      total_length_(0) {
+}
+
+SkiaTextRenderer::DiagonalStrike::~DiagonalStrike() {
+}
+
+void SkiaTextRenderer::DiagonalStrike::AddPiece(int length, SkColor color) {
+  pieces_.push_back(Piece(length, color));
+  total_length_ += length;
+}
+
+void SkiaTextRenderer::DiagonalStrike::Draw() {
   const SkScalar text_size = paint_.getTextSize();
   const SkScalar offset = SkScalarMul(text_size, kDiagonalStrikeMarginOffset);
+  const int thickness =
+      SkScalarCeilToInt(SkScalarMul(text_size, kLineThickness) * 2);
+  const int height = SkScalarCeilToInt(text_size - offset);
+  const Point end = start_ + Vector2d(total_length_, -height);
+  const int clip_height = height + 2 * thickness;
+
+  paint_.setAntiAlias(true);
+  paint_.setStrokeWidth(thickness);
+
+  const bool clipped = pieces_.size() > 1;
+  SkCanvas* sk_canvas = canvas_->sk_canvas();
+  int x = start_.x();
+
+  for (size_t i = 0; i < pieces_.size(); ++i) {
+    paint_.setColor(pieces_[i].second);
 
-  SkPaint paint(paint_);
-  paint.setAntiAlias(true);
-  paint.setStyle(SkPaint::kFill_Style);
-  paint.setStrokeWidth(SkScalarMul(text_size, kLineThickness) * 2);
-  canvas_skia_->drawLine(x, y, x + width, y - text_size + offset, paint);
+    if (clipped) {
+      canvas_->Save();
+      sk_canvas->clipRect(RectToSkRect(
+          Rect(x, end.y() - thickness, pieces_[i].first, clip_height)));
+    }
+
+    canvas_->DrawLine(start_, end, paint_);
+
+    if (clipped)
+      canvas_->Restore();
+
+    x += pieces_[i].first;
+  }
 }
 
 StyleIterator::StyleIterator(const BreakList<SkColor>& colors,
@@ -345,11 +410,30 @@ Line::Line() : preceding_heights(0), baseline(0) {}
 
 Line::~Line() {}
 
+skia::RefPtr<SkTypeface> CreateSkiaTypeface(const std::string& family,
+                                            int style) {
+  SkTypeface::Style skia_style = ConvertFontStyleToSkiaTypefaceStyle(style);
+  return skia::AdoptRef(SkTypeface::CreateFromName(family.c_str(), skia_style));
+}
+
 }  // namespace internal
 
 RenderText::~RenderText() {
 }
 
+RenderText* RenderText::CreateInstance() {
+#if defined(OS_MACOSX) && defined(TOOLKIT_VIEWS)
+  // Use the more complete HarfBuzz implementation for Views controls on Mac.
+  return new RenderTextHarfBuzz;
+#else
+  if (CommandLine::ForCurrentProcess()->HasSwitch(
+          switches::kEnableHarfBuzzRenderText)) {
+    return new RenderTextHarfBuzz;
+  }
+  return CreateNativeInstance();
+#endif
+}
+
 void RenderText::SetText(const base::string16& text) {
   DCHECK(!composition_range_.IsValid());
   if (text_ == text)
@@ -385,6 +469,10 @@ void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) {
 
 void RenderText::SetFontList(const FontList& font_list) {
   font_list_ = font_list;
+  const int font_style = font_list.GetFontStyle();
+  SetStyle(BOLD, (font_style & gfx::Font::BOLD) != 0);
+  SetStyle(ITALIC, (font_style & gfx::Font::ITALIC) != 0);
+  SetStyle(UNDERLINE, (font_style & gfx::Font::UNDERLINE) != 0);
   baseline_ = kInvalidBaseline;
   cached_bounds_and_offset_valid_ = false;
   ResetLayout();
@@ -418,6 +506,12 @@ void RenderText::SetObscuredRevealIndex(int index) {
   UpdateLayoutText();
 }
 
+void RenderText::SetReplaceNewlineCharsWithSymbols(bool replace) {
+  replace_newline_chars_with_symbols_ = replace;
+  cached_bounds_and_offset_valid_ = false;
+  UpdateLayoutText();
+}
+
 void RenderText::SetMultiline(bool multiline) {
   if (multiline != multiline_) {
     multiline_ = multiline;
@@ -440,7 +534,7 @@ void RenderText::SetDisplayRect(const Rect& r) {
     baseline_ = kInvalidBaseline;
     cached_bounds_and_offset_valid_ = false;
     lines_.clear();
-    if (elide_behavior_ != gfx::NO_ELIDE)
+    if (elide_behavior_ != NO_ELIDE)
       UpdateLayoutText();
   }
 }
@@ -452,26 +546,28 @@ void RenderText::SetCursorPosition(size_t position) {
 void RenderText::MoveCursor(BreakType break_type,
                             VisualCursorDirection direction,
                             bool select) {
-  SelectionModel position(cursor_position(), selection_model_.caret_affinity());
+  SelectionModel cursor(cursor_position(), selection_model_.caret_affinity());
   // Cancelling a selection moves to the edge of the selection.
   if (break_type != LINE_BREAK && !selection().is_empty() && !select) {
     SelectionModel selection_start = GetSelectionModelForSelectionStart();
     int start_x = GetCursorBounds(selection_start, true).x();
-    int cursor_x = GetCursorBounds(position, true).x();
+    int cursor_x = GetCursorBounds(cursor, true).x();
     // Use the selection start if it is left (when |direction| is CURSOR_LEFT)
     // or right (when |direction| is CURSOR_RIGHT) of the selection end.
     if (direction == CURSOR_RIGHT ? start_x > cursor_x : start_x < cursor_x)
-      position = selection_start;
-    // For word breaks, use the nearest word boundary in the appropriate
-    // |direction|.
+      cursor = selection_start;
+    // Use the nearest word boundary in the proper |direction| for word breaks.
     if (break_type == WORD_BREAK)
-      position = GetAdjacentSelectionModel(position, break_type, direction);
+      cursor = GetAdjacentSelectionModel(cursor, break_type, direction);
+    // Use an adjacent selection model if the cursor is not at a valid position.
+    if (!IsValidCursorIndex(cursor.caret_pos()))
+      cursor = GetAdjacentSelectionModel(cursor, CHARACTER_BREAK, direction);
   } else {
-    position = GetAdjacentSelectionModel(position, break_type, direction);
+    cursor = GetAdjacentSelectionModel(cursor, break_type, direction);
   }
   if (select)
-    position.set_selection_start(selection().start());
-  MoveCursorTo(position);
+    cursor.set_selection_start(selection().start());
+  MoveCursorTo(cursor);
 }
 
 bool RenderText::MoveCursorTo(const SelectionModel& model) {
@@ -479,9 +575,8 @@ bool RenderText::MoveCursorTo(const SelectionModel& model) {
   size_t text_length = text().length();
   Range range(std::min(model.selection().start(), text_length),
               std::min(model.caret_pos(), text_length));
-  // The current model only supports caret positions at valid character indices.
-  if (!IsCursorablePosition(range.start()) ||
-      !IsCursorablePosition(range.end()))
+  // The current model only supports caret positions at valid cursor indices.
+  if (!IsValidCursorIndex(range.start()) || !IsValidCursorIndex(range.end()))
     return false;
   SelectionModel sel(range, model.caret_affinity());
   bool changed = sel != selection_model_;
@@ -489,17 +584,11 @@ bool RenderText::MoveCursorTo(const SelectionModel& model) {
   return changed;
 }
 
-bool RenderText::MoveCursorTo(const Point& point, bool select) {
-  SelectionModel position = FindCursorPosition(point);
-  if (select)
-    position.set_selection_start(selection().start());
-  return MoveCursorTo(position);
-}
-
 bool RenderText::SelectRange(const Range& range) {
   Range sel(std::min(range.start(), text().length()),
             std::min(range.end(), text().length()));
-  if (!IsCursorablePosition(sel.start()) || !IsCursorablePosition(sel.end()))
+  // Allow selection bounds at valid indicies amid multi-character graphemes.
+  if (!IsValidLogicalIndex(sel.start()) || !IsValidLogicalIndex(sel.end()))
     return false;
   LogicalCursorDirection affinity =
       (sel.is_reversed() || sel.is_empty()) ? CURSOR_FORWARD : CURSOR_BACKWARD;
@@ -677,8 +766,8 @@ SizeF RenderText::GetStringSizeF() {
   return SizeF(size.width(), size.height());
 }
 
-int RenderText::GetContentWidth() {
-  return GetStringSize().width() + (cursor_enabled_ ? 1 : 0);
+float RenderText::GetContentWidth() {
+  return GetStringSizeF().width() + (cursor_enabled_ ? 1 : 0);
 }
 
 int RenderText::GetBaseline() {
@@ -693,7 +782,7 @@ void RenderText::Draw(Canvas* canvas) {
 
   if (clip_to_display_rect()) {
     Rect clip_rect(display_rect());
-    clip_rect.Inset(ShadowValue::GetMargin(text_shadows_));
+    clip_rect.Inset(ShadowValue::GetMargin(shadows_));
 
     canvas->Save();
     canvas->ClipRect(clip_rect);
@@ -718,27 +807,19 @@ void RenderText::DrawCursor(Canvas* canvas, const SelectionModel& position) {
   canvas->FillRect(GetCursorBounds(position, true), cursor_color_);
 }
 
-void RenderText::DrawSelectedTextForDrag(Canvas* canvas) {
-  EnsureLayout();
-  const std::vector<Rect> sel = GetSubstringBounds(selection());
-
-  // Override the selection color with black, and force the background to be
-  // transparent so that it's rendered without subpixel antialiasing.
-  const bool saved_background_is_transparent = background_is_transparent();
-  const SkColor saved_selection_color = selection_color();
-  set_background_is_transparent(true);
-  set_selection_color(SK_ColorBLACK);
-
-  for (size_t i = 0; i < sel.size(); ++i) {
-    canvas->Save();
-    canvas->ClipRect(sel[i]);
-    DrawVisualText(canvas);
-    canvas->Restore();
-  }
-
-  // Restore saved transparency and selection color.
-  set_selection_color(saved_selection_color);
-  set_background_is_transparent(saved_background_is_transparent);
+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).
+  //
+  // Indices within truncated text are disallowed so users can easily interact
+  // with the underlying truncated text using the ellipsis as a proxy. This lets
+  // users select all text, select the truncated text, and transition from the
+  // last rendered glyph to the end of the text without getting invisible cursor
+  // positions nor needing unbounded arrow key presses to traverse the ellipsis.
+  return index == 0 || index == text().length() ||
+      (index < text().length() &&
+       (truncate_length_ == 0 || index < truncate_length_) &&
+       IsValidCodePointIndex(text(), index));
 }
 
 Rect RenderText::GetCursorBounds(const SelectionModel& caret,
@@ -748,9 +829,8 @@ Rect RenderText::GetCursorBounds(const SelectionModel& caret,
   //                 the multiline size, eliminate its use here.
 
   EnsureLayout();
-
   size_t caret_pos = caret.caret_pos();
-  DCHECK(IsCursorablePosition(caret_pos));
+  DCHECK(IsValidLogicalIndex(caret_pos));
   // In overtype mode, ignore the affinity and always indicate that we will
   // overtype the next character.
   LogicalCursorDirection caret_affinity =
@@ -791,7 +871,7 @@ size_t RenderText::IndexOfAdjacentGrapheme(size_t index,
   if (direction == CURSOR_FORWARD) {
     while (index < text().length()) {
       index++;
-      if (IsCursorablePosition(index))
+      if (IsValidCursorIndex(index))
         return index;
     }
     return text().length();
@@ -799,7 +879,7 @@ size_t RenderText::IndexOfAdjacentGrapheme(size_t index,
 
   while (index > 0) {
     index--;
-    if (IsCursorablePosition(index))
+    if (IsValidCursorIndex(index))
       return index;
   }
   return 0;
@@ -813,8 +893,45 @@ SelectionModel RenderText::GetSelectionModelForSelectionStart() {
                         sel.is_reversed() ? CURSOR_BACKWARD : CURSOR_FORWARD);
 }
 
-void RenderText::SetTextShadows(const ShadowValues& shadows) {
-  text_shadows_ = shadows;
+const Vector2d& RenderText::GetUpdatedDisplayOffset() {
+  UpdateCachedBoundsAndOffset();
+  return display_offset_;
+}
+
+void RenderText::SetDisplayOffset(int horizontal_offset) {
+  const int extra_content = GetContentWidth() - display_rect_.width();
+  const int cursor_width = cursor_enabled_ ? 1 : 0;
+
+  int min_offset = 0;
+  int max_offset = 0;
+  if (extra_content > 0) {
+    switch (GetCurrentHorizontalAlignment()) {
+      case ALIGN_LEFT:
+        min_offset = -extra_content;
+        break;
+      case ALIGN_RIGHT:
+        max_offset = extra_content;
+        break;
+      case ALIGN_CENTER:
+        // The extra space reserved for cursor at the end of the text is ignored
+        // when centering text. So, to calculate the valid range for offset, we
+        // exclude that extra space, calculate the range, and add it back to the
+        // range (if cursor is enabled).
+        min_offset = -(extra_content - cursor_width + 1) / 2 - cursor_width;
+        max_offset = (extra_content - cursor_width) / 2;
+        break;
+      default:
+        break;
+    }
+  }
+  if (horizontal_offset < min_offset)
+    horizontal_offset = min_offset;
+  else if (horizontal_offset > max_offset)
+    horizontal_offset = max_offset;
+
+  cached_bounds_and_offset_valid_ = true;
+  display_offset_.set_x(horizontal_offset);
+  cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_);
 }
 
 RenderText::RenderText()
@@ -836,20 +953,14 @@ RenderText::RenderText()
       obscured_reveal_index_(-1),
       truncate_length_(0),
       elide_behavior_(NO_ELIDE),
+      replace_newline_chars_with_symbols_(true),
       multiline_(false),
-      fade_head_(false),
-      fade_tail_(false),
       background_is_transparent_(false),
       clip_to_display_rect_(true),
       baseline_(kInvalidBaseline),
       cached_bounds_and_offset_valid_(false) {
 }
 
-const Vector2d& RenderText::GetUpdatedDisplayOffset() {
-  UpdateCachedBoundsAndOffset();
-  return display_offset_;
-}
-
 SelectionModel RenderText::GetAdjacentSelectionModel(
     const SelectionModel& current,
     BreakType break_type,
@@ -878,7 +989,7 @@ void RenderText::SetSelectionModel(const SelectionModel& model) {
 }
 
 const base::string16& RenderText::GetLayoutText() const {
-  return layout_text_.empty() ? text_ : layout_text_;
+  return layout_text_;
 }
 
 const BreakList<size_t>& RenderText::GetLineBreaks() {
@@ -988,13 +1099,21 @@ std::vector<Rect> RenderText::TextBoundsToViewBounds(const Range& x) {
   return rects;
 }
 
+HorizontalAlignment RenderText::GetCurrentHorizontalAlignment() {
+  if (horizontal_alignment_ != ALIGN_TO_HEAD)
+    return horizontal_alignment_;
+  return GetTextDirection() == base::i18n::RIGHT_TO_LEFT ? ALIGN_RIGHT
+                                                         : ALIGN_LEFT;
+}
+
 Vector2d RenderText::GetAlignmentOffset(size_t line_number) {
   // TODO(ckocagil): Enable |lines_| usage in other platforms.
 #if defined(OS_WIN)
   DCHECK_LT(line_number, lines_.size());
 #endif
   Vector2d offset;
-  if (horizontal_alignment_ != ALIGN_LEFT) {
+  HorizontalAlignment horizontal_alignment = GetCurrentHorizontalAlignment();
+  if (horizontal_alignment != ALIGN_LEFT) {
 #if defined(OS_WIN)
     const int width = lines_[line_number].size.width() +
         (cursor_enabled_ ? 1 : 0);
@@ -1002,8 +1121,9 @@ Vector2d RenderText::GetAlignmentOffset(size_t line_number) {
     const int width = GetContentWidth();
 #endif
     offset.set_x(display_rect().width() - width);
-    if (horizontal_alignment_ == ALIGN_CENTER)
-      offset.set_x(offset.x() / 2);
+    // Put any extra margin pixel on the left to match legacy behavior.
+    if (horizontal_alignment == ALIGN_CENTER)
+      offset.set_x((offset.x() + 1) / 2);
   }
 
   // Vertically center the text.
@@ -1019,37 +1139,24 @@ Vector2d RenderText::GetAlignmentOffset(size_t line_number) {
 }
 
 void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) {
-  if (multiline() || (!fade_head() && !fade_tail()))
-    return;
-
-  const int display_width = display_rect().width();
-
-  // If the text fits as-is, no need to fade.
-  if (GetStringSize().width() <= display_width)
+  const int width = display_rect().width();
+  if (multiline() || elide_behavior_ != FADE_TAIL || GetContentWidth() <= width)
     return;
 
-  int gradient_width = CalculateFadeGradientWidth(font_list(), display_width);
+  const int gradient_width = CalculateFadeGradientWidth(font_list(), width);
   if (gradient_width == 0)
     return;
 
-  bool fade_left = fade_head();
-  bool fade_right = fade_tail();
-  // Under RTL, |fade_right| == |fade_head|.
-  // TODO(asvitkine): This is currently not based on GetTextDirection() because
-  //                  RenderTextWin does not return a direction that's based on
-  //                  the text content.
-  if (horizontal_alignment_ == ALIGN_RIGHT)
-    std::swap(fade_left, fade_right);
-
+  HorizontalAlignment horizontal_alignment = GetCurrentHorizontalAlignment();
   Rect solid_part = display_rect();
   Rect left_part;
   Rect right_part;
-  if (fade_left) {
+  if (horizontal_alignment != ALIGN_LEFT) {
     left_part = solid_part;
     left_part.Inset(0, 0, solid_part.width() - gradient_width, 0);
     solid_part.Inset(gradient_width, 0, 0, 0);
   }
-  if (fade_right) {
+  if (horizontal_alignment != ALIGN_RIGHT) {
     right_part = solid_part;
     right_part.Inset(solid_part.width() - gradient_width, 0, 0, 0);
     solid_part.Inset(0, 0, gradient_width, 0);
@@ -1066,7 +1173,7 @@ void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) {
 }
 
 void RenderText::ApplyTextShadows(internal::SkiaTextRenderer* renderer) {
-  skia::RefPtr<SkDrawLooper> looper = CreateShadowDrawLooper(text_shadows_);
+  skia::RefPtr<SkDrawLooper> looper = CreateShadowDrawLooper(shadows_);
   renderer->SetDrawLooper(looper.get());
 }
 
@@ -1082,7 +1189,7 @@ bool RenderText::RangeContainsCaret(const Range& range,
 
 void RenderText::MoveCursorTo(size_t position, bool select) {
   size_t cursor = std::min(position, text().length());
-  if (IsCursorablePosition(cursor))
+  if (IsValidCursorIndex(cursor))
     SetSelectionModel(SelectionModel(
         Range(select ? selection().start() : cursor, cursor),
         (cursor == 0) ? CURSOR_FORWARD : CURSOR_BACKWARD));
@@ -1094,7 +1201,7 @@ void RenderText::UpdateLayoutText() {
 
   if (obscured_) {
     size_t obscured_text_length =
-        static_cast<size_t>(gfx::UTF16IndexToOffset(text_, 0, text_.length()));
+        static_cast<size_t>(UTF16IndexToOffset(text_, 0, text_.length()));
     layout_text_.assign(obscured_text_length, kPasswordReplacementChar);
 
     if (obscured_reveal_index_ >= 0 &&
@@ -1108,96 +1215,109 @@ void RenderText::UpdateLayoutText() {
 
       // Gets the index in |layout_text_| to be replaced.
       const size_t cp_start =
-          static_cast<size_t>(gfx::UTF16IndexToOffset(text_, 0, start));
+          static_cast<size_t>(UTF16IndexToOffset(text_, 0, start));
       if (layout_text_.length() > cp_start)
         layout_text_.replace(cp_start, 1, text_.substr(start, end - start));
     }
+  } else {
+    layout_text_ = text_;
   }
 
-  const base::string16& text = GetLayoutText();
+  const base::string16& text = layout_text_;
   if (truncate_length_ > 0 && truncate_length_ < text.length()) {
     // Truncate the text at a valid character break and append an ellipsis.
     icu::StringCharacterIterator iter(text.c_str());
-    iter.setIndex32(truncate_length_ - 1);
-    layout_text_.assign(text.substr(0, iter.getIndex()) + gfx::kEllipsisUTF16);
+    // Respect ELIDE_HEAD and ELIDE_MIDDLE preferences during truncation.
+    if (elide_behavior_ == ELIDE_HEAD) {
+      iter.setIndex32(text.length() - truncate_length_ + 1);
+      layout_text_.assign(kEllipsisUTF16 + text.substr(iter.getIndex()));
+    } else if (elide_behavior_ == ELIDE_MIDDLE) {
+      iter.setIndex32(truncate_length_ / 2);
+      const size_t ellipsis_start = iter.getIndex();
+      iter.setIndex32(text.length() - (truncate_length_ / 2));
+      const size_t ellipsis_end = iter.getIndex();
+      DCHECK_LE(ellipsis_start, ellipsis_end);
+      layout_text_.assign(text.substr(0, ellipsis_start) + kEllipsisUTF16 +
+                          text.substr(ellipsis_end));
+    } else {
+      iter.setIndex32(truncate_length_ - 1);
+      layout_text_.assign(text.substr(0, iter.getIndex()) + kEllipsisUTF16);
+    }
   }
 
-  if (elide_behavior_ != NO_ELIDE && display_rect_.width() > 0 &&
-      !GetLayoutText().empty() && GetContentWidth() > display_rect_.width()) {
-    base::string16 elided_text = ElideText(GetLayoutText());
-
+  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(elided_text);
+    layout_text_.assign(
+        Elide(layout_text_, 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_)
+    base::ReplaceChars(layout_text_, kNewline, kNewlineSymbol, &layout_text_);
+
   ResetLayout();
 }
 
-// TODO(skanuj): Fix code duplication with ElideText in ui/gfx/text_elider.cc
-// See crbug.com/327846
-base::string16 RenderText::ElideText(const base::string16& text) {
-  const bool insert_ellipsis = (elide_behavior_ != TRUNCATE_AT_END);
+base::string16 RenderText::Elide(const base::string16& text,
+                                 float available_width,
+                                 ElideBehavior behavior) {
+  if (available_width <= 0 || text.empty())
+    return base::string16();
+  if (behavior == ELIDE_EMAIL)
+    return ElideEmail(text, available_width);
+
   // Create a RenderText copy with attributes that affect the rendering width.
   scoped_ptr<RenderText> render_text(CreateInstance());
   render_text->SetFontList(font_list_);
   render_text->SetDirectionalityMode(directionality_mode_);
   render_text->SetCursorEnabled(cursor_enabled_);
-
+  render_text->set_truncate_length(truncate_length_);
   render_text->styles_ = styles_;
   render_text->colors_ = colors_;
   render_text->SetText(text);
-  const int current_text_pixel_width = render_text->GetContentWidth();
+  if (render_text->GetContentWidth() <= available_width)
+    return text;
 
-  const base::string16 ellipsis = base::string16(gfx::kEllipsisUTF16);
-  const bool elide_in_middle = false;
-  const bool elide_at_beginning = false;
+  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);
 
-  // Pango will return 0 width for absurdly long strings. Cut the string in
-  // half and try again.
-  // This is caused by an int overflow in Pango (specifically, in
-  // pango_glyph_string_extents_range). It's actually more subtle than just
-  // returning 0, since on super absurdly long strings, the int can wrap and
-  // return positive numbers again. Detecting that is probably not worth it
-  // (eliding way too much from a ridiculous string is probably still
-  // ridiculous), but we should check other widths for bogus values as well.
-  if (current_text_pixel_width <= 0 && !text.empty())
-    return ElideText(slicer.CutString(text.length() / 2, insert_ellipsis));
-
-  if (current_text_pixel_width <= display_rect_.width())
-    return text;
-
-  render_text->SetText(base::string16());
   render_text->SetText(ellipsis);
-  const int ellipsis_width = render_text->GetContentWidth();
+  const float ellipsis_width = render_text->GetContentWidth();
 
-  if (insert_ellipsis && (ellipsis_width >= display_rect_.width()))
+  if (insert_ellipsis && (ellipsis_width > available_width))
     return base::string16();
 
   // 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();
   for (size_t guess = (lo + hi) / 2; lo <= hi; guess = (lo + hi) / 2) {
     // Restore styles and colors. They will be truncated to size by SetText.
     render_text->styles_ = styles_;
     render_text->colors_ = colors_;
-    base::string16 new_text = slicer.CutString(guess, false);
+    base::string16 new_text =
+        slicer.CutString(guess, insert_ellipsis && behavior != ELIDE_TAIL);
     render_text->SetText(new_text);
 
     // This has to be an additional step so that the ellipsis is rendered with
     // same style as trailing part of the text.
-    if (insert_ellipsis) {
+    if (insert_ellipsis && behavior == ELIDE_TAIL) {
       // When ellipsis follows text whose directionality is not the same as that
       // of the whole text, it will be rendered with the directionality of the
       // whole text. Since we want ellipsis to indicate continuation of the
       // preceding text, we force the directionality of ellipsis to be same as
       // the preceding text using LTR or RTL markers.
-      base::i18n::TextDirection leading_text_direction =
-          base::i18n::GetFirstStrongCharacterDirection(new_text);
       base::i18n::TextDirection trailing_text_direction =
           base::i18n::GetLastStrongCharacterDirection(new_text);
       new_text.append(ellipsis);
-      if (trailing_text_direction != leading_text_direction) {
+      if (trailing_text_direction != text_direction) {
         if (trailing_text_direction == base::i18n::LEFT_TO_RIGHT)
           new_text += base::i18n::kLeftToRightMark;
         else
@@ -1208,13 +1328,12 @@ base::string16 RenderText::ElideText(const base::string16& text) {
 
     // We check the width of the whole desired string at once to ensure we
     // handle kerning/ligatures/etc. correctly.
-    const int guess_width = render_text->GetContentWidth();
-    if (guess_width == display_rect_.width())
+    const float guess_width = render_text->GetContentWidth();
+    if (guess_width == available_width)
       break;
-    if (guess_width > display_rect_.width()) {
+    if (guess_width > available_width) {
       hi = guess - 1;
-      // Move back if we are on loop terminating condition, and guess is wider
-      // than available.
+      // Move back on the loop terminating condition when the guess is too wide.
       if (hi < lo)
         lo = hi;
     } else {
@@ -1225,53 +1344,85 @@ base::string16 RenderText::ElideText(const base::string16& text) {
   return render_text->text();
 }
 
+base::string16 RenderText::ElideEmail(const base::string16& email,
+                                      float available_width) {
+  // The returned string will have at least one character besides the ellipsis
+  // on either side of '@'; if that's impossible, a single ellipsis is returned.
+  // If possible, only the username is elided. Otherwise, the domain is elided
+  // in the middle, splitting available width equally with the elided username.
+  // If the username is short enough that it doesn't need half the available
+  // width, the elided domain will occupy that extra width.
+
+  // Split the email into its local-part (username) and domain-part. The email
+  // spec allows for @ symbols in the username under some special requirements,
+  // but not in the domain part, so splitting at the last @ symbol is safe.
+  const size_t split_index = email.find_last_of('@');
+  DCHECK_NE(split_index, base::string16::npos);
+  base::string16 username = email.substr(0, split_index);
+  base::string16 domain = email.substr(split_index + 1);
+  DCHECK(!username.empty());
+  DCHECK(!domain.empty());
+
+  // Subtract the @ symbol from the available width as it is mandatory.
+  const base::string16 kAtSignUTF16 = base::ASCIIToUTF16("@");
+  available_width -= GetStringWidthF(kAtSignUTF16, font_list());
+
+  // Check whether eliding the domain is necessary: if eliding the username
+  // is sufficient, the domain will not be elided.
+  const float full_username_width = GetStringWidthF(username, font_list());
+  const float available_domain_width = available_width -
+      std::min(full_username_width,
+          GetStringWidthF(username.substr(0, 1) + kEllipsisUTF16, font_list()));
+  if (GetStringWidthF(domain, font_list()) > available_domain_width) {
+    // Elide the domain so that it only takes half of the available width.
+    // Should the username not need all the width available in its half, the
+    // domain will occupy the leftover width.
+    // If |desired_domain_width| is greater than |available_domain_width|: the
+    // minimal username elision allowed by the specifications will not fit; thus
+    // |desired_domain_width| must be <= |available_domain_width| at all cost.
+    const float desired_domain_width =
+        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);
+    // 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)
+      return base::string16(kEllipsisUTF16);
+  }
+
+  // Fit the username in the remaining width (at this point the elided username
+  // 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);
+  return username + kAtSignUTF16 + domain;
+}
+
 void RenderText::UpdateCachedBoundsAndOffset() {
   if (cached_bounds_and_offset_valid_)
     return;
 
   // TODO(ckocagil): Add support for scrolling multiline text.
 
-  // First, set the valid flag true to calculate the current cursor bounds using
-  // the stale |display_offset_|. Applying |delta_offset| at the end of this
-  // function will set |cursor_bounds_| and |display_offset_| to correct values.
-  cached_bounds_and_offset_valid_ = true;
-  cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_);
-
-  // Update |display_offset_| to ensure the current cursor is visible.
-  const int display_width = display_rect_.width();
-  const int content_width = GetContentWidth();
-
   int delta_x = 0;
-  if (content_width <= display_width || !cursor_enabled()) {
-    // Don't pan if the text fits in the display width or when the cursor is
-    // disabled.
-    delta_x = -display_offset_.x();
-  } else if (cursor_bounds_.right() > display_rect_.right()) {
-    // TODO(xji): when the character overflow is a RTL character, currently, if
-    // we pan cursor at the rightmost position, the entered RTL character is not
-    // displayed. Should pan cursor to show the last logical characters.
-    //
-    // Pan to show the cursor when it overflows to the right.
-    delta_x = display_rect_.right() - cursor_bounds_.right();
-  } else if (cursor_bounds_.x() < display_rect_.x()) {
-    // TODO(xji): have similar problem as above when overflow character is a
-    // LTR character.
-    //
-    // Pan to show the cursor when it overflows to the left.
-    delta_x = display_rect_.x() - cursor_bounds_.x();
-  } else if (display_offset_.x() != 0) {
-    // Reduce the pan offset to show additional overflow text when the display
-    // width increases.
-    const int negate_rtl = horizontal_alignment_ == ALIGN_RIGHT ? -1 : 1;
-    const int offset = negate_rtl * display_offset_.x();
-    if (display_width > (content_width + offset)) {
-      delta_x = negate_rtl * (display_width - (content_width + offset));
-    }
+
+  if (cursor_enabled()) {
+    // When cursor is enabled, ensure it is visible. For this, set the valid
+    // flag true and calculate the current cursor bounds using the stale
+    // |display_offset_|. Then calculate the change in offset needed to move the
+    // cursor into the visible area.
+    cached_bounds_and_offset_valid_ = true;
+    cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_);
+
+    // TODO(bidi): Show RTL glyphs at the cursor position for ALIGN_LEFT, etc.
+    if (cursor_bounds_.right() > display_rect_.right())
+      delta_x = display_rect_.right() - cursor_bounds_.right();
+    else if (cursor_bounds_.x() < display_rect_.x())
+      delta_x = display_rect_.x() - cursor_bounds_.x();
   }
 
-  Vector2d delta_offset(delta_x, 0);
-  display_offset_ += delta_offset;
-  cursor_bounds_ += delta_offset;
+  SetDisplayOffset(display_offset_.x() + delta_x);
 }
 
 void RenderText::DrawSelection(Canvas* canvas) {