Mailbox support for texture layers.
[chromium-blink-merge.git] / ui / gfx / render_text.cc
blobd68d7aaf6a1810340e66dcbadf4e56f464bda3d2
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/gfx/render_text.h"
7 #include <algorithm>
9 #include "base/i18n/break_iterator.h"
10 #include "base/logging.h"
11 #include "base/stl_util.h"
12 #include "third_party/skia/include/core/SkTypeface.h"
13 #include "third_party/skia/include/effects/SkGradientShader.h"
14 #include "ui/base/text/utf16_indexing.h"
15 #include "ui/gfx/canvas.h"
16 #include "ui/gfx/insets.h"
17 #include "ui/gfx/skia_util.h"
18 #include "ui/gfx/text_constants.h"
20 namespace {
22 // All chars are replaced by this char when the password style is set.
23 // TODO(benrg): GTK uses the first of U+25CF, U+2022, U+2731, U+273A, '*'
24 // that's available in the font (find_invisible_char() in gtkentry.c).
25 const char16 kPasswordReplacementChar = '*';
27 // Default color used for the cursor.
28 const SkColor kDefaultCursorColor = SK_ColorBLACK;
30 // Default color used for drawing selection text.
31 const SkColor kDefaultSelectionColor = SK_ColorBLACK;
33 // Default color used for drawing selection background.
34 const SkColor kDefaultSelectionBackgroundColor = SK_ColorGRAY;
36 #ifndef NDEBUG
37 // Check StyleRanges invariant conditions: sorted and non-overlapping ranges.
38 void CheckStyleRanges(const gfx::StyleRanges& style_ranges, size_t length) {
39 if (length == 0) {
40 DCHECK(style_ranges.empty()) << "Style ranges exist for empty text.";
41 return;
43 for (gfx::StyleRanges::size_type i = 0; i < style_ranges.size() - 1; i++) {
44 const ui::Range& former = style_ranges[i].range;
45 const ui::Range& latter = style_ranges[i + 1].range;
46 DCHECK(!former.is_empty()) << "Empty range at " << i << ":" <<
47 former.ToString();
48 DCHECK(former.IsValid()) << "Invalid range at " << i << ":" <<
49 former.ToString();
50 DCHECK(!former.is_reversed()) << "Reversed range at " << i << ":" <<
51 former.ToString();
52 DCHECK(former.end() == latter.start()) << "Ranges gap/overlap/unsorted." <<
53 "former:" << former.ToString() << ", latter:" << latter.ToString();
55 const gfx::StyleRange& end_style = *style_ranges.rbegin();
56 DCHECK(!end_style.range.is_empty()) << "Empty range at end.";
57 DCHECK(end_style.range.IsValid()) << "Invalid range at end.";
58 DCHECK(!end_style.range.is_reversed()) << "Reversed range at end.";
59 DCHECK(end_style.range.end() == length) << "Style and text length mismatch.";
61 #endif
63 void ApplyStyleRangeImpl(gfx::StyleRanges* style_ranges,
64 const gfx::StyleRange& style_range) {
65 const ui::Range& new_range = style_range.range;
66 // Follow StyleRanges invariant conditions: sorted and non-overlapping ranges.
67 gfx::StyleRanges::iterator i;
68 for (i = style_ranges->begin(); i != style_ranges->end();) {
69 if (i->range.end() < new_range.start()) {
70 i++;
71 } else if (i->range.start() == new_range.end()) {
72 break;
73 } else if (new_range.Contains(i->range)) {
74 i = style_ranges->erase(i);
75 if (i == style_ranges->end())
76 break;
77 } else if (i->range.start() < new_range.start() &&
78 i->range.end() > new_range.end()) {
79 // Split the current style into two styles.
80 gfx::StyleRange split_style = gfx::StyleRange(*i);
81 split_style.range.set_end(new_range.start());
82 i = style_ranges->insert(i, split_style) + 1;
83 i->range.set_start(new_range.end());
84 break;
85 } else if (i->range.start() < new_range.start()) {
86 i->range.set_end(new_range.start());
87 i++;
88 } else if (i->range.end() > new_range.end()) {
89 i->range.set_start(new_range.end());
90 break;
91 } else {
92 NOTREACHED();
95 // Add the new range in its sorted location.
96 style_ranges->insert(i, style_range);
99 // Converts |gfx::Font::FontStyle| flags to |SkTypeface::Style| flags.
100 SkTypeface::Style ConvertFontStyleToSkiaTypefaceStyle(int font_style) {
101 int skia_style = SkTypeface::kNormal;
102 if (font_style & gfx::Font::BOLD)
103 skia_style |= SkTypeface::kBold;
104 if (font_style & gfx::Font::ITALIC)
105 skia_style |= SkTypeface::kItalic;
106 return static_cast<SkTypeface::Style>(skia_style);
109 // Given |font| and |display_width|, returns the width of the fade gradient.
110 int CalculateFadeGradientWidth(const gfx::Font& font, int display_width) {
111 // Fade in/out about 2.5 characters of the beginning/end of the string.
112 // The .5 here is helpful if one of the characters is a space.
113 // Use a quarter of the display width if the display width is very short.
114 const int average_character_width = font.GetAverageCharacterWidth();
115 const double gradient_width = std::min(average_character_width * 2.5,
116 display_width / 4.0);
117 DCHECK_GE(gradient_width, 0.0);
118 return static_cast<int>(floor(gradient_width + 0.5));
121 // Appends to |positions| and |colors| values corresponding to the fade over
122 // |fade_rect| from color |c0| to color |c1|.
123 void AddFadeEffect(const gfx::Rect& text_rect,
124 const gfx::Rect& fade_rect,
125 SkColor c0,
126 SkColor c1,
127 std::vector<SkScalar>* positions,
128 std::vector<SkColor>* colors) {
129 const SkScalar left = static_cast<SkScalar>(fade_rect.x() - text_rect.x());
130 const SkScalar width = static_cast<SkScalar>(fade_rect.width());
131 const SkScalar p0 = left / text_rect.width();
132 const SkScalar p1 = (left + width) / text_rect.width();
133 // Prepend 0.0 to |positions|, as required by Skia.
134 if (positions->empty() && p0 != 0.0) {
135 positions->push_back(0.0);
136 colors->push_back(c0);
138 positions->push_back(p0);
139 colors->push_back(c0);
140 positions->push_back(p1);
141 colors->push_back(c1);
144 // Creates a SkShader to fade the text, with |left_part| specifying the left
145 // fade effect, if any, and |right_part| specifying the right fade effect.
146 skia::RefPtr<SkShader> CreateFadeShader(const gfx::Rect& text_rect,
147 const gfx::Rect& left_part,
148 const gfx::Rect& right_part,
149 SkColor color) {
150 // Fade alpha of 51/255 corresponds to a fade of 0.2 of the original color.
151 const SkColor fade_color = SkColorSetA(color, 51);
152 std::vector<SkScalar> positions;
153 std::vector<SkColor> colors;
155 if (!left_part.IsEmpty())
156 AddFadeEffect(text_rect, left_part, fade_color, color,
157 &positions, &colors);
158 if (!right_part.IsEmpty())
159 AddFadeEffect(text_rect, right_part, color, fade_color,
160 &positions, &colors);
161 DCHECK(!positions.empty());
163 // Terminate |positions| with 1.0, as required by Skia.
164 if (positions.back() != 1.0) {
165 positions.push_back(1.0);
166 colors.push_back(colors.back());
169 SkPoint points[2];
170 points[0].iset(text_rect.x(), text_rect.y());
171 points[1].iset(text_rect.right(), text_rect.y());
173 return skia::AdoptRef(
174 SkGradientShader::CreateLinear(&points[0], &colors[0], &positions[0],
175 colors.size(), SkShader::kClamp_TileMode));
178 } // namespace
180 namespace gfx {
182 namespace internal {
184 // Value of |underline_thickness_| that indicates that underline metrics have
185 // not been set explicitly.
186 const SkScalar kUnderlineMetricsNotSet = -1.0f;
188 SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas)
189 : canvas_skia_(canvas->sk_canvas()),
190 started_drawing_(false),
191 underline_thickness_(kUnderlineMetricsNotSet),
192 underline_position_(0.0f) {
193 DCHECK(canvas_skia_);
194 paint_.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
195 paint_.setStyle(SkPaint::kFill_Style);
196 paint_.setAntiAlias(true);
197 paint_.setSubpixelText(true);
198 paint_.setLCDRenderText(true);
199 bounds_.setEmpty();
202 SkiaTextRenderer::~SkiaTextRenderer() {
203 // Work-around for http://crbug.com/122743, where non-ClearType text is
204 // rendered with incorrect gamma when using the fade shader. Draw the text
205 // to a layer and restore it faded by drawing a rect in kDstIn_Mode mode.
207 // TODO(asvitkine): Remove this work-around once the Skia bug is fixed.
208 // http://code.google.com/p/skia/issues/detail?id=590
209 if (deferred_fade_shader_.get()) {
210 paint_.setShader(deferred_fade_shader_.get());
211 paint_.setXfermodeMode(SkXfermode::kDstIn_Mode);
212 canvas_skia_->drawRect(bounds_, paint_);
213 canvas_skia_->restore();
217 void SkiaTextRenderer::SetDrawLooper(SkDrawLooper* draw_looper) {
218 paint_.setLooper(draw_looper);
221 void SkiaTextRenderer::SetFontSmoothingSettings(bool enable_smoothing,
222 bool enable_lcd_text) {
223 paint_.setAntiAlias(enable_smoothing);
224 paint_.setSubpixelText(enable_smoothing);
225 paint_.setLCDRenderText(enable_lcd_text);
228 void SkiaTextRenderer::SetTypeface(SkTypeface* typeface) {
229 paint_.setTypeface(typeface);
232 void SkiaTextRenderer::SetTextSize(SkScalar size) {
233 paint_.setTextSize(size);
236 void SkiaTextRenderer::SetFontFamilyWithStyle(const std::string& family,
237 int style) {
238 DCHECK(!family.empty());
240 SkTypeface::Style skia_style = ConvertFontStyleToSkiaTypefaceStyle(style);
241 skia::RefPtr<SkTypeface> typeface =
242 skia::AdoptRef(SkTypeface::CreateFromName(family.c_str(), skia_style));
243 if (typeface) {
244 // |paint_| adds its own ref. So don't |release()| it from the ref ptr here.
245 SetTypeface(typeface.get());
247 // Enable fake bold text if bold style is needed but new typeface does not
248 // have it.
249 paint_.setFakeBoldText((skia_style & SkTypeface::kBold) &&
250 !typeface->isBold());
254 void SkiaTextRenderer::SetForegroundColor(SkColor foreground) {
255 paint_.setColor(foreground);
258 void SkiaTextRenderer::SetShader(SkShader* shader, const Rect& bounds) {
259 bounds_ = RectToSkRect(bounds);
260 paint_.setShader(shader);
263 void SkiaTextRenderer::SetUnderlineMetrics(SkScalar thickness,
264 SkScalar position) {
265 underline_thickness_ = thickness;
266 underline_position_ = position;
269 void SkiaTextRenderer::DrawPosText(const SkPoint* pos,
270 const uint16* glyphs,
271 size_t glyph_count) {
272 if (!started_drawing_) {
273 started_drawing_ = true;
274 // Work-around for http://crbug.com/122743, where non-ClearType text is
275 // rendered with incorrect gamma when using the fade shader. Draw the text
276 // to a layer and restore it faded by drawing a rect in kDstIn_Mode mode.
278 // Skip this when there is a looper which seems not working well with
279 // deferred paint. Currently a looper is only used for text shadows.
281 // TODO(asvitkine): Remove this work-around once the Skia bug is fixed.
282 // http://code.google.com/p/skia/issues/detail?id=590
283 if (!paint_.isLCDRenderText() &&
284 paint_.getShader() &&
285 !paint_.getLooper()) {
286 deferred_fade_shader_ = paint_.getShader();
287 paint_.setShader(NULL);
288 canvas_skia_->saveLayer(&bounds_, NULL);
292 const size_t byte_length = glyph_count * sizeof(glyphs[0]);
293 canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_);
296 // Draw underline and strike through text decorations.
297 // Based on |SkCanvas::DrawTextDecorations()| and constants from:
298 // third_party/skia/src/core/SkTextFormatParams.h
299 void SkiaTextRenderer::DrawDecorations(int x, int y, int width,
300 const StyleRange& style) {
301 if (!style.underline && !style.strike && !style.diagonal_strike)
302 return;
304 // Fraction of the text size to lower a strike through below the baseline.
305 const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21);
306 // Fraction of the text size to lower an underline below the baseline.
307 const SkScalar kUnderlineOffset = (SK_Scalar1 / 9);
308 // Fraction of the text size to use for a strike through or under-line.
309 const SkScalar kLineThickness = (SK_Scalar1 / 18);
310 // Fraction of the text size to use for a top margin of a diagonal strike.
311 const SkScalar kDiagonalStrikeThroughMarginOffset = (SK_Scalar1 / 4);
313 SkScalar text_size = paint_.getTextSize();
314 SkScalar height = SkScalarMul(text_size, kLineThickness);
315 SkRect r;
317 r.fLeft = x;
318 r.fRight = x + width;
320 if (style.underline) {
321 if (underline_thickness_ == kUnderlineMetricsNotSet) {
322 r.fTop = SkScalarMulAdd(text_size, kUnderlineOffset, y);
323 r.fBottom = r.fTop + height;
324 } else {
325 r.fTop = y + underline_position_;
326 r.fBottom = r.fTop + underline_thickness_;
328 canvas_skia_->drawRect(r, paint_);
330 if (style.strike) {
331 SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y);
332 r.fTop = offset;
333 r.fBottom = offset + height;
334 canvas_skia_->drawRect(r, paint_);
336 if (style.diagonal_strike) {
337 SkScalar offset =
338 SkScalarMul(text_size, kDiagonalStrikeThroughMarginOffset);
339 SkPaint paint(paint_);
340 paint.setAntiAlias(true);
341 paint.setStyle(SkPaint::kFill_Style);
342 paint.setStrokeWidth(height * 2);
343 canvas_skia_->drawLine(
344 SkIntToScalar(x), SkIntToScalar(y),
345 SkIntToScalar(x + width), SkIntToScalar(y) - text_size + offset,
346 paint);
350 } // namespace internal
353 StyleRange::StyleRange()
354 : foreground(SK_ColorBLACK),
355 font_style(gfx::Font::NORMAL),
356 strike(false),
357 diagonal_strike(false),
358 underline(false) {
361 RenderText::~RenderText() {
364 void RenderText::SetText(const string16& text) {
365 DCHECK(!composition_range_.IsValid());
366 size_t old_text_length = text_.length();
367 text_ = text;
369 // Update the style ranges as needed.
370 if (text_.empty()) {
371 style_ranges_.clear();
372 } else if (style_ranges_.empty()) {
373 ApplyDefaultStyle();
374 } else if (text_.length() > old_text_length) {
375 style_ranges_.back().range.set_end(text_.length());
376 } else if (text_.length() < old_text_length) {
377 StyleRanges::iterator i;
378 for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) {
379 if (i->range.start() >= text_.length()) {
380 // Style ranges are sorted and non-overlapping, so all the subsequent
381 // style ranges should be out of text_.length() as well.
382 style_ranges_.erase(i, style_ranges_.end());
383 break;
386 // Since style ranges are sorted and non-overlapping, if there is a style
387 // range ends beyond text_.length, it must be the last one.
388 style_ranges_.back().range.set_end(text_.length());
390 #ifndef NDEBUG
391 CheckStyleRanges(style_ranges_, text_.length());
392 #endif
393 cached_bounds_and_offset_valid_ = false;
395 // Reset selection model. SetText should always followed by SetSelectionModel
396 // or SetCursorPosition in upper layer.
397 SetSelectionModel(SelectionModel());
399 // Invalidate the cached text direction if it depends on the text contents.
400 if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT)
401 text_direction_ = base::i18n::UNKNOWN_DIRECTION;
403 UpdateObscuredText();
404 ResetLayout();
407 void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) {
408 if (horizontal_alignment_ != alignment) {
409 horizontal_alignment_ = alignment;
410 display_offset_ = Vector2d();
411 cached_bounds_and_offset_valid_ = false;
415 void RenderText::SetFontList(const FontList& font_list) {
416 font_list_ = font_list;
417 cached_bounds_and_offset_valid_ = false;
418 ResetLayout();
421 void RenderText::SetFont(const Font& font) {
422 SetFontList(FontList(font));
425 void RenderText::SetFontSize(int size) {
426 font_list_ = font_list_.DeriveFontListWithSize(size);
427 cached_bounds_and_offset_valid_ = false;
428 ResetLayout();
431 void RenderText::SetCursorEnabled(bool cursor_enabled) {
432 cursor_enabled_ = cursor_enabled;
433 cached_bounds_and_offset_valid_ = false;
436 const Font& RenderText::GetFont() const {
437 return font_list_.GetFonts()[0];
440 void RenderText::ToggleInsertMode() {
441 insert_mode_ = !insert_mode_;
442 cached_bounds_and_offset_valid_ = false;
445 void RenderText::SetObscured(bool obscured) {
446 if (obscured != obscured_) {
447 obscured_ = obscured;
448 cached_bounds_and_offset_valid_ = false;
449 UpdateObscuredText();
450 ResetLayout();
454 void RenderText::SetDisplayRect(const Rect& r) {
455 display_rect_ = r;
456 cached_bounds_and_offset_valid_ = false;
459 void RenderText::SetCursorPosition(size_t position) {
460 MoveCursorTo(position, false);
463 void RenderText::MoveCursor(BreakType break_type,
464 VisualCursorDirection direction,
465 bool select) {
466 SelectionModel position(cursor_position(), selection_model_.caret_affinity());
467 // Cancelling a selection moves to the edge of the selection.
468 if (break_type != LINE_BREAK && !selection().is_empty() && !select) {
469 SelectionModel selection_start = GetSelectionModelForSelectionStart();
470 int start_x = GetCursorBounds(selection_start, true).x();
471 int cursor_x = GetCursorBounds(position, true).x();
472 // Use the selection start if it is left (when |direction| is CURSOR_LEFT)
473 // or right (when |direction| is CURSOR_RIGHT) of the selection end.
474 if (direction == CURSOR_RIGHT ? start_x > cursor_x : start_x < cursor_x)
475 position = selection_start;
476 // For word breaks, use the nearest word boundary in the appropriate
477 // |direction|.
478 if (break_type == WORD_BREAK)
479 position = GetAdjacentSelectionModel(position, break_type, direction);
480 } else {
481 position = GetAdjacentSelectionModel(position, break_type, direction);
483 if (select)
484 position.set_selection_start(selection().start());
485 MoveCursorTo(position);
488 bool RenderText::MoveCursorTo(const SelectionModel& model) {
489 // Enforce valid selection model components.
490 size_t text_length = text().length();
491 ui::Range range(std::min(model.selection().start(), text_length),
492 std::min(model.caret_pos(), text_length));
493 // The current model only supports caret positions at valid character indices.
494 if (!IsCursorablePosition(range.start()) ||
495 !IsCursorablePosition(range.end()))
496 return false;
497 SelectionModel sel(range, model.caret_affinity());
498 bool changed = sel != selection_model_;
499 SetSelectionModel(sel);
500 return changed;
503 bool RenderText::MoveCursorTo(const Point& point, bool select) {
504 SelectionModel position = FindCursorPosition(point);
505 if (select)
506 position.set_selection_start(selection().start());
507 return MoveCursorTo(position);
510 bool RenderText::SelectRange(const ui::Range& range) {
511 ui::Range sel(std::min(range.start(), text().length()),
512 std::min(range.end(), text().length()));
513 if (!IsCursorablePosition(sel.start()) || !IsCursorablePosition(sel.end()))
514 return false;
515 LogicalCursorDirection affinity =
516 (sel.is_reversed() || sel.is_empty()) ? CURSOR_FORWARD : CURSOR_BACKWARD;
517 SetSelectionModel(SelectionModel(sel, affinity));
518 return true;
521 bool RenderText::IsPointInSelection(const Point& point) {
522 if (selection().is_empty())
523 return false;
524 SelectionModel cursor = FindCursorPosition(point);
525 return RangeContainsCaret(
526 selection(), cursor.caret_pos(), cursor.caret_affinity());
529 void RenderText::ClearSelection() {
530 SetSelectionModel(SelectionModel(cursor_position(),
531 selection_model_.caret_affinity()));
534 void RenderText::SelectAll(bool reversed) {
535 const size_t length = text().length();
536 const ui::Range all = reversed ? ui::Range(length, 0) : ui::Range(0, length);
537 const bool success = SelectRange(all);
538 DCHECK(success);
541 void RenderText::SelectWord() {
542 if (obscured_) {
543 SelectAll(false);
544 return;
547 size_t cursor_pos = cursor_position();
549 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
550 bool success = iter.Init();
551 DCHECK(success);
552 if (!success)
553 return;
555 size_t selection_start = cursor_pos;
556 for (; selection_start != 0; --selection_start) {
557 if (iter.IsStartOfWord(selection_start) ||
558 iter.IsEndOfWord(selection_start))
559 break;
562 if (selection_start == cursor_pos)
563 ++cursor_pos;
565 for (; cursor_pos < text().length(); ++cursor_pos)
566 if (iter.IsEndOfWord(cursor_pos) || iter.IsStartOfWord(cursor_pos))
567 break;
569 MoveCursorTo(selection_start, false);
570 MoveCursorTo(cursor_pos, true);
573 const ui::Range& RenderText::GetCompositionRange() const {
574 return composition_range_;
577 void RenderText::SetCompositionRange(const ui::Range& composition_range) {
578 CHECK(!composition_range.IsValid() ||
579 ui::Range(0, text_.length()).Contains(composition_range));
580 composition_range_.set_end(composition_range.end());
581 composition_range_.set_start(composition_range.start());
582 ResetLayout();
585 void RenderText::ApplyStyleRange(const StyleRange& style_range) {
586 const ui::Range& new_range = style_range.range;
587 if (!new_range.IsValid() || new_range.is_empty())
588 return;
589 CHECK(!new_range.is_reversed());
590 CHECK(ui::Range(0, text_.length()).Contains(new_range));
591 ApplyStyleRangeImpl(&style_ranges_, style_range);
592 #ifndef NDEBUG
593 CheckStyleRanges(style_ranges_, text_.length());
594 #endif
595 // TODO(xji): only invalidate if font or underline changes.
596 cached_bounds_and_offset_valid_ = false;
597 ResetLayout();
600 void RenderText::ApplyDefaultStyle() {
601 style_ranges_.clear();
602 StyleRange style = StyleRange(default_style_);
603 style.range.set_end(text_.length());
604 style_ranges_.push_back(style);
605 cached_bounds_and_offset_valid_ = false;
606 ResetLayout();
609 void RenderText::SetDirectionalityMode(DirectionalityMode mode) {
610 if (mode == directionality_mode_)
611 return;
613 directionality_mode_ = mode;
614 text_direction_ = base::i18n::UNKNOWN_DIRECTION;
615 ResetLayout();
618 base::i18n::TextDirection RenderText::GetTextDirection() {
619 if (text_direction_ == base::i18n::UNKNOWN_DIRECTION) {
620 switch (directionality_mode_) {
621 case DIRECTIONALITY_FROM_TEXT:
622 // Derive the direction from the display text, which differs from text()
623 // in the case of obscured (password) textfields.
624 text_direction_ =
625 base::i18n::GetFirstStrongCharacterDirection(GetLayoutText());
626 break;
627 case DIRECTIONALITY_FROM_UI:
628 text_direction_ = base::i18n::IsRTL() ? base::i18n::RIGHT_TO_LEFT :
629 base::i18n::LEFT_TO_RIGHT;
630 break;
631 case DIRECTIONALITY_FORCE_LTR:
632 text_direction_ = base::i18n::LEFT_TO_RIGHT;
633 break;
634 case DIRECTIONALITY_FORCE_RTL:
635 text_direction_ = base::i18n::RIGHT_TO_LEFT;
636 break;
637 default:
638 NOTREACHED();
642 return text_direction_;
645 VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() {
646 return GetTextDirection() == base::i18n::LEFT_TO_RIGHT ?
647 CURSOR_RIGHT : CURSOR_LEFT;
650 void RenderText::Draw(Canvas* canvas) {
651 EnsureLayout();
653 if (clip_to_display_rect()) {
654 gfx::Rect clip_rect(display_rect());
655 clip_rect.Inset(ShadowValue::GetMargin(text_shadows_));
657 canvas->Save();
658 canvas->ClipRect(clip_rect);
661 if (!text().empty())
662 DrawSelection(canvas);
664 DrawCursor(canvas);
666 if (!text().empty())
667 DrawVisualText(canvas);
669 if (clip_to_display_rect())
670 canvas->Restore();
673 Rect RenderText::GetCursorBounds(const SelectionModel& caret,
674 bool insert_mode) {
675 EnsureLayout();
677 size_t caret_pos = caret.caret_pos();
678 // In overtype mode, ignore the affinity and always indicate that we will
679 // overtype the next character.
680 LogicalCursorDirection caret_affinity =
681 insert_mode ? caret.caret_affinity() : CURSOR_FORWARD;
682 int x = 0, width = 1, height = 0;
683 if (caret_pos == (caret_affinity == CURSOR_BACKWARD ? 0 : text().length())) {
684 // The caret is attached to the boundary. Always return a 1-dip width caret,
685 // since there is nothing to overtype.
686 Size size = GetStringSize();
687 if ((GetTextDirection() == base::i18n::RIGHT_TO_LEFT) == (caret_pos == 0))
688 x = size.width();
689 height = size.height();
690 } else {
691 size_t grapheme_start = (caret_affinity == CURSOR_FORWARD) ?
692 caret_pos : IndexOfAdjacentGrapheme(caret_pos, CURSOR_BACKWARD);
693 ui::Range xspan;
694 GetGlyphBounds(grapheme_start, &xspan, &height);
695 if (insert_mode) {
696 x = (caret_affinity == CURSOR_BACKWARD) ? xspan.end() : xspan.start();
697 } else { // overtype mode
698 x = xspan.GetMin();
699 width = xspan.length();
702 height = std::min(height, display_rect().height());
703 int y = (display_rect().height() - height) / 2;
704 return Rect(ToViewPoint(Point(x, y)), Size(width, height));
707 const Rect& RenderText::GetUpdatedCursorBounds() {
708 UpdateCachedBoundsAndOffset();
709 return cursor_bounds_;
712 size_t RenderText::IndexOfAdjacentGrapheme(size_t index,
713 LogicalCursorDirection direction) {
714 if (index > text().length())
715 return text().length();
717 EnsureLayout();
719 if (direction == CURSOR_FORWARD) {
720 while (index < text().length()) {
721 index++;
722 if (IsCursorablePosition(index))
723 return index;
725 return text().length();
728 while (index > 0) {
729 index--;
730 if (IsCursorablePosition(index))
731 return index;
733 return 0;
736 SelectionModel RenderText::GetSelectionModelForSelectionStart() {
737 const ui::Range& sel = selection();
738 if (sel.is_empty())
739 return selection_model_;
740 return SelectionModel(sel.start(),
741 sel.is_reversed() ? CURSOR_BACKWARD : CURSOR_FORWARD);
744 void RenderText::SetTextShadows(const ShadowValues& shadows) {
745 text_shadows_ = shadows;
748 RenderText::RenderText()
749 : horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT),
750 directionality_mode_(DIRECTIONALITY_FROM_TEXT),
751 text_direction_(base::i18n::UNKNOWN_DIRECTION),
752 cursor_enabled_(true),
753 cursor_visible_(false),
754 insert_mode_(true),
755 cursor_color_(kDefaultCursorColor),
756 selection_color_(kDefaultSelectionColor),
757 selection_background_focused_color_(kDefaultSelectionBackgroundColor),
758 selection_background_unfocused_color_(kDefaultSelectionBackgroundColor),
759 focused_(false),
760 composition_range_(ui::Range::InvalidRange()),
761 obscured_(false),
762 fade_head_(false),
763 fade_tail_(false),
764 background_is_transparent_(false),
765 clip_to_display_rect_(true),
766 cached_bounds_and_offset_valid_(false) {
769 const Vector2d& RenderText::GetUpdatedDisplayOffset() {
770 UpdateCachedBoundsAndOffset();
771 return display_offset_;
774 SelectionModel RenderText::GetAdjacentSelectionModel(
775 const SelectionModel& current,
776 BreakType break_type,
777 VisualCursorDirection direction) {
778 EnsureLayout();
780 if (break_type == LINE_BREAK || text().empty())
781 return EdgeSelectionModel(direction);
782 if (break_type == CHARACTER_BREAK)
783 return AdjacentCharSelectionModel(current, direction);
784 DCHECK(break_type == WORD_BREAK);
785 return AdjacentWordSelectionModel(current, direction);
788 SelectionModel RenderText::EdgeSelectionModel(
789 VisualCursorDirection direction) {
790 if (direction == GetVisualDirectionOfLogicalEnd())
791 return SelectionModel(text().length(), CURSOR_FORWARD);
792 return SelectionModel(0, CURSOR_BACKWARD);
795 void RenderText::SetSelectionModel(const SelectionModel& model) {
796 DCHECK_LE(model.selection().GetMax(), text().length());
797 selection_model_ = model;
798 cached_bounds_and_offset_valid_ = false;
801 const string16& RenderText::GetLayoutText() const {
802 return obscured() ? obscured_text_ : text();
805 void RenderText::ApplyCompositionAndSelectionStyles(
806 StyleRanges* style_ranges) {
807 // TODO(msw): This pattern ought to be reconsidered; what about composition
808 // and selection overlaps, retain existing local style features?
809 // Apply a composition style override to a copy of the style ranges.
810 if (composition_range_.IsValid() && !composition_range_.is_empty()) {
811 StyleRange composition_style(default_style_);
812 composition_style.underline = true;
813 composition_style.range = composition_range_;
814 ApplyStyleRangeImpl(style_ranges, composition_style);
816 // Apply a selection style override to a copy of the style ranges.
817 if (!selection().is_empty()) {
818 StyleRange selection_style(default_style_);
819 selection_style.foreground = selection_color_;
820 selection_style.range = ui::Range(selection().GetMin(),
821 selection().GetMax());
822 ApplyStyleRangeImpl(style_ranges, selection_style);
824 // Apply replacement-mode style override to a copy of the style ranges.
826 // TODO(xji): NEED TO FIX FOR WINDOWS ASAP. Windows call this function (to
827 // apply styles) in ItemizeLogicalText(). In order for the cursor's underline
828 // character to be drawn correctly, we will need to re-layout the text. It's
829 // not practical to do layout on every cursor blink. We need to fix Windows
830 // port to apply styles during drawing phase like Linux port does.
831 // http://crbug.com/110109
832 if (!insert_mode_ && cursor_visible() && focused()) {
833 StyleRange replacement_mode_style(default_style_);
834 replacement_mode_style.foreground = selection_color_;
835 size_t cursor = cursor_position();
836 replacement_mode_style.range.set_start(cursor);
837 replacement_mode_style.range.set_end(
838 IndexOfAdjacentGrapheme(cursor, CURSOR_FORWARD));
839 ApplyStyleRangeImpl(style_ranges, replacement_mode_style);
843 Vector2d RenderText::GetTextOffset() {
844 Vector2d offset = display_rect().OffsetFromOrigin();
845 offset.Add(GetUpdatedDisplayOffset());
846 offset.Add(GetAlignmentOffset());
847 return offset;
850 Point RenderText::ToTextPoint(const Point& point) {
851 return point - GetTextOffset();
854 Point RenderText::ToViewPoint(const Point& point) {
855 return point + GetTextOffset();
858 int RenderText::GetContentWidth() {
859 return GetStringSize().width() + (cursor_enabled_ ? 1 : 0);
862 Vector2d RenderText::GetAlignmentOffset() {
863 if (horizontal_alignment() == ALIGN_LEFT)
864 return Vector2d();
866 int x_offset = display_rect().width() - GetContentWidth();
867 if (horizontal_alignment() == ALIGN_CENTER)
868 x_offset /= 2;
869 return Vector2d(x_offset, 0);
872 Vector2d RenderText::GetOffsetForDrawing() {
873 // Center the text vertically in the display area.
874 return GetTextOffset() +
875 Vector2d(0, (display_rect().height() - GetStringSize().height()) / 2);
878 void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) {
879 if (!fade_head() && !fade_tail())
880 return;
882 const int text_width = GetStringSize().width();
883 const int display_width = display_rect().width();
885 // If the text fits as-is, no need to fade.
886 if (text_width <= display_width)
887 return;
889 int gradient_width = CalculateFadeGradientWidth(GetFont(), display_width);
890 if (gradient_width == 0)
891 return;
893 bool fade_left = fade_head();
894 bool fade_right = fade_tail();
895 // Under RTL, |fade_right| == |fade_head|.
896 // TODO(asvitkine): This is currently not based on GetTextDirection() because
897 // RenderTextWin does not return a direction that's based on
898 // the text content.
899 if (horizontal_alignment() == ALIGN_RIGHT)
900 std::swap(fade_left, fade_right);
902 gfx::Rect solid_part = display_rect();
903 gfx::Rect left_part;
904 gfx::Rect right_part;
905 if (fade_left) {
906 left_part = solid_part;
907 left_part.Inset(0, 0, solid_part.width() - gradient_width, 0);
908 solid_part.Inset(gradient_width, 0, 0, 0);
910 if (fade_right) {
911 right_part = solid_part;
912 right_part.Inset(solid_part.width() - gradient_width, 0, 0, 0);
913 solid_part.Inset(0, 0, gradient_width, 0);
916 gfx::Rect text_rect = display_rect();
917 text_rect.Inset(GetAlignmentOffset().x(), 0, 0, 0);
919 const SkColor color = default_style().foreground;
920 skia::RefPtr<SkShader> shader =
921 CreateFadeShader(text_rect, left_part, right_part, color);
922 if (shader)
923 renderer->SetShader(shader.get(), display_rect());
926 void RenderText::ApplyTextShadows(internal::SkiaTextRenderer* renderer) {
927 skia::RefPtr<SkDrawLooper> looper =
928 gfx::CreateShadowDrawLooper(text_shadows_);
929 renderer->SetDrawLooper(looper.get());
932 // static
933 bool RenderText::RangeContainsCaret(const ui::Range& range,
934 size_t caret_pos,
935 LogicalCursorDirection caret_affinity) {
936 // NB: exploits unsigned wraparound (WG14/N1124 section 6.2.5 paragraph 9).
937 size_t adjacent = (caret_affinity == CURSOR_BACKWARD) ?
938 caret_pos - 1 : caret_pos + 1;
939 return range.Contains(ui::Range(caret_pos, adjacent));
942 void RenderText::MoveCursorTo(size_t position, bool select) {
943 size_t cursor = std::min(position, text().length());
944 if (IsCursorablePosition(cursor))
945 SetSelectionModel(SelectionModel(
946 ui::Range(select ? selection().start() : cursor, cursor),
947 (cursor == 0) ? CURSOR_FORWARD : CURSOR_BACKWARD));
950 void RenderText::UpdateObscuredText() {
951 if (!obscured_)
952 return;
954 const size_t obscured_text_length =
955 static_cast<size_t>(ui::UTF16IndexToOffset(text_, 0, text_.length()));
956 if (obscured_text_.length() != obscured_text_length)
957 obscured_text_.resize(obscured_text_length, kPasswordReplacementChar);
960 void RenderText::UpdateCachedBoundsAndOffset() {
961 if (cached_bounds_and_offset_valid_)
962 return;
964 // First, set the valid flag true to calculate the current cursor bounds using
965 // the stale |display_offset_|. Applying |delta_offset| at the end of this
966 // function will set |cursor_bounds_| and |display_offset_| to correct values.
967 cached_bounds_and_offset_valid_ = true;
968 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_);
970 // Update |display_offset_| to ensure the current cursor is visible.
971 const int display_width = display_rect_.width();
972 const int content_width = GetContentWidth();
974 int delta_x = 0;
975 if (content_width <= display_width || !cursor_enabled()) {
976 // Don't pan if the text fits in the display width or when the cursor is
977 // disabled.
978 delta_x = -display_offset_.x();
979 } else if (cursor_bounds_.right() >= display_rect_.right()) {
980 // TODO(xji): when the character overflow is a RTL character, currently, if
981 // we pan cursor at the rightmost position, the entered RTL character is not
982 // displayed. Should pan cursor to show the last logical characters.
984 // Pan to show the cursor when it overflows to the right,
985 delta_x = display_rect_.right() - cursor_bounds_.right() - 1;
986 } else if (cursor_bounds_.x() < display_rect_.x()) {
987 // TODO(xji): have similar problem as above when overflow character is a
988 // LTR character.
990 // Pan to show the cursor when it overflows to the left.
991 delta_x = display_rect_.x() - cursor_bounds_.x();
992 } else if (display_offset_.x() != 0) {
993 // Reduce the pan offset to show additional overflow text when the display
994 // width increases.
995 const int negate_rtl = horizontal_alignment_ == ALIGN_RIGHT ? -1 : 1;
996 const int offset = negate_rtl * display_offset_.x();
997 if (display_width > (content_width + offset)) {
998 delta_x = negate_rtl * (display_width - (content_width + offset));
1002 gfx::Vector2d delta_offset(delta_x, 0);
1003 display_offset_ += delta_offset;
1004 cursor_bounds_ += delta_offset;
1007 void RenderText::DrawSelection(Canvas* canvas) {
1008 const SkColor color = focused() ? selection_background_focused_color_ :
1009 selection_background_unfocused_color_;
1010 const std::vector<Rect> sel = GetSubstringBounds(selection());
1011 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
1012 canvas->FillRect(*i, color);
1015 void RenderText::DrawCursor(Canvas* canvas) {
1016 // Paint cursor. Replace cursor is drawn as rectangle for now.
1017 // TODO(msw): Draw a better cursor with a better indication of association.
1018 if (cursor_enabled() && cursor_visible() && focused()) {
1019 const Rect& bounds = GetUpdatedCursorBounds();
1020 DCHECK(bounds.width());
1021 canvas->FillRect(bounds, cursor_color_);
1025 } // namespace gfx