blob: 35134e8ac5daae44a934da4df3a6472379c69107 [file] [log] [blame]
[email protected]8d901a82012-01-04 19:41:301// Copyright (c) 2012 The Chromium Authors. All rights reserved.
[email protected]ff44d712011-07-25 08:42:522// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/gfx/render_text.h"
6
7#include <algorithm>
8
[email protected]592a7ad2011-12-14 05:57:539#include "base/debug/trace_event.h"
[email protected]ff44d712011-07-25 08:42:5210#include "base/i18n/break_iterator.h"
11#include "base/logging.h"
12#include "base/stl_util.h"
[email protected]67b981562011-12-09 00:35:0513#include "third_party/skia/include/core/SkTypeface.h"
[email protected]f308e5b12012-01-17 19:09:1814#include "third_party/skia/include/effects/SkGradientShader.h"
[email protected]bec929c2012-03-02 06:23:5015#include "ui/base/text/utf16_indexing.h"
[email protected]ff44d712011-07-25 08:42:5216#include "ui/gfx/canvas.h"
17#include "ui/gfx/canvas_skia.h"
[email protected]721ea922012-01-23 21:13:3318#include "ui/gfx/native_theme.h"
[email protected]ff44d712011-07-25 08:42:5219
20namespace {
21
[email protected]bec929c2012-03-02 06:23:5022// 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).
25const char16 kPasswordReplacementChar = '*';
26
[email protected]913a1ef2012-02-29 08:40:3427// Default color used for the cursor.
28const SkColor kDefaultCursorColor = SK_ColorBLACK;
[email protected]d66009e2012-01-21 01:27:2829
[email protected]ff44d712011-07-25 08:42:5230#ifndef NDEBUG
31// Check StyleRanges invariant conditions: sorted and non-overlapping ranges.
32void CheckStyleRanges(const gfx::StyleRanges& style_ranges, size_t length) {
33 if (length == 0) {
34 DCHECK(style_ranges.empty()) << "Style ranges exist for empty text.";
35 return;
36 }
37 for (gfx::StyleRanges::size_type i = 0; i < style_ranges.size() - 1; i++) {
38 const ui::Range& former = style_ranges[i].range;
39 const ui::Range& latter = style_ranges[i + 1].range;
40 DCHECK(!former.is_empty()) << "Empty range at " << i << ":" << former;
41 DCHECK(former.IsValid()) << "Invalid range at " << i << ":" << former;
42 DCHECK(!former.is_reversed()) << "Reversed range at " << i << ":" << former;
43 DCHECK(former.end() == latter.start()) << "Ranges gap/overlap/unsorted." <<
44 "former:" << former << ", latter:" << latter;
45 }
46 const gfx::StyleRange& end_style = *style_ranges.rbegin();
47 DCHECK(!end_style.range.is_empty()) << "Empty range at end.";
48 DCHECK(end_style.range.IsValid()) << "Invalid range at end.";
49 DCHECK(!end_style.range.is_reversed()) << "Reversed range at end.";
50 DCHECK(end_style.range.end() == length) << "Style and text length mismatch.";
51}
52#endif
53
[email protected]8e42ba22011-08-04 21:47:0854void ApplyStyleRangeImpl(gfx::StyleRanges* style_ranges,
[email protected]c8636672011-12-29 05:07:5755 const gfx::StyleRange& style_range) {
[email protected]ff44d712011-07-25 08:42:5256 const ui::Range& new_range = style_range.range;
57 // Follow StyleRanges invariant conditions: sorted and non-overlapping ranges.
58 gfx::StyleRanges::iterator i;
[email protected]8e42ba22011-08-04 21:47:0859 for (i = style_ranges->begin(); i != style_ranges->end();) {
[email protected]ff44d712011-07-25 08:42:5260 if (i->range.end() < new_range.start()) {
61 i++;
62 } else if (i->range.start() == new_range.end()) {
63 break;
64 } else if (new_range.Contains(i->range)) {
[email protected]8e42ba22011-08-04 21:47:0865 i = style_ranges->erase(i);
66 if (i == style_ranges->end())
[email protected]ff44d712011-07-25 08:42:5267 break;
68 } else if (i->range.start() < new_range.start() &&
69 i->range.end() > new_range.end()) {
70 // Split the current style into two styles.
71 gfx::StyleRange split_style = gfx::StyleRange(*i);
72 split_style.range.set_end(new_range.start());
[email protected]8e42ba22011-08-04 21:47:0873 i = style_ranges->insert(i, split_style) + 1;
[email protected]ff44d712011-07-25 08:42:5274 i->range.set_start(new_range.end());
75 break;
76 } else if (i->range.start() < new_range.start()) {
77 i->range.set_end(new_range.start());
78 i++;
79 } else if (i->range.end() > new_range.end()) {
80 i->range.set_start(new_range.end());
81 break;
[email protected]f9a221b2011-12-10 20:25:3882 } else {
[email protected]ff44d712011-07-25 08:42:5283 NOTREACHED();
[email protected]f9a221b2011-12-10 20:25:3884 }
[email protected]ff44d712011-07-25 08:42:5285 }
86 // Add the new range in its sorted location.
[email protected]8e42ba22011-08-04 21:47:0887 style_ranges->insert(i, style_range);
[email protected]ff44d712011-07-25 08:42:5288}
89
[email protected]7aba39c2012-01-10 16:10:2790// Converts |gfx::Font::FontStyle| flags to |SkTypeface::Style| flags.
91SkTypeface::Style ConvertFontStyleToSkiaTypefaceStyle(int font_style) {
92 int skia_style = SkTypeface::kNormal;
93 if (font_style & gfx::Font::BOLD)
94 skia_style |= SkTypeface::kBold;
95 if (font_style & gfx::Font::ITALIC)
96 skia_style |= SkTypeface::kItalic;
97 return static_cast<SkTypeface::Style>(skia_style);
98}
99
[email protected]f308e5b12012-01-17 19:09:18100// Given |font| and |display_width|, returns the width of the fade gradient.
101int CalculateFadeGradientWidth(const gfx::Font& font, int display_width) {
102 // Fade in/out about 2.5 characters of the beginning/end of the string.
103 // The .5 here is helpful if one of the characters is a space.
104 // Use a quarter of the display width if the display width is very short.
105 const int average_character_width = font.GetAverageCharacterWidth();
106 const double gradient_width = std::min(average_character_width * 2.5,
107 display_width / 4.0);
108 DCHECK_GE(gradient_width, 0.0);
109 return static_cast<int>(floor(gradient_width + 0.5));
110}
111
112// Appends to |positions| and |colors| values corresponding to the fade over
113// |fade_rect| from color |c0| to color |c1|.
114void AddFadeEffect(const gfx::Rect& text_rect,
115 const gfx::Rect& fade_rect,
116 SkColor c0,
117 SkColor c1,
118 std::vector<SkScalar>* positions,
119 std::vector<SkColor>* colors) {
120 const SkScalar left = static_cast<SkScalar>(fade_rect.x() - text_rect.x());
121 const SkScalar width = static_cast<SkScalar>(fade_rect.width());
122 const SkScalar p0 = left / text_rect.width();
123 const SkScalar p1 = (left + width) / text_rect.width();
124 // Prepend 0.0 to |positions|, as required by Skia.
125 if (positions->empty() && p0 != 0.0) {
126 positions->push_back(0.0);
127 colors->push_back(c0);
128 }
129 positions->push_back(p0);
130 colors->push_back(c0);
131 positions->push_back(p1);
132 colors->push_back(c1);
133}
134
135// Creates a SkShader to fade the text, with |left_part| specifying the left
136// fade effect, if any, and |right_part| specifying the right fade effect.
137SkShader* CreateFadeShader(const gfx::Rect& text_rect,
138 const gfx::Rect& left_part,
139 const gfx::Rect& right_part,
140 SkColor color) {
141 // Fade alpha of 51/255 corresponds to a fade of 0.2 of the original color.
142 const SkColor fade_color = SkColorSetA(color, 51);
[email protected]f308e5b12012-01-17 19:09:18143 std::vector<SkScalar> positions;
144 std::vector<SkColor> colors;
145
146 if (!left_part.IsEmpty())
147 AddFadeEffect(text_rect, left_part, fade_color, color,
148 &positions, &colors);
149 if (!right_part.IsEmpty())
150 AddFadeEffect(text_rect, right_part, color, fade_color,
151 &positions, &colors);
152 DCHECK(!positions.empty());
153
154 // Terminate |positions| with 1.0, as required by Skia.
155 if (positions.back() != 1.0) {
156 positions.push_back(1.0);
157 colors.push_back(colors.back());
158 }
159
[email protected]959e1902012-03-03 02:02:17160 SkPoint points[2];
161 points[0].iset(text_rect.x(), text_rect.y());
162 points[1].iset(text_rect.right(), text_rect.y());
[email protected]f308e5b12012-01-17 19:09:18163
[email protected]959e1902012-03-03 02:02:17164 return SkGradientShader::CreateLinear(&points[0], &colors[0], &positions[0],
165 colors.size(), SkShader::kClamp_TileMode);
166}
[email protected]f308e5b12012-01-17 19:09:18167
[email protected]ff44d712011-07-25 08:42:52168} // namespace
169
170namespace gfx {
171
[email protected]67b981562011-12-09 00:35:05172namespace internal {
173
174SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas)
175 : canvas_skia_(canvas->GetSkCanvas()) {
176 DCHECK(canvas_skia_);
177 paint_.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
178 paint_.setStyle(SkPaint::kFill_Style);
179 paint_.setAntiAlias(true);
180 paint_.setSubpixelText(true);
181 paint_.setLCDRenderText(true);
182}
183
184SkiaTextRenderer::~SkiaTextRenderer() {
185}
186
[email protected]be5756e22012-02-17 15:53:01187void SkiaTextRenderer::SetFontSmoothingSettings(bool enable_smoothing,
188 bool enable_lcd_text) {
189 paint_.setAntiAlias(enable_smoothing);
190 paint_.setSubpixelText(enable_smoothing);
191 paint_.setLCDRenderText(enable_lcd_text);
192}
193
[email protected]cd8e5522011-12-15 00:24:10194void SkiaTextRenderer::SetTypeface(SkTypeface* typeface) {
195 paint_.setTypeface(typeface);
196}
197
198void SkiaTextRenderer::SetTextSize(int size) {
199 paint_.setTextSize(size);
200}
201
[email protected]7aba39c2012-01-10 16:10:27202void SkiaTextRenderer::SetFontStyle(int style) {
203 SkTypeface::Style skia_style = ConvertFontStyleToSkiaTypefaceStyle(style);
204 SkTypeface* current_typeface = paint_.getTypeface();
205
206 if (current_typeface->style() == skia_style)
207 return;
208
[email protected]ab96da42012-01-09 23:05:30209 SkAutoTUnref<SkTypeface> typeface(
[email protected]7aba39c2012-01-10 16:10:27210 SkTypeface::CreateFromTypeface(current_typeface, skia_style));
211 if (typeface.get()) {
212 // |paint_| adds its own ref. So don't |release()| it from the ref ptr here.
213 SetTypeface(typeface.get());
214 }
215}
216
217void SkiaTextRenderer::SetFont(const gfx::Font& font) {
218 SkTypeface::Style skia_style =
219 ConvertFontStyleToSkiaTypefaceStyle(font.GetStyle());
220 SkAutoTUnref<SkTypeface> typeface(
221 SkTypeface::CreateFromName(font.GetFontName().c_str(), skia_style));
[email protected]67b981562011-12-09 00:35:05222 if (typeface.get()) {
223 // |paint_| adds its own ref. So don't |release()| it from the ref ptr here.
[email protected]cd8e5522011-12-15 00:24:10224 SetTypeface(typeface.get());
[email protected]67b981562011-12-09 00:35:05225 }
[email protected]cd8e5522011-12-15 00:24:10226 SetTextSize(font.GetFontSize());
[email protected]67b981562011-12-09 00:35:05227}
228
229void SkiaTextRenderer::SetForegroundColor(SkColor foreground) {
230 paint_.setColor(foreground);
231}
232
[email protected]f308e5b12012-01-17 19:09:18233void SkiaTextRenderer::SetShader(SkShader* shader) {
234 paint_.setShader(shader);
235}
236
[email protected]67b981562011-12-09 00:35:05237void SkiaTextRenderer::DrawPosText(const SkPoint* pos,
238 const uint16* glyphs,
239 size_t glyph_count) {
240 size_t byte_length = glyph_count * sizeof(glyphs[0]);
241 canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_);
242}
243
244// Draw underline and strike through text decorations.
245// Based on |SkCanvas::DrawTextDecorations()| and constants from:
246// third_party/skia/src/core/SkTextFormatParams.h
247void SkiaTextRenderer::DrawDecorations(int x, int y, int width,
[email protected]54d32cc2012-01-27 19:52:18248 const StyleRange& style) {
249 if (!style.underline && !style.strike && !style.diagonal_strike)
250 return;
251
[email protected]67b981562011-12-09 00:35:05252 // Fraction of the text size to lower a strike through below the baseline.
253 const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21);
254 // Fraction of the text size to lower an underline below the baseline.
255 const SkScalar kUnderlineOffset = (SK_Scalar1 / 9);
256 // Fraction of the text size to use for a strike through or under-line.
257 const SkScalar kLineThickness = (SK_Scalar1 / 18);
[email protected]54d32cc2012-01-27 19:52:18258 // Fraction of the text size to use for a top margin of a diagonal strike.
259 const SkScalar kDiagonalStrikeThroughMarginOffset = (SK_Scalar1 / 4);
[email protected]67b981562011-12-09 00:35:05260
261 SkScalar text_size = paint_.getTextSize();
262 SkScalar height = SkScalarMul(text_size, kLineThickness);
263 SkRect r;
264
265 r.fLeft = x;
266 r.fRight = x + width;
267
[email protected]54d32cc2012-01-27 19:52:18268 if (style.underline) {
[email protected]67b981562011-12-09 00:35:05269 SkScalar offset = SkScalarMulAdd(text_size, kUnderlineOffset, y);
270 r.fTop = offset;
271 r.fBottom = offset + height;
272 canvas_skia_->drawRect(r, paint_);
273 }
[email protected]54d32cc2012-01-27 19:52:18274 if (style.strike) {
[email protected]67b981562011-12-09 00:35:05275 SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y);
276 r.fTop = offset;
277 r.fBottom = offset + height;
278 canvas_skia_->drawRect(r, paint_);
279 }
[email protected]54d32cc2012-01-27 19:52:18280 if (style.diagonal_strike) {
281 SkScalar offset =
282 SkScalarMul(text_size, kDiagonalStrikeThroughMarginOffset);
283 SkPaint paint(paint_);
284 paint.setAntiAlias(true);
285 paint.setStyle(SkPaint::kFill_Style);
286 paint.setStrokeWidth(height);
287 canvas_skia_->drawLine(
288 SkIntToScalar(x), SkIntToScalar(y) - text_size + offset,
289 SkIntToScalar(x + width), SkIntToScalar(y),
290 paint);
291 }
[email protected]67b981562011-12-09 00:35:05292}
293
294} // namespace internal
295
296
[email protected]ff44d712011-07-25 08:42:52297StyleRange::StyleRange()
[email protected]8d901a82012-01-04 19:41:30298 : foreground(SK_ColorBLACK),
[email protected]7aba39c2012-01-10 16:10:27299 font_style(gfx::Font::NORMAL),
[email protected]ff44d712011-07-25 08:42:52300 strike(false),
[email protected]54d32cc2012-01-27 19:52:18301 diagonal_strike(false),
[email protected]8d901a82012-01-04 19:41:30302 underline(false) {
[email protected]ff44d712011-07-25 08:42:52303}
304
[email protected]8e42ba22011-08-04 21:47:08305RenderText::~RenderText() {
306}
307
[email protected]ff44d712011-07-25 08:42:52308void RenderText::SetText(const string16& text) {
[email protected]d3c6b0602011-09-07 19:26:06309 DCHECK(!composition_range_.IsValid());
[email protected]ff44d712011-07-25 08:42:52310 size_t old_text_length = text_.length();
311 text_ = text;
312
313 // Update the style ranges as needed.
314 if (text_.empty()) {
315 style_ranges_.clear();
316 } else if (style_ranges_.empty()) {
317 ApplyDefaultStyle();
318 } else if (text_.length() > old_text_length) {
319 style_ranges_.back().range.set_end(text_.length());
320 } else if (text_.length() < old_text_length) {
321 StyleRanges::iterator i;
322 for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) {
323 if (i->range.start() >= text_.length()) {
[email protected]052ce6bb2011-10-14 19:48:20324 // Style ranges are sorted and non-overlapping, so all the subsequent
325 // style ranges should be out of text_.length() as well.
326 style_ranges_.erase(i, style_ranges_.end());
327 break;
[email protected]ff44d712011-07-25 08:42:52328 }
329 }
[email protected]052ce6bb2011-10-14 19:48:20330 // Since style ranges are sorted and non-overlapping, if there is a style
331 // range ends beyond text_.length, it must be the last one.
[email protected]ff44d712011-07-25 08:42:52332 style_ranges_.back().range.set_end(text_.length());
333 }
334#ifndef NDEBUG
335 CheckStyleRanges(style_ranges_, text_.length());
336#endif
[email protected]f6aaa0f2011-08-11 07:05:46337 cached_bounds_and_offset_valid_ = false;
[email protected]d3c6b0602011-09-07 19:26:06338
339 // Reset selection model. SetText should always followed by SetSelectionModel
340 // or SetCursorPosition in upper layer.
341 SetSelectionModel(SelectionModel(0, 0, SelectionModel::LEADING));
[email protected]6002a4f32011-11-30 10:18:42342
343 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52344}
345
[email protected]f0ed8a2f2012-01-24 17:45:59346void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) {
347 if (horizontal_alignment_ != alignment) {
348 horizontal_alignment_ = alignment;
349 display_offset_ = Point();
350 cached_bounds_and_offset_valid_ = false;
351 }
352}
353
[email protected]8d901a82012-01-04 19:41:30354void RenderText::SetFontList(const FontList& font_list) {
355 font_list_ = font_list;
356 cached_bounds_and_offset_valid_ = false;
357 UpdateLayout();
358}
359
[email protected]fdf481b2012-01-24 06:14:07360void RenderText::SetFontSize(int size) {
361 font_list_ = font_list_.DeriveFontListWithSize(size);
362 cached_bounds_and_offset_valid_ = false;
363 UpdateLayout();
364}
365
[email protected]f0ed8a2f2012-01-24 17:45:59366void RenderText::SetCursorEnabled(bool cursor_enabled) {
367 cursor_enabled_ = cursor_enabled;
368 cached_bounds_and_offset_valid_ = false;
369}
370
[email protected]8d901a82012-01-04 19:41:30371const Font& RenderText::GetFont() const {
372 return font_list_.GetFonts()[0];
373}
374
[email protected]0d717602011-08-30 06:21:14375void RenderText::ToggleInsertMode() {
376 insert_mode_ = !insert_mode_;
[email protected]f6aaa0f2011-08-11 07:05:46377 cached_bounds_and_offset_valid_ = false;
378}
379
[email protected]bec929c2012-03-02 06:23:50380void RenderText::SetObscured(bool obscured) {
381 if (obscured != obscured_) {
382 obscured_ = obscured;
383 cached_bounds_and_offset_valid_ = false;
384 UpdateLayout();
385 }
386}
387
[email protected]f6aaa0f2011-08-11 07:05:46388void RenderText::SetDisplayRect(const Rect& r) {
389 display_rect_ = r;
390 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25391 UpdateLayout();
[email protected]8e42ba22011-08-04 21:47:08392}
393
[email protected]ff44d712011-07-25 08:42:52394size_t RenderText::GetCursorPosition() const {
[email protected]8e42ba22011-08-04 21:47:08395 return selection_model_.selection_end();
[email protected]ff44d712011-07-25 08:42:52396}
397
[email protected]0d717602011-08-30 06:21:14398void RenderText::SetCursorPosition(size_t position) {
399 MoveCursorTo(position, false);
[email protected]ff44d712011-07-25 08:42:52400}
401
[email protected]d66009e2012-01-21 01:27:28402void RenderText::MoveCursor(BreakType break_type,
403 VisualCursorDirection direction,
404 bool select) {
[email protected]ec7f48d2011-08-09 03:48:50405 SelectionModel position(selection_model());
406 position.set_selection_start(GetCursorPosition());
[email protected]ff44d712011-07-25 08:42:52407 // Cancelling a selection moves to the edge of the selection.
[email protected]ec7f48d2011-08-09 03:48:50408 if (break_type != LINE_BREAK && !EmptySelection() && !select) {
[email protected]d3c6b0602011-09-07 19:26:06409 SelectionModel selection_start = GetSelectionModelForSelectionStart();
[email protected]d66009e2012-01-21 01:27:28410 int start_x = GetCursorBounds(selection_start, true).x();
411 int cursor_x = GetCursorBounds(position, true).x();
412 // Use the selection start if it is left (when |direction| is CURSOR_LEFT)
413 // or right (when |direction| is CURSOR_RIGHT) of the selection end.
414 if (direction == CURSOR_RIGHT ? start_x > cursor_x : start_x < cursor_x)
[email protected]8e42ba22011-08-04 21:47:08415 position = selection_start;
[email protected]d66009e2012-01-21 01:27:28416 // For word breaks, use the nearest word boundary in the appropriate
417 // |direction|.
[email protected]ff44d712011-07-25 08:42:52418 if (break_type == WORD_BREAK)
[email protected]d66009e2012-01-21 01:27:28419 position = GetAdjacentSelectionModel(position, break_type, direction);
[email protected]ff44d712011-07-25 08:42:52420 } else {
[email protected]d66009e2012-01-21 01:27:28421 position = GetAdjacentSelectionModel(position, break_type, direction);
[email protected]ff44d712011-07-25 08:42:52422 }
[email protected]ec7f48d2011-08-09 03:48:50423 if (select)
424 position.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08425 MoveCursorTo(position);
[email protected]ff44d712011-07-25 08:42:52426}
427
[email protected]f9a221b2011-12-10 20:25:38428bool RenderText::MoveCursorTo(const SelectionModel& model) {
429 SelectionModel sel(model);
[email protected]0d717602011-08-30 06:21:14430 size_t text_length = text().length();
431 // Enforce valid selection model components.
432 if (sel.selection_start() > text_length)
433 sel.set_selection_start(text_length);
434 if (sel.selection_end() > text_length)
435 sel.set_selection_end(text_length);
436 // The current model only supports caret positions at valid character indices.
437 if (text_length == 0) {
438 sel.set_caret_pos(0);
439 sel.set_caret_placement(SelectionModel::LEADING);
440 } else if (sel.caret_pos() >= text_length) {
[email protected]d66009e2012-01-21 01:27:28441 SelectionModel end_selection =
442 EdgeSelectionModel(GetVisualDirectionOfLogicalEnd());
443 sel.set_caret_pos(end_selection.caret_pos());
444 sel.set_caret_placement(end_selection.caret_placement());
[email protected]0d717602011-08-30 06:21:14445 }
[email protected]53c0b1b2011-09-21 20:32:29446
447 if (!IsCursorablePosition(sel.selection_start()) ||
448 !IsCursorablePosition(sel.selection_end()) ||
449 !IsCursorablePosition(sel.caret_pos()))
450 return false;
451
[email protected]0d717602011-08-30 06:21:14452 bool changed = !sel.Equals(selection_model_);
453 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52454 return changed;
455}
456
[email protected]7b3cb4b22011-08-02 18:36:40457bool RenderText::MoveCursorTo(const Point& point, bool select) {
[email protected]8e42ba22011-08-04 21:47:08458 SelectionModel selection = FindCursorPosition(point);
459 if (select)
460 selection.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08461 return MoveCursorTo(selection);
[email protected]ff44d712011-07-25 08:42:52462}
463
[email protected]67e85512011-10-12 20:03:45464bool RenderText::SelectRange(const ui::Range& range) {
465 size_t text_length = text().length();
466 size_t start = std::min(range.start(), text_length);
467 size_t end = std::min(range.end(), text_length);
468
469 if (!IsCursorablePosition(start) || !IsCursorablePosition(end))
470 return false;
471
472 size_t pos = end;
473 SelectionModel::CaretPlacement placement = SelectionModel::LEADING;
474 if (start < end) {
[email protected]d66009e2012-01-21 01:27:28475 pos = IndexOfAdjacentGrapheme(end, CURSOR_BACKWARD);
[email protected]67e85512011-10-12 20:03:45476 DCHECK_LT(pos, end);
477 placement = SelectionModel::TRAILING;
478 } else if (end == text_length) {
[email protected]d66009e2012-01-21 01:27:28479 SelectionModel end_selection =
480 EdgeSelectionModel(GetVisualDirectionOfLogicalEnd());
481 pos = end_selection.caret_pos();
482 placement = end_selection.caret_placement();
[email protected]67e85512011-10-12 20:03:45483 }
484 SetSelectionModel(SelectionModel(start, end, pos, placement));
485 return true;
486}
487
[email protected]8e42ba22011-08-04 21:47:08488bool RenderText::IsPointInSelection(const Point& point) {
[email protected]0d717602011-08-30 06:21:14489 if (EmptySelection())
490 return false;
[email protected]8e42ba22011-08-04 21:47:08491 // TODO(xji): should this check whether the point is inside the visual
492 // selection bounds? In case of "abcFED", if "ED" is selected, |point| points
493 // to the right half of 'c', is the point in selection?
494 size_t pos = FindCursorPosition(point).selection_end();
495 return (pos >= MinOfSelection() && pos < MaxOfSelection());
[email protected]ff44d712011-07-25 08:42:52496}
497
498void RenderText::ClearSelection() {
[email protected]ec7f48d2011-08-09 03:48:50499 SelectionModel sel(selection_model());
500 sel.set_selection_start(GetCursorPosition());
501 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52502}
503
504void RenderText::SelectAll() {
[email protected]d66009e2012-01-21 01:27:28505 SelectionModel sel = EdgeSelectionModel(CURSOR_RIGHT);
506 sel.set_selection_start(EdgeSelectionModel(CURSOR_LEFT).selection_start());
[email protected]8e42ba22011-08-04 21:47:08507 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52508}
509
510void RenderText::SelectWord() {
[email protected]bec929c2012-03-02 06:23:50511 if (obscured_) {
512 SelectAll();
513 return;
514 }
515
[email protected]ff44d712011-07-25 08:42:52516 size_t cursor_position = GetCursorPosition();
[email protected]ff44d712011-07-25 08:42:52517
[email protected]53c0b1b2011-09-21 20:32:29518 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
519 bool success = iter.Init();
520 DCHECK(success);
521 if (!success)
522 return;
523
524 size_t selection_start = cursor_position;
525 for (; selection_start != 0; --selection_start) {
526 if (iter.IsStartOfWord(selection_start) ||
527 iter.IsEndOfWord(selection_start))
[email protected]ff44d712011-07-25 08:42:52528 break;
529 }
530
[email protected]53c0b1b2011-09-21 20:32:29531 if (selection_start == cursor_position)
532 ++cursor_position;
533
534 for (; cursor_position < text().length(); ++cursor_position) {
535 if (iter.IsEndOfWord(cursor_position) ||
536 iter.IsStartOfWord(cursor_position))
[email protected]ff44d712011-07-25 08:42:52537 break;
538 }
539
[email protected]0d717602011-08-30 06:21:14540 MoveCursorTo(selection_start, false);
541 MoveCursorTo(cursor_position, true);
[email protected]ff44d712011-07-25 08:42:52542}
543
544const ui::Range& RenderText::GetCompositionRange() const {
545 return composition_range_;
546}
547
548void RenderText::SetCompositionRange(const ui::Range& composition_range) {
549 CHECK(!composition_range.IsValid() ||
550 ui::Range(0, text_.length()).Contains(composition_range));
551 composition_range_.set_end(composition_range.end());
552 composition_range_.set_start(composition_range.start());
[email protected]79791682011-11-09 19:11:25553 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52554}
555
[email protected]c8636672011-12-29 05:07:57556void RenderText::ApplyStyleRange(const StyleRange& style_range) {
[email protected]ff44d712011-07-25 08:42:52557 const ui::Range& new_range = style_range.range;
558 if (!new_range.IsValid() || new_range.is_empty())
559 return;
560 CHECK(!new_range.is_reversed());
561 CHECK(ui::Range(0, text_.length()).Contains(new_range));
[email protected]8e42ba22011-08-04 21:47:08562 ApplyStyleRangeImpl(&style_ranges_, style_range);
[email protected]ff44d712011-07-25 08:42:52563#ifndef NDEBUG
564 CheckStyleRanges(style_ranges_, text_.length());
565#endif
[email protected]f6aaa0f2011-08-11 07:05:46566 // TODO(xji): only invalidate if font or underline changes.
567 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25568 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52569}
570
571void RenderText::ApplyDefaultStyle() {
572 style_ranges_.clear();
573 StyleRange style = StyleRange(default_style_);
574 style.range.set_end(text_.length());
575 style_ranges_.push_back(style);
[email protected]f6aaa0f2011-08-11 07:05:46576 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25577 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52578}
579
[email protected]d66009e2012-01-21 01:27:28580VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() {
581 return GetTextDirection() == base::i18n::LEFT_TO_RIGHT ?
582 CURSOR_RIGHT : CURSOR_LEFT;
[email protected]ff44d712011-07-25 08:42:52583}
584
[email protected]7b3cb4b22011-08-02 18:36:40585void RenderText::Draw(Canvas* canvas) {
[email protected]592a7ad2011-12-14 05:57:53586 TRACE_EVENT0("gfx", "RenderText::Draw");
587 {
588 TRACE_EVENT0("gfx", "RenderText::EnsureLayout");
589 EnsureLayout();
590 }
[email protected]ff44d712011-07-25 08:42:52591
[email protected]f308e5b12012-01-17 19:09:18592 canvas->Save();
593 canvas->ClipRect(display_rect());
[email protected]1a353f12012-01-18 04:20:56594
595 if (!text().empty())
596 DrawSelection(canvas);
597
598 DrawCursor(canvas);
599
[email protected]6002a4f32011-11-30 10:18:42600 if (!text().empty()) {
[email protected]592a7ad2011-12-14 05:57:53601 TRACE_EVENT0("gfx", "RenderText::Draw draw text");
[email protected]6002a4f32011-11-30 10:18:42602 DrawVisualText(canvas);
[email protected]ff44d712011-07-25 08:42:52603 }
[email protected]f308e5b12012-01-17 19:09:18604 canvas->Restore();
[email protected]ff44d712011-07-25 08:42:52605}
606
[email protected]f6aaa0f2011-08-11 07:05:46607const Rect& RenderText::GetUpdatedCursorBounds() {
608 UpdateCachedBoundsAndOffset();
[email protected]8e42ba22011-08-04 21:47:08609 return cursor_bounds_;
610}
611
[email protected]04b7bf322011-10-03 19:08:46612SelectionModel RenderText::GetSelectionModelForSelectionStart() {
613 size_t selection_start = GetSelectionStart();
614 size_t selection_end = GetCursorPosition();
615 if (selection_start < selection_end)
616 return SelectionModel(selection_start,
617 selection_start,
618 SelectionModel::LEADING);
619 else if (selection_start > selection_end)
[email protected]d66009e2012-01-21 01:27:28620 return SelectionModel(
621 selection_start,
622 IndexOfAdjacentGrapheme(selection_start, CURSOR_BACKWARD),
623 SelectionModel::TRAILING);
[email protected]04b7bf322011-10-03 19:08:46624 return selection_model_;
625}
626
[email protected]8e42ba22011-08-04 21:47:08627RenderText::RenderText()
[email protected]f0ed8a2f2012-01-24 17:45:59628 : horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT),
629 cursor_enabled_(true),
630 cursor_visible_(false),
[email protected]8e42ba22011-08-04 21:47:08631 insert_mode_(true),
[email protected]913a1ef2012-02-29 08:40:34632 cursor_color_(kDefaultCursorColor),
[email protected]5abe6302011-12-20 23:44:32633 focused_(false),
[email protected]d3c6b0602011-09-07 19:26:06634 composition_range_(ui::Range::InvalidRange()),
[email protected]bec929c2012-03-02 06:23:50635 obscured_(false),
[email protected]f308e5b12012-01-17 19:09:18636 fade_head_(false),
637 fade_tail_(false),
[email protected]32d582d2012-02-28 21:50:22638 background_is_transparent_(false),
[email protected]f6aaa0f2011-08-11 07:05:46639 cached_bounds_and_offset_valid_(false) {
640}
641
642const Point& RenderText::GetUpdatedDisplayOffset() {
643 UpdateCachedBoundsAndOffset();
644 return display_offset_;
[email protected]8e42ba22011-08-04 21:47:08645}
646
[email protected]d66009e2012-01-21 01:27:28647SelectionModel RenderText::GetAdjacentSelectionModel(
648 const SelectionModel& current,
649 BreakType break_type,
650 VisualCursorDirection direction) {
651 EnsureLayout();
652
653 if (break_type == LINE_BREAK || text().empty())
654 return EdgeSelectionModel(direction);
[email protected]ec7f48d2011-08-09 03:48:50655 if (break_type == CHARACTER_BREAK)
[email protected]d66009e2012-01-21 01:27:28656 return AdjacentCharSelectionModel(current, direction);
657 DCHECK(break_type == WORD_BREAK);
658 return AdjacentWordSelectionModel(current, direction);
[email protected]0d717602011-08-30 06:21:14659}
660
[email protected]6002a4f32011-11-30 10:18:42661void RenderText::SetSelectionModel(const SelectionModel& model) {
662 DCHECK_LE(model.selection_start(), text().length());
663 selection_model_.set_selection_start(model.selection_start());
664 DCHECK_LE(model.selection_end(), text().length());
665 selection_model_.set_selection_end(model.selection_end());
[email protected]f9a221b2011-12-10 20:25:38666 DCHECK_LT(model.caret_pos(), std::max<size_t>(text().length(), 1));
[email protected]6002a4f32011-11-30 10:18:42667 selection_model_.set_caret_pos(model.caret_pos());
668 selection_model_.set_caret_placement(model.caret_placement());
669
670 cached_bounds_and_offset_valid_ = false;
[email protected]0d717602011-08-30 06:21:14671}
672
[email protected]bec929c2012-03-02 06:23:50673string16 RenderText::GetDisplayText() const {
674 if (!obscured_)
675 return text_;
676 size_t obscured_text_length =
677 static_cast<size_t>(ui::UTF16IndexToOffset(text_, 0, text_.length()));
678 return string16(obscured_text_length, kPasswordReplacementChar);
679}
680
[email protected]8e42ba22011-08-04 21:47:08681void RenderText::ApplyCompositionAndSelectionStyles(
[email protected]1a353f12012-01-18 04:20:56682 StyleRanges* style_ranges) {
[email protected]8e42ba22011-08-04 21:47:08683 // TODO(msw): This pattern ought to be reconsidered; what about composition
684 // and selection overlaps, retain existing local style features?
685 // Apply a composition style override to a copy of the style ranges.
686 if (composition_range_.IsValid() && !composition_range_.is_empty()) {
687 StyleRange composition_style(default_style_);
688 composition_style.underline = true;
689 composition_style.range.set_start(composition_range_.start());
690 composition_style.range.set_end(composition_range_.end());
691 ApplyStyleRangeImpl(style_ranges, composition_style);
692 }
693 // Apply a selection style override to a copy of the style ranges.
694 if (!EmptySelection()) {
695 StyleRange selection_style(default_style_);
[email protected]721ea922012-01-23 21:13:33696 selection_style.foreground = NativeTheme::instance()->GetSystemColor(
697 NativeTheme::kColorId_TextfieldSelectionColor);
[email protected]8e42ba22011-08-04 21:47:08698 selection_style.range.set_start(MinOfSelection());
699 selection_style.range.set_end(MaxOfSelection());
700 ApplyStyleRangeImpl(style_ranges, selection_style);
701 }
[email protected]1a353f12012-01-18 04:20:56702 // Apply replacement-mode style override to a copy of the style ranges.
703 //
704 // TODO(xji): NEED TO FIX FOR WINDOWS ASAP. Windows call this function (to
705 // apply styles) in ItemizeLogicalText(). In order for the cursor's underline
706 // character to be drawn correctly, we will need to re-layout the text. It's
707 // not practical to do layout on every cursor blink. We need to fix Windows
708 // port to apply styles during drawing phase like Linux port does.
709 // http://crbug.com/110109
710 if (!insert_mode_ && cursor_visible() && focused()) {
711 StyleRange replacement_mode_style(default_style_);
[email protected]721ea922012-01-23 21:13:33712 replacement_mode_style.foreground = NativeTheme::instance()->GetSystemColor(
713 NativeTheme::kColorId_TextfieldSelectionColor);
[email protected]1a353f12012-01-18 04:20:56714 size_t cursor = GetCursorPosition();
715 replacement_mode_style.range.set_start(cursor);
[email protected]d66009e2012-01-21 01:27:28716 replacement_mode_style.range.set_end(
717 IndexOfAdjacentGrapheme(cursor, CURSOR_FORWARD));
[email protected]1a353f12012-01-18 04:20:56718 ApplyStyleRangeImpl(style_ranges, replacement_mode_style);
719 }
[email protected]ff44d712011-07-25 08:42:52720}
721
[email protected]f0ed8a2f2012-01-24 17:45:59722Point RenderText::GetTextOrigin() {
723 Point origin = display_rect().origin();
724 origin = origin.Add(GetUpdatedDisplayOffset());
725 origin = origin.Add(GetAlignmentOffset());
726 return origin;
727}
728
[email protected]0d717602011-08-30 06:21:14729Point RenderText::ToTextPoint(const Point& point) {
[email protected]f0ed8a2f2012-01-24 17:45:59730 return point.Subtract(GetTextOrigin());
[email protected]0d717602011-08-30 06:21:14731}
732
733Point RenderText::ToViewPoint(const Point& point) {
[email protected]f0ed8a2f2012-01-24 17:45:59734 return point.Add(GetTextOrigin());
735}
736
737int RenderText::GetContentWidth() {
738 return GetStringWidth() + (cursor_enabled_ ? 1 : 0);
739}
740
741Point RenderText::GetAlignmentOffset() {
742 if (horizontal_alignment() != ALIGN_LEFT) {
743 int x_offset = display_rect().width() - GetContentWidth();
744 if (horizontal_alignment() == ALIGN_CENTER)
745 x_offset /= 2;
746 return Point(x_offset, 0);
747 }
748 return Point();
[email protected]0d717602011-08-30 06:21:14749}
750
[email protected]67b981562011-12-09 00:35:05751Point RenderText::GetOriginForSkiaDrawing() {
[email protected]f0ed8a2f2012-01-24 17:45:59752 Point origin(GetTextOrigin());
[email protected]67b981562011-12-09 00:35:05753 // TODO(msw): Establish a vertical baseline for strings of mixed font heights.
[email protected]8d901a82012-01-04 19:41:30754 const Font& font = GetFont();
[email protected]5f4fd30e2012-01-12 02:19:33755 int height = font.GetHeight();
[email protected]fdf481b2012-01-24 06:14:07756 DCHECK_LE(height, display_rect().height());
[email protected]67b981562011-12-09 00:35:05757 // Center the text vertically in the display area.
758 origin.Offset(0, (display_rect().height() - height) / 2);
759 // Offset by the font size to account for Skia expecting y to be the bottom.
760 origin.Offset(0, font.GetFontSize());
761 return origin;
762}
763
[email protected]f0ed8a2f2012-01-24 17:45:59764void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) {
[email protected]f308e5b12012-01-17 19:09:18765 if (!fade_head() && !fade_tail())
[email protected]f0ed8a2f2012-01-24 17:45:59766 return;
[email protected]f308e5b12012-01-17 19:09:18767
768 const int text_width = GetStringWidth();
769 const int display_width = display_rect().width();
770
771 // If the text fits as-is, no need to fade.
772 if (text_width <= display_width)
[email protected]f0ed8a2f2012-01-24 17:45:59773 return;
[email protected]f308e5b12012-01-17 19:09:18774
775 int gradient_width = CalculateFadeGradientWidth(GetFont(), display_width);
776 if (gradient_width == 0)
[email protected]f0ed8a2f2012-01-24 17:45:59777 return;
[email protected]f308e5b12012-01-17 19:09:18778
779 bool fade_left = fade_head();
780 bool fade_right = fade_tail();
781 // Under RTL, |fade_right| == |fade_head|.
782 if (GetTextDirection() == base::i18n::RIGHT_TO_LEFT)
783 std::swap(fade_left, fade_right);
784
785 gfx::Rect solid_part = display_rect();
786 gfx::Rect left_part;
787 gfx::Rect right_part;
788 if (fade_left) {
789 left_part = solid_part;
790 left_part.Inset(0, 0, solid_part.width() - gradient_width, 0);
791 solid_part.Inset(gradient_width, 0, 0, 0);
792 }
793 if (fade_right) {
794 right_part = solid_part;
795 right_part.Inset(solid_part.width() - gradient_width, 0, 0, 0);
796 solid_part.Inset(0, 0, gradient_width, 0);
797 }
798
[email protected]f308e5b12012-01-17 19:09:18799 gfx::Rect text_rect = display_rect();
[email protected]f0ed8a2f2012-01-24 17:45:59800 text_rect.Inset(GetAlignmentOffset().x(), 0, 0, 0);
[email protected]f308e5b12012-01-17 19:09:18801
802 const SkColor color = default_style().foreground;
803 SkAutoTUnref<SkShader> shader(
804 CreateFadeShader(text_rect, left_part, right_part, color));
805 if (shader.get()) {
806 // |renderer| adds its own ref. So don't |release()| it from the ref ptr.
807 renderer->SetShader(shader.get());
808 }
[email protected]f308e5b12012-01-17 19:09:18809}
810
[email protected]0d717602011-08-30 06:21:14811void RenderText::MoveCursorTo(size_t position, bool select) {
812 size_t cursor = std::min(position, text().length());
[email protected]d66009e2012-01-21 01:27:28813 size_t caret_pos = IndexOfAdjacentGrapheme(cursor, CURSOR_BACKWARD);
[email protected]0d717602011-08-30 06:21:14814 SelectionModel::CaretPlacement placement = (caret_pos == cursor) ?
815 SelectionModel::LEADING : SelectionModel::TRAILING;
816 size_t selection_start = select ? GetSelectionStart() : cursor;
[email protected]53c0b1b2011-09-21 20:32:29817 if (IsCursorablePosition(cursor)) {
818 SelectionModel sel(selection_start, cursor, caret_pos, placement);
819 SetSelectionModel(sel);
820 }
[email protected]8e42ba22011-08-04 21:47:08821}
822
[email protected]f6aaa0f2011-08-11 07:05:46823void RenderText::UpdateCachedBoundsAndOffset() {
824 if (cached_bounds_and_offset_valid_)
825 return;
[email protected]f0ed8a2f2012-01-24 17:45:59826
[email protected]f6aaa0f2011-08-11 07:05:46827 // First, set the valid flag true to calculate the current cursor bounds using
828 // the stale |display_offset_|. Applying |delta_offset| at the end of this
829 // function will set |cursor_bounds_| and |display_offset_| to correct values.
830 cached_bounds_and_offset_valid_ = true;
831 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_);
[email protected]f0ed8a2f2012-01-24 17:45:59832
[email protected]8e42ba22011-08-04 21:47:08833 // Update |display_offset_| to ensure the current cursor is visible.
[email protected]f0ed8a2f2012-01-24 17:45:59834 const int display_width = display_rect_.width();
835 const int content_width = GetContentWidth();
836
[email protected]f6aaa0f2011-08-11 07:05:46837 int delta_offset = 0;
[email protected]f0ed8a2f2012-01-24 17:45:59838 if (content_width <= display_width || !cursor_enabled()) {
839 // Don't pan if the text fits in the display width or when the cursor is
840 // disabled.
[email protected]f6aaa0f2011-08-11 07:05:46841 delta_offset = -display_offset_.x();
[email protected]65788132011-09-07 20:20:23842 } else if (cursor_bounds_.right() >= display_rect_.right()) {
[email protected]d3c6b0602011-09-07 19:26:06843 // TODO(xji): when the character overflow is a RTL character, currently, if
844 // we pan cursor at the rightmost position, the entered RTL character is not
845 // displayed. Should pan cursor to show the last logical characters.
846 //
[email protected]8e42ba22011-08-04 21:47:08847 // Pan to show the cursor when it overflows to the right,
[email protected]65788132011-09-07 20:20:23848 delta_offset = display_rect_.right() - cursor_bounds_.right() - 1;
[email protected]f6aaa0f2011-08-11 07:05:46849 } else if (cursor_bounds_.x() < display_rect_.x()) {
[email protected]d3c6b0602011-09-07 19:26:06850 // TODO(xji): have similar problem as above when overflow character is a
851 // LTR character.
852 //
[email protected]8e42ba22011-08-04 21:47:08853 // Pan to show the cursor when it overflows to the left.
[email protected]f6aaa0f2011-08-11 07:05:46854 delta_offset = display_rect_.x() - cursor_bounds_.x();
[email protected]f0ed8a2f2012-01-24 17:45:59855 } else if (display_offset_.x() != 0) {
856 // Reduce the pan offset to show additional overflow text when the display
857 // width increases.
858 const int negate_rtl = horizontal_alignment_ == ALIGN_RIGHT ? -1 : 1;
859 const int offset = negate_rtl * display_offset_.x();
860 if (display_width > (content_width + offset))
861 delta_offset = negate_rtl * (display_width - (content_width + offset));
[email protected]8e42ba22011-08-04 21:47:08862 }
[email protected]f0ed8a2f2012-01-24 17:45:59863
[email protected]f6aaa0f2011-08-11 07:05:46864 display_offset_.Offset(delta_offset, 0);
865 cursor_bounds_.Offset(delta_offset, 0);
[email protected]ff44d712011-07-25 08:42:52866}
867
[email protected]6002a4f32011-11-30 10:18:42868void RenderText::DrawSelection(Canvas* canvas) {
[email protected]d66009e2012-01-21 01:27:28869 std::vector<Rect> sel = GetSubstringBounds(
870 GetSelectionStart(), GetCursorPosition());
[email protected]721ea922012-01-23 21:13:33871 NativeTheme::ColorId color_id = focused() ?
872 NativeTheme::kColorId_TextfieldSelectionBackgroundFocused :
873 NativeTheme::kColorId_TextfieldSelectionBackgroundUnfocused;
874 SkColor color = NativeTheme::instance()->GetSystemColor(color_id);
[email protected]6002a4f32011-11-30 10:18:42875 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
[email protected]b8bdc422012-02-01 21:44:40876 canvas->FillRect(*i, color);
[email protected]6002a4f32011-11-30 10:18:42877}
878
879void RenderText::DrawCursor(Canvas* canvas) {
880 // Paint cursor. Replace cursor is drawn as rectangle for now.
881 // TODO(msw): Draw a better cursor with a better indication of association.
[email protected]f0ed8a2f2012-01-24 17:45:59882 if (cursor_enabled() && cursor_visible() && focused()) {
[email protected]1a353f12012-01-18 04:20:56883 const Rect& bounds = GetUpdatedCursorBounds();
884 if (bounds.width() != 0)
[email protected]913a1ef2012-02-29 08:40:34885 canvas->FillRect(bounds, cursor_color_);
[email protected]1a353f12012-01-18 04:20:56886 else
[email protected]913a1ef2012-02-29 08:40:34887 canvas->DrawRect(bounds, cursor_color_);
[email protected]1a353f12012-01-18 04:20:56888 }
[email protected]6002a4f32011-11-30 10:18:42889}
890
[email protected]ff44d712011-07-25 08:42:52891} // namespace gfx