blob: d6bbc163325424873d5020c8e1b073a8de4c5e0e [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"
[email protected]721ea922012-01-23 21:13:3317#include "ui/gfx/native_theme.h"
[email protected]ff44d712011-07-25 08:42:5218
19namespace {
20
[email protected]bec929c2012-03-02 06:23:5021// All chars are replaced by this char when the password style is set.
22// TODO(benrg): GTK uses the first of U+25CF, U+2022, U+2731, U+273A, '*'
23// that's available in the font (find_invisible_char() in gtkentry.c).
24const char16 kPasswordReplacementChar = '*';
25
[email protected]913a1ef2012-02-29 08:40:3426// Default color used for the cursor.
27const SkColor kDefaultCursorColor = SK_ColorBLACK;
[email protected]d66009e2012-01-21 01:27:2828
[email protected]ff44d712011-07-25 08:42:5229#ifndef NDEBUG
30// Check StyleRanges invariant conditions: sorted and non-overlapping ranges.
31void CheckStyleRanges(const gfx::StyleRanges& style_ranges, size_t length) {
32 if (length == 0) {
33 DCHECK(style_ranges.empty()) << "Style ranges exist for empty text.";
34 return;
35 }
36 for (gfx::StyleRanges::size_type i = 0; i < style_ranges.size() - 1; i++) {
37 const ui::Range& former = style_ranges[i].range;
38 const ui::Range& latter = style_ranges[i + 1].range;
39 DCHECK(!former.is_empty()) << "Empty range at " << i << ":" << former;
40 DCHECK(former.IsValid()) << "Invalid range at " << i << ":" << former;
41 DCHECK(!former.is_reversed()) << "Reversed range at " << i << ":" << former;
42 DCHECK(former.end() == latter.start()) << "Ranges gap/overlap/unsorted." <<
43 "former:" << former << ", latter:" << latter;
44 }
45 const gfx::StyleRange& end_style = *style_ranges.rbegin();
46 DCHECK(!end_style.range.is_empty()) << "Empty range at end.";
47 DCHECK(end_style.range.IsValid()) << "Invalid range at end.";
48 DCHECK(!end_style.range.is_reversed()) << "Reversed range at end.";
49 DCHECK(end_style.range.end() == length) << "Style and text length mismatch.";
50}
51#endif
52
[email protected]8e42ba22011-08-04 21:47:0853void ApplyStyleRangeImpl(gfx::StyleRanges* style_ranges,
[email protected]c8636672011-12-29 05:07:5754 const gfx::StyleRange& style_range) {
[email protected]ff44d712011-07-25 08:42:5255 const ui::Range& new_range = style_range.range;
56 // Follow StyleRanges invariant conditions: sorted and non-overlapping ranges.
57 gfx::StyleRanges::iterator i;
[email protected]8e42ba22011-08-04 21:47:0858 for (i = style_ranges->begin(); i != style_ranges->end();) {
[email protected]ff44d712011-07-25 08:42:5259 if (i->range.end() < new_range.start()) {
60 i++;
61 } else if (i->range.start() == new_range.end()) {
62 break;
63 } else if (new_range.Contains(i->range)) {
[email protected]8e42ba22011-08-04 21:47:0864 i = style_ranges->erase(i);
65 if (i == style_ranges->end())
[email protected]ff44d712011-07-25 08:42:5266 break;
67 } else if (i->range.start() < new_range.start() &&
68 i->range.end() > new_range.end()) {
69 // Split the current style into two styles.
70 gfx::StyleRange split_style = gfx::StyleRange(*i);
71 split_style.range.set_end(new_range.start());
[email protected]8e42ba22011-08-04 21:47:0872 i = style_ranges->insert(i, split_style) + 1;
[email protected]ff44d712011-07-25 08:42:5273 i->range.set_start(new_range.end());
74 break;
75 } else if (i->range.start() < new_range.start()) {
76 i->range.set_end(new_range.start());
77 i++;
78 } else if (i->range.end() > new_range.end()) {
79 i->range.set_start(new_range.end());
80 break;
[email protected]f9a221b2011-12-10 20:25:3881 } else {
[email protected]ff44d712011-07-25 08:42:5282 NOTREACHED();
[email protected]f9a221b2011-12-10 20:25:3883 }
[email protected]ff44d712011-07-25 08:42:5284 }
85 // Add the new range in its sorted location.
[email protected]8e42ba22011-08-04 21:47:0886 style_ranges->insert(i, style_range);
[email protected]ff44d712011-07-25 08:42:5287}
88
[email protected]7aba39c2012-01-10 16:10:2789// Converts |gfx::Font::FontStyle| flags to |SkTypeface::Style| flags.
90SkTypeface::Style ConvertFontStyleToSkiaTypefaceStyle(int font_style) {
91 int skia_style = SkTypeface::kNormal;
92 if (font_style & gfx::Font::BOLD)
93 skia_style |= SkTypeface::kBold;
94 if (font_style & gfx::Font::ITALIC)
95 skia_style |= SkTypeface::kItalic;
96 return static_cast<SkTypeface::Style>(skia_style);
97}
98
[email protected]f308e5b12012-01-17 19:09:1899// Given |font| and |display_width|, returns the width of the fade gradient.
100int CalculateFadeGradientWidth(const gfx::Font& font, int display_width) {
101 // Fade in/out about 2.5 characters of the beginning/end of the string.
102 // The .5 here is helpful if one of the characters is a space.
103 // Use a quarter of the display width if the display width is very short.
104 const int average_character_width = font.GetAverageCharacterWidth();
105 const double gradient_width = std::min(average_character_width * 2.5,
106 display_width / 4.0);
107 DCHECK_GE(gradient_width, 0.0);
108 return static_cast<int>(floor(gradient_width + 0.5));
109}
110
111// Appends to |positions| and |colors| values corresponding to the fade over
112// |fade_rect| from color |c0| to color |c1|.
113void AddFadeEffect(const gfx::Rect& text_rect,
114 const gfx::Rect& fade_rect,
115 SkColor c0,
116 SkColor c1,
117 std::vector<SkScalar>* positions,
118 std::vector<SkColor>* colors) {
119 const SkScalar left = static_cast<SkScalar>(fade_rect.x() - text_rect.x());
120 const SkScalar width = static_cast<SkScalar>(fade_rect.width());
121 const SkScalar p0 = left / text_rect.width();
122 const SkScalar p1 = (left + width) / text_rect.width();
123 // Prepend 0.0 to |positions|, as required by Skia.
124 if (positions->empty() && p0 != 0.0) {
125 positions->push_back(0.0);
126 colors->push_back(c0);
127 }
128 positions->push_back(p0);
129 colors->push_back(c0);
130 positions->push_back(p1);
131 colors->push_back(c1);
132}
133
134// Creates a SkShader to fade the text, with |left_part| specifying the left
135// fade effect, if any, and |right_part| specifying the right fade effect.
136SkShader* CreateFadeShader(const gfx::Rect& text_rect,
137 const gfx::Rect& left_part,
138 const gfx::Rect& right_part,
139 SkColor color) {
140 // Fade alpha of 51/255 corresponds to a fade of 0.2 of the original color.
141 const SkColor fade_color = SkColorSetA(color, 51);
[email protected]f308e5b12012-01-17 19:09:18142 std::vector<SkScalar> positions;
143 std::vector<SkColor> colors;
144
145 if (!left_part.IsEmpty())
146 AddFadeEffect(text_rect, left_part, fade_color, color,
147 &positions, &colors);
148 if (!right_part.IsEmpty())
149 AddFadeEffect(text_rect, right_part, color, fade_color,
150 &positions, &colors);
151 DCHECK(!positions.empty());
152
153 // Terminate |positions| with 1.0, as required by Skia.
154 if (positions.back() != 1.0) {
155 positions.push_back(1.0);
156 colors.push_back(colors.back());
157 }
158
[email protected]959e1902012-03-03 02:02:17159 SkPoint points[2];
160 points[0].iset(text_rect.x(), text_rect.y());
161 points[1].iset(text_rect.right(), text_rect.y());
[email protected]f308e5b12012-01-17 19:09:18162
[email protected]959e1902012-03-03 02:02:17163 return SkGradientShader::CreateLinear(&points[0], &colors[0], &positions[0],
164 colors.size(), SkShader::kClamp_TileMode);
165}
[email protected]f308e5b12012-01-17 19:09:18166
[email protected]ff44d712011-07-25 08:42:52167} // namespace
168
169namespace gfx {
170
[email protected]67b981562011-12-09 00:35:05171namespace internal {
172
173SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas)
[email protected]f0e90a42012-03-13 23:05:57174 : canvas_skia_(canvas->sk_canvas()) {
[email protected]67b981562011-12-09 00:35:05175 DCHECK(canvas_skia_);
176 paint_.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
177 paint_.setStyle(SkPaint::kFill_Style);
178 paint_.setAntiAlias(true);
179 paint_.setSubpixelText(true);
180 paint_.setLCDRenderText(true);
181}
182
183SkiaTextRenderer::~SkiaTextRenderer() {
184}
185
[email protected]be5756e22012-02-17 15:53:01186void SkiaTextRenderer::SetFontSmoothingSettings(bool enable_smoothing,
187 bool enable_lcd_text) {
188 paint_.setAntiAlias(enable_smoothing);
189 paint_.setSubpixelText(enable_smoothing);
190 paint_.setLCDRenderText(enable_lcd_text);
191}
192
[email protected]cd8e5522011-12-15 00:24:10193void SkiaTextRenderer::SetTypeface(SkTypeface* typeface) {
194 paint_.setTypeface(typeface);
195}
196
197void SkiaTextRenderer::SetTextSize(int size) {
198 paint_.setTextSize(size);
199}
200
[email protected]f3dc6fb2012-03-24 21:01:24201void SkiaTextRenderer::SetFontFamilyWithStyle(const std::string& family,
202 int style) {
203 DCHECK(!family.empty());
204
205 SkTypeface::Style skia_style = ConvertFontStyleToSkiaTypefaceStyle(style);
206 SkAutoTUnref<SkTypeface> typeface(
207 SkTypeface::CreateFromName(family.c_str(), skia_style));
208 if (typeface.get()) {
209 // |paint_| adds its own ref. So don't |release()| it from the ref ptr here.
210 SetTypeface(typeface.get());
211
212 // Enable fake bold text if bold style is needed but new typeface does not
213 // have it.
214 paint_.setFakeBoldText((skia_style & SkTypeface::kBold) &&
215 !typeface.get()->isBold());
216 }
217}
218
[email protected]7aba39c2012-01-10 16:10:27219void SkiaTextRenderer::SetFontStyle(int style) {
220 SkTypeface::Style skia_style = ConvertFontStyleToSkiaTypefaceStyle(style);
221 SkTypeface* current_typeface = paint_.getTypeface();
222
223 if (current_typeface->style() == skia_style)
224 return;
225
[email protected]ab96da42012-01-09 23:05:30226 SkAutoTUnref<SkTypeface> typeface(
[email protected]7aba39c2012-01-10 16:10:27227 SkTypeface::CreateFromTypeface(current_typeface, skia_style));
228 if (typeface.get()) {
229 // |paint_| adds its own ref. So don't |release()| it from the ref ptr here.
230 SetTypeface(typeface.get());
231 }
232}
233
234void SkiaTextRenderer::SetFont(const gfx::Font& font) {
235 SkTypeface::Style skia_style =
236 ConvertFontStyleToSkiaTypefaceStyle(font.GetStyle());
237 SkAutoTUnref<SkTypeface> typeface(
238 SkTypeface::CreateFromName(font.GetFontName().c_str(), skia_style));
[email protected]67b981562011-12-09 00:35:05239 if (typeface.get()) {
240 // |paint_| adds its own ref. So don't |release()| it from the ref ptr here.
[email protected]cd8e5522011-12-15 00:24:10241 SetTypeface(typeface.get());
[email protected]67b981562011-12-09 00:35:05242 }
[email protected]cd8e5522011-12-15 00:24:10243 SetTextSize(font.GetFontSize());
[email protected]67b981562011-12-09 00:35:05244}
245
246void SkiaTextRenderer::SetForegroundColor(SkColor foreground) {
247 paint_.setColor(foreground);
248}
249
[email protected]f308e5b12012-01-17 19:09:18250void SkiaTextRenderer::SetShader(SkShader* shader) {
251 paint_.setShader(shader);
252}
253
[email protected]67b981562011-12-09 00:35:05254void SkiaTextRenderer::DrawPosText(const SkPoint* pos,
255 const uint16* glyphs,
256 size_t glyph_count) {
257 size_t byte_length = glyph_count * sizeof(glyphs[0]);
258 canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_);
259}
260
261// Draw underline and strike through text decorations.
262// Based on |SkCanvas::DrawTextDecorations()| and constants from:
263// third_party/skia/src/core/SkTextFormatParams.h
264void SkiaTextRenderer::DrawDecorations(int x, int y, int width,
[email protected]54d32cc2012-01-27 19:52:18265 const StyleRange& style) {
266 if (!style.underline && !style.strike && !style.diagonal_strike)
267 return;
268
[email protected]67b981562011-12-09 00:35:05269 // Fraction of the text size to lower a strike through below the baseline.
270 const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21);
271 // Fraction of the text size to lower an underline below the baseline.
272 const SkScalar kUnderlineOffset = (SK_Scalar1 / 9);
273 // Fraction of the text size to use for a strike through or under-line.
274 const SkScalar kLineThickness = (SK_Scalar1 / 18);
[email protected]54d32cc2012-01-27 19:52:18275 // Fraction of the text size to use for a top margin of a diagonal strike.
276 const SkScalar kDiagonalStrikeThroughMarginOffset = (SK_Scalar1 / 4);
[email protected]67b981562011-12-09 00:35:05277
278 SkScalar text_size = paint_.getTextSize();
279 SkScalar height = SkScalarMul(text_size, kLineThickness);
280 SkRect r;
281
282 r.fLeft = x;
283 r.fRight = x + width;
284
[email protected]54d32cc2012-01-27 19:52:18285 if (style.underline) {
[email protected]67b981562011-12-09 00:35:05286 SkScalar offset = SkScalarMulAdd(text_size, kUnderlineOffset, y);
287 r.fTop = offset;
288 r.fBottom = offset + height;
289 canvas_skia_->drawRect(r, paint_);
290 }
[email protected]54d32cc2012-01-27 19:52:18291 if (style.strike) {
[email protected]67b981562011-12-09 00:35:05292 SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y);
293 r.fTop = offset;
294 r.fBottom = offset + height;
295 canvas_skia_->drawRect(r, paint_);
296 }
[email protected]54d32cc2012-01-27 19:52:18297 if (style.diagonal_strike) {
298 SkScalar offset =
299 SkScalarMul(text_size, kDiagonalStrikeThroughMarginOffset);
300 SkPaint paint(paint_);
301 paint.setAntiAlias(true);
302 paint.setStyle(SkPaint::kFill_Style);
303 paint.setStrokeWidth(height);
304 canvas_skia_->drawLine(
305 SkIntToScalar(x), SkIntToScalar(y) - text_size + offset,
306 SkIntToScalar(x + width), SkIntToScalar(y),
307 paint);
308 }
[email protected]67b981562011-12-09 00:35:05309}
310
311} // namespace internal
312
313
[email protected]ff44d712011-07-25 08:42:52314StyleRange::StyleRange()
[email protected]8d901a82012-01-04 19:41:30315 : foreground(SK_ColorBLACK),
[email protected]7aba39c2012-01-10 16:10:27316 font_style(gfx::Font::NORMAL),
[email protected]ff44d712011-07-25 08:42:52317 strike(false),
[email protected]54d32cc2012-01-27 19:52:18318 diagonal_strike(false),
[email protected]8d901a82012-01-04 19:41:30319 underline(false) {
[email protected]ff44d712011-07-25 08:42:52320}
321
[email protected]8e42ba22011-08-04 21:47:08322RenderText::~RenderText() {
323}
324
[email protected]ff44d712011-07-25 08:42:52325void RenderText::SetText(const string16& text) {
[email protected]d3c6b0602011-09-07 19:26:06326 DCHECK(!composition_range_.IsValid());
[email protected]ff44d712011-07-25 08:42:52327 size_t old_text_length = text_.length();
328 text_ = text;
329
330 // Update the style ranges as needed.
331 if (text_.empty()) {
332 style_ranges_.clear();
333 } else if (style_ranges_.empty()) {
334 ApplyDefaultStyle();
335 } else if (text_.length() > old_text_length) {
336 style_ranges_.back().range.set_end(text_.length());
337 } else if (text_.length() < old_text_length) {
338 StyleRanges::iterator i;
339 for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) {
340 if (i->range.start() >= text_.length()) {
[email protected]052ce6bb2011-10-14 19:48:20341 // Style ranges are sorted and non-overlapping, so all the subsequent
342 // style ranges should be out of text_.length() as well.
343 style_ranges_.erase(i, style_ranges_.end());
344 break;
[email protected]ff44d712011-07-25 08:42:52345 }
346 }
[email protected]052ce6bb2011-10-14 19:48:20347 // Since style ranges are sorted and non-overlapping, if there is a style
348 // range ends beyond text_.length, it must be the last one.
[email protected]ff44d712011-07-25 08:42:52349 style_ranges_.back().range.set_end(text_.length());
350 }
351#ifndef NDEBUG
352 CheckStyleRanges(style_ranges_, text_.length());
353#endif
[email protected]f6aaa0f2011-08-11 07:05:46354 cached_bounds_and_offset_valid_ = false;
[email protected]d3c6b0602011-09-07 19:26:06355
356 // Reset selection model. SetText should always followed by SetSelectionModel
357 // or SetCursorPosition in upper layer.
[email protected]d9990212012-03-13 01:09:31358 SetSelectionModel(SelectionModel());
[email protected]6002a4f32011-11-30 10:18:42359
[email protected]caa9e1e2012-03-13 20:47:04360 ResetLayout();
[email protected]ff44d712011-07-25 08:42:52361}
362
[email protected]f0ed8a2f2012-01-24 17:45:59363void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) {
364 if (horizontal_alignment_ != alignment) {
365 horizontal_alignment_ = alignment;
366 display_offset_ = Point();
367 cached_bounds_and_offset_valid_ = false;
368 }
369}
370
[email protected]8d901a82012-01-04 19:41:30371void RenderText::SetFontList(const FontList& font_list) {
372 font_list_ = font_list;
373 cached_bounds_and_offset_valid_ = false;
[email protected]caa9e1e2012-03-13 20:47:04374 ResetLayout();
[email protected]8d901a82012-01-04 19:41:30375}
376
[email protected]fdf481b2012-01-24 06:14:07377void RenderText::SetFontSize(int size) {
378 font_list_ = font_list_.DeriveFontListWithSize(size);
379 cached_bounds_and_offset_valid_ = false;
[email protected]caa9e1e2012-03-13 20:47:04380 ResetLayout();
[email protected]fdf481b2012-01-24 06:14:07381}
382
[email protected]f0ed8a2f2012-01-24 17:45:59383void RenderText::SetCursorEnabled(bool cursor_enabled) {
384 cursor_enabled_ = cursor_enabled;
385 cached_bounds_and_offset_valid_ = false;
386}
387
[email protected]8d901a82012-01-04 19:41:30388const Font& RenderText::GetFont() const {
389 return font_list_.GetFonts()[0];
390}
391
[email protected]0d717602011-08-30 06:21:14392void RenderText::ToggleInsertMode() {
393 insert_mode_ = !insert_mode_;
[email protected]f6aaa0f2011-08-11 07:05:46394 cached_bounds_and_offset_valid_ = false;
395}
396
[email protected]bec929c2012-03-02 06:23:50397void RenderText::SetObscured(bool obscured) {
398 if (obscured != obscured_) {
399 obscured_ = obscured;
400 cached_bounds_and_offset_valid_ = false;
[email protected]caa9e1e2012-03-13 20:47:04401 ResetLayout();
[email protected]bec929c2012-03-02 06:23:50402 }
403}
404
[email protected]f6aaa0f2011-08-11 07:05:46405void RenderText::SetDisplayRect(const Rect& r) {
406 display_rect_ = r;
407 cached_bounds_and_offset_valid_ = false;
[email protected]caa9e1e2012-03-13 20:47:04408 ResetLayout();
[email protected]8e42ba22011-08-04 21:47:08409}
410
[email protected]0d717602011-08-30 06:21:14411void RenderText::SetCursorPosition(size_t position) {
412 MoveCursorTo(position, false);
[email protected]ff44d712011-07-25 08:42:52413}
414
[email protected]d66009e2012-01-21 01:27:28415void RenderText::MoveCursor(BreakType break_type,
416 VisualCursorDirection direction,
417 bool select) {
[email protected]d9990212012-03-13 01:09:31418 SelectionModel position(cursor_position(), selection_model_.caret_affinity());
[email protected]ff44d712011-07-25 08:42:52419 // Cancelling a selection moves to the edge of the selection.
[email protected]d9990212012-03-13 01:09:31420 if (break_type != LINE_BREAK && !selection().is_empty() && !select) {
[email protected]d3c6b0602011-09-07 19:26:06421 SelectionModel selection_start = GetSelectionModelForSelectionStart();
[email protected]d66009e2012-01-21 01:27:28422 int start_x = GetCursorBounds(selection_start, true).x();
423 int cursor_x = GetCursorBounds(position, true).x();
424 // Use the selection start if it is left (when |direction| is CURSOR_LEFT)
425 // or right (when |direction| is CURSOR_RIGHT) of the selection end.
426 if (direction == CURSOR_RIGHT ? start_x > cursor_x : start_x < cursor_x)
[email protected]8e42ba22011-08-04 21:47:08427 position = selection_start;
[email protected]d66009e2012-01-21 01:27:28428 // For word breaks, use the nearest word boundary in the appropriate
429 // |direction|.
[email protected]ff44d712011-07-25 08:42:52430 if (break_type == WORD_BREAK)
[email protected]d66009e2012-01-21 01:27:28431 position = GetAdjacentSelectionModel(position, break_type, direction);
[email protected]ff44d712011-07-25 08:42:52432 } else {
[email protected]d66009e2012-01-21 01:27:28433 position = GetAdjacentSelectionModel(position, break_type, direction);
[email protected]ff44d712011-07-25 08:42:52434 }
[email protected]ec7f48d2011-08-09 03:48:50435 if (select)
[email protected]d9990212012-03-13 01:09:31436 position.set_selection_start(selection().start());
[email protected]8e42ba22011-08-04 21:47:08437 MoveCursorTo(position);
[email protected]ff44d712011-07-25 08:42:52438}
439
[email protected]f9a221b2011-12-10 20:25:38440bool RenderText::MoveCursorTo(const SelectionModel& model) {
[email protected]0d717602011-08-30 06:21:14441 // Enforce valid selection model components.
[email protected]d9990212012-03-13 01:09:31442 size_t text_length = text().length();
443 ui::Range range(std::min(model.selection().start(), text_length),
444 std::min(model.caret_pos(), text_length));
[email protected]0d717602011-08-30 06:21:14445 // The current model only supports caret positions at valid character indices.
[email protected]d9990212012-03-13 01:09:31446 if (!IsCursorablePosition(range.start()) ||
447 !IsCursorablePosition(range.end()))
[email protected]53c0b1b2011-09-21 20:32:29448 return false;
[email protected]d9990212012-03-13 01:09:31449 SelectionModel sel(range, model.caret_affinity());
450 bool changed = sel != selection_model_;
[email protected]0d717602011-08-30 06:21:14451 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52452 return changed;
453}
454
[email protected]7b3cb4b22011-08-02 18:36:40455bool RenderText::MoveCursorTo(const Point& point, bool select) {
[email protected]d9990212012-03-13 01:09:31456 SelectionModel position = FindCursorPosition(point);
[email protected]8e42ba22011-08-04 21:47:08457 if (select)
[email protected]d9990212012-03-13 01:09:31458 position.set_selection_start(selection().start());
459 return MoveCursorTo(position);
[email protected]ff44d712011-07-25 08:42:52460}
461
[email protected]67e85512011-10-12 20:03:45462bool RenderText::SelectRange(const ui::Range& range) {
[email protected]d9990212012-03-13 01:09:31463 ui::Range sel(std::min(range.start(), text().length()),
464 std::min(range.end(), text().length()));
465 if (!IsCursorablePosition(sel.start()) || !IsCursorablePosition(sel.end()))
[email protected]67e85512011-10-12 20:03:45466 return false;
[email protected]d9990212012-03-13 01:09:31467 LogicalCursorDirection affinity =
468 (sel.is_reversed() || sel.is_empty()) ? CURSOR_FORWARD : CURSOR_BACKWARD;
469 SetSelectionModel(SelectionModel(sel, affinity));
[email protected]67e85512011-10-12 20:03:45470 return true;
471}
472
[email protected]8e42ba22011-08-04 21:47:08473bool RenderText::IsPointInSelection(const Point& point) {
[email protected]d9990212012-03-13 01:09:31474 if (selection().is_empty())
[email protected]0d717602011-08-30 06:21:14475 return false;
[email protected]d9990212012-03-13 01:09:31476 SelectionModel cursor = FindCursorPosition(point);
477 return RangeContainsCaret(
478 selection(), cursor.caret_pos(), cursor.caret_affinity());
[email protected]ff44d712011-07-25 08:42:52479}
480
481void RenderText::ClearSelection() {
[email protected]d9990212012-03-13 01:09:31482 SetSelectionModel(SelectionModel(cursor_position(),
483 selection_model_.caret_affinity()));
[email protected]ff44d712011-07-25 08:42:52484}
485
486void RenderText::SelectAll() {
[email protected]d9990212012-03-13 01:09:31487 SelectionModel all;
488 if (GetTextDirection() == base::i18n::LEFT_TO_RIGHT)
489 all = SelectionModel(ui::Range(0, text().length()), CURSOR_FORWARD);
490 else
491 all = SelectionModel(ui::Range(text().length(), 0), CURSOR_BACKWARD);
492 SetSelectionModel(all);
[email protected]ff44d712011-07-25 08:42:52493}
494
495void RenderText::SelectWord() {
[email protected]bec929c2012-03-02 06:23:50496 if (obscured_) {
497 SelectAll();
498 return;
499 }
500
[email protected]d9990212012-03-13 01:09:31501 size_t cursor_pos = cursor_position();
[email protected]ff44d712011-07-25 08:42:52502
[email protected]53c0b1b2011-09-21 20:32:29503 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
504 bool success = iter.Init();
505 DCHECK(success);
506 if (!success)
507 return;
508
[email protected]d9990212012-03-13 01:09:31509 size_t selection_start = cursor_pos;
[email protected]53c0b1b2011-09-21 20:32:29510 for (; selection_start != 0; --selection_start) {
511 if (iter.IsStartOfWord(selection_start) ||
512 iter.IsEndOfWord(selection_start))
[email protected]ff44d712011-07-25 08:42:52513 break;
514 }
515
[email protected]d9990212012-03-13 01:09:31516 if (selection_start == cursor_pos)
517 ++cursor_pos;
[email protected]53c0b1b2011-09-21 20:32:29518
[email protected]d9990212012-03-13 01:09:31519 for (; cursor_pos < text().length(); ++cursor_pos)
520 if (iter.IsEndOfWord(cursor_pos) || iter.IsStartOfWord(cursor_pos))
[email protected]ff44d712011-07-25 08:42:52521 break;
[email protected]ff44d712011-07-25 08:42:52522
[email protected]0d717602011-08-30 06:21:14523 MoveCursorTo(selection_start, false);
[email protected]d9990212012-03-13 01:09:31524 MoveCursorTo(cursor_pos, true);
[email protected]ff44d712011-07-25 08:42:52525}
526
527const ui::Range& RenderText::GetCompositionRange() const {
528 return composition_range_;
529}
530
531void RenderText::SetCompositionRange(const ui::Range& composition_range) {
532 CHECK(!composition_range.IsValid() ||
533 ui::Range(0, text_.length()).Contains(composition_range));
534 composition_range_.set_end(composition_range.end());
535 composition_range_.set_start(composition_range.start());
[email protected]caa9e1e2012-03-13 20:47:04536 ResetLayout();
[email protected]ff44d712011-07-25 08:42:52537}
538
[email protected]c8636672011-12-29 05:07:57539void RenderText::ApplyStyleRange(const StyleRange& style_range) {
[email protected]ff44d712011-07-25 08:42:52540 const ui::Range& new_range = style_range.range;
541 if (!new_range.IsValid() || new_range.is_empty())
542 return;
543 CHECK(!new_range.is_reversed());
544 CHECK(ui::Range(0, text_.length()).Contains(new_range));
[email protected]8e42ba22011-08-04 21:47:08545 ApplyStyleRangeImpl(&style_ranges_, style_range);
[email protected]ff44d712011-07-25 08:42:52546#ifndef NDEBUG
547 CheckStyleRanges(style_ranges_, text_.length());
548#endif
[email protected]f6aaa0f2011-08-11 07:05:46549 // TODO(xji): only invalidate if font or underline changes.
550 cached_bounds_and_offset_valid_ = false;
[email protected]caa9e1e2012-03-13 20:47:04551 ResetLayout();
[email protected]ff44d712011-07-25 08:42:52552}
553
554void RenderText::ApplyDefaultStyle() {
555 style_ranges_.clear();
556 StyleRange style = StyleRange(default_style_);
557 style.range.set_end(text_.length());
558 style_ranges_.push_back(style);
[email protected]f6aaa0f2011-08-11 07:05:46559 cached_bounds_and_offset_valid_ = false;
[email protected]caa9e1e2012-03-13 20:47:04560 ResetLayout();
[email protected]ff44d712011-07-25 08:42:52561}
562
[email protected]d66009e2012-01-21 01:27:28563VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() {
564 return GetTextDirection() == base::i18n::LEFT_TO_RIGHT ?
565 CURSOR_RIGHT : CURSOR_LEFT;
[email protected]ff44d712011-07-25 08:42:52566}
567
[email protected]7b3cb4b22011-08-02 18:36:40568void RenderText::Draw(Canvas* canvas) {
[email protected]592a7ad2011-12-14 05:57:53569 TRACE_EVENT0("gfx", "RenderText::Draw");
570 {
571 TRACE_EVENT0("gfx", "RenderText::EnsureLayout");
572 EnsureLayout();
573 }
[email protected]ff44d712011-07-25 08:42:52574
[email protected]f308e5b12012-01-17 19:09:18575 canvas->Save();
576 canvas->ClipRect(display_rect());
[email protected]1a353f12012-01-18 04:20:56577
578 if (!text().empty())
579 DrawSelection(canvas);
580
581 DrawCursor(canvas);
582
[email protected]6002a4f32011-11-30 10:18:42583 if (!text().empty()) {
[email protected]592a7ad2011-12-14 05:57:53584 TRACE_EVENT0("gfx", "RenderText::Draw draw text");
[email protected]6002a4f32011-11-30 10:18:42585 DrawVisualText(canvas);
[email protected]ff44d712011-07-25 08:42:52586 }
[email protected]f308e5b12012-01-17 19:09:18587 canvas->Restore();
[email protected]ff44d712011-07-25 08:42:52588}
589
[email protected]d9990212012-03-13 01:09:31590Rect RenderText::GetCursorBounds(const SelectionModel& caret,
591 bool insert_mode) {
592 EnsureLayout();
593
594 size_t caret_pos = caret.caret_pos();
595 // In overtype mode, ignore the affinity and always indicate that we will
596 // overtype the next character.
597 LogicalCursorDirection caret_affinity =
598 insert_mode ? caret.caret_affinity() : CURSOR_FORWARD;
599 int x = 0, width = 0, height = 0;
600 if (caret_pos == (caret_affinity == CURSOR_BACKWARD ? 0 : text().length())) {
601 // The caret is attached to the boundary. Always return a zero-width caret,
602 // since there is nothing to overtype.
603 Size size = GetStringSize();
604 if ((GetTextDirection() == base::i18n::RIGHT_TO_LEFT) == (caret_pos == 0))
605 x = size.width();
606 height = size.height();
607 } else {
608 size_t grapheme_start = (caret_affinity == CURSOR_FORWARD) ?
609 caret_pos : IndexOfAdjacentGrapheme(caret_pos, CURSOR_BACKWARD);
610 ui::Range xspan;
611 GetGlyphBounds(grapheme_start, &xspan, &height);
612 if (insert_mode) {
613 x = (caret_affinity == CURSOR_BACKWARD) ? xspan.end() : xspan.start();
614 } else { // overtype mode
615 x = xspan.GetMin();
616 width = xspan.length();
617 }
618 }
619 height = std::min(height, display_rect().height());
620 int y = (display_rect().height() - height) / 2;
621 return Rect(ToViewPoint(Point(x, y)), Size(width, height));
622}
623
[email protected]f6aaa0f2011-08-11 07:05:46624const Rect& RenderText::GetUpdatedCursorBounds() {
625 UpdateCachedBoundsAndOffset();
[email protected]8e42ba22011-08-04 21:47:08626 return cursor_bounds_;
627}
628
[email protected]04b7bf322011-10-03 19:08:46629SelectionModel RenderText::GetSelectionModelForSelectionStart() {
[email protected]d9990212012-03-13 01:09:31630 const ui::Range& sel = selection();
631 if (sel.is_empty())
632 return selection_model_;
633 return SelectionModel(sel.start(),
634 sel.is_reversed() ? CURSOR_BACKWARD : CURSOR_FORWARD);
[email protected]04b7bf322011-10-03 19:08:46635}
636
[email protected]8e42ba22011-08-04 21:47:08637RenderText::RenderText()
[email protected]f0ed8a2f2012-01-24 17:45:59638 : horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT),
639 cursor_enabled_(true),
640 cursor_visible_(false),
[email protected]8e42ba22011-08-04 21:47:08641 insert_mode_(true),
[email protected]913a1ef2012-02-29 08:40:34642 cursor_color_(kDefaultCursorColor),
[email protected]5abe6302011-12-20 23:44:32643 focused_(false),
[email protected]d3c6b0602011-09-07 19:26:06644 composition_range_(ui::Range::InvalidRange()),
[email protected]bec929c2012-03-02 06:23:50645 obscured_(false),
[email protected]f308e5b12012-01-17 19:09:18646 fade_head_(false),
647 fade_tail_(false),
[email protected]32d582d2012-02-28 21:50:22648 background_is_transparent_(false),
[email protected]f6aaa0f2011-08-11 07:05:46649 cached_bounds_and_offset_valid_(false) {
650}
651
652const Point& RenderText::GetUpdatedDisplayOffset() {
653 UpdateCachedBoundsAndOffset();
654 return display_offset_;
[email protected]8e42ba22011-08-04 21:47:08655}
656
[email protected]d66009e2012-01-21 01:27:28657SelectionModel RenderText::GetAdjacentSelectionModel(
658 const SelectionModel& current,
659 BreakType break_type,
660 VisualCursorDirection direction) {
661 EnsureLayout();
662
663 if (break_type == LINE_BREAK || text().empty())
664 return EdgeSelectionModel(direction);
[email protected]ec7f48d2011-08-09 03:48:50665 if (break_type == CHARACTER_BREAK)
[email protected]d66009e2012-01-21 01:27:28666 return AdjacentCharSelectionModel(current, direction);
667 DCHECK(break_type == WORD_BREAK);
668 return AdjacentWordSelectionModel(current, direction);
[email protected]0d717602011-08-30 06:21:14669}
670
[email protected]d9990212012-03-13 01:09:31671SelectionModel RenderText::EdgeSelectionModel(
672 VisualCursorDirection direction) {
673 if (direction == GetVisualDirectionOfLogicalEnd())
674 return SelectionModel(text().length(), CURSOR_FORWARD);
675 return SelectionModel(0, CURSOR_BACKWARD);
676}
[email protected]6002a4f32011-11-30 10:18:42677
[email protected]d9990212012-03-13 01:09:31678void RenderText::SetSelectionModel(const SelectionModel& model) {
679 DCHECK_LE(model.selection().GetMax(), text().length());
680 selection_model_ = model;
[email protected]6002a4f32011-11-30 10:18:42681 cached_bounds_and_offset_valid_ = false;
[email protected]0d717602011-08-30 06:21:14682}
683
[email protected]bec929c2012-03-02 06:23:50684string16 RenderText::GetDisplayText() const {
685 if (!obscured_)
686 return text_;
687 size_t obscured_text_length =
688 static_cast<size_t>(ui::UTF16IndexToOffset(text_, 0, text_.length()));
689 return string16(obscured_text_length, kPasswordReplacementChar);
690}
691
[email protected]8e42ba22011-08-04 21:47:08692void RenderText::ApplyCompositionAndSelectionStyles(
[email protected]1a353f12012-01-18 04:20:56693 StyleRanges* style_ranges) {
[email protected]8e42ba22011-08-04 21:47:08694 // TODO(msw): This pattern ought to be reconsidered; what about composition
695 // and selection overlaps, retain existing local style features?
696 // Apply a composition style override to a copy of the style ranges.
697 if (composition_range_.IsValid() && !composition_range_.is_empty()) {
698 StyleRange composition_style(default_style_);
699 composition_style.underline = true;
[email protected]d9990212012-03-13 01:09:31700 composition_style.range = composition_range_;
[email protected]8e42ba22011-08-04 21:47:08701 ApplyStyleRangeImpl(style_ranges, composition_style);
702 }
703 // Apply a selection style override to a copy of the style ranges.
[email protected]d9990212012-03-13 01:09:31704 if (!selection().is_empty()) {
[email protected]8e42ba22011-08-04 21:47:08705 StyleRange selection_style(default_style_);
[email protected]721ea922012-01-23 21:13:33706 selection_style.foreground = NativeTheme::instance()->GetSystemColor(
707 NativeTheme::kColorId_TextfieldSelectionColor);
[email protected]d9990212012-03-13 01:09:31708 selection_style.range = ui::Range(selection().GetMin(),
709 selection().GetMax());
[email protected]8e42ba22011-08-04 21:47:08710 ApplyStyleRangeImpl(style_ranges, selection_style);
711 }
[email protected]1a353f12012-01-18 04:20:56712 // Apply replacement-mode style override to a copy of the style ranges.
713 //
714 // TODO(xji): NEED TO FIX FOR WINDOWS ASAP. Windows call this function (to
715 // apply styles) in ItemizeLogicalText(). In order for the cursor's underline
716 // character to be drawn correctly, we will need to re-layout the text. It's
717 // not practical to do layout on every cursor blink. We need to fix Windows
718 // port to apply styles during drawing phase like Linux port does.
719 // http://crbug.com/110109
720 if (!insert_mode_ && cursor_visible() && focused()) {
721 StyleRange replacement_mode_style(default_style_);
[email protected]721ea922012-01-23 21:13:33722 replacement_mode_style.foreground = NativeTheme::instance()->GetSystemColor(
723 NativeTheme::kColorId_TextfieldSelectionColor);
[email protected]d9990212012-03-13 01:09:31724 size_t cursor = cursor_position();
[email protected]1a353f12012-01-18 04:20:56725 replacement_mode_style.range.set_start(cursor);
[email protected]d66009e2012-01-21 01:27:28726 replacement_mode_style.range.set_end(
727 IndexOfAdjacentGrapheme(cursor, CURSOR_FORWARD));
[email protected]1a353f12012-01-18 04:20:56728 ApplyStyleRangeImpl(style_ranges, replacement_mode_style);
729 }
[email protected]ff44d712011-07-25 08:42:52730}
731
[email protected]f0ed8a2f2012-01-24 17:45:59732Point RenderText::GetTextOrigin() {
733 Point origin = display_rect().origin();
734 origin = origin.Add(GetUpdatedDisplayOffset());
735 origin = origin.Add(GetAlignmentOffset());
736 return origin;
737}
738
[email protected]0d717602011-08-30 06:21:14739Point RenderText::ToTextPoint(const Point& point) {
[email protected]f0ed8a2f2012-01-24 17:45:59740 return point.Subtract(GetTextOrigin());
[email protected]0d717602011-08-30 06:21:14741}
742
743Point RenderText::ToViewPoint(const Point& point) {
[email protected]f0ed8a2f2012-01-24 17:45:59744 return point.Add(GetTextOrigin());
745}
746
747int RenderText::GetContentWidth() {
[email protected]d9990212012-03-13 01:09:31748 return GetStringSize().width() + (cursor_enabled_ ? 1 : 0);
[email protected]f0ed8a2f2012-01-24 17:45:59749}
750
751Point RenderText::GetAlignmentOffset() {
752 if (horizontal_alignment() != ALIGN_LEFT) {
753 int x_offset = display_rect().width() - GetContentWidth();
754 if (horizontal_alignment() == ALIGN_CENTER)
755 x_offset /= 2;
756 return Point(x_offset, 0);
757 }
758 return Point();
[email protected]0d717602011-08-30 06:21:14759}
760
[email protected]67b981562011-12-09 00:35:05761Point RenderText::GetOriginForSkiaDrawing() {
[email protected]f0ed8a2f2012-01-24 17:45:59762 Point origin(GetTextOrigin());
[email protected]67b981562011-12-09 00:35:05763 // TODO(msw): Establish a vertical baseline for strings of mixed font heights.
[email protected]8d901a82012-01-04 19:41:30764 const Font& font = GetFont();
[email protected]5f4fd30e2012-01-12 02:19:33765 int height = font.GetHeight();
[email protected]fdf481b2012-01-24 06:14:07766 DCHECK_LE(height, display_rect().height());
[email protected]67b981562011-12-09 00:35:05767 // Center the text vertically in the display area.
768 origin.Offset(0, (display_rect().height() - height) / 2);
769 // Offset by the font size to account for Skia expecting y to be the bottom.
770 origin.Offset(0, font.GetFontSize());
771 return origin;
772}
773
[email protected]f0ed8a2f2012-01-24 17:45:59774void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) {
[email protected]f308e5b12012-01-17 19:09:18775 if (!fade_head() && !fade_tail())
[email protected]f0ed8a2f2012-01-24 17:45:59776 return;
[email protected]f308e5b12012-01-17 19:09:18777
[email protected]d9990212012-03-13 01:09:31778 const int text_width = GetStringSize().width();
[email protected]f308e5b12012-01-17 19:09:18779 const int display_width = display_rect().width();
780
781 // If the text fits as-is, no need to fade.
782 if (text_width <= display_width)
[email protected]f0ed8a2f2012-01-24 17:45:59783 return;
[email protected]f308e5b12012-01-17 19:09:18784
785 int gradient_width = CalculateFadeGradientWidth(GetFont(), display_width);
786 if (gradient_width == 0)
[email protected]f0ed8a2f2012-01-24 17:45:59787 return;
[email protected]f308e5b12012-01-17 19:09:18788
789 bool fade_left = fade_head();
790 bool fade_right = fade_tail();
791 // Under RTL, |fade_right| == |fade_head|.
792 if (GetTextDirection() == base::i18n::RIGHT_TO_LEFT)
793 std::swap(fade_left, fade_right);
794
795 gfx::Rect solid_part = display_rect();
796 gfx::Rect left_part;
797 gfx::Rect right_part;
798 if (fade_left) {
799 left_part = solid_part;
800 left_part.Inset(0, 0, solid_part.width() - gradient_width, 0);
801 solid_part.Inset(gradient_width, 0, 0, 0);
802 }
803 if (fade_right) {
804 right_part = solid_part;
805 right_part.Inset(solid_part.width() - gradient_width, 0, 0, 0);
806 solid_part.Inset(0, 0, gradient_width, 0);
807 }
808
[email protected]f308e5b12012-01-17 19:09:18809 gfx::Rect text_rect = display_rect();
[email protected]f0ed8a2f2012-01-24 17:45:59810 text_rect.Inset(GetAlignmentOffset().x(), 0, 0, 0);
[email protected]f308e5b12012-01-17 19:09:18811
812 const SkColor color = default_style().foreground;
813 SkAutoTUnref<SkShader> shader(
814 CreateFadeShader(text_rect, left_part, right_part, color));
815 if (shader.get()) {
816 // |renderer| adds its own ref. So don't |release()| it from the ref ptr.
817 renderer->SetShader(shader.get());
818 }
[email protected]f308e5b12012-01-17 19:09:18819}
820
[email protected]d9990212012-03-13 01:09:31821// static
822bool RenderText::RangeContainsCaret(const ui::Range& range,
823 size_t caret_pos,
824 LogicalCursorDirection caret_affinity) {
825 // NB: exploits unsigned wraparound (WG14/N1124 section 6.2.5 paragraph 9).
826 size_t adjacent = (caret_affinity == CURSOR_BACKWARD) ?
827 caret_pos - 1 : caret_pos + 1;
828 return range.Contains(ui::Range(caret_pos, adjacent));
829}
830
[email protected]0d717602011-08-30 06:21:14831void RenderText::MoveCursorTo(size_t position, bool select) {
832 size_t cursor = std::min(position, text().length());
[email protected]d9990212012-03-13 01:09:31833 if (IsCursorablePosition(cursor))
834 SetSelectionModel(SelectionModel(
835 ui::Range(select ? selection().start() : cursor, cursor),
836 (cursor == 0) ? CURSOR_FORWARD : CURSOR_BACKWARD));
[email protected]8e42ba22011-08-04 21:47:08837}
838
[email protected]f6aaa0f2011-08-11 07:05:46839void RenderText::UpdateCachedBoundsAndOffset() {
840 if (cached_bounds_and_offset_valid_)
841 return;
[email protected]f0ed8a2f2012-01-24 17:45:59842
[email protected]f6aaa0f2011-08-11 07:05:46843 // First, set the valid flag true to calculate the current cursor bounds using
844 // the stale |display_offset_|. Applying |delta_offset| at the end of this
845 // function will set |cursor_bounds_| and |display_offset_| to correct values.
846 cached_bounds_and_offset_valid_ = true;
847 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_);
[email protected]f0ed8a2f2012-01-24 17:45:59848
[email protected]8e42ba22011-08-04 21:47:08849 // Update |display_offset_| to ensure the current cursor is visible.
[email protected]f0ed8a2f2012-01-24 17:45:59850 const int display_width = display_rect_.width();
851 const int content_width = GetContentWidth();
852
[email protected]f6aaa0f2011-08-11 07:05:46853 int delta_offset = 0;
[email protected]f0ed8a2f2012-01-24 17:45:59854 if (content_width <= display_width || !cursor_enabled()) {
855 // Don't pan if the text fits in the display width or when the cursor is
856 // disabled.
[email protected]f6aaa0f2011-08-11 07:05:46857 delta_offset = -display_offset_.x();
[email protected]65788132011-09-07 20:20:23858 } else if (cursor_bounds_.right() >= display_rect_.right()) {
[email protected]d3c6b0602011-09-07 19:26:06859 // TODO(xji): when the character overflow is a RTL character, currently, if
860 // we pan cursor at the rightmost position, the entered RTL character is not
861 // displayed. Should pan cursor to show the last logical characters.
862 //
[email protected]8e42ba22011-08-04 21:47:08863 // Pan to show the cursor when it overflows to the right,
[email protected]65788132011-09-07 20:20:23864 delta_offset = display_rect_.right() - cursor_bounds_.right() - 1;
[email protected]f6aaa0f2011-08-11 07:05:46865 } else if (cursor_bounds_.x() < display_rect_.x()) {
[email protected]d3c6b0602011-09-07 19:26:06866 // TODO(xji): have similar problem as above when overflow character is a
867 // LTR character.
868 //
[email protected]8e42ba22011-08-04 21:47:08869 // Pan to show the cursor when it overflows to the left.
[email protected]f6aaa0f2011-08-11 07:05:46870 delta_offset = display_rect_.x() - cursor_bounds_.x();
[email protected]f0ed8a2f2012-01-24 17:45:59871 } else if (display_offset_.x() != 0) {
872 // Reduce the pan offset to show additional overflow text when the display
873 // width increases.
874 const int negate_rtl = horizontal_alignment_ == ALIGN_RIGHT ? -1 : 1;
875 const int offset = negate_rtl * display_offset_.x();
876 if (display_width > (content_width + offset))
877 delta_offset = negate_rtl * (display_width - (content_width + offset));
[email protected]8e42ba22011-08-04 21:47:08878 }
[email protected]f0ed8a2f2012-01-24 17:45:59879
[email protected]f6aaa0f2011-08-11 07:05:46880 display_offset_.Offset(delta_offset, 0);
881 cursor_bounds_.Offset(delta_offset, 0);
[email protected]ff44d712011-07-25 08:42:52882}
883
[email protected]6002a4f32011-11-30 10:18:42884void RenderText::DrawSelection(Canvas* canvas) {
[email protected]d9990212012-03-13 01:09:31885 std::vector<Rect> sel = GetSubstringBounds(selection());
[email protected]721ea922012-01-23 21:13:33886 NativeTheme::ColorId color_id = focused() ?
887 NativeTheme::kColorId_TextfieldSelectionBackgroundFocused :
888 NativeTheme::kColorId_TextfieldSelectionBackgroundUnfocused;
889 SkColor color = NativeTheme::instance()->GetSystemColor(color_id);
[email protected]6002a4f32011-11-30 10:18:42890 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
[email protected]b8bdc422012-02-01 21:44:40891 canvas->FillRect(*i, color);
[email protected]6002a4f32011-11-30 10:18:42892}
893
894void RenderText::DrawCursor(Canvas* canvas) {
895 // Paint cursor. Replace cursor is drawn as rectangle for now.
896 // TODO(msw): Draw a better cursor with a better indication of association.
[email protected]f0ed8a2f2012-01-24 17:45:59897 if (cursor_enabled() && cursor_visible() && focused()) {
[email protected]1a353f12012-01-18 04:20:56898 const Rect& bounds = GetUpdatedCursorBounds();
899 if (bounds.width() != 0)
[email protected]913a1ef2012-02-29 08:40:34900 canvas->FillRect(bounds, cursor_color_);
[email protected]1a353f12012-01-18 04:20:56901 else
[email protected]913a1ef2012-02-29 08:40:34902 canvas->DrawRect(bounds, cursor_color_);
[email protected]1a353f12012-01-18 04:20:56903 }
[email protected]6002a4f32011-11-30 10:18:42904}
905
[email protected]ff44d712011-07-25 08:42:52906} // namespace gfx