blob: 416e96df7691cad32eb62a576900b9d0b9c2b513 [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,
[email protected]54d32cc2012-01-27 19:52:18242 const StyleRange& style) {
243 if (!style.underline && !style.strike && !style.diagonal_strike)
244 return;
245
[email protected]67b981562011-12-09 00:35:05246 // Fraction of the text size to lower a strike through below the baseline.
247 const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21);
248 // Fraction of the text size to lower an underline below the baseline.
249 const SkScalar kUnderlineOffset = (SK_Scalar1 / 9);
250 // Fraction of the text size to use for a strike through or under-line.
251 const SkScalar kLineThickness = (SK_Scalar1 / 18);
[email protected]54d32cc2012-01-27 19:52:18252 // Fraction of the text size to use for a top margin of a diagonal strike.
253 const SkScalar kDiagonalStrikeThroughMarginOffset = (SK_Scalar1 / 4);
[email protected]67b981562011-12-09 00:35:05254
255 SkScalar text_size = paint_.getTextSize();
256 SkScalar height = SkScalarMul(text_size, kLineThickness);
257 SkRect r;
258
259 r.fLeft = x;
260 r.fRight = x + width;
261
[email protected]54d32cc2012-01-27 19:52:18262 if (style.underline) {
[email protected]67b981562011-12-09 00:35:05263 SkScalar offset = SkScalarMulAdd(text_size, kUnderlineOffset, y);
264 r.fTop = offset;
265 r.fBottom = offset + height;
266 canvas_skia_->drawRect(r, paint_);
267 }
[email protected]54d32cc2012-01-27 19:52:18268 if (style.strike) {
[email protected]67b981562011-12-09 00:35:05269 SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y);
270 r.fTop = offset;
271 r.fBottom = offset + height;
272 canvas_skia_->drawRect(r, paint_);
273 }
[email protected]54d32cc2012-01-27 19:52:18274 if (style.diagonal_strike) {
275 SkScalar offset =
276 SkScalarMul(text_size, kDiagonalStrikeThroughMarginOffset);
277 SkPaint paint(paint_);
278 paint.setAntiAlias(true);
279 paint.setStyle(SkPaint::kFill_Style);
280 paint.setStrokeWidth(height);
281 canvas_skia_->drawLine(
282 SkIntToScalar(x), SkIntToScalar(y) - text_size + offset,
283 SkIntToScalar(x + width), SkIntToScalar(y),
284 paint);
285 }
[email protected]67b981562011-12-09 00:35:05286}
287
288} // namespace internal
289
290
[email protected]ff44d712011-07-25 08:42:52291StyleRange::StyleRange()
[email protected]8d901a82012-01-04 19:41:30292 : foreground(SK_ColorBLACK),
[email protected]7aba39c2012-01-10 16:10:27293 font_style(gfx::Font::NORMAL),
[email protected]ff44d712011-07-25 08:42:52294 strike(false),
[email protected]54d32cc2012-01-27 19:52:18295 diagonal_strike(false),
[email protected]8d901a82012-01-04 19:41:30296 underline(false) {
[email protected]ff44d712011-07-25 08:42:52297}
298
[email protected]8e42ba22011-08-04 21:47:08299RenderText::~RenderText() {
300}
301
[email protected]ff44d712011-07-25 08:42:52302void RenderText::SetText(const string16& text) {
[email protected]d3c6b0602011-09-07 19:26:06303 DCHECK(!composition_range_.IsValid());
[email protected]ff44d712011-07-25 08:42:52304 size_t old_text_length = text_.length();
305 text_ = text;
306
307 // Update the style ranges as needed.
308 if (text_.empty()) {
309 style_ranges_.clear();
310 } else if (style_ranges_.empty()) {
311 ApplyDefaultStyle();
312 } else if (text_.length() > old_text_length) {
313 style_ranges_.back().range.set_end(text_.length());
314 } else if (text_.length() < old_text_length) {
315 StyleRanges::iterator i;
316 for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) {
317 if (i->range.start() >= text_.length()) {
[email protected]052ce6bb2011-10-14 19:48:20318 // Style ranges are sorted and non-overlapping, so all the subsequent
319 // style ranges should be out of text_.length() as well.
320 style_ranges_.erase(i, style_ranges_.end());
321 break;
[email protected]ff44d712011-07-25 08:42:52322 }
323 }
[email protected]052ce6bb2011-10-14 19:48:20324 // Since style ranges are sorted and non-overlapping, if there is a style
325 // range ends beyond text_.length, it must be the last one.
[email protected]ff44d712011-07-25 08:42:52326 style_ranges_.back().range.set_end(text_.length());
327 }
328#ifndef NDEBUG
329 CheckStyleRanges(style_ranges_, text_.length());
330#endif
[email protected]f6aaa0f2011-08-11 07:05:46331 cached_bounds_and_offset_valid_ = false;
[email protected]d3c6b0602011-09-07 19:26:06332
333 // Reset selection model. SetText should always followed by SetSelectionModel
334 // or SetCursorPosition in upper layer.
335 SetSelectionModel(SelectionModel(0, 0, SelectionModel::LEADING));
[email protected]6002a4f32011-11-30 10:18:42336
337 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52338}
339
[email protected]f0ed8a2f2012-01-24 17:45:59340void RenderText::SetHorizontalAlignment(HorizontalAlignment alignment) {
341 if (horizontal_alignment_ != alignment) {
342 horizontal_alignment_ = alignment;
343 display_offset_ = Point();
344 cached_bounds_and_offset_valid_ = false;
345 }
346}
347
[email protected]8d901a82012-01-04 19:41:30348void RenderText::SetFontList(const FontList& font_list) {
349 font_list_ = font_list;
350 cached_bounds_and_offset_valid_ = false;
351 UpdateLayout();
352}
353
[email protected]fdf481b2012-01-24 06:14:07354void RenderText::SetFontSize(int size) {
355 font_list_ = font_list_.DeriveFontListWithSize(size);
356 cached_bounds_and_offset_valid_ = false;
357 UpdateLayout();
358}
359
[email protected]f0ed8a2f2012-01-24 17:45:59360void RenderText::SetCursorEnabled(bool cursor_enabled) {
361 cursor_enabled_ = cursor_enabled;
362 cached_bounds_and_offset_valid_ = false;
363}
364
[email protected]8d901a82012-01-04 19:41:30365const Font& RenderText::GetFont() const {
366 return font_list_.GetFonts()[0];
367}
368
[email protected]0d717602011-08-30 06:21:14369void RenderText::ToggleInsertMode() {
370 insert_mode_ = !insert_mode_;
[email protected]f6aaa0f2011-08-11 07:05:46371 cached_bounds_and_offset_valid_ = false;
372}
373
374void RenderText::SetDisplayRect(const Rect& r) {
375 display_rect_ = r;
376 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25377 UpdateLayout();
[email protected]8e42ba22011-08-04 21:47:08378}
379
[email protected]ff44d712011-07-25 08:42:52380size_t RenderText::GetCursorPosition() const {
[email protected]8e42ba22011-08-04 21:47:08381 return selection_model_.selection_end();
[email protected]ff44d712011-07-25 08:42:52382}
383
[email protected]0d717602011-08-30 06:21:14384void RenderText::SetCursorPosition(size_t position) {
385 MoveCursorTo(position, false);
[email protected]ff44d712011-07-25 08:42:52386}
387
[email protected]d66009e2012-01-21 01:27:28388void RenderText::MoveCursor(BreakType break_type,
389 VisualCursorDirection direction,
390 bool select) {
[email protected]ec7f48d2011-08-09 03:48:50391 SelectionModel position(selection_model());
392 position.set_selection_start(GetCursorPosition());
[email protected]ff44d712011-07-25 08:42:52393 // Cancelling a selection moves to the edge of the selection.
[email protected]ec7f48d2011-08-09 03:48:50394 if (break_type != LINE_BREAK && !EmptySelection() && !select) {
[email protected]d3c6b0602011-09-07 19:26:06395 SelectionModel selection_start = GetSelectionModelForSelectionStart();
[email protected]d66009e2012-01-21 01:27:28396 int start_x = GetCursorBounds(selection_start, true).x();
397 int cursor_x = GetCursorBounds(position, true).x();
398 // Use the selection start if it is left (when |direction| is CURSOR_LEFT)
399 // or right (when |direction| is CURSOR_RIGHT) of the selection end.
400 if (direction == CURSOR_RIGHT ? start_x > cursor_x : start_x < cursor_x)
[email protected]8e42ba22011-08-04 21:47:08401 position = selection_start;
[email protected]d66009e2012-01-21 01:27:28402 // For word breaks, use the nearest word boundary in the appropriate
403 // |direction|.
[email protected]ff44d712011-07-25 08:42:52404 if (break_type == WORD_BREAK)
[email protected]d66009e2012-01-21 01:27:28405 position = GetAdjacentSelectionModel(position, break_type, direction);
[email protected]ff44d712011-07-25 08:42:52406 } else {
[email protected]d66009e2012-01-21 01:27:28407 position = GetAdjacentSelectionModel(position, break_type, direction);
[email protected]ff44d712011-07-25 08:42:52408 }
[email protected]ec7f48d2011-08-09 03:48:50409 if (select)
410 position.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08411 MoveCursorTo(position);
[email protected]ff44d712011-07-25 08:42:52412}
413
[email protected]f9a221b2011-12-10 20:25:38414bool RenderText::MoveCursorTo(const SelectionModel& model) {
415 SelectionModel sel(model);
[email protected]0d717602011-08-30 06:21:14416 size_t text_length = text().length();
417 // Enforce valid selection model components.
418 if (sel.selection_start() > text_length)
419 sel.set_selection_start(text_length);
420 if (sel.selection_end() > text_length)
421 sel.set_selection_end(text_length);
422 // The current model only supports caret positions at valid character indices.
423 if (text_length == 0) {
424 sel.set_caret_pos(0);
425 sel.set_caret_placement(SelectionModel::LEADING);
426 } else if (sel.caret_pos() >= text_length) {
[email protected]d66009e2012-01-21 01:27:28427 SelectionModel end_selection =
428 EdgeSelectionModel(GetVisualDirectionOfLogicalEnd());
429 sel.set_caret_pos(end_selection.caret_pos());
430 sel.set_caret_placement(end_selection.caret_placement());
[email protected]0d717602011-08-30 06:21:14431 }
[email protected]53c0b1b2011-09-21 20:32:29432
433 if (!IsCursorablePosition(sel.selection_start()) ||
434 !IsCursorablePosition(sel.selection_end()) ||
435 !IsCursorablePosition(sel.caret_pos()))
436 return false;
437
[email protected]0d717602011-08-30 06:21:14438 bool changed = !sel.Equals(selection_model_);
439 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52440 return changed;
441}
442
[email protected]7b3cb4b22011-08-02 18:36:40443bool RenderText::MoveCursorTo(const Point& point, bool select) {
[email protected]8e42ba22011-08-04 21:47:08444 SelectionModel selection = FindCursorPosition(point);
445 if (select)
446 selection.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08447 return MoveCursorTo(selection);
[email protected]ff44d712011-07-25 08:42:52448}
449
[email protected]67e85512011-10-12 20:03:45450bool RenderText::SelectRange(const ui::Range& range) {
451 size_t text_length = text().length();
452 size_t start = std::min(range.start(), text_length);
453 size_t end = std::min(range.end(), text_length);
454
455 if (!IsCursorablePosition(start) || !IsCursorablePosition(end))
456 return false;
457
458 size_t pos = end;
459 SelectionModel::CaretPlacement placement = SelectionModel::LEADING;
460 if (start < end) {
[email protected]d66009e2012-01-21 01:27:28461 pos = IndexOfAdjacentGrapheme(end, CURSOR_BACKWARD);
[email protected]67e85512011-10-12 20:03:45462 DCHECK_LT(pos, end);
463 placement = SelectionModel::TRAILING;
464 } else if (end == text_length) {
[email protected]d66009e2012-01-21 01:27:28465 SelectionModel end_selection =
466 EdgeSelectionModel(GetVisualDirectionOfLogicalEnd());
467 pos = end_selection.caret_pos();
468 placement = end_selection.caret_placement();
[email protected]67e85512011-10-12 20:03:45469 }
470 SetSelectionModel(SelectionModel(start, end, pos, placement));
471 return true;
472}
473
[email protected]8e42ba22011-08-04 21:47:08474bool RenderText::IsPointInSelection(const Point& point) {
[email protected]0d717602011-08-30 06:21:14475 if (EmptySelection())
476 return false;
[email protected]8e42ba22011-08-04 21:47:08477 // TODO(xji): should this check whether the point is inside the visual
478 // selection bounds? In case of "abcFED", if "ED" is selected, |point| points
479 // to the right half of 'c', is the point in selection?
480 size_t pos = FindCursorPosition(point).selection_end();
481 return (pos >= MinOfSelection() && pos < MaxOfSelection());
[email protected]ff44d712011-07-25 08:42:52482}
483
484void RenderText::ClearSelection() {
[email protected]ec7f48d2011-08-09 03:48:50485 SelectionModel sel(selection_model());
486 sel.set_selection_start(GetCursorPosition());
487 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52488}
489
490void RenderText::SelectAll() {
[email protected]d66009e2012-01-21 01:27:28491 SelectionModel sel = EdgeSelectionModel(CURSOR_RIGHT);
492 sel.set_selection_start(EdgeSelectionModel(CURSOR_LEFT).selection_start());
[email protected]8e42ba22011-08-04 21:47:08493 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52494}
495
496void RenderText::SelectWord() {
[email protected]ff44d712011-07-25 08:42:52497 size_t cursor_position = GetCursorPosition();
[email protected]ff44d712011-07-25 08:42:52498
[email protected]53c0b1b2011-09-21 20:32:29499 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
500 bool success = iter.Init();
501 DCHECK(success);
502 if (!success)
503 return;
504
505 size_t selection_start = cursor_position;
506 for (; selection_start != 0; --selection_start) {
507 if (iter.IsStartOfWord(selection_start) ||
508 iter.IsEndOfWord(selection_start))
[email protected]ff44d712011-07-25 08:42:52509 break;
510 }
511
[email protected]53c0b1b2011-09-21 20:32:29512 if (selection_start == cursor_position)
513 ++cursor_position;
514
515 for (; cursor_position < text().length(); ++cursor_position) {
516 if (iter.IsEndOfWord(cursor_position) ||
517 iter.IsStartOfWord(cursor_position))
[email protected]ff44d712011-07-25 08:42:52518 break;
519 }
520
[email protected]0d717602011-08-30 06:21:14521 MoveCursorTo(selection_start, false);
522 MoveCursorTo(cursor_position, true);
[email protected]ff44d712011-07-25 08:42:52523}
524
525const ui::Range& RenderText::GetCompositionRange() const {
526 return composition_range_;
527}
528
529void RenderText::SetCompositionRange(const ui::Range& composition_range) {
530 CHECK(!composition_range.IsValid() ||
531 ui::Range(0, text_.length()).Contains(composition_range));
532 composition_range_.set_end(composition_range.end());
533 composition_range_.set_start(composition_range.start());
[email protected]79791682011-11-09 19:11:25534 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52535}
536
[email protected]c8636672011-12-29 05:07:57537void RenderText::ApplyStyleRange(const StyleRange& style_range) {
[email protected]ff44d712011-07-25 08:42:52538 const ui::Range& new_range = style_range.range;
539 if (!new_range.IsValid() || new_range.is_empty())
540 return;
541 CHECK(!new_range.is_reversed());
542 CHECK(ui::Range(0, text_.length()).Contains(new_range));
[email protected]8e42ba22011-08-04 21:47:08543 ApplyStyleRangeImpl(&style_ranges_, style_range);
[email protected]ff44d712011-07-25 08:42:52544#ifndef NDEBUG
545 CheckStyleRanges(style_ranges_, text_.length());
546#endif
[email protected]f6aaa0f2011-08-11 07:05:46547 // TODO(xji): only invalidate if font or underline changes.
548 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25549 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52550}
551
552void RenderText::ApplyDefaultStyle() {
553 style_ranges_.clear();
554 StyleRange style = StyleRange(default_style_);
555 style.range.set_end(text_.length());
556 style_ranges_.push_back(style);
[email protected]f6aaa0f2011-08-11 07:05:46557 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25558 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52559}
560
[email protected]d66009e2012-01-21 01:27:28561VisualCursorDirection RenderText::GetVisualDirectionOfLogicalEnd() {
562 return GetTextDirection() == base::i18n::LEFT_TO_RIGHT ?
563 CURSOR_RIGHT : CURSOR_LEFT;
[email protected]ff44d712011-07-25 08:42:52564}
565
[email protected]7b3cb4b22011-08-02 18:36:40566void RenderText::Draw(Canvas* canvas) {
[email protected]592a7ad2011-12-14 05:57:53567 TRACE_EVENT0("gfx", "RenderText::Draw");
568 {
569 TRACE_EVENT0("gfx", "RenderText::EnsureLayout");
570 EnsureLayout();
571 }
[email protected]ff44d712011-07-25 08:42:52572
[email protected]f308e5b12012-01-17 19:09:18573 canvas->Save();
574 canvas->ClipRect(display_rect());
[email protected]1a353f12012-01-18 04:20:56575
576 if (!text().empty())
577 DrawSelection(canvas);
578
579 DrawCursor(canvas);
580
[email protected]6002a4f32011-11-30 10:18:42581 if (!text().empty()) {
[email protected]592a7ad2011-12-14 05:57:53582 TRACE_EVENT0("gfx", "RenderText::Draw draw text");
[email protected]6002a4f32011-11-30 10:18:42583 DrawVisualText(canvas);
[email protected]ff44d712011-07-25 08:42:52584 }
[email protected]f308e5b12012-01-17 19:09:18585 canvas->Restore();
[email protected]ff44d712011-07-25 08:42:52586}
587
[email protected]f6aaa0f2011-08-11 07:05:46588const Rect& RenderText::GetUpdatedCursorBounds() {
589 UpdateCachedBoundsAndOffset();
[email protected]8e42ba22011-08-04 21:47:08590 return cursor_bounds_;
591}
592
[email protected]04b7bf322011-10-03 19:08:46593SelectionModel RenderText::GetSelectionModelForSelectionStart() {
594 size_t selection_start = GetSelectionStart();
595 size_t selection_end = GetCursorPosition();
596 if (selection_start < selection_end)
597 return SelectionModel(selection_start,
598 selection_start,
599 SelectionModel::LEADING);
600 else if (selection_start > selection_end)
[email protected]d66009e2012-01-21 01:27:28601 return SelectionModel(
602 selection_start,
603 IndexOfAdjacentGrapheme(selection_start, CURSOR_BACKWARD),
604 SelectionModel::TRAILING);
[email protected]04b7bf322011-10-03 19:08:46605 return selection_model_;
606}
607
[email protected]8e42ba22011-08-04 21:47:08608RenderText::RenderText()
[email protected]f0ed8a2f2012-01-24 17:45:59609 : horizontal_alignment_(base::i18n::IsRTL() ? ALIGN_RIGHT : ALIGN_LEFT),
610 cursor_enabled_(true),
611 cursor_visible_(false),
[email protected]8e42ba22011-08-04 21:47:08612 insert_mode_(true),
[email protected]5abe6302011-12-20 23:44:32613 focused_(false),
[email protected]d3c6b0602011-09-07 19:26:06614 composition_range_(ui::Range::InvalidRange()),
[email protected]f308e5b12012-01-17 19:09:18615 fade_head_(false),
616 fade_tail_(false),
[email protected]f6aaa0f2011-08-11 07:05:46617 cached_bounds_and_offset_valid_(false) {
618}
619
620const Point& RenderText::GetUpdatedDisplayOffset() {
621 UpdateCachedBoundsAndOffset();
622 return display_offset_;
[email protected]8e42ba22011-08-04 21:47:08623}
624
[email protected]d66009e2012-01-21 01:27:28625SelectionModel RenderText::GetAdjacentSelectionModel(
626 const SelectionModel& current,
627 BreakType break_type,
628 VisualCursorDirection direction) {
629 EnsureLayout();
630
631 if (break_type == LINE_BREAK || text().empty())
632 return EdgeSelectionModel(direction);
[email protected]ec7f48d2011-08-09 03:48:50633 if (break_type == CHARACTER_BREAK)
[email protected]d66009e2012-01-21 01:27:28634 return AdjacentCharSelectionModel(current, direction);
635 DCHECK(break_type == WORD_BREAK);
636 return AdjacentWordSelectionModel(current, direction);
[email protected]0d717602011-08-30 06:21:14637}
638
[email protected]6002a4f32011-11-30 10:18:42639void RenderText::SetSelectionModel(const SelectionModel& model) {
640 DCHECK_LE(model.selection_start(), text().length());
641 selection_model_.set_selection_start(model.selection_start());
642 DCHECK_LE(model.selection_end(), text().length());
643 selection_model_.set_selection_end(model.selection_end());
[email protected]f9a221b2011-12-10 20:25:38644 DCHECK_LT(model.caret_pos(), std::max<size_t>(text().length(), 1));
[email protected]6002a4f32011-11-30 10:18:42645 selection_model_.set_caret_pos(model.caret_pos());
646 selection_model_.set_caret_placement(model.caret_placement());
647
648 cached_bounds_and_offset_valid_ = false;
[email protected]0d717602011-08-30 06:21:14649}
650
[email protected]8e42ba22011-08-04 21:47:08651void RenderText::ApplyCompositionAndSelectionStyles(
[email protected]1a353f12012-01-18 04:20:56652 StyleRanges* style_ranges) {
[email protected]8e42ba22011-08-04 21:47:08653 // TODO(msw): This pattern ought to be reconsidered; what about composition
654 // and selection overlaps, retain existing local style features?
655 // Apply a composition style override to a copy of the style ranges.
656 if (composition_range_.IsValid() && !composition_range_.is_empty()) {
657 StyleRange composition_style(default_style_);
658 composition_style.underline = true;
659 composition_style.range.set_start(composition_range_.start());
660 composition_style.range.set_end(composition_range_.end());
661 ApplyStyleRangeImpl(style_ranges, composition_style);
662 }
663 // Apply a selection style override to a copy of the style ranges.
664 if (!EmptySelection()) {
665 StyleRange selection_style(default_style_);
[email protected]721ea922012-01-23 21:13:33666 selection_style.foreground = NativeTheme::instance()->GetSystemColor(
667 NativeTheme::kColorId_TextfieldSelectionColor);
[email protected]8e42ba22011-08-04 21:47:08668 selection_style.range.set_start(MinOfSelection());
669 selection_style.range.set_end(MaxOfSelection());
670 ApplyStyleRangeImpl(style_ranges, selection_style);
671 }
[email protected]1a353f12012-01-18 04:20:56672 // Apply replacement-mode style override to a copy of the style ranges.
673 //
674 // TODO(xji): NEED TO FIX FOR WINDOWS ASAP. Windows call this function (to
675 // apply styles) in ItemizeLogicalText(). In order for the cursor's underline
676 // character to be drawn correctly, we will need to re-layout the text. It's
677 // not practical to do layout on every cursor blink. We need to fix Windows
678 // port to apply styles during drawing phase like Linux port does.
679 // http://crbug.com/110109
680 if (!insert_mode_ && cursor_visible() && focused()) {
681 StyleRange replacement_mode_style(default_style_);
[email protected]721ea922012-01-23 21:13:33682 replacement_mode_style.foreground = NativeTheme::instance()->GetSystemColor(
683 NativeTheme::kColorId_TextfieldSelectionColor);
[email protected]1a353f12012-01-18 04:20:56684 size_t cursor = GetCursorPosition();
685 replacement_mode_style.range.set_start(cursor);
[email protected]d66009e2012-01-21 01:27:28686 replacement_mode_style.range.set_end(
687 IndexOfAdjacentGrapheme(cursor, CURSOR_FORWARD));
[email protected]1a353f12012-01-18 04:20:56688 ApplyStyleRangeImpl(style_ranges, replacement_mode_style);
689 }
[email protected]ff44d712011-07-25 08:42:52690}
691
[email protected]f0ed8a2f2012-01-24 17:45:59692Point RenderText::GetTextOrigin() {
693 Point origin = display_rect().origin();
694 origin = origin.Add(GetUpdatedDisplayOffset());
695 origin = origin.Add(GetAlignmentOffset());
696 return origin;
697}
698
[email protected]0d717602011-08-30 06:21:14699Point RenderText::ToTextPoint(const Point& point) {
[email protected]f0ed8a2f2012-01-24 17:45:59700 return point.Subtract(GetTextOrigin());
[email protected]0d717602011-08-30 06:21:14701}
702
703Point RenderText::ToViewPoint(const Point& point) {
[email protected]f0ed8a2f2012-01-24 17:45:59704 return point.Add(GetTextOrigin());
705}
706
707int RenderText::GetContentWidth() {
708 return GetStringWidth() + (cursor_enabled_ ? 1 : 0);
709}
710
711Point RenderText::GetAlignmentOffset() {
712 if (horizontal_alignment() != ALIGN_LEFT) {
713 int x_offset = display_rect().width() - GetContentWidth();
714 if (horizontal_alignment() == ALIGN_CENTER)
715 x_offset /= 2;
716 return Point(x_offset, 0);
717 }
718 return Point();
[email protected]0d717602011-08-30 06:21:14719}
720
[email protected]67b981562011-12-09 00:35:05721Point RenderText::GetOriginForSkiaDrawing() {
[email protected]f0ed8a2f2012-01-24 17:45:59722 Point origin(GetTextOrigin());
[email protected]67b981562011-12-09 00:35:05723 // TODO(msw): Establish a vertical baseline for strings of mixed font heights.
[email protected]8d901a82012-01-04 19:41:30724 const Font& font = GetFont();
[email protected]5f4fd30e2012-01-12 02:19:33725 int height = font.GetHeight();
[email protected]fdf481b2012-01-24 06:14:07726 DCHECK_LE(height, display_rect().height());
[email protected]67b981562011-12-09 00:35:05727 // Center the text vertically in the display area.
728 origin.Offset(0, (display_rect().height() - height) / 2);
729 // Offset by the font size to account for Skia expecting y to be the bottom.
730 origin.Offset(0, font.GetFontSize());
731 return origin;
732}
733
[email protected]f0ed8a2f2012-01-24 17:45:59734void RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) {
[email protected]f308e5b12012-01-17 19:09:18735 if (!fade_head() && !fade_tail())
[email protected]f0ed8a2f2012-01-24 17:45:59736 return;
[email protected]f308e5b12012-01-17 19:09:18737
738 const int text_width = GetStringWidth();
739 const int display_width = display_rect().width();
740
741 // If the text fits as-is, no need to fade.
742 if (text_width <= display_width)
[email protected]f0ed8a2f2012-01-24 17:45:59743 return;
[email protected]f308e5b12012-01-17 19:09:18744
745 int gradient_width = CalculateFadeGradientWidth(GetFont(), display_width);
746 if (gradient_width == 0)
[email protected]f0ed8a2f2012-01-24 17:45:59747 return;
[email protected]f308e5b12012-01-17 19:09:18748
749 bool fade_left = fade_head();
750 bool fade_right = fade_tail();
751 // Under RTL, |fade_right| == |fade_head|.
752 if (GetTextDirection() == base::i18n::RIGHT_TO_LEFT)
753 std::swap(fade_left, fade_right);
754
755 gfx::Rect solid_part = display_rect();
756 gfx::Rect left_part;
757 gfx::Rect right_part;
758 if (fade_left) {
759 left_part = solid_part;
760 left_part.Inset(0, 0, solid_part.width() - gradient_width, 0);
761 solid_part.Inset(gradient_width, 0, 0, 0);
762 }
763 if (fade_right) {
764 right_part = solid_part;
765 right_part.Inset(solid_part.width() - gradient_width, 0, 0, 0);
766 solid_part.Inset(0, 0, gradient_width, 0);
767 }
768
[email protected]f308e5b12012-01-17 19:09:18769 gfx::Rect text_rect = display_rect();
[email protected]f0ed8a2f2012-01-24 17:45:59770 text_rect.Inset(GetAlignmentOffset().x(), 0, 0, 0);
[email protected]f308e5b12012-01-17 19:09:18771
772 const SkColor color = default_style().foreground;
773 SkAutoTUnref<SkShader> shader(
774 CreateFadeShader(text_rect, left_part, right_part, color));
775 if (shader.get()) {
776 // |renderer| adds its own ref. So don't |release()| it from the ref ptr.
777 renderer->SetShader(shader.get());
778 }
[email protected]f308e5b12012-01-17 19:09:18779}
780
[email protected]0d717602011-08-30 06:21:14781void RenderText::MoveCursorTo(size_t position, bool select) {
782 size_t cursor = std::min(position, text().length());
[email protected]d66009e2012-01-21 01:27:28783 size_t caret_pos = IndexOfAdjacentGrapheme(cursor, CURSOR_BACKWARD);
[email protected]0d717602011-08-30 06:21:14784 SelectionModel::CaretPlacement placement = (caret_pos == cursor) ?
785 SelectionModel::LEADING : SelectionModel::TRAILING;
786 size_t selection_start = select ? GetSelectionStart() : cursor;
[email protected]53c0b1b2011-09-21 20:32:29787 if (IsCursorablePosition(cursor)) {
788 SelectionModel sel(selection_start, cursor, caret_pos, placement);
789 SetSelectionModel(sel);
790 }
[email protected]8e42ba22011-08-04 21:47:08791}
792
[email protected]f6aaa0f2011-08-11 07:05:46793void RenderText::UpdateCachedBoundsAndOffset() {
794 if (cached_bounds_and_offset_valid_)
795 return;
[email protected]f0ed8a2f2012-01-24 17:45:59796
[email protected]f6aaa0f2011-08-11 07:05:46797 // First, set the valid flag true to calculate the current cursor bounds using
798 // the stale |display_offset_|. Applying |delta_offset| at the end of this
799 // function will set |cursor_bounds_| and |display_offset_| to correct values.
800 cached_bounds_and_offset_valid_ = true;
801 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_);
[email protected]f0ed8a2f2012-01-24 17:45:59802
[email protected]8e42ba22011-08-04 21:47:08803 // Update |display_offset_| to ensure the current cursor is visible.
[email protected]f0ed8a2f2012-01-24 17:45:59804 const int display_width = display_rect_.width();
805 const int content_width = GetContentWidth();
806
[email protected]f6aaa0f2011-08-11 07:05:46807 int delta_offset = 0;
[email protected]f0ed8a2f2012-01-24 17:45:59808 if (content_width <= display_width || !cursor_enabled()) {
809 // Don't pan if the text fits in the display width or when the cursor is
810 // disabled.
[email protected]f6aaa0f2011-08-11 07:05:46811 delta_offset = -display_offset_.x();
[email protected]65788132011-09-07 20:20:23812 } else if (cursor_bounds_.right() >= display_rect_.right()) {
[email protected]d3c6b0602011-09-07 19:26:06813 // TODO(xji): when the character overflow is a RTL character, currently, if
814 // we pan cursor at the rightmost position, the entered RTL character is not
815 // displayed. Should pan cursor to show the last logical characters.
816 //
[email protected]8e42ba22011-08-04 21:47:08817 // Pan to show the cursor when it overflows to the right,
[email protected]65788132011-09-07 20:20:23818 delta_offset = display_rect_.right() - cursor_bounds_.right() - 1;
[email protected]f6aaa0f2011-08-11 07:05:46819 } else if (cursor_bounds_.x() < display_rect_.x()) {
[email protected]d3c6b0602011-09-07 19:26:06820 // TODO(xji): have similar problem as above when overflow character is a
821 // LTR character.
822 //
[email protected]8e42ba22011-08-04 21:47:08823 // Pan to show the cursor when it overflows to the left.
[email protected]f6aaa0f2011-08-11 07:05:46824 delta_offset = display_rect_.x() - cursor_bounds_.x();
[email protected]f0ed8a2f2012-01-24 17:45:59825 } else if (display_offset_.x() != 0) {
826 // Reduce the pan offset to show additional overflow text when the display
827 // width increases.
828 const int negate_rtl = horizontal_alignment_ == ALIGN_RIGHT ? -1 : 1;
829 const int offset = negate_rtl * display_offset_.x();
830 if (display_width > (content_width + offset))
831 delta_offset = negate_rtl * (display_width - (content_width + offset));
[email protected]8e42ba22011-08-04 21:47:08832 }
[email protected]f0ed8a2f2012-01-24 17:45:59833
[email protected]f6aaa0f2011-08-11 07:05:46834 display_offset_.Offset(delta_offset, 0);
835 cursor_bounds_.Offset(delta_offset, 0);
[email protected]ff44d712011-07-25 08:42:52836}
837
[email protected]6002a4f32011-11-30 10:18:42838void RenderText::DrawSelection(Canvas* canvas) {
[email protected]d66009e2012-01-21 01:27:28839 std::vector<Rect> sel = GetSubstringBounds(
840 GetSelectionStart(), GetCursorPosition());
[email protected]721ea922012-01-23 21:13:33841 NativeTheme::ColorId color_id = focused() ?
842 NativeTheme::kColorId_TextfieldSelectionBackgroundFocused :
843 NativeTheme::kColorId_TextfieldSelectionBackgroundUnfocused;
844 SkColor color = NativeTheme::instance()->GetSystemColor(color_id);
[email protected]6002a4f32011-11-30 10:18:42845 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
846 canvas->FillRect(color, *i);
847}
848
849void RenderText::DrawCursor(Canvas* canvas) {
850 // Paint cursor. Replace cursor is drawn as rectangle for now.
851 // TODO(msw): Draw a better cursor with a better indication of association.
[email protected]f0ed8a2f2012-01-24 17:45:59852 if (cursor_enabled() && cursor_visible() && focused()) {
[email protected]1a353f12012-01-18 04:20:56853 const Rect& bounds = GetUpdatedCursorBounds();
854 if (bounds.width() != 0)
855 canvas->FillRect(kCursorColor, bounds);
856 else
857 canvas->DrawRect(bounds, kCursorColor);
858 }
[email protected]6002a4f32011-11-30 10:18:42859}
860
[email protected]ff44d712011-07-25 08:42:52861} // namespace gfx