blob: bb352947e0c9dfc450a87ec13db52392e0ed9210 [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]ff44d712011-07-25 08:42:5215#include "ui/gfx/canvas.h"
16#include "ui/gfx/canvas_skia.h"
[email protected]721ea922012-01-23 21:13:3317#include "ui/gfx/native_theme.h"
[email protected]8e42ba22011-08-04 21:47:0818#include "unicode/uchar.h"
[email protected]ff44d712011-07-25 08:42:5219
20namespace {
21
[email protected]d66009e2012-01-21 01:27:2822// Color settings for text, backgrounds and cursor.
23// These are tentative, and should be derived from theme, system
24// settings and current settings.
25// TODO(oshima): Change this to match the standard chrome
26// before dogfooding textfield views.
[email protected]d66009e2012-01-21 01:27:2827const SkColor kCursorColor = SK_ColorBLACK;
28
[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);
142 const SkPoint points[2] = {
143 SkPoint::Make(text_rect.x(), text_rect.y()),
144 SkPoint::Make(text_rect.right(), text_rect.y())
145 };
146 std::vector<SkScalar> positions;
147 std::vector<SkColor> colors;
148
149 if (!left_part.IsEmpty())
150 AddFadeEffect(text_rect, left_part, fade_color, color,
151 &positions, &colors);
152 if (!right_part.IsEmpty())
153 AddFadeEffect(text_rect, right_part, color, fade_color,
154 &positions, &colors);
155 DCHECK(!positions.empty());
156
157 // Terminate |positions| with 1.0, as required by Skia.
158 if (positions.back() != 1.0) {
159 positions.push_back(1.0);
160 colors.push_back(colors.back());
161 }
162
163 return SkGradientShader::CreateLinear(&points[0], &colors[0], &positions[0],
164 colors.size(),
165 SkShader::kClamp_TileMode);
166}
167
168
[email protected]ff44d712011-07-25 08:42:52169} // namespace
170
171namespace gfx {
172
[email protected]67b981562011-12-09 00:35:05173namespace internal {
174
175SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas)
176 : canvas_skia_(canvas->GetSkCanvas()) {
177 DCHECK(canvas_skia_);
178 paint_.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
179 paint_.setStyle(SkPaint::kFill_Style);
180 paint_.setAntiAlias(true);
181 paint_.setSubpixelText(true);
182 paint_.setLCDRenderText(true);
183}
184
185SkiaTextRenderer::~SkiaTextRenderer() {
186}
187
[email protected]cd8e5522011-12-15 00:24:10188void SkiaTextRenderer::SetTypeface(SkTypeface* typeface) {
189 paint_.setTypeface(typeface);
190}
191
192void SkiaTextRenderer::SetTextSize(int size) {
193 paint_.setTextSize(size);
194}
195
[email protected]7aba39c2012-01-10 16:10:27196void SkiaTextRenderer::SetFontStyle(int style) {
197 SkTypeface::Style skia_style = ConvertFontStyleToSkiaTypefaceStyle(style);
198 SkTypeface* current_typeface = paint_.getTypeface();
199
200 if (current_typeface->style() == skia_style)
201 return;
202
[email protected]ab96da42012-01-09 23:05:30203 SkAutoTUnref<SkTypeface> typeface(
[email protected]7aba39c2012-01-10 16:10:27204 SkTypeface::CreateFromTypeface(current_typeface, skia_style));
205 if (typeface.get()) {
206 // |paint_| adds its own ref. So don't |release()| it from the ref ptr here.
207 SetTypeface(typeface.get());
208 }
209}
210
211void SkiaTextRenderer::SetFont(const gfx::Font& font) {
212 SkTypeface::Style skia_style =
213 ConvertFontStyleToSkiaTypefaceStyle(font.GetStyle());
214 SkAutoTUnref<SkTypeface> typeface(
215 SkTypeface::CreateFromName(font.GetFontName().c_str(), skia_style));
[email protected]67b981562011-12-09 00:35:05216 if (typeface.get()) {
217 // |paint_| adds its own ref. So don't |release()| it from the ref ptr here.
[email protected]cd8e5522011-12-15 00:24:10218 SetTypeface(typeface.get());
[email protected]67b981562011-12-09 00:35:05219 }
[email protected]cd8e5522011-12-15 00:24:10220 SetTextSize(font.GetFontSize());
[email protected]67b981562011-12-09 00:35:05221}
222
223void SkiaTextRenderer::SetForegroundColor(SkColor foreground) {
224 paint_.setColor(foreground);
225}
226
[email protected]f308e5b12012-01-17 19:09:18227void SkiaTextRenderer::SetShader(SkShader* shader) {
228 paint_.setShader(shader);
229}
230
[email protected]67b981562011-12-09 00:35:05231void SkiaTextRenderer::DrawPosText(const SkPoint* pos,
232 const uint16* glyphs,
233 size_t glyph_count) {
234 size_t byte_length = glyph_count * sizeof(glyphs[0]);
235 canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_);
236}
237
238// Draw underline and strike through text decorations.
239// Based on |SkCanvas::DrawTextDecorations()| and constants from:
240// third_party/skia/src/core/SkTextFormatParams.h
241void SkiaTextRenderer::DrawDecorations(int x, int y, int width,
242 bool underline, bool strike) {
243 // Fraction of the text size to lower a strike through below the baseline.
244 const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21);
245 // Fraction of the text size to lower an underline below the baseline.
246 const SkScalar kUnderlineOffset = (SK_Scalar1 / 9);
247 // Fraction of the text size to use for a strike through or under-line.
248 const SkScalar kLineThickness = (SK_Scalar1 / 18);
249
250 SkScalar text_size = paint_.getTextSize();
251 SkScalar height = SkScalarMul(text_size, kLineThickness);
252 SkRect r;
253
254 r.fLeft = x;
255 r.fRight = x + width;
256
257 if (underline) {
258 SkScalar offset = SkScalarMulAdd(text_size, kUnderlineOffset, y);
259 r.fTop = offset;
260 r.fBottom = offset + height;
261 canvas_skia_->drawRect(r, paint_);
262 }
263 if (strike) {
264 SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y);
265 r.fTop = offset;
266 r.fBottom = offset + height;
267 canvas_skia_->drawRect(r, paint_);
268 }
269}
270
271} // namespace internal
272
273
[email protected]ff44d712011-07-25 08:42:52274StyleRange::StyleRange()
[email protected]8d901a82012-01-04 19:41:30275 : foreground(SK_ColorBLACK),
[email protected]7aba39c2012-01-10 16:10:27276 font_style(gfx::Font::NORMAL),
[email protected]ff44d712011-07-25 08:42:52277 strike(false),
[email protected]8d901a82012-01-04 19:41:30278 underline(false) {
[email protected]ff44d712011-07-25 08:42:52279}
280
[email protected]8e42ba22011-08-04 21:47:08281RenderText::~RenderText() {
282}
283
[email protected]ff44d712011-07-25 08:42:52284void RenderText::SetText(const string16& text) {
[email protected]d3c6b0602011-09-07 19:26:06285 DCHECK(!composition_range_.IsValid());
[email protected]ff44d712011-07-25 08:42:52286 size_t old_text_length = text_.length();
287 text_ = text;
288
289 // Update the style ranges as needed.
290 if (text_.empty()) {
291 style_ranges_.clear();
292 } else if (style_ranges_.empty()) {
293 ApplyDefaultStyle();
294 } else if (text_.length() > old_text_length) {
295 style_ranges_.back().range.set_end(text_.length());
296 } else if (text_.length() < old_text_length) {
297 StyleRanges::iterator i;
298 for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) {
299 if (i->range.start() >= text_.length()) {
[email protected]052ce6bb2011-10-14 19:48:20300 // Style ranges are sorted and non-overlapping, so all the subsequent
301 // style ranges should be out of text_.length() as well.
302 style_ranges_.erase(i, style_ranges_.end());
303 break;
[email protected]ff44d712011-07-25 08:42:52304 }
305 }
[email protected]052ce6bb2011-10-14 19:48:20306 // Since style ranges are sorted and non-overlapping, if there is a style
307 // range ends beyond text_.length, it must be the last one.
[email protected]ff44d712011-07-25 08:42:52308 style_ranges_.back().range.set_end(text_.length());
309 }
310#ifndef NDEBUG
311 CheckStyleRanges(style_ranges_, text_.length());
312#endif
[email protected]f6aaa0f2011-08-11 07:05:46313 cached_bounds_and_offset_valid_ = false;
[email protected]d3c6b0602011-09-07 19:26:06314
315 // Reset selection model. SetText should always followed by SetSelectionModel
316 // or SetCursorPosition in upper layer.
317 SetSelectionModel(SelectionModel(0, 0, SelectionModel::LEADING));
[email protected]6002a4f32011-11-30 10:18:42318
319 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52320}
321
[email protected]8d901a82012-01-04 19:41:30322void RenderText::SetFontList(const FontList& font_list) {
323 font_list_ = font_list;
324 cached_bounds_and_offset_valid_ = false;
325 UpdateLayout();
326}
327
[email protected]fdf481b2012-01-24 06:14:07328void RenderText::SetFontSize(int size) {
329 font_list_ = font_list_.DeriveFontListWithSize(size);
330 cached_bounds_and_offset_valid_ = false;
331 UpdateLayout();
332}
333
[email protected]8d901a82012-01-04 19:41:30334const Font& RenderText::GetFont() const {
335 return font_list_.GetFonts()[0];
336}
337
[email protected]0d717602011-08-30 06:21:14338void RenderText::ToggleInsertMode() {
339 insert_mode_ = !insert_mode_;
[email protected]f6aaa0f2011-08-11 07:05:46340 cached_bounds_and_offset_valid_ = false;
341}
342
343void RenderText::SetDisplayRect(const Rect& r) {
344 display_rect_ = r;
345 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25346 UpdateLayout();
[email protected]8e42ba22011-08-04 21:47:08347}
348
[email protected]ff44d712011-07-25 08:42:52349size_t RenderText::GetCursorPosition() const {
[email protected]8e42ba22011-08-04 21:47:08350 return selection_model_.selection_end();
[email protected]ff44d712011-07-25 08:42:52351}
352
[email protected]0d717602011-08-30 06:21:14353void RenderText::SetCursorPosition(size_t position) {
354 MoveCursorTo(position, false);
[email protected]ff44d712011-07-25 08:42:52355}
356
[email protected]d66009e2012-01-21 01:27:28357void RenderText::MoveCursor(BreakType break_type,
358 VisualCursorDirection direction,
359 bool select) {
[email protected]ec7f48d2011-08-09 03:48:50360 SelectionModel position(selection_model());
361 position.set_selection_start(GetCursorPosition());
[email protected]ff44d712011-07-25 08:42:52362 // Cancelling a selection moves to the edge of the selection.
[email protected]ec7f48d2011-08-09 03:48:50363 if (break_type != LINE_BREAK && !EmptySelection() && !select) {
[email protected]d3c6b0602011-09-07 19:26:06364 SelectionModel selection_start = GetSelectionModelForSelectionStart();
[email protected]d66009e2012-01-21 01:27:28365 int start_x = GetCursorBounds(selection_start, true).x();
366 int cursor_x = GetCursorBounds(position, true).x();
367 // Use the selection start if it is left (when |direction| is CURSOR_LEFT)
368 // or right (when |direction| is CURSOR_RIGHT) of the selection end.
369 if (direction == CURSOR_RIGHT ? start_x > cursor_x : start_x < cursor_x)
[email protected]8e42ba22011-08-04 21:47:08370 position = selection_start;
[email protected]d66009e2012-01-21 01:27:28371 // For word breaks, use the nearest word boundary in the appropriate
372 // |direction|.
[email protected]ff44d712011-07-25 08:42:52373 if (break_type == WORD_BREAK)
[email protected]d66009e2012-01-21 01:27:28374 position = GetAdjacentSelectionModel(position, break_type, direction);
[email protected]ff44d712011-07-25 08:42:52375 } else {
[email protected]d66009e2012-01-21 01:27:28376 position = GetAdjacentSelectionModel(position, break_type, direction);
[email protected]ff44d712011-07-25 08:42:52377 }
[email protected]ec7f48d2011-08-09 03:48:50378 if (select)
379 position.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08380 MoveCursorTo(position);
[email protected]ff44d712011-07-25 08:42:52381}
382
[email protected]f9a221b2011-12-10 20:25:38383bool RenderText::MoveCursorTo(const SelectionModel& model) {
384 SelectionModel sel(model);
[email protected]0d717602011-08-30 06:21:14385 size_t text_length = text().length();
386 // Enforce valid selection model components.
387 if (sel.selection_start() > text_length)
388 sel.set_selection_start(text_length);
389 if (sel.selection_end() > text_length)
390 sel.set_selection_end(text_length);
391 // The current model only supports caret positions at valid character indices.
392 if (text_length == 0) {
393 sel.set_caret_pos(0);
394 sel.set_caret_placement(SelectionModel::LEADING);
395 } else if (sel.caret_pos() >= text_length) {
[email protected]d66009e2012-01-21 01:27:28396 SelectionModel end_selection =
397 EdgeSelectionModel(GetVisualDirectionOfLogicalEnd());
398 sel.set_caret_pos(end_selection.caret_pos());
399 sel.set_caret_placement(end_selection.caret_placement());
[email protected]0d717602011-08-30 06:21:14400 }
[email protected]53c0b1b2011-09-21 20:32:29401
402 if (!IsCursorablePosition(sel.selection_start()) ||
403 !IsCursorablePosition(sel.selection_end()) ||
404 !IsCursorablePosition(sel.caret_pos()))
405 return false;
406
[email protected]0d717602011-08-30 06:21:14407 bool changed = !sel.Equals(selection_model_);
408 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52409 return changed;
410}
411
[email protected]7b3cb4b22011-08-02 18:36:40412bool RenderText::MoveCursorTo(const Point& point, bool select) {
[email protected]8e42ba22011-08-04 21:47:08413 SelectionModel selection = FindCursorPosition(point);
414 if (select)
415 selection.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08416 return MoveCursorTo(selection);
[email protected]ff44d712011-07-25 08:42:52417}
418
[email protected]67e85512011-10-12 20:03:45419bool RenderText::SelectRange(const ui::Range& range) {
420 size_t text_length = text().length();
421 size_t start = std::min(range.start(), text_length);
422 size_t end = std::min(range.end(), text_length);
423
424 if (!IsCursorablePosition(start) || !IsCursorablePosition(end))
425 return false;
426
427 size_t pos = end;
428 SelectionModel::CaretPlacement placement = SelectionModel::LEADING;
429 if (start < end) {
[email protected]d66009e2012-01-21 01:27:28430 pos = IndexOfAdjacentGrapheme(end, CURSOR_BACKWARD);
[email protected]67e85512011-10-12 20:03:45431 DCHECK_LT(pos, end);
432 placement = SelectionModel::TRAILING;
433 } else if (end == text_length) {
[email protected]d66009e2012-01-21 01:27:28434 SelectionModel end_selection =
435 EdgeSelectionModel(GetVisualDirectionOfLogicalEnd());
436 pos = end_selection.caret_pos();
437 placement = end_selection.caret_placement();
[email protected]67e85512011-10-12 20:03:45438 }
439 SetSelectionModel(SelectionModel(start, end, pos, placement));
440 return true;
441}
442
[email protected]8e42ba22011-08-04 21:47:08443bool RenderText::IsPointInSelection(const Point& point) {
[email protected]0d717602011-08-30 06:21:14444 if (EmptySelection())
445 return false;
[email protected]8e42ba22011-08-04 21:47:08446 // TODO(xji): should this check whether the point is inside the visual
447 // selection bounds? In case of "abcFED", if "ED" is selected, |point| points
448 // to the right half of 'c', is the point in selection?
449 size_t pos = FindCursorPosition(point).selection_end();
450 return (pos >= MinOfSelection() && pos < MaxOfSelection());
[email protected]ff44d712011-07-25 08:42:52451}
452
453void RenderText::ClearSelection() {
[email protected]ec7f48d2011-08-09 03:48:50454 SelectionModel sel(selection_model());
455 sel.set_selection_start(GetCursorPosition());
456 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52457}
458
459void RenderText::SelectAll() {
[email protected]d66009e2012-01-21 01:27:28460 SelectionModel sel = EdgeSelectionModel(CURSOR_RIGHT);
461 sel.set_selection_start(EdgeSelectionModel(CURSOR_LEFT).selection_start());
[email protected]8e42ba22011-08-04 21:47:08462 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52463}
464
465void RenderText::SelectWord() {
[email protected]ff44d712011-07-25 08:42:52466 size_t cursor_position = GetCursorPosition();
[email protected]ff44d712011-07-25 08:42:52467
[email protected]53c0b1b2011-09-21 20:32:29468 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
469 bool success = iter.Init();
470 DCHECK(success);
471 if (!success)
472 return;
473
474 size_t selection_start = cursor_position;
475 for (; selection_start != 0; --selection_start) {
476 if (iter.IsStartOfWord(selection_start) ||
477 iter.IsEndOfWord(selection_start))
[email protected]ff44d712011-07-25 08:42:52478 break;
479 }
480
[email protected]53c0b1b2011-09-21 20:32:29481 if (selection_start == cursor_position)
482 ++cursor_position;
483
484 for (; cursor_position < text().length(); ++cursor_position) {
485 if (iter.IsEndOfWord(cursor_position) ||
486 iter.IsStartOfWord(cursor_position))
[email protected]ff44d712011-07-25 08:42:52487 break;
488 }
489
[email protected]0d717602011-08-30 06:21:14490 MoveCursorTo(selection_start, false);
491 MoveCursorTo(cursor_position, true);
[email protected]ff44d712011-07-25 08:42:52492}
493
494const ui::Range& RenderText::GetCompositionRange() const {
495 return composition_range_;
496}
497
498void RenderText::SetCompositionRange(const ui::Range& composition_range) {
499 CHECK(!composition_range.IsValid() ||
500 ui::Range(0, text_.length()).Contains(composition_range));
501 composition_range_.set_end(composition_range.end());
502 composition_range_.set_start(composition_range.start());
[email protected]79791682011-11-09 19:11:25503 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52504}
505
[email protected]c8636672011-12-29 05:07:57506void RenderText::ApplyStyleRange(const StyleRange& style_range) {
[email protected]ff44d712011-07-25 08:42:52507 const ui::Range& new_range = style_range.range;
508 if (!new_range.IsValid() || new_range.is_empty())
509 return;
510 CHECK(!new_range.is_reversed());
511 CHECK(ui::Range(0, text_.length()).Contains(new_range));
[email protected]8e42ba22011-08-04 21:47:08512 ApplyStyleRangeImpl(&style_ranges_, style_range);
[email protected]ff44d712011-07-25 08:42:52513#ifndef NDEBUG
514 CheckStyleRanges(style_ranges_, text_.length());
515#endif
[email protected]f6aaa0f2011-08-11 07:05:46516 // TODO(xji): only invalidate if font or underline changes.
517 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25518 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52519}
520
521void RenderText::ApplyDefaultStyle() {
522 style_ranges_.clear();
523 StyleRange style = StyleRange(default_style_);
524 style.range.set_end(text_.length());
525 style_ranges_.push_back(style);
[email protected]f6aaa0f2011-08-11 07:05:46526 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25527 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52528}
529
[email protected]d66009e2012-01-21 01:27:28530VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() {
531 return GetTextDirection() == base::i18n::LEFT_TO_RIGHT ?
532 CURSOR_RIGHT : CURSOR_LEFT;
[email protected]ff44d712011-07-25 08:42:52533}
534
[email protected]7b3cb4b22011-08-02 18:36:40535void RenderText::Draw(Canvas* canvas) {
[email protected]592a7ad2011-12-14 05:57:53536 TRACE_EVENT0("gfx", "RenderText::Draw");
537 {
538 TRACE_EVENT0("gfx", "RenderText::EnsureLayout");
539 EnsureLayout();
540 }
[email protected]ff44d712011-07-25 08:42:52541
[email protected]f308e5b12012-01-17 19:09:18542 canvas->Save();
543 canvas->ClipRect(display_rect());
[email protected]1a353f12012-01-18 04:20:56544
545 if (!text().empty())
546 DrawSelection(canvas);
547
548 DrawCursor(canvas);
549
[email protected]6002a4f32011-11-30 10:18:42550 if (!text().empty()) {
[email protected]592a7ad2011-12-14 05:57:53551 TRACE_EVENT0("gfx", "RenderText::Draw draw text");
[email protected]6002a4f32011-11-30 10:18:42552 DrawVisualText(canvas);
[email protected]ff44d712011-07-25 08:42:52553 }
[email protected]f308e5b12012-01-17 19:09:18554 canvas->Restore();
[email protected]ff44d712011-07-25 08:42:52555}
556
[email protected]f6aaa0f2011-08-11 07:05:46557const Rect& RenderText::GetUpdatedCursorBounds() {
558 UpdateCachedBoundsAndOffset();
[email protected]8e42ba22011-08-04 21:47:08559 return cursor_bounds_;
560}
561
[email protected]04b7bf322011-10-03 19:08:46562SelectionModel RenderText::GetSelectionModelForSelectionStart() {
563 size_t selection_start = GetSelectionStart();
564 size_t selection_end = GetCursorPosition();
565 if (selection_start < selection_end)
566 return SelectionModel(selection_start,
567 selection_start,
568 SelectionModel::LEADING);
569 else if (selection_start > selection_end)
[email protected]d66009e2012-01-21 01:27:28570 return SelectionModel(
571 selection_start,
572 IndexOfAdjacentGrapheme(selection_start, CURSOR_BACKWARD),
573 SelectionModel::TRAILING);
[email protected]04b7bf322011-10-03 19:08:46574 return selection_model_;
575}
576
[email protected]8e42ba22011-08-04 21:47:08577RenderText::RenderText()
[email protected]f308e5b12012-01-17 19:09:18578 : cursor_visible_(false),
[email protected]8e42ba22011-08-04 21:47:08579 insert_mode_(true),
[email protected]5abe6302011-12-20 23:44:32580 focused_(false),
[email protected]d3c6b0602011-09-07 19:26:06581 composition_range_(ui::Range::InvalidRange()),
[email protected]f308e5b12012-01-17 19:09:18582 fade_head_(false),
583 fade_tail_(false),
[email protected]f6aaa0f2011-08-11 07:05:46584 cached_bounds_and_offset_valid_(false) {
585}
586
587const Point& RenderText::GetUpdatedDisplayOffset() {
588 UpdateCachedBoundsAndOffset();
589 return display_offset_;
[email protected]8e42ba22011-08-04 21:47:08590}
591
[email protected]d66009e2012-01-21 01:27:28592SelectionModel RenderText::GetAdjacentSelectionModel(
593 const SelectionModel& current,
594 BreakType break_type,
595 VisualCursorDirection direction) {
596 EnsureLayout();
597
598 if (break_type == LINE_BREAK || text().empty())
599 return EdgeSelectionModel(direction);
[email protected]ec7f48d2011-08-09 03:48:50600 if (break_type == CHARACTER_BREAK)
[email protected]d66009e2012-01-21 01:27:28601 return AdjacentCharSelectionModel(current, direction);
602 DCHECK(break_type == WORD_BREAK);
603 return AdjacentWordSelectionModel(current, direction);
[email protected]0d717602011-08-30 06:21:14604}
605
[email protected]6002a4f32011-11-30 10:18:42606void RenderText::SetSelectionModel(const SelectionModel& model) {
607 DCHECK_LE(model.selection_start(), text().length());
608 selection_model_.set_selection_start(model.selection_start());
609 DCHECK_LE(model.selection_end(), text().length());
610 selection_model_.set_selection_end(model.selection_end());
[email protected]f9a221b2011-12-10 20:25:38611 DCHECK_LT(model.caret_pos(), std::max<size_t>(text().length(), 1));
[email protected]6002a4f32011-11-30 10:18:42612 selection_model_.set_caret_pos(model.caret_pos());
613 selection_model_.set_caret_placement(model.caret_placement());
614
615 cached_bounds_and_offset_valid_ = false;
[email protected]0d717602011-08-30 06:21:14616}
617
[email protected]8e42ba22011-08-04 21:47:08618void RenderText::ApplyCompositionAndSelectionStyles(
[email protected]1a353f12012-01-18 04:20:56619 StyleRanges* style_ranges) {
[email protected]8e42ba22011-08-04 21:47:08620 // TODO(msw): This pattern ought to be reconsidered; what about composition
621 // and selection overlaps, retain existing local style features?
622 // Apply a composition style override to a copy of the style ranges.
623 if (composition_range_.IsValid() && !composition_range_.is_empty()) {
624 StyleRange composition_style(default_style_);
625 composition_style.underline = true;
626 composition_style.range.set_start(composition_range_.start());
627 composition_style.range.set_end(composition_range_.end());
628 ApplyStyleRangeImpl(style_ranges, composition_style);
629 }
630 // Apply a selection style override to a copy of the style ranges.
631 if (!EmptySelection()) {
632 StyleRange selection_style(default_style_);
[email protected]721ea922012-01-23 21:13:33633 selection_style.foreground = NativeTheme::instance()->GetSystemColor(
634 NativeTheme::kColorId_TextfieldSelectionColor);
[email protected]8e42ba22011-08-04 21:47:08635 selection_style.range.set_start(MinOfSelection());
636 selection_style.range.set_end(MaxOfSelection());
637 ApplyStyleRangeImpl(style_ranges, selection_style);
638 }
[email protected]1a353f12012-01-18 04:20:56639 // Apply replacement-mode style override to a copy of the style ranges.
640 //
641 // TODO(xji): NEED TO FIX FOR WINDOWS ASAP. Windows call this function (to
642 // apply styles) in ItemizeLogicalText(). In order for the cursor's underline
643 // character to be drawn correctly, we will need to re-layout the text. It's
644 // not practical to do layout on every cursor blink. We need to fix Windows
645 // port to apply styles during drawing phase like Linux port does.
646 // http://crbug.com/110109
647 if (!insert_mode_ && cursor_visible() && focused()) {
648 StyleRange replacement_mode_style(default_style_);
[email protected]721ea922012-01-23 21:13:33649 replacement_mode_style.foreground = NativeTheme::instance()->GetSystemColor(
650 NativeTheme::kColorId_TextfieldSelectionColor);
[email protected]1a353f12012-01-18 04:20:56651 size_t cursor = GetCursorPosition();
652 replacement_mode_style.range.set_start(cursor);
[email protected]d66009e2012-01-21 01:27:28653 replacement_mode_style.range.set_end(
654 IndexOfAdjacentGrapheme(cursor, CURSOR_FORWARD));
[email protected]1a353f12012-01-18 04:20:56655 ApplyStyleRangeImpl(style_ranges, replacement_mode_style);
656 }
[email protected]ff44d712011-07-25 08:42:52657}
658
[email protected]0d717602011-08-30 06:21:14659Point RenderText::ToTextPoint(const Point& point) {
660 Point p(point.Subtract(display_rect().origin()));
661 p = p.Subtract(GetUpdatedDisplayOffset());
662 if (base::i18n::IsRTL())
663 p.Offset(GetStringWidth() - display_rect().width() + 1, 0);
664 return p;
665}
666
667Point RenderText::ToViewPoint(const Point& point) {
668 Point p(point.Add(display_rect().origin()));
669 p = p.Add(GetUpdatedDisplayOffset());
670 if (base::i18n::IsRTL())
671 p.Offset(display_rect().width() - GetStringWidth() - 1, 0);
672 return p;
673}
674
[email protected]67b981562011-12-09 00:35:05675Point RenderText::GetOriginForSkiaDrawing() {
676 Point origin(ToViewPoint(Point()));
677 // TODO(msw): Establish a vertical baseline for strings of mixed font heights.
[email protected]8d901a82012-01-04 19:41:30678 const Font& font = GetFont();
[email protected]5f4fd30e2012-01-12 02:19:33679 int height = font.GetHeight();
[email protected]fdf481b2012-01-24 06:14:07680 DCHECK_LE(height, display_rect().height());
[email protected]67b981562011-12-09 00:35:05681 // Center the text vertically in the display area.
682 origin.Offset(0, (display_rect().height() - height) / 2);
683 // Offset by the font size to account for Skia expecting y to be the bottom.
684 origin.Offset(0, font.GetFontSize());
685 return origin;
686}
687
[email protected]f308e5b12012-01-17 19:09:18688int RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) {
689 if (!fade_head() && !fade_tail())
690 return 0;
691
692 const int text_width = GetStringWidth();
693 const int display_width = display_rect().width();
694
695 // If the text fits as-is, no need to fade.
696 if (text_width <= display_width)
697 return 0;
698
699 int gradient_width = CalculateFadeGradientWidth(GetFont(), display_width);
700 if (gradient_width == 0)
701 return 0;
702
703 bool fade_left = fade_head();
704 bool fade_right = fade_tail();
705 // Under RTL, |fade_right| == |fade_head|.
706 if (GetTextDirection() == base::i18n::RIGHT_TO_LEFT)
707 std::swap(fade_left, fade_right);
708
709 gfx::Rect solid_part = display_rect();
710 gfx::Rect left_part;
711 gfx::Rect right_part;
712 if (fade_left) {
713 left_part = solid_part;
714 left_part.Inset(0, 0, solid_part.width() - gradient_width, 0);
715 solid_part.Inset(gradient_width, 0, 0, 0);
716 }
717 if (fade_right) {
718 right_part = solid_part;
719 right_part.Inset(solid_part.width() - gradient_width, 0, 0, 0);
720 solid_part.Inset(0, 0, gradient_width, 0);
721 }
722
723 // Right-align the text when fading left.
724 int x_offset = 0;
725 gfx::Rect text_rect = display_rect();
726 if (fade_left && !fade_right) {
727 x_offset = display_width - text_width;
728 text_rect.Offset(x_offset, 0);
729 text_rect.set_width(text_width);
730 }
731
732 const SkColor color = default_style().foreground;
733 SkAutoTUnref<SkShader> shader(
734 CreateFadeShader(text_rect, left_part, right_part, color));
735 if (shader.get()) {
736 // |renderer| adds its own ref. So don't |release()| it from the ref ptr.
737 renderer->SetShader(shader.get());
738 }
739
740 return x_offset;
741}
742
[email protected]0d717602011-08-30 06:21:14743void RenderText::MoveCursorTo(size_t position, bool select) {
744 size_t cursor = std::min(position, text().length());
[email protected]d66009e2012-01-21 01:27:28745 size_t caret_pos = IndexOfAdjacentGrapheme(cursor, CURSOR_BACKWARD);
[email protected]0d717602011-08-30 06:21:14746 SelectionModel::CaretPlacement placement = (caret_pos == cursor) ?
747 SelectionModel::LEADING : SelectionModel::TRAILING;
748 size_t selection_start = select ? GetSelectionStart() : cursor;
[email protected]53c0b1b2011-09-21 20:32:29749 if (IsCursorablePosition(cursor)) {
750 SelectionModel sel(selection_start, cursor, caret_pos, placement);
751 SetSelectionModel(sel);
752 }
[email protected]8e42ba22011-08-04 21:47:08753}
754
[email protected]f6aaa0f2011-08-11 07:05:46755void RenderText::UpdateCachedBoundsAndOffset() {
756 if (cached_bounds_and_offset_valid_)
757 return;
758 // First, set the valid flag true to calculate the current cursor bounds using
759 // the stale |display_offset_|. Applying |delta_offset| at the end of this
760 // function will set |cursor_bounds_| and |display_offset_| to correct values.
761 cached_bounds_and_offset_valid_ = true;
762 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_);
[email protected]8e42ba22011-08-04 21:47:08763 // Update |display_offset_| to ensure the current cursor is visible.
764 int display_width = display_rect_.width();
765 int string_width = GetStringWidth();
[email protected]f6aaa0f2011-08-11 07:05:46766 int delta_offset = 0;
[email protected]8e42ba22011-08-04 21:47:08767 if (string_width < display_width) {
[email protected]ae2bc522012-01-18 04:25:32768 // Show all text whenever it fits in the display width.
[email protected]f6aaa0f2011-08-11 07:05:46769 delta_offset = -display_offset_.x();
[email protected]65788132011-09-07 20:20:23770 } else if (cursor_bounds_.right() >= display_rect_.right()) {
[email protected]d3c6b0602011-09-07 19:26:06771 // TODO(xji): when the character overflow is a RTL character, currently, if
772 // we pan cursor at the rightmost position, the entered RTL character is not
773 // displayed. Should pan cursor to show the last logical characters.
774 //
[email protected]8e42ba22011-08-04 21:47:08775 // Pan to show the cursor when it overflows to the right,
[email protected]65788132011-09-07 20:20:23776 delta_offset = display_rect_.right() - cursor_bounds_.right() - 1;
[email protected]f6aaa0f2011-08-11 07:05:46777 } else if (cursor_bounds_.x() < display_rect_.x()) {
[email protected]d3c6b0602011-09-07 19:26:06778 // TODO(xji): have similar problem as above when overflow character is a
779 // LTR character.
780 //
[email protected]8e42ba22011-08-04 21:47:08781 // Pan to show the cursor when it overflows to the left.
[email protected]f6aaa0f2011-08-11 07:05:46782 delta_offset = display_rect_.x() - cursor_bounds_.x();
[email protected]ae2bc522012-01-18 04:25:32783 } else {
784 // Pan to show additional overflow text when the display width increases.
785 int negate_rtl = base::i18n::IsRTL() ? -1 : 1;
786 int offset = negate_rtl * display_offset_.x();
787 if (display_width > (string_width + offset))
788 delta_offset = negate_rtl * (display_width - (string_width + offset) - 1);
[email protected]8e42ba22011-08-04 21:47:08789 }
[email protected]f6aaa0f2011-08-11 07:05:46790 display_offset_.Offset(delta_offset, 0);
791 cursor_bounds_.Offset(delta_offset, 0);
[email protected]ff44d712011-07-25 08:42:52792}
793
[email protected]6002a4f32011-11-30 10:18:42794void RenderText::DrawSelection(Canvas* canvas) {
[email protected]d66009e2012-01-21 01:27:28795 std::vector<Rect> sel = GetSubstringBounds(
796 GetSelectionStart(), GetCursorPosition());
[email protected]721ea922012-01-23 21:13:33797 NativeTheme::ColorId color_id = focused() ?
798 NativeTheme::kColorId_TextfieldSelectionBackgroundFocused :
799 NativeTheme::kColorId_TextfieldSelectionBackgroundUnfocused;
800 SkColor color = NativeTheme::instance()->GetSystemColor(color_id);
[email protected]6002a4f32011-11-30 10:18:42801 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
802 canvas->FillRect(color, *i);
803}
804
805void RenderText::DrawCursor(Canvas* canvas) {
806 // Paint cursor. Replace cursor is drawn as rectangle for now.
807 // TODO(msw): Draw a better cursor with a better indication of association.
[email protected]1a353f12012-01-18 04:20:56808 if (cursor_visible() && focused()) {
809 const Rect& bounds = GetUpdatedCursorBounds();
810 if (bounds.width() != 0)
811 canvas->FillRect(kCursorColor, bounds);
812 else
813 canvas->DrawRect(bounds, kCursorColor);
814 }
[email protected]6002a4f32011-11-30 10:18:42815}
816
[email protected]ff44d712011-07-25 08:42:52817} // namespace gfx