blob: 3c1578e1991d2b3ecc58b7aa679fb6906be959c4 [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]8e42ba22011-08-04 21:47:0817#include "unicode/uchar.h"
[email protected]ff44d712011-07-25 08:42:5218
19namespace {
20
21#ifndef NDEBUG
22// Check StyleRanges invariant conditions: sorted and non-overlapping ranges.
23void CheckStyleRanges(const gfx::StyleRanges& style_ranges, size_t length) {
24 if (length == 0) {
25 DCHECK(style_ranges.empty()) << "Style ranges exist for empty text.";
26 return;
27 }
28 for (gfx::StyleRanges::size_type i = 0; i < style_ranges.size() - 1; i++) {
29 const ui::Range& former = style_ranges[i].range;
30 const ui::Range& latter = style_ranges[i + 1].range;
31 DCHECK(!former.is_empty()) << "Empty range at " << i << ":" << former;
32 DCHECK(former.IsValid()) << "Invalid range at " << i << ":" << former;
33 DCHECK(!former.is_reversed()) << "Reversed range at " << i << ":" << former;
34 DCHECK(former.end() == latter.start()) << "Ranges gap/overlap/unsorted." <<
35 "former:" << former << ", latter:" << latter;
36 }
37 const gfx::StyleRange& end_style = *style_ranges.rbegin();
38 DCHECK(!end_style.range.is_empty()) << "Empty range at end.";
39 DCHECK(end_style.range.IsValid()) << "Invalid range at end.";
40 DCHECK(!end_style.range.is_reversed()) << "Reversed range at end.";
41 DCHECK(end_style.range.end() == length) << "Style and text length mismatch.";
42}
43#endif
44
[email protected]8e42ba22011-08-04 21:47:0845void ApplyStyleRangeImpl(gfx::StyleRanges* style_ranges,
[email protected]c8636672011-12-29 05:07:5746 const gfx::StyleRange& style_range) {
[email protected]ff44d712011-07-25 08:42:5247 const ui::Range& new_range = style_range.range;
48 // Follow StyleRanges invariant conditions: sorted and non-overlapping ranges.
49 gfx::StyleRanges::iterator i;
[email protected]8e42ba22011-08-04 21:47:0850 for (i = style_ranges->begin(); i != style_ranges->end();) {
[email protected]ff44d712011-07-25 08:42:5251 if (i->range.end() < new_range.start()) {
52 i++;
53 } else if (i->range.start() == new_range.end()) {
54 break;
55 } else if (new_range.Contains(i->range)) {
[email protected]8e42ba22011-08-04 21:47:0856 i = style_ranges->erase(i);
57 if (i == style_ranges->end())
[email protected]ff44d712011-07-25 08:42:5258 break;
59 } else if (i->range.start() < new_range.start() &&
60 i->range.end() > new_range.end()) {
61 // Split the current style into two styles.
62 gfx::StyleRange split_style = gfx::StyleRange(*i);
63 split_style.range.set_end(new_range.start());
[email protected]8e42ba22011-08-04 21:47:0864 i = style_ranges->insert(i, split_style) + 1;
[email protected]ff44d712011-07-25 08:42:5265 i->range.set_start(new_range.end());
66 break;
67 } else if (i->range.start() < new_range.start()) {
68 i->range.set_end(new_range.start());
69 i++;
70 } else if (i->range.end() > new_range.end()) {
71 i->range.set_start(new_range.end());
72 break;
[email protected]f9a221b2011-12-10 20:25:3873 } else {
[email protected]ff44d712011-07-25 08:42:5274 NOTREACHED();
[email protected]f9a221b2011-12-10 20:25:3875 }
[email protected]ff44d712011-07-25 08:42:5276 }
77 // Add the new range in its sorted location.
[email protected]8e42ba22011-08-04 21:47:0878 style_ranges->insert(i, style_range);
[email protected]ff44d712011-07-25 08:42:5279}
80
[email protected]7aba39c2012-01-10 16:10:2781// Converts |gfx::Font::FontStyle| flags to |SkTypeface::Style| flags.
82SkTypeface::Style ConvertFontStyleToSkiaTypefaceStyle(int font_style) {
83 int skia_style = SkTypeface::kNormal;
84 if (font_style & gfx::Font::BOLD)
85 skia_style |= SkTypeface::kBold;
86 if (font_style & gfx::Font::ITALIC)
87 skia_style |= SkTypeface::kItalic;
88 return static_cast<SkTypeface::Style>(skia_style);
89}
90
[email protected]f308e5b12012-01-17 19:09:1891// Given |font| and |display_width|, returns the width of the fade gradient.
92int CalculateFadeGradientWidth(const gfx::Font& font, int display_width) {
93 // Fade in/out about 2.5 characters of the beginning/end of the string.
94 // The .5 here is helpful if one of the characters is a space.
95 // Use a quarter of the display width if the display width is very short.
96 const int average_character_width = font.GetAverageCharacterWidth();
97 const double gradient_width = std::min(average_character_width * 2.5,
98 display_width / 4.0);
99 DCHECK_GE(gradient_width, 0.0);
100 return static_cast<int>(floor(gradient_width + 0.5));
101}
102
103// Appends to |positions| and |colors| values corresponding to the fade over
104// |fade_rect| from color |c0| to color |c1|.
105void AddFadeEffect(const gfx::Rect& text_rect,
106 const gfx::Rect& fade_rect,
107 SkColor c0,
108 SkColor c1,
109 std::vector<SkScalar>* positions,
110 std::vector<SkColor>* colors) {
111 const SkScalar left = static_cast<SkScalar>(fade_rect.x() - text_rect.x());
112 const SkScalar width = static_cast<SkScalar>(fade_rect.width());
113 const SkScalar p0 = left / text_rect.width();
114 const SkScalar p1 = (left + width) / text_rect.width();
115 // Prepend 0.0 to |positions|, as required by Skia.
116 if (positions->empty() && p0 != 0.0) {
117 positions->push_back(0.0);
118 colors->push_back(c0);
119 }
120 positions->push_back(p0);
121 colors->push_back(c0);
122 positions->push_back(p1);
123 colors->push_back(c1);
124}
125
126// Creates a SkShader to fade the text, with |left_part| specifying the left
127// fade effect, if any, and |right_part| specifying the right fade effect.
128SkShader* CreateFadeShader(const gfx::Rect& text_rect,
129 const gfx::Rect& left_part,
130 const gfx::Rect& right_part,
131 SkColor color) {
132 // Fade alpha of 51/255 corresponds to a fade of 0.2 of the original color.
133 const SkColor fade_color = SkColorSetA(color, 51);
134 const SkPoint points[2] = {
135 SkPoint::Make(text_rect.x(), text_rect.y()),
136 SkPoint::Make(text_rect.right(), text_rect.y())
137 };
138 std::vector<SkScalar> positions;
139 std::vector<SkColor> colors;
140
141 if (!left_part.IsEmpty())
142 AddFadeEffect(text_rect, left_part, fade_color, color,
143 &positions, &colors);
144 if (!right_part.IsEmpty())
145 AddFadeEffect(text_rect, right_part, color, fade_color,
146 &positions, &colors);
147 DCHECK(!positions.empty());
148
149 // Terminate |positions| with 1.0, as required by Skia.
150 if (positions.back() != 1.0) {
151 positions.push_back(1.0);
152 colors.push_back(colors.back());
153 }
154
155 return SkGradientShader::CreateLinear(&points[0], &colors[0], &positions[0],
156 colors.size(),
157 SkShader::kClamp_TileMode);
158}
159
160
[email protected]ff44d712011-07-25 08:42:52161} // namespace
162
163namespace gfx {
164
[email protected]67b981562011-12-09 00:35:05165namespace internal {
166
167SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas)
168 : canvas_skia_(canvas->GetSkCanvas()) {
169 DCHECK(canvas_skia_);
170 paint_.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
171 paint_.setStyle(SkPaint::kFill_Style);
172 paint_.setAntiAlias(true);
173 paint_.setSubpixelText(true);
174 paint_.setLCDRenderText(true);
175}
176
177SkiaTextRenderer::~SkiaTextRenderer() {
178}
179
[email protected]cd8e5522011-12-15 00:24:10180void SkiaTextRenderer::SetTypeface(SkTypeface* typeface) {
181 paint_.setTypeface(typeface);
182}
183
184void SkiaTextRenderer::SetTextSize(int size) {
185 paint_.setTextSize(size);
186}
187
[email protected]7aba39c2012-01-10 16:10:27188void SkiaTextRenderer::SetFontStyle(int style) {
189 SkTypeface::Style skia_style = ConvertFontStyleToSkiaTypefaceStyle(style);
190 SkTypeface* current_typeface = paint_.getTypeface();
191
192 if (current_typeface->style() == skia_style)
193 return;
194
[email protected]ab96da42012-01-09 23:05:30195 SkAutoTUnref<SkTypeface> typeface(
[email protected]7aba39c2012-01-10 16:10:27196 SkTypeface::CreateFromTypeface(current_typeface, skia_style));
197 if (typeface.get()) {
198 // |paint_| adds its own ref. So don't |release()| it from the ref ptr here.
199 SetTypeface(typeface.get());
200 }
201}
202
203void SkiaTextRenderer::SetFont(const gfx::Font& font) {
204 SkTypeface::Style skia_style =
205 ConvertFontStyleToSkiaTypefaceStyle(font.GetStyle());
206 SkAutoTUnref<SkTypeface> typeface(
207 SkTypeface::CreateFromName(font.GetFontName().c_str(), skia_style));
[email protected]67b981562011-12-09 00:35:05208 if (typeface.get()) {
209 // |paint_| adds its own ref. So don't |release()| it from the ref ptr here.
[email protected]cd8e5522011-12-15 00:24:10210 SetTypeface(typeface.get());
[email protected]67b981562011-12-09 00:35:05211 }
[email protected]cd8e5522011-12-15 00:24:10212 SetTextSize(font.GetFontSize());
[email protected]67b981562011-12-09 00:35:05213}
214
215void SkiaTextRenderer::SetForegroundColor(SkColor foreground) {
216 paint_.setColor(foreground);
217}
218
[email protected]f308e5b12012-01-17 19:09:18219void SkiaTextRenderer::SetShader(SkShader* shader) {
220 paint_.setShader(shader);
221}
222
[email protected]67b981562011-12-09 00:35:05223void SkiaTextRenderer::DrawPosText(const SkPoint* pos,
224 const uint16* glyphs,
225 size_t glyph_count) {
226 size_t byte_length = glyph_count * sizeof(glyphs[0]);
227 canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_);
228}
229
230// Draw underline and strike through text decorations.
231// Based on |SkCanvas::DrawTextDecorations()| and constants from:
232// third_party/skia/src/core/SkTextFormatParams.h
233void SkiaTextRenderer::DrawDecorations(int x, int y, int width,
234 bool underline, bool strike) {
235 // Fraction of the text size to lower a strike through below the baseline.
236 const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21);
237 // Fraction of the text size to lower an underline below the baseline.
238 const SkScalar kUnderlineOffset = (SK_Scalar1 / 9);
239 // Fraction of the text size to use for a strike through or under-line.
240 const SkScalar kLineThickness = (SK_Scalar1 / 18);
241
242 SkScalar text_size = paint_.getTextSize();
243 SkScalar height = SkScalarMul(text_size, kLineThickness);
244 SkRect r;
245
246 r.fLeft = x;
247 r.fRight = x + width;
248
249 if (underline) {
250 SkScalar offset = SkScalarMulAdd(text_size, kUnderlineOffset, y);
251 r.fTop = offset;
252 r.fBottom = offset + height;
253 canvas_skia_->drawRect(r, paint_);
254 }
255 if (strike) {
256 SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y);
257 r.fTop = offset;
258 r.fBottom = offset + height;
259 canvas_skia_->drawRect(r, paint_);
260 }
261}
262
263} // namespace internal
264
265
[email protected]ff44d712011-07-25 08:42:52266StyleRange::StyleRange()
[email protected]8d901a82012-01-04 19:41:30267 : foreground(SK_ColorBLACK),
[email protected]7aba39c2012-01-10 16:10:27268 font_style(gfx::Font::NORMAL),
[email protected]ff44d712011-07-25 08:42:52269 strike(false),
[email protected]8d901a82012-01-04 19:41:30270 underline(false) {
[email protected]ff44d712011-07-25 08:42:52271}
272
[email protected]8e42ba22011-08-04 21:47:08273RenderText::~RenderText() {
274}
275
[email protected]ff44d712011-07-25 08:42:52276void RenderText::SetText(const string16& text) {
[email protected]d3c6b0602011-09-07 19:26:06277 DCHECK(!composition_range_.IsValid());
[email protected]ff44d712011-07-25 08:42:52278 size_t old_text_length = text_.length();
279 text_ = text;
280
281 // Update the style ranges as needed.
282 if (text_.empty()) {
283 style_ranges_.clear();
284 } else if (style_ranges_.empty()) {
285 ApplyDefaultStyle();
286 } else if (text_.length() > old_text_length) {
287 style_ranges_.back().range.set_end(text_.length());
288 } else if (text_.length() < old_text_length) {
289 StyleRanges::iterator i;
290 for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) {
291 if (i->range.start() >= text_.length()) {
[email protected]052ce6bb2011-10-14 19:48:20292 // Style ranges are sorted and non-overlapping, so all the subsequent
293 // style ranges should be out of text_.length() as well.
294 style_ranges_.erase(i, style_ranges_.end());
295 break;
[email protected]ff44d712011-07-25 08:42:52296 }
297 }
[email protected]052ce6bb2011-10-14 19:48:20298 // Since style ranges are sorted and non-overlapping, if there is a style
299 // range ends beyond text_.length, it must be the last one.
[email protected]ff44d712011-07-25 08:42:52300 style_ranges_.back().range.set_end(text_.length());
301 }
302#ifndef NDEBUG
303 CheckStyleRanges(style_ranges_, text_.length());
304#endif
[email protected]f6aaa0f2011-08-11 07:05:46305 cached_bounds_and_offset_valid_ = false;
[email protected]d3c6b0602011-09-07 19:26:06306
307 // Reset selection model. SetText should always followed by SetSelectionModel
308 // or SetCursorPosition in upper layer.
309 SetSelectionModel(SelectionModel(0, 0, SelectionModel::LEADING));
[email protected]6002a4f32011-11-30 10:18:42310
311 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52312}
313
[email protected]8d901a82012-01-04 19:41:30314void RenderText::SetFontList(const FontList& font_list) {
315 font_list_ = font_list;
316 cached_bounds_and_offset_valid_ = false;
317 UpdateLayout();
318}
319
320const Font& RenderText::GetFont() const {
321 return font_list_.GetFonts()[0];
322}
323
[email protected]0d717602011-08-30 06:21:14324void RenderText::ToggleInsertMode() {
325 insert_mode_ = !insert_mode_;
[email protected]f6aaa0f2011-08-11 07:05:46326 cached_bounds_and_offset_valid_ = false;
327}
328
329void RenderText::SetDisplayRect(const Rect& r) {
330 display_rect_ = r;
331 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25332 UpdateLayout();
[email protected]8e42ba22011-08-04 21:47:08333}
334
[email protected]ff44d712011-07-25 08:42:52335size_t RenderText::GetCursorPosition() const {
[email protected]8e42ba22011-08-04 21:47:08336 return selection_model_.selection_end();
[email protected]ff44d712011-07-25 08:42:52337}
338
[email protected]0d717602011-08-30 06:21:14339void RenderText::SetCursorPosition(size_t position) {
340 MoveCursorTo(position, false);
[email protected]ff44d712011-07-25 08:42:52341}
342
343void RenderText::MoveCursorLeft(BreakType break_type, bool select) {
[email protected]ec7f48d2011-08-09 03:48:50344 SelectionModel position(selection_model());
345 position.set_selection_start(GetCursorPosition());
[email protected]ff44d712011-07-25 08:42:52346 // Cancelling a selection moves to the edge of the selection.
[email protected]ec7f48d2011-08-09 03:48:50347 if (break_type != LINE_BREAK && !EmptySelection() && !select) {
[email protected]ff44d712011-07-25 08:42:52348 // Use the selection start if it is left of the selection end.
[email protected]d3c6b0602011-09-07 19:26:06349 SelectionModel selection_start = GetSelectionModelForSelectionStart();
350 if (GetCursorBounds(selection_start, true).x() <
351 GetCursorBounds(position, true).x())
[email protected]8e42ba22011-08-04 21:47:08352 position = selection_start;
[email protected]ec7f48d2011-08-09 03:48:50353 // For word breaks, use the nearest word boundary left of the selection.
[email protected]ff44d712011-07-25 08:42:52354 if (break_type == WORD_BREAK)
[email protected]ec7f48d2011-08-09 03:48:50355 position = GetLeftSelectionModel(position, break_type);
[email protected]ff44d712011-07-25 08:42:52356 } else {
[email protected]ec7f48d2011-08-09 03:48:50357 position = GetLeftSelectionModel(position, break_type);
[email protected]ff44d712011-07-25 08:42:52358 }
[email protected]ec7f48d2011-08-09 03:48:50359 if (select)
360 position.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08361 MoveCursorTo(position);
[email protected]ff44d712011-07-25 08:42:52362}
363
364void RenderText::MoveCursorRight(BreakType break_type, bool select) {
[email protected]ec7f48d2011-08-09 03:48:50365 SelectionModel position(selection_model());
366 position.set_selection_start(GetCursorPosition());
[email protected]ff44d712011-07-25 08:42:52367 // Cancelling a selection moves to the edge of the selection.
[email protected]ec7f48d2011-08-09 03:48:50368 if (break_type != LINE_BREAK && !EmptySelection() && !select) {
[email protected]ff44d712011-07-25 08:42:52369 // Use the selection start if it is right of the selection end.
[email protected]d3c6b0602011-09-07 19:26:06370 SelectionModel selection_start = GetSelectionModelForSelectionStart();
371 if (GetCursorBounds(selection_start, true).x() >
372 GetCursorBounds(position, true).x())
[email protected]8e42ba22011-08-04 21:47:08373 position = selection_start;
[email protected]ec7f48d2011-08-09 03:48:50374 // For word breaks, use the nearest word boundary right of the selection.
[email protected]ff44d712011-07-25 08:42:52375 if (break_type == WORD_BREAK)
[email protected]ec7f48d2011-08-09 03:48:50376 position = GetRightSelectionModel(position, break_type);
[email protected]ff44d712011-07-25 08:42:52377 } else {
[email protected]ec7f48d2011-08-09 03:48:50378 position = GetRightSelectionModel(position, break_type);
[email protected]ff44d712011-07-25 08:42:52379 }
[email protected]ec7f48d2011-08-09 03:48:50380 if (select)
381 position.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08382 MoveCursorTo(position);
[email protected]ff44d712011-07-25 08:42:52383}
384
[email protected]f9a221b2011-12-10 20:25:38385bool RenderText::MoveCursorTo(const SelectionModel& model) {
386 SelectionModel sel(model);
[email protected]0d717602011-08-30 06:21:14387 size_t text_length = text().length();
388 // Enforce valid selection model components.
389 if (sel.selection_start() > text_length)
390 sel.set_selection_start(text_length);
391 if (sel.selection_end() > text_length)
392 sel.set_selection_end(text_length);
393 // The current model only supports caret positions at valid character indices.
394 if (text_length == 0) {
395 sel.set_caret_pos(0);
396 sel.set_caret_placement(SelectionModel::LEADING);
397 } else if (sel.caret_pos() >= text_length) {
398 SelectionModel end = GetTextDirection() == base::i18n::RIGHT_TO_LEFT ?
399 LeftEndSelectionModel() : RightEndSelectionModel();
400 sel.set_caret_pos(end.caret_pos());
401 sel.set_caret_placement(end.caret_placement());
402 }
[email protected]53c0b1b2011-09-21 20:32:29403
404 if (!IsCursorablePosition(sel.selection_start()) ||
405 !IsCursorablePosition(sel.selection_end()) ||
406 !IsCursorablePosition(sel.caret_pos()))
407 return false;
408
[email protected]0d717602011-08-30 06:21:14409 bool changed = !sel.Equals(selection_model_);
410 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52411 return changed;
412}
413
[email protected]7b3cb4b22011-08-02 18:36:40414bool RenderText::MoveCursorTo(const Point& point, bool select) {
[email protected]8e42ba22011-08-04 21:47:08415 SelectionModel selection = FindCursorPosition(point);
416 if (select)
417 selection.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08418 return MoveCursorTo(selection);
[email protected]ff44d712011-07-25 08:42:52419}
420
[email protected]67e85512011-10-12 20:03:45421bool RenderText::SelectRange(const ui::Range& range) {
422 size_t text_length = text().length();
423 size_t start = std::min(range.start(), text_length);
424 size_t end = std::min(range.end(), text_length);
425
426 if (!IsCursorablePosition(start) || !IsCursorablePosition(end))
427 return false;
428
429 size_t pos = end;
430 SelectionModel::CaretPlacement placement = SelectionModel::LEADING;
431 if (start < end) {
432 pos = GetIndexOfPreviousGrapheme(end);
433 DCHECK_LT(pos, end);
434 placement = SelectionModel::TRAILING;
435 } else if (end == text_length) {
436 SelectionModel boundary = GetTextDirection() == base::i18n::RIGHT_TO_LEFT ?
437 LeftEndSelectionModel() : RightEndSelectionModel();
438 pos = boundary.caret_pos();
439 placement = boundary.caret_placement();
440 }
441 SetSelectionModel(SelectionModel(start, end, pos, placement));
442 return true;
443}
444
[email protected]8e42ba22011-08-04 21:47:08445bool RenderText::IsPointInSelection(const Point& point) {
[email protected]0d717602011-08-30 06:21:14446 if (EmptySelection())
447 return false;
[email protected]8e42ba22011-08-04 21:47:08448 // TODO(xji): should this check whether the point is inside the visual
449 // selection bounds? In case of "abcFED", if "ED" is selected, |point| points
450 // to the right half of 'c', is the point in selection?
451 size_t pos = FindCursorPosition(point).selection_end();
452 return (pos >= MinOfSelection() && pos < MaxOfSelection());
[email protected]ff44d712011-07-25 08:42:52453}
454
455void RenderText::ClearSelection() {
[email protected]ec7f48d2011-08-09 03:48:50456 SelectionModel sel(selection_model());
457 sel.set_selection_start(GetCursorPosition());
458 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52459}
460
461void RenderText::SelectAll() {
[email protected]0d717602011-08-30 06:21:14462 SelectionModel sel(RightEndSelectionModel());
463 sel.set_selection_start(LeftEndSelectionModel().selection_start());
[email protected]8e42ba22011-08-04 21:47:08464 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52465}
466
467void RenderText::SelectWord() {
[email protected]ff44d712011-07-25 08:42:52468 size_t cursor_position = GetCursorPosition();
[email protected]ff44d712011-07-25 08:42:52469
[email protected]53c0b1b2011-09-21 20:32:29470 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
471 bool success = iter.Init();
472 DCHECK(success);
473 if (!success)
474 return;
475
476 size_t selection_start = cursor_position;
477 for (; selection_start != 0; --selection_start) {
478 if (iter.IsStartOfWord(selection_start) ||
479 iter.IsEndOfWord(selection_start))
[email protected]ff44d712011-07-25 08:42:52480 break;
481 }
482
[email protected]53c0b1b2011-09-21 20:32:29483 if (selection_start == cursor_position)
484 ++cursor_position;
485
486 for (; cursor_position < text().length(); ++cursor_position) {
487 if (iter.IsEndOfWord(cursor_position) ||
488 iter.IsStartOfWord(cursor_position))
[email protected]ff44d712011-07-25 08:42:52489 break;
490 }
491
[email protected]0d717602011-08-30 06:21:14492 MoveCursorTo(selection_start, false);
493 MoveCursorTo(cursor_position, true);
[email protected]ff44d712011-07-25 08:42:52494}
495
496const ui::Range& RenderText::GetCompositionRange() const {
497 return composition_range_;
498}
499
500void RenderText::SetCompositionRange(const ui::Range& composition_range) {
501 CHECK(!composition_range.IsValid() ||
502 ui::Range(0, text_.length()).Contains(composition_range));
503 composition_range_.set_end(composition_range.end());
504 composition_range_.set_start(composition_range.start());
[email protected]79791682011-11-09 19:11:25505 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52506}
507
[email protected]c8636672011-12-29 05:07:57508void RenderText::ApplyStyleRange(const StyleRange& style_range) {
[email protected]ff44d712011-07-25 08:42:52509 const ui::Range& new_range = style_range.range;
510 if (!new_range.IsValid() || new_range.is_empty())
511 return;
512 CHECK(!new_range.is_reversed());
513 CHECK(ui::Range(0, text_.length()).Contains(new_range));
[email protected]8e42ba22011-08-04 21:47:08514 ApplyStyleRangeImpl(&style_ranges_, style_range);
[email protected]ff44d712011-07-25 08:42:52515#ifndef NDEBUG
516 CheckStyleRanges(style_ranges_, text_.length());
517#endif
[email protected]f6aaa0f2011-08-11 07:05:46518 // TODO(xji): only invalidate if font or underline changes.
519 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25520 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52521}
522
523void RenderText::ApplyDefaultStyle() {
524 style_ranges_.clear();
525 StyleRange style = StyleRange(default_style_);
526 style.range.set_end(text_.length());
527 style_ranges_.push_back(style);
[email protected]f6aaa0f2011-08-11 07:05:46528 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25529 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52530}
531
[email protected]d3c6b0602011-09-07 19:26:06532base::i18n::TextDirection RenderText::GetTextDirection() {
[email protected]0d717602011-08-30 06:21:14533 if (base::i18n::IsRTL())
534 return base::i18n::RIGHT_TO_LEFT;
[email protected]ff44d712011-07-25 08:42:52535 return base::i18n::LEFT_TO_RIGHT;
536}
537
[email protected]7b3cb4b22011-08-02 18:36:40538void RenderText::Draw(Canvas* canvas) {
[email protected]592a7ad2011-12-14 05:57:53539 TRACE_EVENT0("gfx", "RenderText::Draw");
540 {
541 TRACE_EVENT0("gfx", "RenderText::EnsureLayout");
542 EnsureLayout();
543 }
[email protected]ff44d712011-07-25 08:42:52544
[email protected]f308e5b12012-01-17 19:09:18545 canvas->Save();
546 canvas->ClipRect(display_rect());
[email protected]1a353f12012-01-18 04:20:56547
548 if (!text().empty())
549 DrawSelection(canvas);
550
551 DrawCursor(canvas);
552
[email protected]6002a4f32011-11-30 10:18:42553 if (!text().empty()) {
[email protected]592a7ad2011-12-14 05:57:53554 TRACE_EVENT0("gfx", "RenderText::Draw draw text");
[email protected]6002a4f32011-11-30 10:18:42555 DrawVisualText(canvas);
[email protected]ff44d712011-07-25 08:42:52556 }
[email protected]f308e5b12012-01-17 19:09:18557 canvas->Restore();
[email protected]ff44d712011-07-25 08:42:52558}
559
[email protected]f6aaa0f2011-08-11 07:05:46560const Rect& RenderText::GetUpdatedCursorBounds() {
561 UpdateCachedBoundsAndOffset();
[email protected]8e42ba22011-08-04 21:47:08562 return cursor_bounds_;
563}
564
[email protected]53c0b1b2011-09-21 20:32:29565size_t RenderText::GetIndexOfNextGrapheme(size_t position) {
566 return IndexOfAdjacentGrapheme(position, true);
567}
568
[email protected]04b7bf322011-10-03 19:08:46569SelectionModel RenderText::GetSelectionModelForSelectionStart() {
570 size_t selection_start = GetSelectionStart();
571 size_t selection_end = GetCursorPosition();
572 if (selection_start < selection_end)
573 return SelectionModel(selection_start,
574 selection_start,
575 SelectionModel::LEADING);
576 else if (selection_start > selection_end)
577 return SelectionModel(selection_start,
578 GetIndexOfPreviousGrapheme(selection_start),
579 SelectionModel::TRAILING);
580 return selection_model_;
581}
582
[email protected]8e42ba22011-08-04 21:47:08583RenderText::RenderText()
[email protected]f308e5b12012-01-17 19:09:18584 : cursor_visible_(false),
[email protected]8e42ba22011-08-04 21:47:08585 insert_mode_(true),
[email protected]5abe6302011-12-20 23:44:32586 focused_(false),
[email protected]d3c6b0602011-09-07 19:26:06587 composition_range_(ui::Range::InvalidRange()),
[email protected]f308e5b12012-01-17 19:09:18588 fade_head_(false),
589 fade_tail_(false),
[email protected]f6aaa0f2011-08-11 07:05:46590 cached_bounds_and_offset_valid_(false) {
591}
592
593const Point& RenderText::GetUpdatedDisplayOffset() {
594 UpdateCachedBoundsAndOffset();
595 return display_offset_;
[email protected]8e42ba22011-08-04 21:47:08596}
597
[email protected]ec7f48d2011-08-09 03:48:50598SelectionModel RenderText::GetLeftSelectionModel(const SelectionModel& current,
599 BreakType break_type) {
600 if (break_type == LINE_BREAK)
[email protected]67e85512011-10-12 20:03:45601 return LeftEndSelectionModel();
[email protected]f9a221b2011-12-10 20:25:38602 size_t pos = std::max<int>(current.selection_end() - 1, 0);
[email protected]ec7f48d2011-08-09 03:48:50603 if (break_type == CHARACTER_BREAK)
604 return SelectionModel(pos, pos, SelectionModel::LEADING);
605
[email protected]d3c6b0602011-09-07 19:26:06606 // Notes: We always iterate words from the beginning.
[email protected]ff44d712011-07-25 08:42:52607 // This is probably fast enough for our usage, but we may
608 // want to modify WordIterator so that it can start from the
609 // middle of string and advance backwards.
610 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
611 bool success = iter.Init();
612 DCHECK(success);
[email protected]ec7f48d2011-08-09 03:48:50613 if (!success)
614 return current;
[email protected]ff44d712011-07-25 08:42:52615 while (iter.Advance()) {
616 if (iter.IsWord()) {
617 size_t begin = iter.pos() - iter.GetString().length();
[email protected]ec7f48d2011-08-09 03:48:50618 if (begin == current.selection_end()) {
[email protected]ff44d712011-07-25 08:42:52619 // The cursor is at the beginning of a word.
620 // Move to previous word.
621 break;
[email protected]ec7f48d2011-08-09 03:48:50622 } else if (iter.pos() >= current.selection_end()) {
[email protected]ff44d712011-07-25 08:42:52623 // The cursor is in the middle or at the end of a word.
624 // Move to the top of current word.
[email protected]ec7f48d2011-08-09 03:48:50625 pos = begin;
[email protected]ff44d712011-07-25 08:42:52626 break;
627 } else {
[email protected]ec7f48d2011-08-09 03:48:50628 pos = iter.pos() - iter.GetString().length();
[email protected]ff44d712011-07-25 08:42:52629 }
630 }
631 }
632
[email protected]ec7f48d2011-08-09 03:48:50633 return SelectionModel(pos, pos, SelectionModel::LEADING);
[email protected]ff44d712011-07-25 08:42:52634}
635
[email protected]ec7f48d2011-08-09 03:48:50636SelectionModel RenderText::GetRightSelectionModel(const SelectionModel& current,
637 BreakType break_type) {
[email protected]d3c6b0602011-09-07 19:26:06638 if (text_.empty())
639 return SelectionModel(0, 0, SelectionModel::LEADING);
[email protected]ec7f48d2011-08-09 03:48:50640 if (break_type == LINE_BREAK)
[email protected]67e85512011-10-12 20:03:45641 return RightEndSelectionModel();
[email protected]ec7f48d2011-08-09 03:48:50642 size_t pos = std::min(current.selection_end() + 1, text().length());
643 if (break_type == CHARACTER_BREAK)
644 return SelectionModel(pos, pos, SelectionModel::LEADING);
[email protected]8e42ba22011-08-04 21:47:08645
[email protected]ff44d712011-07-25 08:42:52646 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
647 bool success = iter.Init();
648 DCHECK(success);
[email protected]ec7f48d2011-08-09 03:48:50649 if (!success)
650 return current;
[email protected]ff44d712011-07-25 08:42:52651 while (iter.Advance()) {
652 pos = iter.pos();
[email protected]ec7f48d2011-08-09 03:48:50653 if (iter.IsWord() && pos > current.selection_end())
[email protected]ff44d712011-07-25 08:42:52654 break;
[email protected]ff44d712011-07-25 08:42:52655 }
[email protected]ec7f48d2011-08-09 03:48:50656 return SelectionModel(pos, pos, SelectionModel::LEADING);
657}
658
[email protected]0d717602011-08-30 06:21:14659SelectionModel RenderText::LeftEndSelectionModel() {
660 return SelectionModel(0, 0, SelectionModel::LEADING);
661}
662
663SelectionModel RenderText::RightEndSelectionModel() {
664 size_t cursor = text().length();
665 size_t caret_pos = GetIndexOfPreviousGrapheme(cursor);
666 SelectionModel::CaretPlacement placement = (caret_pos == cursor) ?
667 SelectionModel::LEADING : SelectionModel::TRAILING;
668 return SelectionModel(cursor, caret_pos, placement);
669}
670
[email protected]6002a4f32011-11-30 10:18:42671void RenderText::SetSelectionModel(const SelectionModel& model) {
672 DCHECK_LE(model.selection_start(), text().length());
673 selection_model_.set_selection_start(model.selection_start());
674 DCHECK_LE(model.selection_end(), text().length());
675 selection_model_.set_selection_end(model.selection_end());
[email protected]f9a221b2011-12-10 20:25:38676 DCHECK_LT(model.caret_pos(), std::max<size_t>(text().length(), 1));
[email protected]6002a4f32011-11-30 10:18:42677 selection_model_.set_caret_pos(model.caret_pos());
678 selection_model_.set_caret_placement(model.caret_placement());
679
680 cached_bounds_and_offset_valid_ = false;
[email protected]0d717602011-08-30 06:21:14681}
682
[email protected]6002a4f32011-11-30 10:18:42683size_t RenderText::GetIndexOfPreviousGrapheme(size_t position) {
684 return IndexOfAdjacentGrapheme(position, false);
[email protected]ff44d712011-07-25 08:42:52685}
686
[email protected]8e42ba22011-08-04 21:47:08687void RenderText::ApplyCompositionAndSelectionStyles(
[email protected]1a353f12012-01-18 04:20:56688 StyleRanges* style_ranges) {
[email protected]8e42ba22011-08-04 21:47:08689 // TODO(msw): This pattern ought to be reconsidered; what about composition
690 // and selection overlaps, retain existing local style features?
691 // Apply a composition style override to a copy of the style ranges.
692 if (composition_range_.IsValid() && !composition_range_.is_empty()) {
693 StyleRange composition_style(default_style_);
694 composition_style.underline = true;
695 composition_style.range.set_start(composition_range_.start());
696 composition_style.range.set_end(composition_range_.end());
697 ApplyStyleRangeImpl(style_ranges, composition_style);
698 }
699 // Apply a selection style override to a copy of the style ranges.
700 if (!EmptySelection()) {
701 StyleRange selection_style(default_style_);
702 selection_style.foreground = kSelectedTextColor;
703 selection_style.range.set_start(MinOfSelection());
704 selection_style.range.set_end(MaxOfSelection());
705 ApplyStyleRangeImpl(style_ranges, selection_style);
706 }
[email protected]1a353f12012-01-18 04:20:56707 // Apply replacement-mode style override to a copy of the style ranges.
708 //
709 // TODO(xji): NEED TO FIX FOR WINDOWS ASAP. Windows call this function (to
710 // apply styles) in ItemizeLogicalText(). In order for the cursor's underline
711 // character to be drawn correctly, we will need to re-layout the text. It's
712 // not practical to do layout on every cursor blink. We need to fix Windows
713 // port to apply styles during drawing phase like Linux port does.
714 // http://crbug.com/110109
715 if (!insert_mode_ && cursor_visible() && focused()) {
716 StyleRange replacement_mode_style(default_style_);
717 replacement_mode_style.foreground = kSelectedTextColor;
718 size_t cursor = GetCursorPosition();
719 replacement_mode_style.range.set_start(cursor);
720 replacement_mode_style.range.set_end(GetIndexOfNextGrapheme(cursor));
721 ApplyStyleRangeImpl(style_ranges, replacement_mode_style);
722 }
[email protected]ff44d712011-07-25 08:42:52723}
724
[email protected]0d717602011-08-30 06:21:14725Point RenderText::ToTextPoint(const Point& point) {
726 Point p(point.Subtract(display_rect().origin()));
727 p = p.Subtract(GetUpdatedDisplayOffset());
728 if (base::i18n::IsRTL())
729 p.Offset(GetStringWidth() - display_rect().width() + 1, 0);
730 return p;
731}
732
733Point RenderText::ToViewPoint(const Point& point) {
734 Point p(point.Add(display_rect().origin()));
735 p = p.Add(GetUpdatedDisplayOffset());
736 if (base::i18n::IsRTL())
737 p.Offset(display_rect().width() - GetStringWidth() - 1, 0);
738 return p;
739}
740
[email protected]67b981562011-12-09 00:35:05741Point RenderText::GetOriginForSkiaDrawing() {
742 Point origin(ToViewPoint(Point()));
743 // TODO(msw): Establish a vertical baseline for strings of mixed font heights.
[email protected]8d901a82012-01-04 19:41:30744 const Font& font = GetFont();
[email protected]5f4fd30e2012-01-12 02:19:33745 int height = font.GetHeight();
[email protected]67b981562011-12-09 00:35:05746 // Center the text vertically in the display area.
747 origin.Offset(0, (display_rect().height() - height) / 2);
748 // Offset by the font size to account for Skia expecting y to be the bottom.
749 origin.Offset(0, font.GetFontSize());
750 return origin;
751}
752
[email protected]f308e5b12012-01-17 19:09:18753int RenderText::ApplyFadeEffects(internal::SkiaTextRenderer* renderer) {
754 if (!fade_head() && !fade_tail())
755 return 0;
756
757 const int text_width = GetStringWidth();
758 const int display_width = display_rect().width();
759
760 // If the text fits as-is, no need to fade.
761 if (text_width <= display_width)
762 return 0;
763
764 int gradient_width = CalculateFadeGradientWidth(GetFont(), display_width);
765 if (gradient_width == 0)
766 return 0;
767
768 bool fade_left = fade_head();
769 bool fade_right = fade_tail();
770 // Under RTL, |fade_right| == |fade_head|.
771 if (GetTextDirection() == base::i18n::RIGHT_TO_LEFT)
772 std::swap(fade_left, fade_right);
773
774 gfx::Rect solid_part = display_rect();
775 gfx::Rect left_part;
776 gfx::Rect right_part;
777 if (fade_left) {
778 left_part = solid_part;
779 left_part.Inset(0, 0, solid_part.width() - gradient_width, 0);
780 solid_part.Inset(gradient_width, 0, 0, 0);
781 }
782 if (fade_right) {
783 right_part = solid_part;
784 right_part.Inset(solid_part.width() - gradient_width, 0, 0, 0);
785 solid_part.Inset(0, 0, gradient_width, 0);
786 }
787
788 // Right-align the text when fading left.
789 int x_offset = 0;
790 gfx::Rect text_rect = display_rect();
791 if (fade_left && !fade_right) {
792 x_offset = display_width - text_width;
793 text_rect.Offset(x_offset, 0);
794 text_rect.set_width(text_width);
795 }
796
797 const SkColor color = default_style().foreground;
798 SkAutoTUnref<SkShader> shader(
799 CreateFadeShader(text_rect, left_part, right_part, color));
800 if (shader.get()) {
801 // |renderer| adds its own ref. So don't |release()| it from the ref ptr.
802 renderer->SetShader(shader.get());
803 }
804
805 return x_offset;
806}
807
[email protected]0d717602011-08-30 06:21:14808void RenderText::MoveCursorTo(size_t position, bool select) {
809 size_t cursor = std::min(position, text().length());
810 size_t caret_pos = GetIndexOfPreviousGrapheme(cursor);
811 SelectionModel::CaretPlacement placement = (caret_pos == cursor) ?
812 SelectionModel::LEADING : SelectionModel::TRAILING;
813 size_t selection_start = select ? GetSelectionStart() : cursor;
[email protected]53c0b1b2011-09-21 20:32:29814 if (IsCursorablePosition(cursor)) {
815 SelectionModel sel(selection_start, cursor, caret_pos, placement);
816 SetSelectionModel(sel);
817 }
[email protected]8e42ba22011-08-04 21:47:08818}
819
[email protected]f6aaa0f2011-08-11 07:05:46820void RenderText::UpdateCachedBoundsAndOffset() {
821 if (cached_bounds_and_offset_valid_)
822 return;
823 // First, set the valid flag true to calculate the current cursor bounds using
824 // the stale |display_offset_|. Applying |delta_offset| at the end of this
825 // function will set |cursor_bounds_| and |display_offset_| to correct values.
826 cached_bounds_and_offset_valid_ = true;
827 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_);
[email protected]8e42ba22011-08-04 21:47:08828 // Update |display_offset_| to ensure the current cursor is visible.
829 int display_width = display_rect_.width();
830 int string_width = GetStringWidth();
[email protected]f6aaa0f2011-08-11 07:05:46831 int delta_offset = 0;
[email protected]8e42ba22011-08-04 21:47:08832 if (string_width < display_width) {
833 // Show all text whenever the text fits to the size.
[email protected]f6aaa0f2011-08-11 07:05:46834 delta_offset = -display_offset_.x();
[email protected]65788132011-09-07 20:20:23835 } else if (cursor_bounds_.right() >= display_rect_.right()) {
[email protected]d3c6b0602011-09-07 19:26:06836 // TODO(xji): when the character overflow is a RTL character, currently, if
837 // we pan cursor at the rightmost position, the entered RTL character is not
838 // displayed. Should pan cursor to show the last logical characters.
839 //
[email protected]8e42ba22011-08-04 21:47:08840 // Pan to show the cursor when it overflows to the right,
[email protected]65788132011-09-07 20:20:23841 delta_offset = display_rect_.right() - cursor_bounds_.right() - 1;
[email protected]f6aaa0f2011-08-11 07:05:46842 } else if (cursor_bounds_.x() < display_rect_.x()) {
[email protected]d3c6b0602011-09-07 19:26:06843 // TODO(xji): have similar problem as above when overflow character is a
844 // LTR character.
845 //
[email protected]8e42ba22011-08-04 21:47:08846 // Pan to show the cursor when it overflows to the left.
[email protected]f6aaa0f2011-08-11 07:05:46847 delta_offset = display_rect_.x() - cursor_bounds_.x();
[email protected]8e42ba22011-08-04 21:47:08848 }
[email protected]f6aaa0f2011-08-11 07:05:46849 display_offset_.Offset(delta_offset, 0);
850 cursor_bounds_.Offset(delta_offset, 0);
[email protected]ff44d712011-07-25 08:42:52851}
852
[email protected]6002a4f32011-11-30 10:18:42853void RenderText::DrawSelection(Canvas* canvas) {
[email protected]592a7ad2011-12-14 05:57:53854 TRACE_EVENT0("gfx", "RenderText::DrawSelection");
[email protected]6002a4f32011-11-30 10:18:42855 std::vector<Rect> sel;
856 GetSubstringBounds(GetSelectionStart(), GetCursorPosition(), &sel);
857 SkColor color = focused() ? kFocusedSelectionColor : kUnfocusedSelectionColor;
858 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
859 canvas->FillRect(color, *i);
860}
861
862void RenderText::DrawCursor(Canvas* canvas) {
[email protected]592a7ad2011-12-14 05:57:53863 TRACE_EVENT0("gfx", "RenderText::DrawCursor");
[email protected]6002a4f32011-11-30 10:18:42864 // Paint cursor. Replace cursor is drawn as rectangle for now.
865 // TODO(msw): Draw a better cursor with a better indication of association.
[email protected]1a353f12012-01-18 04:20:56866 if (cursor_visible() && focused()) {
867 const Rect& bounds = GetUpdatedCursorBounds();
868 if (bounds.width() != 0)
869 canvas->FillRect(kCursorColor, bounds);
870 else
871 canvas->DrawRect(bounds, kCursorColor);
872 }
[email protected]6002a4f32011-11-30 10:18:42873}
874
[email protected]ff44d712011-07-25 08:42:52875} // namespace gfx