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"
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"
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
;
37 // Check StyleRanges invariant conditions: sorted and non-overlapping ranges.
38 void CheckStyleRanges(const gfx::StyleRanges
& style_ranges
, size_t length
) {
40 DCHECK(style_ranges
.empty()) << "Style ranges exist for empty text.";
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
<< ":" <<
48 DCHECK(former
.IsValid()) << "Invalid range at " << i
<< ":" <<
50 DCHECK(!former
.is_reversed()) << "Reversed range at " << i
<< ":" <<
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.";
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()) {
71 } else if (i
->range
.start() == new_range
.end()) {
73 } else if (new_range
.Contains(i
->range
)) {
74 i
= style_ranges
->erase(i
);
75 if (i
== style_ranges
->end())
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());
85 } else if (i
->range
.start() < new_range
.start()) {
86 i
->range
.set_end(new_range
.start());
88 } else if (i
->range
.end() > new_range
.end()) {
89 i
->range
.set_start(new_range
.end());
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
,
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
,
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());
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
));
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);
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
,
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
));
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
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
,
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
)
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
);
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
;
325 r
.fTop
= y
+ underline_position_
;
326 r
.fBottom
= r
.fTop
+ underline_thickness_
;
328 canvas_skia_
->drawRect(r
, paint_
);
331 SkScalar offset
= SkScalarMulAdd(text_size
, kStrikeThroughOffset
, y
);
333 r
.fBottom
= offset
+ height
;
334 canvas_skia_
->drawRect(r
, paint_
);
336 if (style
.diagonal_strike
) {
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
,
350 } // namespace internal
353 StyleRange::StyleRange()
354 : foreground(SK_ColorBLACK
),
355 font_style(gfx::Font::NORMAL
),
357 diagonal_strike(false),
361 RenderText::~RenderText() {
364 void RenderText::SetText(const string16
& text
) {
365 DCHECK(!composition_range_
.IsValid());
366 size_t old_text_length
= text_
.length();
369 // Update the style ranges as needed.
371 style_ranges_
.clear();
372 } else if (style_ranges_
.empty()) {
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());
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());
391 CheckStyleRanges(style_ranges_
, text_
.length());
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();
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;
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;
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();
454 void RenderText::SetDisplayRect(const 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
,
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
478 if (break_type
== WORD_BREAK
)
479 position
= GetAdjacentSelectionModel(position
, break_type
, direction
);
481 position
= GetAdjacentSelectionModel(position
, break_type
, direction
);
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()))
497 SelectionModel
sel(range
, model
.caret_affinity());
498 bool changed
= sel
!= selection_model_
;
499 SetSelectionModel(sel
);
503 bool RenderText::MoveCursorTo(const Point
& point
, bool select
) {
504 SelectionModel position
= FindCursorPosition(point
);
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()))
515 LogicalCursorDirection affinity
=
516 (sel
.is_reversed() || sel
.is_empty()) ? CURSOR_FORWARD
: CURSOR_BACKWARD
;
517 SetSelectionModel(SelectionModel(sel
, affinity
));
521 bool RenderText::IsPointInSelection(const Point
& point
) {
522 if (selection().is_empty())
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
);
541 void RenderText::SelectWord() {
547 size_t cursor_pos
= cursor_position();
549 base::i18n::BreakIterator
iter(text(), base::i18n::BreakIterator::BREAK_WORD
);
550 bool success
= iter
.Init();
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
))
562 if (selection_start
== cursor_pos
)
565 for (; cursor_pos
< text().length(); ++cursor_pos
)
566 if (iter
.IsEndOfWord(cursor_pos
) || iter
.IsStartOfWord(cursor_pos
))
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());
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())
589 CHECK(!new_range
.is_reversed());
590 CHECK(ui::Range(0, text_
.length()).Contains(new_range
));
591 ApplyStyleRangeImpl(&style_ranges_
, style_range
);
593 CheckStyleRanges(style_ranges_
, text_
.length());
595 // TODO(xji): only invalidate if font or underline changes.
596 cached_bounds_and_offset_valid_
= false;
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;
609 void RenderText::SetDirectionalityMode(DirectionalityMode mode
) {
610 if (mode
== directionality_mode_
)
613 directionality_mode_
= mode
;
614 text_direction_
= base::i18n::UNKNOWN_DIRECTION
;
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.
625 base::i18n::GetFirstStrongCharacterDirection(GetLayoutText());
627 case DIRECTIONALITY_FROM_UI
:
628 text_direction_
= base::i18n::IsRTL() ? base::i18n::RIGHT_TO_LEFT
:
629 base::i18n::LEFT_TO_RIGHT
;
631 case DIRECTIONALITY_FORCE_LTR
:
632 text_direction_
= base::i18n::LEFT_TO_RIGHT
;
634 case DIRECTIONALITY_FORCE_RTL
:
635 text_direction_
= base::i18n::RIGHT_TO_LEFT
;
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
) {
653 if (clip_to_display_rect()) {
654 gfx::Rect
clip_rect(display_rect());
655 clip_rect
.Inset(ShadowValue::GetMargin(text_shadows_
));
658 canvas
->ClipRect(clip_rect
);
662 DrawSelection(canvas
);
667 DrawVisualText(canvas
);
669 if (clip_to_display_rect())
673 Rect
RenderText::GetCursorBounds(const SelectionModel
& caret
,
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))
689 height
= size
.height();
691 size_t grapheme_start
= (caret_affinity
== CURSOR_FORWARD
) ?
692 caret_pos
: IndexOfAdjacentGrapheme(caret_pos
, CURSOR_BACKWARD
);
694 GetGlyphBounds(grapheme_start
, &xspan
, &height
);
696 x
= (caret_affinity
== CURSOR_BACKWARD
) ? xspan
.end() : xspan
.start();
697 } else { // overtype mode
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();
719 if (direction
== CURSOR_FORWARD
) {
720 while (index
< text().length()) {
722 if (IsCursorablePosition(index
))
725 return text().length();
730 if (IsCursorablePosition(index
))
736 SelectionModel
RenderText::GetSelectionModelForSelectionStart() {
737 const ui::Range
& sel
= selection();
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),
755 cursor_color_(kDefaultCursorColor
),
756 selection_color_(kDefaultSelectionColor
),
757 selection_background_focused_color_(kDefaultSelectionBackgroundColor
),
758 selection_background_unfocused_color_(kDefaultSelectionBackgroundColor
),
760 composition_range_(ui::Range::InvalidRange()),
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
) {
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());
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
)
866 int x_offset
= display_rect().width() - GetContentWidth();
867 if (horizontal_alignment() == ALIGN_CENTER
)
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())
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
)
889 int gradient_width
= CalculateFadeGradientWidth(GetFont(), display_width
);
890 if (gradient_width
== 0)
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
899 if (horizontal_alignment() == ALIGN_RIGHT
)
900 std::swap(fade_left
, fade_right
);
902 gfx::Rect solid_part
= display_rect();
904 gfx::Rect right_part
;
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);
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
);
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());
933 bool RenderText::RangeContainsCaret(const ui::Range
& range
,
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() {
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_
)
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();
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
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
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
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_
);