blob: 17d1fe8cf5a529096dd421d069dc9c7daaa0040e [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
9#include "base/i18n/break_iterator.h"
10#include "base/logging.h"
11#include "base/stl_util.h"
[email protected]67b981562011-12-09 00:35:0512#include "third_party/skia/include/core/SkTypeface.h"
[email protected]f308e5b12012-01-17 19:09:1813#include "third_party/skia/include/effects/SkGradientShader.h"
[email protected]bec929c2012-03-02 06:23:5014#include "ui/base/text/utf16_indexing.h"
[email protected]ff44d712011-07-25 08:42:5215#include "ui/gfx/canvas.h"
[email protected]0834e1b12012-04-11 02:23:5616#include "ui/gfx/insets.h"
[email protected]40be2012012-05-14 22:34:4517#include "ui/gfx/skia_util.h"
[email protected]46cb5382012-08-01 21:57:3118#include "ui/gfx/text_constants.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]311a09d2012-05-14 20:38:1730// Default color used for drawing selection text.
31const SkColor kDefaultSelectionColor = SK_ColorBLACK;
32
33// Default color used for drawing selection background.
34const SkColor kDefaultSelectionBackgroundColor = SK_ColorGRAY;
35
[email protected]ff44d712011-07-25 08:42:5236#ifndef NDEBUG
37// Check StyleRanges invariant conditions: sorted and non-overlapping ranges.
38void CheckStyleRanges(const gfx::StyleRanges& style_ranges, size_t length) {
39 if (length == 0) {
40 DCHECK(style_ranges.empty()) << "Style ranges exist for empty text.";
41 return;
42 }
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;
[email protected]9a55b8c92012-05-17 20:53:0846 DCHECK(!former.is_empty()) << "Empty range at " << i << ":" <<
47 former.ToString();
48 DCHECK(former.IsValid()) << "Invalid range at " << i << ":" <<
49 former.ToString();
50 DCHECK(!former.is_reversed()) << "Reversed range at " << i << ":" <<
51 former.ToString();
[email protected]ff44d712011-07-25 08:42:5252 DCHECK(former.end() == latter.start()) << "Ranges gap/overlap/unsorted." <<
[email protected]9a55b8c92012-05-17 20:53:0853 "former:" << former.ToString() << ", latter:" << latter.ToString();
[email protected]ff44d712011-07-25 08:42:5254 }
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.";
60}
61#endif
62
[email protected]8e42ba22011-08-04 21:47:0863void ApplyStyleRangeImpl(gfx::StyleRanges* style_ranges,
[email protected]c8636672011-12-29 05:07:5764 const gfx::StyleRange& style_range) {
[email protected]ff44d712011-07-25 08:42:5265 const ui::Range& new_range = style_range.range;
66 // Follow StyleRanges invariant conditions: sorted and non-overlapping ranges.
67 gfx::StyleRanges::iterator i;
[email protected]8e42ba22011-08-04 21:47:0868 for (i = style_ranges->begin(); i != style_ranges->end();) {
[email protected]ff44d712011-07-25 08:42:5269 if (i->range.end() < new_range.start()) {
70 i++;
71 } else if (i->range.start() == new_range.end()) {
72 break;
73 } else if (new_range.Contains(i->range)) {
[email protected]8e42ba22011-08-04 21:47:0874 i = style_ranges->erase(i);
75 if (i == style_ranges->end())
[email protected]ff44d712011-07-25 08:42:5276 break;
77 } else if (i->range.start() < new_range.start() &&
78 i->range.end() > new_range.end()) {
79 // Split the current style into two styles.
80 gfx::StyleRange split_style = gfx::StyleRange(*i);
81 split_style.range.set_end(new_range.start());
[email protected]8e42ba22011-08-04 21:47:0882 i = style_ranges->insert(i, split_style) + 1;
[email protected]ff44d712011-07-25 08:42:5283 i->range.set_start(new_range.end());
84 break;
85 } else if (i->range.start() < new_range.start()) {
86 i->range.set_end(new_range.start());
87 i++;
88 } else if (i->range.end() > new_range.end()) {
89 i->range.set_start(new_range.end());
90 break;
[email protected]f9a221b2011-12-10 20:25:3891 } else {
[email protected]ff44d712011-07-25 08:42:5292 NOTREACHED();
[email protected]f9a221b2011-12-10 20:25:3893 }
[email protected]ff44d712011-07-25 08:42:5294 }
95 // Add the new range in its sorted location.
[email protected]8e42ba22011-08-04 21:47:0896 style_ranges->insert(i, style_range);
[email protected]ff44d712011-07-25 08:42:5297}
98
[email protected]7aba39c2012-01-10 16:10:2799// Converts |gfx::Font::FontStyle| flags to |SkTypeface::Style| flags.
100SkTypeface::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);
107}
108
[email protected]f308e5b12012-01-17 19:09:18109// Given |font| and |display_width|, returns the width of the fade gradient.
110int 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));
119}
120
121// Appends to |positions| and |colors| values corresponding to the fade over
122// |fade_rect| from color |c0| to color |c1|.
123void AddFadeEffect(const gfx::Rect& text_rect,
124 const gfx::Rect& fade_rect,
125 SkColor c0,
126 SkColor c1,
127 std::vector<SkScalar>* positions,
128 std::vector<SkColor>* colors) {
129 const SkScalar left = static_cast<SkScalar>(fade_rect.x() - text_rect.x());
130 const SkScalar width = static_cast<SkScalar>(fade_rect.width());
131 const SkScalar p0 = left / text_rect.width();
132 const SkScalar p1 = (left + width) / text_rect.width();
133 // Prepend 0.0 to |positions|, as required by Skia.
134 if (positions->empty() && p0 != 0.0) {
135 positions->push_back(0.0);
136 colors->push_back(c0);
137 }
138 positions->push_back(p0);
139 colors->push_back(c0);
140 positions->push_back(p1);
141 colors->push_back(c1);
142}
143
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.
146SkShader* CreateFadeShader(const gfx::Rect& text_rect,
147 const gfx::Rect& left_part,
148 const gfx::Rect& right_part,
149 SkColor color) {
150 // Fade alpha of 51/255 corresponds to a fade of 0.2 of the original color.
151 const SkColor fade_color = SkColorSetA(color, 51);
[email protected]f308e5b12012-01-17 19:09:18152 std::vector<SkScalar> positions;
153 std::vector<SkColor> colors;
154
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());
162
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());
167 }
168
[email protected]959e1902012-03-03 02:02:17169 SkPoint points[2];
170 points[0].iset(text_rect.x(), text_rect.y());
171 points[1].iset(text_rect.right(), text_rect.y());
[email protected]f308e5b12012-01-17 19:09:18172
[email protected]959e1902012-03-03 02:02:17173 return SkGradientShader::CreateLinear(&points[0], &colors[0], &positions[0],
174 colors.size(), SkShader::kClamp_TileMode);
175}
[email protected]f308e5b12012-01-17 19:09:18176
[email protected]ff44d712011-07-25 08:42:52177} // namespace
178
179namespace gfx {
180
[email protected]67b981562011-12-09 00:35:05181namespace internal {
182
[email protected]e2326012012-06-12 22:59:41183// Value of |underline_thickness_| that indicates that underline metrics have
184// not been set explicitly.
185const SkScalar kUnderlineMetricsNotSet = -1.0f;
186
[email protected]67b981562011-12-09 00:35:05187SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas)
[email protected]3bf21e12012-05-02 15:33:36188 : canvas_skia_(canvas->sk_canvas()),
[email protected]e2326012012-06-12 22:59:41189 started_drawing_(false),
190 underline_thickness_(kUnderlineMetricsNotSet),
191 underline_position_(0.0f) {
[email protected]67b981562011-12-09 00:35:05192 DCHECK(canvas_skia_);
193 paint_.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
194 paint_.setStyle(SkPaint::kFill_Style);
195 paint_.setAntiAlias(true);
196 paint_.setSubpixelText(true);
197 paint_.setLCDRenderText(true);
[email protected]808d37a2012-05-10 03:06:29198 bounds_.setEmpty();
[email protected]67b981562011-12-09 00:35:05199}
200
201SkiaTextRenderer::~SkiaTextRenderer() {
[email protected]3bf21e12012-05-02 15:33:36202 // Work-around for http://crbug.com/122743, where non-ClearType text is
203 // rendered with incorrect gamma when using the fade shader. Draw the text
204 // to a layer and restore it faded by drawing a rect in kDstIn_Mode mode.
205 //
206 // TODO(asvitkine): Remove this work-around once the Skia bug is fixed.
207 // http://code.google.com/p/skia/issues/detail?id=590
208 if (deferred_fade_shader_.get()) {
209 paint_.setShader(deferred_fade_shader_.get());
210 paint_.setXfermodeMode(SkXfermode::kDstIn_Mode);
211 canvas_skia_->drawRect(bounds_, paint_);
212 canvas_skia_->restore();
213 }
[email protected]67b981562011-12-09 00:35:05214}
215
[email protected]0834e1b12012-04-11 02:23:56216void SkiaTextRenderer::SetDrawLooper(SkDrawLooper* draw_looper) {
217 paint_.setLooper(draw_looper);
218}
219
[email protected]be5756e22012-02-17 15:53:01220void SkiaTextRenderer::SetFontSmoothingSettings(bool enable_smoothing,
221 bool enable_lcd_text) {
222 paint_.setAntiAlias(enable_smoothing);
223 paint_.setSubpixelText(enable_smoothing);
224 paint_.setLCDRenderText(enable_lcd_text);
225}
226
[email protected]cd8e5522011-12-15 00:24:10227void SkiaTextRenderer::SetTypeface(SkTypeface* typeface) {
228 paint_.setTypeface(typeface);
229}
230
[email protected]e2326012012-06-12 22:59:41231void SkiaTextRenderer::SetTextSize(SkScalar size) {
[email protected]cd8e5522011-12-15 00:24:10232 paint_.setTextSize(size);
233}
234
[email protected]f3dc6fb2012-03-24 21:01:24235void SkiaTextRenderer::SetFontFamilyWithStyle(const std::string& family,
236 int style) {
237 DCHECK(!family.empty());
238
239 SkTypeface::Style skia_style = ConvertFontStyleToSkiaTypefaceStyle(style);
[email protected]e166e54d2012-03-27 14:11:51240 SkTypeface* typeface = SkTypeface::CreateFromName(family.c_str(), skia_style);
241 SkAutoUnref auto_unref(typeface);
242 if (typeface) {
[email protected]f3dc6fb2012-03-24 21:01:24243 // |paint_| adds its own ref. So don't |release()| it from the ref ptr here.
[email protected]e166e54d2012-03-27 14:11:51244 SetTypeface(typeface);
[email protected]f3dc6fb2012-03-24 21:01:24245
246 // Enable fake bold text if bold style is needed but new typeface does not
247 // have it.
248 paint_.setFakeBoldText((skia_style & SkTypeface::kBold) &&
[email protected]e166e54d2012-03-27 14:11:51249 !typeface->isBold());
[email protected]f3dc6fb2012-03-24 21:01:24250 }
251}
252
[email protected]67b981562011-12-09 00:35:05253void SkiaTextRenderer::SetForegroundColor(SkColor foreground) {
254 paint_.setColor(foreground);
255}
256
[email protected]3bf21e12012-05-02 15:33:36257void SkiaTextRenderer::SetShader(SkShader* shader, const Rect& bounds) {
258 bounds_ = RectToSkRect(bounds);
[email protected]f308e5b12012-01-17 19:09:18259 paint_.setShader(shader);
260}
261
[email protected]e2326012012-06-12 22:59:41262void SkiaTextRenderer::SetUnderlineMetrics(SkScalar thickness,
263 SkScalar position) {
264 underline_thickness_ = thickness;
265 underline_position_ = position;
266}
267
[email protected]67b981562011-12-09 00:35:05268void SkiaTextRenderer::DrawPosText(const SkPoint* pos,
269 const uint16* glyphs,
270 size_t glyph_count) {
[email protected]3bf21e12012-05-02 15:33:36271 if (!started_drawing_) {
272 started_drawing_ = true;
273 // Work-around for http://crbug.com/122743, where non-ClearType text is
274 // rendered with incorrect gamma when using the fade shader. Draw the text
275 // to a layer and restore it faded by drawing a rect in kDstIn_Mode mode.
276 //
[email protected]110b0b62012-05-04 19:57:29277 // Skip this when there is a looper which seems not working well with
278 // deferred paint. Currently a looper is only used for text shadows.
279 //
[email protected]3bf21e12012-05-02 15:33:36280 // TODO(asvitkine): Remove this work-around once the Skia bug is fixed.
281 // http://code.google.com/p/skia/issues/detail?id=590
[email protected]110b0b62012-05-04 19:57:29282 if (!paint_.isLCDRenderText() &&
283 paint_.getShader() &&
284 !paint_.getLooper()) {
[email protected]3bf21e12012-05-02 15:33:36285 deferred_fade_shader_ = paint_.getShader();
286 paint_.setShader(NULL);
287 canvas_skia_->saveLayer(&bounds_, NULL);
288 }
289 }
290
291 const size_t byte_length = glyph_count * sizeof(glyphs[0]);
[email protected]67b981562011-12-09 00:35:05292 canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_);
293}
294
295// Draw underline and strike through text decorations.
296// Based on |SkCanvas::DrawTextDecorations()| and constants from:
297// third_party/skia/src/core/SkTextFormatParams.h
298void SkiaTextRenderer::DrawDecorations(int x, int y, int width,
[email protected]54d32cc2012-01-27 19:52:18299 const StyleRange& style) {
300 if (!style.underline && !style.strike && !style.diagonal_strike)
301 return;
302
[email protected]67b981562011-12-09 00:35:05303 // Fraction of the text size to lower a strike through below the baseline.
304 const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21);
305 // Fraction of the text size to lower an underline below the baseline.
306 const SkScalar kUnderlineOffset = (SK_Scalar1 / 9);
307 // Fraction of the text size to use for a strike through or under-line.
308 const SkScalar kLineThickness = (SK_Scalar1 / 18);
[email protected]54d32cc2012-01-27 19:52:18309 // Fraction of the text size to use for a top margin of a diagonal strike.
310 const SkScalar kDiagonalStrikeThroughMarginOffset = (SK_Scalar1 / 4);
[email protected]67b981562011-12-09 00:35:05311
312 SkScalar text_size = paint_.getTextSize();
313 SkScalar height = SkScalarMul(text_size, kLineThickness);
314 SkRect r;
315
316 r.fLeft = x;
317 r.fRight = x + width;
318
[email protected]54d32cc2012-01-27 19:52:18319 if (style.underline) {
[email protected]e2326012012-06-12 22:59:41320 if (underline_thickness_ == kUnderlineMetricsNotSet) {
321 r.fTop = SkScalarMulAdd(text_size, kUnderlineOffset, y);
322 r.fBottom = r.fTop + height;
323 } else {
324 r.fTop = y + underline_position_;
325 r.fBottom = r.fTop + underline_thickness_;
326 }
[email protected]67b981562011-12-09 00:35:05327 canvas_skia_->drawRect(r, paint_);
328 }
[email protected]54d32cc2012-01-27 19:52:18329 if (style.strike) {
[email protected]67b981562011-12-09 00:35:05330 SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y);
331 r.fTop = offset;
332 r.fBottom = offset + height;
333 canvas_skia_->drawRect(r, paint_);
334 }
[email protected]54d32cc2012-01-27 19:52:18335 if (style.diagonal_strike) {
336 SkScalar offset =
337 SkScalarMul(text_size, kDiagonalStrikeThroughMarginOffset);
338 SkPaint paint(paint_);
339 paint.setAntiAlias(true);
340 paint.setStyle(SkPaint::kFill_Style);
341 paint.setStrokeWidth(height);
342 canvas_skia_->drawLine(
343 SkIntToScalar(x), SkIntToScalar(y) - text_size + offset,
344 SkIntToScalar(x + width), SkIntToScalar(y),
345 paint);
346 }
[email protected]67b981562011-12-09 00:35:05347}
348
349} // namespace internal
350
351
[email protected]ff44d712011-07-25 08:42:52352StyleRange::StyleRange()
[email protected]8d901a82012-01-04 19:41:30353 : foreground(SK_ColorBLACK),
[email protected]7aba39c2012-01-10 16:10:27354 font_style(gfx::Font::NORMAL),
[email protected]ff44d712011-07-25 08:42:52355 strike(false),
[email protected]54d32cc2012-01-27 19:52:18356 diagonal_strike(false),
[email protected]8d901a82012-01-04 19:41:30357 underline(false) {
[email protected]ff44d712011-07-25 08:42:52358}
359
[email protected]8e42ba22011-08-04 21:47:08360RenderText::~RenderText() {
361}
362
[email protected]ff44d712011-07-25 08:42:52363void RenderText::SetText(const string16& text) {
[email protected]d3c6b0602011-09-07 19:26:06364 DCHECK(!composition_range_.IsValid());
[email protected]ff44d712011-07-25 08:42:52365 size_t old_text_length = text_.length();
366 text_ = text;
367
368 // Update the style ranges as needed.
369 if (text_.empty()) {
370 style_ranges_.clear();
371 } else if (style_ranges_.empty()) {
372 ApplyDefaultStyle();
373 } else if (text_.length() > old_text_length) {
374 style_ranges_.back().range.set_end(text_.length());
375 } else if (text_.length() < old_text_length) {
376 StyleRanges::iterator i;
377 for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) {
378 if (i->range.start() >= text_.length()) {
[email protected]052ce6bb2011-10-14 19:48:20379 // Style ranges are sorted and non-overlapping, so all the subsequent
380 // style ranges should be out of text_.length() as well.
381 style_ranges_.erase(i, style_ranges_.end());
382 break;
[email protected]ff44d712011-07-25 08:42:52383 }
384 }
[email protected]052ce6bb2011-10-14 19:48:20385 // Since style ranges are sorted and non-overlapping, if there is a style
386 // range ends beyond text_.length, it must be the last one.
[email protected]ff44d712011-07-25 08:42:52387 style_ranges_.back().range.set_end(text_.length());
388 }
389#ifndef NDEBUG
390 CheckStyleRanges(style_ranges_, text_.length());
391#endif
[email protected]f6aaa0f2011-08-11 07:05:46392 cached_bounds_and_offset_valid_ = false;
[email protected]d3c6b0602011-09-07 19:26:06393
394 // Reset selection model. SetText should always followed by SetSelectionModel
395 // or SetCursorPosition in upper layer.
[email protected]d9990212012-03-13 01:09:31396 SetSelectionModel(SelectionModel());
[email protected]6002a4f32011-11-30 10:18:42397
[email protected]46cb5382012-08-01 21:57:31398 // Invalidate the cached text direction if it depends on the text contents.
399 if (directionality_mode_ == DIRECTIONALITY_FROM_TEXT)
400 text_direction_ = base::i18n::UNKNOWN_DIRECTION;
401
[email protected]6bc200d2012-09-28 21:01:51402 UpdateObscuredText();
[email protected]caa9e1e2012-03-13 20:47:04403 ResetLayout();
[email protected]ff44d712011-07-25 08:42:52404}
405
[email protected]f0ed8a2f2012-01-24 17:45:59406void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) {
407 if (horizontal_alignment_ != alignment) {
408 horizontal_alignment_ = alignment;
[email protected]ceb36f7d2012-10-31 18:33:24409 display_offset_ = Vector2d();
[email protected]f0ed8a2f2012-01-24 17:45:59410 cached_bounds_and_offset_valid_ = false;
411 }
412}
413
[email protected]8d901a82012-01-04 19:41:30414void RenderText::SetFontList(const FontList& font_list) {
415 font_list_ = font_list;
416 cached_bounds_and_offset_valid_ = false;
[email protected]caa9e1e2012-03-13 20:47:04417 ResetLayout();
[email protected]8d901a82012-01-04 19:41:30418}
419
[email protected]add84782012-09-18 16:00:41420void RenderText::SetFont(const Font& font) {
421 SetFontList(FontList(font));
422}
423
[email protected]fdf481b2012-01-24 06:14:07424void RenderText::SetFontSize(int size) {
425 font_list_ = font_list_.DeriveFontListWithSize(size);
426 cached_bounds_and_offset_valid_ = false;
[email protected]caa9e1e2012-03-13 20:47:04427 ResetLayout();
[email protected]fdf481b2012-01-24 06:14:07428}
429
[email protected]f0ed8a2f2012-01-24 17:45:59430void RenderText::SetCursorEnabled(bool cursor_enabled) {
431 cursor_enabled_ = cursor_enabled;
432 cached_bounds_and_offset_valid_ = false;
433}
434
[email protected]8d901a82012-01-04 19:41:30435const Font& RenderText::GetFont() const {
436 return font_list_.GetFonts()[0];
437}
438
[email protected]0d717602011-08-30 06:21:14439void RenderText::ToggleInsertMode() {
440 insert_mode_ = !insert_mode_;
[email protected]f6aaa0f2011-08-11 07:05:46441 cached_bounds_and_offset_valid_ = false;
442}
443
[email protected]bec929c2012-03-02 06:23:50444void RenderText::SetObscured(bool obscured) {
445 if (obscured != obscured_) {
446 obscured_ = obscured;
447 cached_bounds_and_offset_valid_ = false;
[email protected]6bc200d2012-09-28 21:01:51448 UpdateObscuredText();
[email protected]caa9e1e2012-03-13 20:47:04449 ResetLayout();
[email protected]bec929c2012-03-02 06:23:50450 }
451}
452
[email protected]f6aaa0f2011-08-11 07:05:46453void RenderText::SetDisplayRect(const Rect& r) {
454 display_rect_ = r;
455 cached_bounds_and_offset_valid_ = false;
[email protected]8e42ba22011-08-04 21:47:08456}
457
[email protected]0d717602011-08-30 06:21:14458void RenderText::SetCursorPosition(size_t position) {
459 MoveCursorTo(position, false);
[email protected]ff44d712011-07-25 08:42:52460}
461
[email protected]d66009e2012-01-21 01:27:28462void RenderText::MoveCursor(BreakType break_type,
463 VisualCursorDirection direction,
464 bool select) {
[email protected]d9990212012-03-13 01:09:31465 SelectionModel position(cursor_position(), selection_model_.caret_affinity());
[email protected]ff44d712011-07-25 08:42:52466 // Cancelling a selection moves to the edge of the selection.
[email protected]d9990212012-03-13 01:09:31467 if (break_type != LINE_BREAK && !selection().is_empty() && !select) {
[email protected]d3c6b0602011-09-07 19:26:06468 SelectionModel selection_start = GetSelectionModelForSelectionStart();
[email protected]d66009e2012-01-21 01:27:28469 int start_x = GetCursorBounds(selection_start, true).x();
470 int cursor_x = GetCursorBounds(position, true).x();
471 // Use the selection start if it is left (when |direction| is CURSOR_LEFT)
472 // or right (when |direction| is CURSOR_RIGHT) of the selection end.
473 if (direction == CURSOR_RIGHT ? start_x > cursor_x : start_x < cursor_x)
[email protected]8e42ba22011-08-04 21:47:08474 position = selection_start;
[email protected]d66009e2012-01-21 01:27:28475 // For word breaks, use the nearest word boundary in the appropriate
476 // |direction|.
[email protected]ff44d712011-07-25 08:42:52477 if (break_type == WORD_BREAK)
[email protected]d66009e2012-01-21 01:27:28478 position = GetAdjacentSelectionModel(position, break_type, direction);
[email protected]ff44d712011-07-25 08:42:52479 } else {
[email protected]d66009e2012-01-21 01:27:28480 position = GetAdjacentSelectionModel(position, break_type, direction);
[email protected]ff44d712011-07-25 08:42:52481 }
[email protected]ec7f48d2011-08-09 03:48:50482 if (select)
[email protected]d9990212012-03-13 01:09:31483 position.set_selection_start(selection().start());
[email protected]8e42ba22011-08-04 21:47:08484 MoveCursorTo(position);
[email protected]ff44d712011-07-25 08:42:52485}
486
[email protected]f9a221b2011-12-10 20:25:38487bool RenderText::MoveCursorTo(const SelectionModel& model) {
[email protected]0d717602011-08-30 06:21:14488 // Enforce valid selection model components.
[email protected]d9990212012-03-13 01:09:31489 size_t text_length = text().length();
490 ui::Range range(std::min(model.selection().start(), text_length),
491 std::min(model.caret_pos(), text_length));
[email protected]0d717602011-08-30 06:21:14492 // The current model only supports caret positions at valid character indices.
[email protected]d9990212012-03-13 01:09:31493 if (!IsCursorablePosition(range.start()) ||
494 !IsCursorablePosition(range.end()))
[email protected]53c0b1b2011-09-21 20:32:29495 return false;
[email protected]d9990212012-03-13 01:09:31496 SelectionModel sel(range, model.caret_affinity());
497 bool changed = sel != selection_model_;
[email protected]0d717602011-08-30 06:21:14498 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52499 return changed;
500}
501
[email protected]7b3cb4b22011-08-02 18:36:40502bool RenderText::MoveCursorTo(const Point& point, bool select) {
[email protected]d9990212012-03-13 01:09:31503 SelectionModel position = FindCursorPosition(point);
[email protected]8e42ba22011-08-04 21:47:08504 if (select)
[email protected]d9990212012-03-13 01:09:31505 position.set_selection_start(selection().start());
506 return MoveCursorTo(position);
[email protected]ff44d712011-07-25 08:42:52507}
508
[email protected]67e85512011-10-12 20:03:45509bool RenderText::SelectRange(const ui::Range& range) {
[email protected]d9990212012-03-13 01:09:31510 ui::Range sel(std::min(range.start(), text().length()),
511 std::min(range.end(), text().length()));
512 if (!IsCursorablePosition(sel.start()) || !IsCursorablePosition(sel.end()))
[email protected]67e85512011-10-12 20:03:45513 return false;
[email protected]d9990212012-03-13 01:09:31514 LogicalCursorDirection affinity =
515 (sel.is_reversed() || sel.is_empty()) ? CURSOR_FORWARD : CURSOR_BACKWARD;
516 SetSelectionModel(SelectionModel(sel, affinity));
[email protected]67e85512011-10-12 20:03:45517 return true;
518}
519
[email protected]8e42ba22011-08-04 21:47:08520bool RenderText::IsPointInSelection(const Point& point) {
[email protected]d9990212012-03-13 01:09:31521 if (selection().is_empty())
[email protected]0d717602011-08-30 06:21:14522 return false;
[email protected]d9990212012-03-13 01:09:31523 SelectionModel cursor = FindCursorPosition(point);
524 return RangeContainsCaret(
525 selection(), cursor.caret_pos(), cursor.caret_affinity());
[email protected]ff44d712011-07-25 08:42:52526}
527
528void RenderText::ClearSelection() {
[email protected]d9990212012-03-13 01:09:31529 SetSelectionModel(SelectionModel(cursor_position(),
530 selection_model_.caret_affinity()));
[email protected]ff44d712011-07-25 08:42:52531}
532
[email protected]f5f0c2a2012-07-13 04:44:55533void RenderText::SelectAll(bool reversed) {
534 const size_t length = text().length();
535 const ui::Range all = reversed ? ui::Range(length, 0) : ui::Range(0, length);
536 const bool success = SelectRange(all);
537 DCHECK(success);
[email protected]ff44d712011-07-25 08:42:52538}
539
540void RenderText::SelectWord() {
[email protected]bec929c2012-03-02 06:23:50541 if (obscured_) {
[email protected]f5f0c2a2012-07-13 04:44:55542 SelectAll(false);
[email protected]bec929c2012-03-02 06:23:50543 return;
544 }
545
[email protected]d9990212012-03-13 01:09:31546 size_t cursor_pos = cursor_position();
[email protected]ff44d712011-07-25 08:42:52547
[email protected]53c0b1b2011-09-21 20:32:29548 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
549 bool success = iter.Init();
550 DCHECK(success);
551 if (!success)
552 return;
553
[email protected]d9990212012-03-13 01:09:31554 size_t selection_start = cursor_pos;
[email protected]53c0b1b2011-09-21 20:32:29555 for (; selection_start != 0; --selection_start) {
556 if (iter.IsStartOfWord(selection_start) ||
557 iter.IsEndOfWord(selection_start))
[email protected]ff44d712011-07-25 08:42:52558 break;
559 }
560
[email protected]d9990212012-03-13 01:09:31561 if (selection_start == cursor_pos)
562 ++cursor_pos;
[email protected]53c0b1b2011-09-21 20:32:29563
[email protected]d9990212012-03-13 01:09:31564 for (; cursor_pos < text().length(); ++cursor_pos)
565 if (iter.IsEndOfWord(cursor_pos) || iter.IsStartOfWord(cursor_pos))
[email protected]ff44d712011-07-25 08:42:52566 break;
[email protected]ff44d712011-07-25 08:42:52567
[email protected]0d717602011-08-30 06:21:14568 MoveCursorTo(selection_start, false);
[email protected]d9990212012-03-13 01:09:31569 MoveCursorTo(cursor_pos, true);
[email protected]ff44d712011-07-25 08:42:52570}
571
572const ui::Range& RenderText::GetCompositionRange() const {
573 return composition_range_;
574}
575
576void RenderText::SetCompositionRange(const ui::Range& composition_range) {
577 CHECK(!composition_range.IsValid() ||
578 ui::Range(0, text_.length()).Contains(composition_range));
579 composition_range_.set_end(composition_range.end());
580 composition_range_.set_start(composition_range.start());
[email protected]caa9e1e2012-03-13 20:47:04581 ResetLayout();
[email protected]ff44d712011-07-25 08:42:52582}
583
[email protected]c8636672011-12-29 05:07:57584void RenderText::ApplyStyleRange(const StyleRange& style_range) {
[email protected]ff44d712011-07-25 08:42:52585 const ui::Range& new_range = style_range.range;
586 if (!new_range.IsValid() || new_range.is_empty())
587 return;
588 CHECK(!new_range.is_reversed());
589 CHECK(ui::Range(0, text_.length()).Contains(new_range));
[email protected]8e42ba22011-08-04 21:47:08590 ApplyStyleRangeImpl(&style_ranges_, style_range);
[email protected]ff44d712011-07-25 08:42:52591#ifndef NDEBUG
592 CheckStyleRanges(style_ranges_, text_.length());
593#endif
[email protected]f6aaa0f2011-08-11 07:05:46594 // TODO(xji): only invalidate if font or underline changes.
595 cached_bounds_and_offset_valid_ = false;
[email protected]caa9e1e2012-03-13 20:47:04596 ResetLayout();
[email protected]ff44d712011-07-25 08:42:52597}
598
599void RenderText::ApplyDefaultStyle() {
600 style_ranges_.clear();
601 StyleRange style = StyleRange(default_style_);
602 style.range.set_end(text_.length());
603 style_ranges_.push_back(style);
[email protected]f6aaa0f2011-08-11 07:05:46604 cached_bounds_and_offset_valid_ = false;
[email protected]caa9e1e2012-03-13 20:47:04605 ResetLayout();
[email protected]ff44d712011-07-25 08:42:52606}
607
[email protected]46cb5382012-08-01 21:57:31608void RenderText::SetDirectionalityMode(DirectionalityMode mode) {
609 if (mode == directionality_mode_)
610 return;
611
612 directionality_mode_ = mode;
613 text_direction_ = base::i18n::UNKNOWN_DIRECTION;
614 ResetLayout();
615}
616
617base::i18n::TextDirection RenderText::GetTextDirection() {
618 if (text_direction_ == base::i18n::UNKNOWN_DIRECTION) {
619 switch (directionality_mode_) {
620 case DIRECTIONALITY_FROM_TEXT:
621 // Derive the direction from the display text, which differs from text()
622 // in the case of obscured (password) textfields.
623 text_direction_ =
[email protected]6bc200d2012-09-28 21:01:51624 base::i18n::GetFirstStrongCharacterDirection(GetLayoutText());
[email protected]46cb5382012-08-01 21:57:31625 break;
626 case DIRECTIONALITY_FROM_UI:
627 text_direction_ = base::i18n::IsRTL() ? base::i18n::RIGHT_TO_LEFT :
628 base::i18n::LEFT_TO_RIGHT;
629 break;
630 case DIRECTIONALITY_FORCE_LTR:
631 text_direction_ = base::i18n::LEFT_TO_RIGHT;
632 break;
633 case DIRECTIONALITY_FORCE_RTL:
634 text_direction_ = base::i18n::RIGHT_TO_LEFT;
635 break;
636 default:
637 NOTREACHED();
638 }
639 }
640
641 return text_direction_;
642}
643
[email protected]d66009e2012-01-21 01:27:28644VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() {
645 return GetTextDirection() == base::i18n::LEFT_TO_RIGHT ?
646 CURSOR_RIGHT : CURSOR_LEFT;
[email protected]ff44d712011-07-25 08:42:52647}
648
[email protected]7b3cb4b22011-08-02 18:36:40649void RenderText::Draw(Canvas* canvas) {
[email protected]49aa120a2012-05-31 00:01:22650 EnsureLayout();
[email protected]ff44d712011-07-25 08:42:52651
[email protected]f7816ad2012-06-22 22:15:43652 if (clip_to_display_rect()) {
653 gfx::Rect clip_rect(display_rect());
654 clip_rect.Inset(ShadowValue::GetMargin(text_shadows_));
[email protected]0834e1b12012-04-11 02:23:56655
[email protected]f7816ad2012-06-22 22:15:43656 canvas->Save();
657 canvas->ClipRect(clip_rect);
658 }
[email protected]1a353f12012-01-18 04:20:56659
660 if (!text().empty())
661 DrawSelection(canvas);
662
663 DrawCursor(canvas);
664
[email protected]49aa120a2012-05-31 00:01:22665 if (!text().empty())
[email protected]6002a4f32011-11-30 10:18:42666 DrawVisualText(canvas);
[email protected]f7816ad2012-06-22 22:15:43667
668 if (clip_to_display_rect())
669 canvas->Restore();
[email protected]ff44d712011-07-25 08:42:52670}
671
[email protected]d9990212012-03-13 01:09:31672Rect RenderText::GetCursorBounds(const SelectionModel& caret,
673 bool insert_mode) {
674 EnsureLayout();
675
676 size_t caret_pos = caret.caret_pos();
677 // In overtype mode, ignore the affinity and always indicate that we will
678 // overtype the next character.
679 LogicalCursorDirection caret_affinity =
680 insert_mode ? caret.caret_affinity() : CURSOR_FORWARD;
681 int x = 0, width = 0, height = 0;
682 if (caret_pos == (caret_affinity == CURSOR_BACKWARD ? 0 : text().length())) {
683 // The caret is attached to the boundary. Always return a zero-width caret,
684 // since there is nothing to overtype.
685 Size size = GetStringSize();
686 if ((GetTextDirection() == base::i18n::RIGHT_TO_LEFT) == (caret_pos == 0))
687 x = size.width();
688 height = size.height();
689 } else {
690 size_t grapheme_start = (caret_affinity == CURSOR_FORWARD) ?
691 caret_pos : IndexOfAdjacentGrapheme(caret_pos, CURSOR_BACKWARD);
692 ui::Range xspan;
693 GetGlyphBounds(grapheme_start, &xspan, &height);
694 if (insert_mode) {
695 x = (caret_affinity == CURSOR_BACKWARD) ? xspan.end() : xspan.start();
696 } else { // overtype mode
697 x = xspan.GetMin();
698 width = xspan.length();
699 }
700 }
701 height = std::min(height, display_rect().height());
702 int y = (display_rect().height() - height) / 2;
703 return Rect(ToViewPoint(Point(x, y)), Size(width, height));
704}
705
[email protected]f6aaa0f2011-08-11 07:05:46706const Rect& RenderText::GetUpdatedCursorBounds() {
707 UpdateCachedBoundsAndOffset();
[email protected]8e42ba22011-08-04 21:47:08708 return cursor_bounds_;
709}
710
[email protected]0a12d5f2012-05-16 00:37:09711size_t RenderText::IndexOfAdjacentGrapheme(size_t index,
712 LogicalCursorDirection direction) {
713 if (index > text().length())
714 return text().length();
715
716 EnsureLayout();
717
718 if (direction == CURSOR_FORWARD) {
719 while (index < text().length()) {
720 index++;
721 if (IsCursorablePosition(index))
722 return index;
723 }
724 return text().length();
725 }
726
727 while (index > 0) {
728 index--;
729 if (IsCursorablePosition(index))
730 return index;
731 }
732 return 0;
733}
734
[email protected]04b7bf322011-10-03 19:08:46735SelectionModel RenderText::GetSelectionModelForSelectionStart() {
[email protected]d9990212012-03-13 01:09:31736 const ui::Range& sel = selection();
737 if (sel.is_empty())
738 return selection_model_;
739 return SelectionModel(sel.start(),
740 sel.is_reversed() ? CURSOR_BACKWARD : CURSOR_FORWARD);
[email protected]04b7bf322011-10-03 19:08:46741}
742
[email protected]25650682012-05-29 23:09:19743void RenderText::SetTextShadows(const ShadowValues& shadows) {
[email protected]0834e1b12012-04-11 02:23:56744 text_shadows_ = shadows;
745}
746
[email protected]8e42ba22011-08-04 21:47:08747RenderText::RenderText()
[email protected]f0ed8a2f2012-01-24 17:45:59748 : horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT),
[email protected]46cb5382012-08-01 21:57:31749 directionality_mode_(DIRECTIONALITY_FROM_TEXT),
750 text_direction_(base::i18n::UNKNOWN_DIRECTION),
[email protected]f0ed8a2f2012-01-24 17:45:59751 cursor_enabled_(true),
752 cursor_visible_(false),
[email protected]8e42ba22011-08-04 21:47:08753 insert_mode_(true),
[email protected]913a1ef2012-02-29 08:40:34754 cursor_color_(kDefaultCursorColor),
[email protected]311a09d2012-05-14 20:38:17755 selection_color_(kDefaultSelectionColor),
756 selection_background_focused_color_(kDefaultSelectionBackgroundColor),
757 selection_background_unfocused_color_(kDefaultSelectionBackgroundColor),
[email protected]5abe6302011-12-20 23:44:32758 focused_(false),
[email protected]d3c6b0602011-09-07 19:26:06759 composition_range_(ui::Range::InvalidRange()),
[email protected]bec929c2012-03-02 06:23:50760 obscured_(false),
[email protected]f308e5b12012-01-17 19:09:18761 fade_head_(false),
762 fade_tail_(false),
[email protected]32d582d2012-02-28 21:50:22763 background_is_transparent_(false),
[email protected]f7816ad2012-06-22 22:15:43764 clip_to_display_rect_(true),
[email protected]f6aaa0f2011-08-11 07:05:46765 cached_bounds_and_offset_valid_(false) {
766}
767
[email protected]ceb36f7d2012-10-31 18:33:24768const Vector2d& RenderText::GetUpdatedDisplayOffset() {
[email protected]f6aaa0f2011-08-11 07:05:46769 UpdateCachedBoundsAndOffset();
770 return display_offset_;
[email protected]8e42ba22011-08-04 21:47:08771}
772
[email protected]d66009e2012-01-21 01:27:28773SelectionModel RenderText::GetAdjacentSelectionModel(
774 const SelectionModel& current,
775 BreakType break_type,
776 VisualCursorDirection direction) {
777 EnsureLayout();
778
779 if (break_type == LINE_BREAK || text().empty())
780 return EdgeSelectionModel(direction);
[email protected]ec7f48d2011-08-09 03:48:50781 if (break_type == CHARACTER_BREAK)
[email protected]d66009e2012-01-21 01:27:28782 return AdjacentCharSelectionModel(current, direction);
783 DCHECK(break_type == WORD_BREAK);
784 return AdjacentWordSelectionModel(current, direction);
[email protected]0d717602011-08-30 06:21:14785}
786
[email protected]d9990212012-03-13 01:09:31787SelectionModel RenderText::EdgeSelectionModel(
788 VisualCursorDirection direction) {
789 if (direction == GetVisualDirectionOfLogicalEnd())
790 return SelectionModel(text().length(), CURSOR_FORWARD);
791 return SelectionModel(0, CURSOR_BACKWARD);
792}
[email protected]6002a4f32011-11-30 10:18:42793
[email protected]d9990212012-03-13 01:09:31794void RenderText::SetSelectionModel(const SelectionModel& model) {
795 DCHECK_LE(model.selection().GetMax(), text().length());
796 selection_model_ = model;
[email protected]6002a4f32011-11-30 10:18:42797 cached_bounds_and_offset_valid_ = false;
[email protected]0d717602011-08-30 06:21:14798}
799
[email protected]6bc200d2012-09-28 21:01:51800const string16& RenderText::GetLayoutText() const {
801 return obscured() ? obscured_text_ : text();
[email protected]bec929c2012-03-02 06:23:50802}
803
[email protected]8e42ba22011-08-04 21:47:08804void RenderText::ApplyCompositionAndSelectionStyles(
[email protected]1a353f12012-01-18 04:20:56805 StyleRanges* style_ranges) {
[email protected]8e42ba22011-08-04 21:47:08806 // TODO(msw): This pattern ought to be reconsidered; what about composition
807 // and selection overlaps, retain existing local style features?
808 // Apply a composition style override to a copy of the style ranges.
809 if (composition_range_.IsValid() && !composition_range_.is_empty()) {
810 StyleRange composition_style(default_style_);
811 composition_style.underline = true;
[email protected]d9990212012-03-13 01:09:31812 composition_style.range = composition_range_;
[email protected]8e42ba22011-08-04 21:47:08813 ApplyStyleRangeImpl(style_ranges, composition_style);
814 }
815 // Apply a selection style override to a copy of the style ranges.
[email protected]d9990212012-03-13 01:09:31816 if (!selection().is_empty()) {
[email protected]8e42ba22011-08-04 21:47:08817 StyleRange selection_style(default_style_);
[email protected]311a09d2012-05-14 20:38:17818 selection_style.foreground = selection_color_;
[email protected]d9990212012-03-13 01:09:31819 selection_style.range = ui::Range(selection().GetMin(),
820 selection().GetMax());
[email protected]8e42ba22011-08-04 21:47:08821 ApplyStyleRangeImpl(style_ranges, selection_style);
822 }
[email protected]1a353f12012-01-18 04:20:56823 // Apply replacement-mode style override to a copy of the style ranges.
824 //
825 // TODO(xji): NEED TO FIX FOR WINDOWS ASAP. Windows call this function (to
826 // apply styles) in ItemizeLogicalText(). In order for the cursor's underline
827 // character to be drawn correctly, we will need to re-layout the text. It's
828 // not practical to do layout on every cursor blink. We need to fix Windows
829 // port to apply styles during drawing phase like Linux port does.
830 // http://crbug.com/110109
831 if (!insert_mode_ && cursor_visible() && focused()) {
832 StyleRange replacement_mode_style(default_style_);
[email protected]311a09d2012-05-14 20:38:17833 replacement_mode_style.foreground = selection_color_;
[email protected]d9990212012-03-13 01:09:31834 size_t cursor = cursor_position();
[email protected]1a353f12012-01-18 04:20:56835 replacement_mode_style.range.set_start(cursor);
[email protected]d66009e2012-01-21 01:27:28836 replacement_mode_style.range.set_end(
837 IndexOfAdjacentGrapheme(cursor, CURSOR_FORWARD));
[email protected]1a353f12012-01-18 04:20:56838 ApplyStyleRangeImpl(style_ranges, replacement_mode_style);
839 }
[email protected]ff44d712011-07-25 08:42:52840}
841
[email protected]ceb36f7d2012-10-31 18:33:24842Vector2d RenderText::GetTextOffset() {
843 Vector2d offset = display_rect().OffsetFromOrigin();
844 offset.Add(GetUpdatedDisplayOffset());
845 offset.Add(GetAlignmentOffset());
846 return offset;
[email protected]f0ed8a2f2012-01-24 17:45:59847}
848
[email protected]0d717602011-08-30 06:21:14849Point RenderText::ToTextPoint(const Point& point) {
[email protected]ceb36f7d2012-10-31 18:33:24850 return point.Subtract(GetTextOffset());
[email protected]0d717602011-08-30 06:21:14851}
852
853Point RenderText::ToViewPoint(const Point& point) {
[email protected]ceb36f7d2012-10-31 18:33:24854 return point.Add(GetTextOffset());
[email protected]f0ed8a2f2012-01-24 17:45:59855}
856
857int RenderText::GetContentWidth() {
[email protected]d9990212012-03-13 01:09:31858 return GetStringSize().width() + (cursor_enabled_ ? 1 : 0);
[email protected]f0ed8a2f2012-01-24 17:45:59859}
860
[email protected]ceb36f7d2012-10-31 18:33:24861Vector2d RenderText::GetAlignmentOffset() {
862 if (horizontal_alignment() == ALIGN_LEFT)
863 return Vector2d();
864
865 int x_offset = display_rect().width() - GetContentWidth();
866 if (horizontal_alignment() == ALIGN_CENTER)
867 x_offset /= 2;
868 return Vector2d(x_offset, 0);
[email protected]0d717602011-08-30 06:21:14869}
870
[email protected]e3ccc012012-11-01 15:39:49871Vector2d RenderText::GetOffsetForDrawing() {
[email protected]67b981562011-12-09 00:35:05872 // Center the text vertically in the display area.
[email protected]e3ccc012012-11-01 15:39:49873 return GetTextOffset() +
[email protected]ceb36f7d2012-10-31 18:33:24874 Vector2d(0, (display_rect().height() - GetStringSize().height()) / 2);
[email protected]67b981562011-12-09 00:35:05875}
876
[email protected]f0ed8a2f2012-01-24 17:45:59877void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) {
[email protected]f308e5b12012-01-17 19:09:18878 if (!fade_head() && !fade_tail())
[email protected]f0ed8a2f2012-01-24 17:45:59879 return;
[email protected]f308e5b12012-01-17 19:09:18880
[email protected]d9990212012-03-13 01:09:31881 const int text_width = GetStringSize().width();
[email protected]f308e5b12012-01-17 19:09:18882 const int display_width = display_rect().width();
883
884 // If the text fits as-is, no need to fade.
885 if (text_width <= display_width)
[email protected]f0ed8a2f2012-01-24 17:45:59886 return;
[email protected]f308e5b12012-01-17 19:09:18887
888 int gradient_width = CalculateFadeGradientWidth(GetFont(), display_width);
889 if (gradient_width == 0)
[email protected]f0ed8a2f2012-01-24 17:45:59890 return;
[email protected]f308e5b12012-01-17 19:09:18891
892 bool fade_left = fade_head();
893 bool fade_right = fade_tail();
894 // Under RTL, |fade_right| == |fade_head|.
[email protected]1e7bc052012-06-28 20:30:36895 // TODO(asvitkine): This is currently not based on GetTextDirection() because
896 // RenderTextWin does not return a direction that's based on
897 // the text content.
898 if (horizontal_alignment() == ALIGN_RIGHT)
[email protected]f308e5b12012-01-17 19:09:18899 std::swap(fade_left, fade_right);
900
901 gfx::Rect solid_part = display_rect();
902 gfx::Rect left_part;
903 gfx::Rect right_part;
904 if (fade_left) {
905 left_part = solid_part;
906 left_part.Inset(0, 0, solid_part.width() - gradient_width, 0);
907 solid_part.Inset(gradient_width, 0, 0, 0);
908 }
909 if (fade_right) {
910 right_part = solid_part;
911 right_part.Inset(solid_part.width() - gradient_width, 0, 0, 0);
912 solid_part.Inset(0, 0, gradient_width, 0);
913 }
914
[email protected]f308e5b12012-01-17 19:09:18915 gfx::Rect text_rect = display_rect();
[email protected]f0ed8a2f2012-01-24 17:45:59916 text_rect.Inset(GetAlignmentOffset().x(), 0, 0, 0);
[email protected]f308e5b12012-01-17 19:09:18917
918 const SkColor color = default_style().foreground;
[email protected]e166e54d2012-03-27 14:11:51919 SkShader* shader = CreateFadeShader(text_rect, left_part, right_part, color);
920 SkAutoUnref auto_unref(shader);
921 if (shader) {
[email protected]f308e5b12012-01-17 19:09:18922 // |renderer| adds its own ref. So don't |release()| it from the ref ptr.
[email protected]3bf21e12012-05-02 15:33:36923 renderer->SetShader(shader, display_rect());
[email protected]f308e5b12012-01-17 19:09:18924 }
[email protected]f308e5b12012-01-17 19:09:18925}
926
[email protected]0834e1b12012-04-11 02:23:56927void RenderText::ApplyTextShadows(internal::SkiaTextRenderer* renderer) {
[email protected]9499a0b2012-05-25 16:17:59928 SkDrawLooper* looper = gfx::CreateShadowDrawLooper(text_shadows_);
[email protected]0834e1b12012-04-11 02:23:56929 SkAutoUnref auto_unref(looper);
[email protected]0834e1b12012-04-11 02:23:56930 renderer->SetDrawLooper(looper);
931}
932
[email protected]d9990212012-03-13 01:09:31933// static
934bool RenderText::RangeContainsCaret(const ui::Range& range,
935 size_t caret_pos,
936 LogicalCursorDirection caret_affinity) {
937 // NB: exploits unsigned wraparound (WG14/N1124 section 6.2.5 paragraph 9).
938 size_t adjacent = (caret_affinity == CURSOR_BACKWARD) ?
939 caret_pos - 1 : caret_pos + 1;
940 return range.Contains(ui::Range(caret_pos, adjacent));
941}
942
[email protected]0d717602011-08-30 06:21:14943void RenderText::MoveCursorTo(size_t position, bool select) {
944 size_t cursor = std::min(position, text().length());
[email protected]d9990212012-03-13 01:09:31945 if (IsCursorablePosition(cursor))
946 SetSelectionModel(SelectionModel(
947 ui::Range(select ? selection().start() : cursor, cursor),
948 (cursor == 0) ? CURSOR_FORWARD : CURSOR_BACKWARD));
[email protected]8e42ba22011-08-04 21:47:08949}
950
[email protected]6bc200d2012-09-28 21:01:51951void RenderText::UpdateObscuredText() {
952 if (!obscured_)
953 return;
954
955 const size_t obscured_text_length =
956 static_cast<size_t>(ui::UTF16IndexToOffset(text_, 0, text_.length()));
957 if (obscured_text_.length() != obscured_text_length)
958 obscured_text_.resize(obscured_text_length, kPasswordReplacementChar);
959}
960
[email protected]f6aaa0f2011-08-11 07:05:46961void RenderText::UpdateCachedBoundsAndOffset() {
962 if (cached_bounds_and_offset_valid_)
963 return;
[email protected]f0ed8a2f2012-01-24 17:45:59964
[email protected]f6aaa0f2011-08-11 07:05:46965 // First, set the valid flag true to calculate the current cursor bounds using
966 // the stale |display_offset_|. Applying |delta_offset| at the end of this
967 // function will set |cursor_bounds_| and |display_offset_| to correct values.
968 cached_bounds_and_offset_valid_ = true;
969 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_);
[email protected]f0ed8a2f2012-01-24 17:45:59970
[email protected]8e42ba22011-08-04 21:47:08971 // Update |display_offset_| to ensure the current cursor is visible.
[email protected]f0ed8a2f2012-01-24 17:45:59972 const int display_width = display_rect_.width();
973 const int content_width = GetContentWidth();
974
[email protected]ceb36f7d2012-10-31 18:33:24975 int delta_x = 0;
[email protected]f0ed8a2f2012-01-24 17:45:59976 if (content_width <= display_width || !cursor_enabled()) {
977 // Don't pan if the text fits in the display width or when the cursor is
978 // disabled.
[email protected]ceb36f7d2012-10-31 18:33:24979 delta_x = -display_offset_.x();
[email protected]65788132011-09-07 20:20:23980 } else if (cursor_bounds_.right() >= display_rect_.right()) {
[email protected]d3c6b0602011-09-07 19:26:06981 // TODO(xji): when the character overflow is a RTL character, currently, if
982 // we pan cursor at the rightmost position, the entered RTL character is not
983 // displayed. Should pan cursor to show the last logical characters.
984 //
[email protected]8e42ba22011-08-04 21:47:08985 // Pan to show the cursor when it overflows to the right,
[email protected]ceb36f7d2012-10-31 18:33:24986 delta_x = display_rect_.right() - cursor_bounds_.right() - 1;
[email protected]f6aaa0f2011-08-11 07:05:46987 } else if (cursor_bounds_.x() < display_rect_.x()) {
[email protected]d3c6b0602011-09-07 19:26:06988 // TODO(xji): have similar problem as above when overflow character is a
989 // LTR character.
990 //
[email protected]8e42ba22011-08-04 21:47:08991 // Pan to show the cursor when it overflows to the left.
[email protected]ceb36f7d2012-10-31 18:33:24992 delta_x = display_rect_.x() - cursor_bounds_.x();
[email protected]f0ed8a2f2012-01-24 17:45:59993 } else if (display_offset_.x() != 0) {
994 // Reduce the pan offset to show additional overflow text when the display
995 // width increases.
996 const int negate_rtl = horizontal_alignment_ == ALIGN_RIGHT ? -1 : 1;
997 const int offset = negate_rtl * display_offset_.x();
[email protected]ceb36f7d2012-10-31 18:33:24998 if (display_width > (content_width + offset)) {
999 delta_x = negate_rtl * (display_width - (content_width + offset));
1000 }
[email protected]8e42ba22011-08-04 21:47:081001 }
[email protected]f0ed8a2f2012-01-24 17:45:591002
[email protected]ceb36f7d2012-10-31 18:33:241003 gfx::Vector2d delta_offset(delta_x, 0);
1004 display_offset_ += delta_offset;
1005 cursor_bounds_.Offset(delta_offset);
[email protected]ff44d712011-07-25 08:42:521006}
1007
[email protected]6002a4f32011-11-30 10:18:421008void RenderText::DrawSelection(Canvas* canvas) {
[email protected]311a09d2012-05-14 20:38:171009 const SkColor color = focused() ? selection_background_focused_color_ :
1010 selection_background_unfocused_color_;
1011 const std::vector<Rect> sel = GetSubstringBounds(selection());
[email protected]6002a4f32011-11-30 10:18:421012 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
[email protected]b8bdc422012-02-01 21:44:401013 canvas->FillRect(*i, color);
[email protected]6002a4f32011-11-30 10:18:421014}
1015
1016void RenderText::DrawCursor(Canvas* canvas) {
1017 // Paint cursor. Replace cursor is drawn as rectangle for now.
1018 // TODO(msw): Draw a better cursor with a better indication of association.
[email protected]f0ed8a2f2012-01-24 17:45:591019 if (cursor_enabled() && cursor_visible() && focused()) {
[email protected]1a353f12012-01-18 04:20:561020 const Rect& bounds = GetUpdatedCursorBounds();
1021 if (bounds.width() != 0)
[email protected]913a1ef2012-02-29 08:40:341022 canvas->FillRect(bounds, cursor_color_);
[email protected]1a353f12012-01-18 04:20:561023 else
[email protected]913a1ef2012-02-29 08:40:341024 canvas->DrawRect(bounds, cursor_color_);
[email protected]1a353f12012-01-18 04:20:561025 }
[email protected]6002a4f32011-11-30 10:18:421026}
1027
[email protected]ff44d712011-07-25 08:42:521028} // namespace gfx