#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/core/SkTypeface.h"
#include "third_party/skia/include/effects/SkGradientShader.h"
#include "ui/gfx/canvas.h"
+#include "ui/gfx/geometry/safe_integer_conversions.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/switches.h"
-#include "ui/gfx/text_constants.h"
#include "ui/gfx/text_elider.h"
#include "ui/gfx/text_utils.h"
#include "ui/gfx/utf16_indexing.h"
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 {
SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas)
: canvas_(canvas),
canvas_skia_(canvas->sk_canvas()),
- started_drawing_(false),
underline_thickness_(kUnderlineMetricsNotSet),
underline_position_(0.0f) {
DCHECK(canvas_skia_);
paint_.setSubpixelText(true);
paint_.setLCDRenderText(true);
paint_.setHinting(SkPaint::kNormal_Hinting);
- bounds_.setEmpty();
}
SkiaTextRenderer::~SkiaTextRenderer() {
- // Work-around for http://crbug.com/122743, where non-ClearType text is
- // rendered with incorrect gamma when using the fade shader. Draw the text
- // to a layer and restore it faded by drawing a rect in kDstIn_Mode mode.
- //
- // TODO(asvitkine): Remove this work-around once the Skia bug is fixed.
- // http://code.google.com/p/skia/issues/detail?id=590
- if (deferred_fade_shader_.get()) {
- paint_.setShader(deferred_fade_shader_.get());
- paint_.setXfermodeMode(SkXfermode::kDstIn_Mode);
- canvas_skia_->drawRect(bounds_, paint_);
- canvas_skia_->restore();
- }
}
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) {
+ ApplyRenderParams(params, background_is_transparent, &paint_);
}
void SkiaTextRenderer::SetTypeface(SkTypeface* typeface) {
paint_.setColor(foreground);
}
-void SkiaTextRenderer::SetShader(SkShader* shader, const Rect& bounds) {
- bounds_ = RectToSkRect(bounds);
+void SkiaTextRenderer::SetShader(SkShader* shader) {
paint_.setShader(shader);
}
void SkiaTextRenderer::DrawPosText(const SkPoint* pos,
const uint16* glyphs,
size_t glyph_count) {
- if (!started_drawing_) {
- started_drawing_ = true;
- // Work-around for http://crbug.com/122743, where non-ClearType text is
- // rendered with incorrect gamma when using the fade shader. Draw the text
- // to a layer and restore it faded by drawing a rect in kDstIn_Mode mode.
- //
- // Skip this when there is a looper which seems not working well with
- // deferred paint. Currently a looper is only used for text shadows.
- //
- // TODO(asvitkine): Remove this work-around once the Skia bug is fixed.
- // http://code.google.com/p/skia/issues/detail?id=590
- if (!paint_.isLCDRenderText() &&
- paint_.getShader() &&
- !paint_.getLooper()) {
- deferred_fade_shader_ = skia::SharePtr(paint_.getShader());
- paint_.setShader(NULL);
- canvas_skia_->saveLayer(&bounds_, NULL);
- }
- }
-
const size_t byte_length = glyph_count * sizeof(glyphs[0]);
canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_);
}
}
void SkiaTextRenderer::DrawUnderline(int x, int y, int width) {
- SkRect r = SkRect::MakeLTRB(x, y + underline_position_, x + width,
- y + underline_position_ + underline_thickness_);
+ SkScalar x_scalar = SkIntToScalar(x);
+ SkRect r = SkRect::MakeLTRB(
+ x_scalar, y + underline_position_, x_scalar + width,
+ y + underline_position_ + underline_thickness_);
if (underline_thickness_ == kUnderlineMetricsNotSet) {
const SkScalar text_size = paint_.getTextSize();
r.fTop = SkScalarMulAdd(text_size, kUnderlineOffset, y);
const SkScalar text_size = paint_.getTextSize();
const SkScalar height = SkScalarMul(text_size, kLineThickness);
const SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y);
- const SkRect r = SkRect::MakeLTRB(x, offset, x + width, offset + height);
+ SkScalar x_scalar = SkIntToScalar(x);
+ const SkRect r =
+ SkRect::MakeLTRB(x_scalar, offset, x_scalar + width, offset + height);
canvas_skia_->drawRect(r, paint_);
}
Point start,
const SkPaint& paint)
: canvas_(canvas),
- matrix_(canvas->sk_canvas()->getTotalMatrix()),
start_(start),
paint_(paint),
total_length_(0) {
const int clip_height = height + 2 * thickness;
paint_.setAntiAlias(true);
- paint_.setStrokeWidth(thickness);
-
- ScopedCanvas scoped_canvas(canvas_);
-
- SkCanvas* sk_canvas = canvas_->sk_canvas();
- sk_canvas->setMatrix(matrix_);
+ paint_.setStrokeWidth(SkIntToScalar(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);
if (clipped) {
+ canvas_->Save();
sk_canvas->clipRect(RectToSkRect(
- Rect(x, end.y() - thickness, pieces_[i].first, clip_height)),
- SkRegion::kReplace_Op);
+ Rect(x, end.y() - thickness, pieces_[i].first, clip_height)));
}
canvas_->DrawLine(start_, end, paint_);
+ if (clipped)
+ canvas_->Restore();
+
x += pieces_[i].first;
}
}
return skia::AdoptRef(SkTypeface::CreateFromName(family.c_str(), skia_style));
}
+void ApplyRenderParams(const FontRenderParams& params,
+ bool background_is_transparent,
+ SkPaint* paint) {
+ 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));
+}
+
} // 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;
+#if defined(OS_MACOSX) && !defined(TOOLKIT_VIEWS)
+ static const bool use_harfbuzz = CommandLine::ForCurrentProcess()->
+ HasSwitch(switches::kEnableHarfBuzzRenderText);
#else
- if (CommandLine::ForCurrentProcess()->HasSwitch(
- switches::kEnableHarfBuzzRenderText)) {
- return new RenderTextHarfBuzz;
- }
- return CreateNativeInstance();
+ static const bool use_harfbuzz = !CommandLine::ForCurrentProcess()->
+ HasSwitch(switches::kDisableHarfBuzzRenderText);
#endif
+ return use_harfbuzz ? new RenderTextHarfBuzz : CreateNativeInstance();
}
void RenderText::SetText(const base::string16& text) {
text_ = text;
// Adjust ranged styles and colors to accommodate a new text length.
+ // Clear style ranges as they might break new text graphemes and apply
+ // the first style to the whole text instead.
const size_t text_length = text_.length();
colors_.SetMax(text_length);
- for (size_t style = 0; style < NUM_TEXT_STYLES; ++style)
- styles_[style].SetMax(text_length);
+ for (size_t style = 0; style < NUM_TEXT_STYLES; ++style) {
+ BreakList<bool>& break_list = styles_[style];
+ break_list.SetValue(break_list.breaks().begin()->second);
+ break_list.SetMax(text_length);
+ }
cached_bounds_and_offset_valid_ = false;
// Reset selection model. SetText should always followed by SetSelectionModel
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();
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;
baseline_ = kInvalidBaseline;
cached_bounds_and_offset_valid_ = false;
lines_.clear();
- if (elide_behavior_ != TRUNCATE)
+ if (elide_behavior_ != NO_ELIDE)
UpdateLayoutText();
}
}
void RenderText::SetColor(SkColor value) {
colors_.SetValue(value);
-
-#if defined(OS_WIN)
- // TODO(msw): Windows applies colors and decorations in the layout process.
- cached_bounds_and_offset_valid_ = false;
- ResetLayout();
-#endif
}
void RenderText::ApplyColor(SkColor value, const Range& range) {
colors_.ApplyValue(value, range);
-
-#if defined(OS_WIN)
- // TODO(msw): Windows applies colors and decorations in the layout process.
- cached_bounds_and_offset_valid_ = false;
- ResetLayout();
-#endif
}
void RenderText::SetStyle(TextStyle style, bool value) {
styles_[style].SetValue(value);
- // Only invalidate the layout on font changes; not for colors or decorations.
- bool invalidate = (style == BOLD) || (style == ITALIC);
-#if defined(OS_WIN)
- // TODO(msw): Windows applies colors and decorations in the layout process.
- invalidate = true;
-#endif
- if (invalidate) {
- cached_bounds_and_offset_valid_ = false;
- ResetLayout();
- }
+ cached_bounds_and_offset_valid_ = false;
+ ResetLayout();
}
void RenderText::ApplyStyle(TextStyle style, bool value, const Range& range) {
- styles_[style].ApplyValue(value, range);
+ // Do not change styles mid-grapheme to avoid breaking ligatures.
+ const size_t start = IsValidCursorIndex(range.start()) ? range.start() :
+ IndexOfAdjacentGrapheme(range.start(), CURSOR_BACKWARD);
+ const size_t end = IsValidCursorIndex(range.end()) ? range.end() :
+ IndexOfAdjacentGrapheme(range.end(), CURSOR_FORWARD);
+ styles_[style].ApplyValue(value, Range(start, end));
- // Only invalidate the layout on font changes; not for colors or decorations.
- bool invalidate = (style == BOLD) || (style == ITALIC);
-#if defined(OS_WIN)
- // TODO(msw): Windows applies colors and decorations in the layout process.
- invalidate = true;
-#endif
- if (invalidate) {
- cached_bounds_and_offset_valid_ = false;
- ResetLayout();
- }
+ cached_bounds_and_offset_valid_ = false;
+ ResetLayout();
}
bool RenderText::GetStyle(TextStyle style) const {
}
SizeF RenderText::GetStringSizeF() {
- const Size size = GetStringSize();
- return SizeF(size.width(), size.height());
+ return GetStringSize();
}
-int RenderText::GetContentWidth() {
- return GetStringSize().width() + (cursor_enabled_ ? 1 : 0);
+float RenderText::GetContentWidth() {
+ return GetStringSizeF().width() + (cursor_enabled_ ? 1 : 0);
}
int RenderText::GetBaseline() {
sel.is_reversed() ? CURSOR_BACKWARD : CURSOR_FORWARD);
}
+const Vector2d& RenderText::GetUpdatedDisplayOffset() {
+ UpdateCachedBoundsAndOffset();
+ return display_offset_;
+}
+
+void RenderText::SetDisplayOffset(int horizontal_offset) {
+ const int extra_content =
+ ToFlooredInt(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()
: horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT),
directionality_mode_(DIRECTIONALITY_FROM_TEXT),
obscured_(false),
obscured_reveal_index_(-1),
truncate_length_(0),
- elide_behavior_(TRUNCATE),
+ elide_behavior_(NO_ELIDE),
+ replace_newline_chars_with_symbols_(true),
multiline_(false),
background_is_transparent_(false),
clip_to_display_rect_(true),
cached_bounds_and_offset_valid_(false) {
}
-const Vector2d& RenderText::GetUpdatedDisplayOffset() {
- UpdateCachedBoundsAndOffset();
- return display_offset_;
-}
-
SelectionModel RenderText::GetAdjacentSelectionModel(
const SelectionModel& current,
BreakType break_type,
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);
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.
void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) {
const int width = display_rect().width();
- if (multiline() || elide_behavior_ != FADE_TAIL || GetContentWidth() <= width)
+ if (multiline() || elide_behavior_ != FADE_TAIL ||
+ static_cast<int>(GetContentWidth()) <= width)
return;
const int gradient_width = CalculateFadeGradientWidth(font_list(), width);
if (gradient_width == 0)
return;
+ HorizontalAlignment horizontal_alignment = GetCurrentHorizontalAlignment();
Rect solid_part = display_rect();
Rect left_part;
Rect right_part;
- if (horizontal_alignment_ != ALIGN_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 (horizontal_alignment_ != ALIGN_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);
skia::RefPtr<SkShader> shader = CreateFadeShader(
text_rect, left_part, right_part, colors_.breaks().front().second);
if (shader)
- renderer->SetShader(shader.get(), display_rect());
+ renderer->SetShader(shader.get());
}
void RenderText::ApplyTextShadows(internal::SkiaTextRenderer* renderer) {
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()) + 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_ != TRUNCATE && elide_behavior_ != FADE_TAIL &&
- display_rect_.width() > 0 && !layout_text_.empty() &&
- GetContentWidth() > display_rect_.width()) {
+ if (elide_behavior_ != NO_ELIDE &&
+ elide_behavior_ != FADE_TAIL &&
+ !layout_text_.empty() &&
+ static_cast<int>(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(ElideText(layout_text_));
+ 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_)
+ 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);
+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(kEllipsisUTF16);
- const bool elide_in_middle = false;
- const bool elide_at_beginning = false;
+ 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 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_;
+ // Restore colors. They will be truncated to size by SetText.
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
render_text->SetText(new_text);
}
+ // Restore styles. Make sure style ranges don't break new text graphemes.
+ render_text->styles_ = styles_;
+ for (size_t style = 0; style < NUM_TEXT_STYLES; ++style) {
+ BreakList<bool>& break_list = render_text->styles_[style];
+ break_list.SetMax(render_text->text_.length());
+ Range range;
+ while (range.end() < break_list.max()) {
+ BreakList<bool>::const_iterator current_break =
+ break_list.GetBreak(range.end());
+ range = break_list.GetRange(current_break);
+ if (range.end() < break_list.max() &&
+ !render_text->IsValidCursorIndex(range.end())) {
+ range.set_end(render_text->IndexOfAdjacentGrapheme(range.end(),
+ CURSOR_FORWARD));
+ break_list.ApplyValue(current_break->second, range);
+ }
+ }
+ }
+
// 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 {
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;
- if (cursor_enabled())
- cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_);
+ int delta_x = 0;
- // Update |display_offset_| to ensure the current cursor is visible.
- const int display_width = display_rect_.width();
- const int content_width = GetContentWidth();
+ 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_);
- 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));
- }
+ // 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) {