blob: 10e6590871cf3f53e99b31b2e36d9b646dae292c [file] [log] [blame]
[email protected]ff44d712011-07-25 08:42:521// Copyright (c) 2011 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "ui/gfx/render_text.h"
6
7#include <algorithm>
8
9#include "base/i18n/break_iterator.h"
10#include "base/logging.h"
11#include "base/stl_util.h"
[email protected]67b981562011-12-09 00:35:0512#include "third_party/skia/include/core/SkTypeface.h"
[email protected]ff44d712011-07-25 08:42:5213#include "ui/gfx/canvas.h"
14#include "ui/gfx/canvas_skia.h"
[email protected]8e42ba22011-08-04 21:47:0815#include "unicode/uchar.h"
[email protected]ff44d712011-07-25 08:42:5216
17namespace {
18
19#ifndef NDEBUG
20// Check StyleRanges invariant conditions: sorted and non-overlapping ranges.
21void CheckStyleRanges(const gfx::StyleRanges& style_ranges, size_t length) {
22 if (length == 0) {
23 DCHECK(style_ranges.empty()) << "Style ranges exist for empty text.";
24 return;
25 }
26 for (gfx::StyleRanges::size_type i = 0; i < style_ranges.size() - 1; i++) {
27 const ui::Range& former = style_ranges[i].range;
28 const ui::Range& latter = style_ranges[i + 1].range;
29 DCHECK(!former.is_empty()) << "Empty range at " << i << ":" << former;
30 DCHECK(former.IsValid()) << "Invalid range at " << i << ":" << former;
31 DCHECK(!former.is_reversed()) << "Reversed range at " << i << ":" << former;
32 DCHECK(former.end() == latter.start()) << "Ranges gap/overlap/unsorted." <<
33 "former:" << former << ", latter:" << latter;
34 }
35 const gfx::StyleRange& end_style = *style_ranges.rbegin();
36 DCHECK(!end_style.range.is_empty()) << "Empty range at end.";
37 DCHECK(end_style.range.IsValid()) << "Invalid range at end.";
38 DCHECK(!end_style.range.is_reversed()) << "Reversed range at end.";
39 DCHECK(end_style.range.end() == length) << "Style and text length mismatch.";
40}
41#endif
42
[email protected]8e42ba22011-08-04 21:47:0843void ApplyStyleRangeImpl(gfx::StyleRanges* style_ranges,
[email protected]ff44d712011-07-25 08:42:5244 gfx::StyleRange style_range) {
45 const ui::Range& new_range = style_range.range;
46 // Follow StyleRanges invariant conditions: sorted and non-overlapping ranges.
47 gfx::StyleRanges::iterator i;
[email protected]8e42ba22011-08-04 21:47:0848 for (i = style_ranges->begin(); i != style_ranges->end();) {
[email protected]ff44d712011-07-25 08:42:5249 if (i->range.end() < new_range.start()) {
50 i++;
51 } else if (i->range.start() == new_range.end()) {
52 break;
53 } else if (new_range.Contains(i->range)) {
[email protected]8e42ba22011-08-04 21:47:0854 i = style_ranges->erase(i);
55 if (i == style_ranges->end())
[email protected]ff44d712011-07-25 08:42:5256 break;
57 } else if (i->range.start() < new_range.start() &&
58 i->range.end() > new_range.end()) {
59 // Split the current style into two styles.
60 gfx::StyleRange split_style = gfx::StyleRange(*i);
61 split_style.range.set_end(new_range.start());
[email protected]8e42ba22011-08-04 21:47:0862 i = style_ranges->insert(i, split_style) + 1;
[email protected]ff44d712011-07-25 08:42:5263 i->range.set_start(new_range.end());
64 break;
65 } else if (i->range.start() < new_range.start()) {
66 i->range.set_end(new_range.start());
67 i++;
68 } else if (i->range.end() > new_range.end()) {
69 i->range.set_start(new_range.end());
70 break;
[email protected]f9a221b2011-12-10 20:25:3871 } else {
[email protected]ff44d712011-07-25 08:42:5272 NOTREACHED();
[email protected]f9a221b2011-12-10 20:25:3873 }
[email protected]ff44d712011-07-25 08:42:5274 }
75 // Add the new range in its sorted location.
[email protected]8e42ba22011-08-04 21:47:0876 style_ranges->insert(i, style_range);
[email protected]ff44d712011-07-25 08:42:5277}
78
79} // namespace
80
81namespace gfx {
82
[email protected]67b981562011-12-09 00:35:0583namespace internal {
84
85SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas)
86 : canvas_skia_(canvas->GetSkCanvas()) {
87 DCHECK(canvas_skia_);
88 paint_.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
89 paint_.setStyle(SkPaint::kFill_Style);
90 paint_.setAntiAlias(true);
91 paint_.setSubpixelText(true);
92 paint_.setLCDRenderText(true);
93}
94
95SkiaTextRenderer::~SkiaTextRenderer() {
96}
97
98void SkiaTextRenderer::SetFont(const gfx::Font& font) {
99 SkAutoTUnref<SkTypeface> typeface(
100 SkTypeface::CreateFromName(font.GetFontName().c_str(),
101 SkTypeface::kNormal));
102 if (typeface.get()) {
103 // |paint_| adds its own ref. So don't |release()| it from the ref ptr here.
104 paint_.setTypeface(typeface.get());
105 }
106 paint_.setTextSize(font.GetFontSize());
107}
108
109void SkiaTextRenderer::SetForegroundColor(SkColor foreground) {
110 paint_.setColor(foreground);
111}
112
113void SkiaTextRenderer::DrawPosText(const SkPoint* pos,
114 const uint16* glyphs,
115 size_t glyph_count) {
116 size_t byte_length = glyph_count * sizeof(glyphs[0]);
117 canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_);
118}
119
120// Draw underline and strike through text decorations.
121// Based on |SkCanvas::DrawTextDecorations()| and constants from:
122// third_party/skia/src/core/SkTextFormatParams.h
123void SkiaTextRenderer::DrawDecorations(int x, int y, int width,
124 bool underline, bool strike) {
125 // Fraction of the text size to lower a strike through below the baseline.
126 const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21);
127 // Fraction of the text size to lower an underline below the baseline.
128 const SkScalar kUnderlineOffset = (SK_Scalar1 / 9);
129 // Fraction of the text size to use for a strike through or under-line.
130 const SkScalar kLineThickness = (SK_Scalar1 / 18);
131
132 SkScalar text_size = paint_.getTextSize();
133 SkScalar height = SkScalarMul(text_size, kLineThickness);
134 SkRect r;
135
136 r.fLeft = x;
137 r.fRight = x + width;
138
139 if (underline) {
140 SkScalar offset = SkScalarMulAdd(text_size, kUnderlineOffset, y);
141 r.fTop = offset;
142 r.fBottom = offset + height;
143 canvas_skia_->drawRect(r, paint_);
144 }
145 if (strike) {
146 SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y);
147 r.fTop = offset;
148 r.fBottom = offset + height;
149 canvas_skia_->drawRect(r, paint_);
150 }
151}
152
153} // namespace internal
154
155
[email protected]ff44d712011-07-25 08:42:52156StyleRange::StyleRange()
157 : font(),
158 foreground(SK_ColorBLACK),
159 strike(false),
160 underline(false),
161 range() {
162}
163
[email protected]8e42ba22011-08-04 21:47:08164RenderText::~RenderText() {
165}
166
[email protected]ff44d712011-07-25 08:42:52167void RenderText::SetText(const string16& text) {
[email protected]d3c6b0602011-09-07 19:26:06168 DCHECK(!composition_range_.IsValid());
[email protected]ff44d712011-07-25 08:42:52169 size_t old_text_length = text_.length();
170 text_ = text;
171
172 // Update the style ranges as needed.
173 if (text_.empty()) {
174 style_ranges_.clear();
175 } else if (style_ranges_.empty()) {
176 ApplyDefaultStyle();
177 } else if (text_.length() > old_text_length) {
178 style_ranges_.back().range.set_end(text_.length());
179 } else if (text_.length() < old_text_length) {
180 StyleRanges::iterator i;
181 for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) {
182 if (i->range.start() >= text_.length()) {
[email protected]052ce6bb2011-10-14 19:48:20183 // Style ranges are sorted and non-overlapping, so all the subsequent
184 // style ranges should be out of text_.length() as well.
185 style_ranges_.erase(i, style_ranges_.end());
186 break;
[email protected]ff44d712011-07-25 08:42:52187 }
188 }
[email protected]052ce6bb2011-10-14 19:48:20189 // Since style ranges are sorted and non-overlapping, if there is a style
190 // range ends beyond text_.length, it must be the last one.
[email protected]ff44d712011-07-25 08:42:52191 style_ranges_.back().range.set_end(text_.length());
192 }
193#ifndef NDEBUG
194 CheckStyleRanges(style_ranges_, text_.length());
195#endif
[email protected]f6aaa0f2011-08-11 07:05:46196 cached_bounds_and_offset_valid_ = false;
[email protected]d3c6b0602011-09-07 19:26:06197
198 // Reset selection model. SetText should always followed by SetSelectionModel
199 // or SetCursorPosition in upper layer.
200 SetSelectionModel(SelectionModel(0, 0, SelectionModel::LEADING));
[email protected]6002a4f32011-11-30 10:18:42201
202 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52203}
204
[email protected]0d717602011-08-30 06:21:14205void RenderText::ToggleInsertMode() {
206 insert_mode_ = !insert_mode_;
[email protected]f6aaa0f2011-08-11 07:05:46207 cached_bounds_and_offset_valid_ = false;
208}
209
210void RenderText::SetDisplayRect(const Rect& r) {
211 display_rect_ = r;
212 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25213 UpdateLayout();
[email protected]8e42ba22011-08-04 21:47:08214}
215
[email protected]ff44d712011-07-25 08:42:52216size_t RenderText::GetCursorPosition() const {
[email protected]8e42ba22011-08-04 21:47:08217 return selection_model_.selection_end();
[email protected]ff44d712011-07-25 08:42:52218}
219
[email protected]0d717602011-08-30 06:21:14220void RenderText::SetCursorPosition(size_t position) {
221 MoveCursorTo(position, false);
[email protected]ff44d712011-07-25 08:42:52222}
223
224void RenderText::MoveCursorLeft(BreakType break_type, bool select) {
[email protected]ec7f48d2011-08-09 03:48:50225 SelectionModel position(selection_model());
226 position.set_selection_start(GetCursorPosition());
[email protected]ff44d712011-07-25 08:42:52227 // Cancelling a selection moves to the edge of the selection.
[email protected]ec7f48d2011-08-09 03:48:50228 if (break_type != LINE_BREAK && !EmptySelection() && !select) {
[email protected]ff44d712011-07-25 08:42:52229 // Use the selection start if it is left of the selection end.
[email protected]d3c6b0602011-09-07 19:26:06230 SelectionModel selection_start = GetSelectionModelForSelectionStart();
231 if (GetCursorBounds(selection_start, true).x() <
232 GetCursorBounds(position, true).x())
[email protected]8e42ba22011-08-04 21:47:08233 position = selection_start;
[email protected]ec7f48d2011-08-09 03:48:50234 // For word breaks, use the nearest word boundary left of the selection.
[email protected]ff44d712011-07-25 08:42:52235 if (break_type == WORD_BREAK)
[email protected]ec7f48d2011-08-09 03:48:50236 position = GetLeftSelectionModel(position, break_type);
[email protected]ff44d712011-07-25 08:42:52237 } else {
[email protected]ec7f48d2011-08-09 03:48:50238 position = GetLeftSelectionModel(position, break_type);
[email protected]ff44d712011-07-25 08:42:52239 }
[email protected]ec7f48d2011-08-09 03:48:50240 if (select)
241 position.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08242 MoveCursorTo(position);
[email protected]ff44d712011-07-25 08:42:52243}
244
245void RenderText::MoveCursorRight(BreakType break_type, bool select) {
[email protected]ec7f48d2011-08-09 03:48:50246 SelectionModel position(selection_model());
247 position.set_selection_start(GetCursorPosition());
[email protected]ff44d712011-07-25 08:42:52248 // Cancelling a selection moves to the edge of the selection.
[email protected]ec7f48d2011-08-09 03:48:50249 if (break_type != LINE_BREAK && !EmptySelection() && !select) {
[email protected]ff44d712011-07-25 08:42:52250 // Use the selection start if it is right of the selection end.
[email protected]d3c6b0602011-09-07 19:26:06251 SelectionModel selection_start = GetSelectionModelForSelectionStart();
252 if (GetCursorBounds(selection_start, true).x() >
253 GetCursorBounds(position, true).x())
[email protected]8e42ba22011-08-04 21:47:08254 position = selection_start;
[email protected]ec7f48d2011-08-09 03:48:50255 // For word breaks, use the nearest word boundary right of the selection.
[email protected]ff44d712011-07-25 08:42:52256 if (break_type == WORD_BREAK)
[email protected]ec7f48d2011-08-09 03:48:50257 position = GetRightSelectionModel(position, break_type);
[email protected]ff44d712011-07-25 08:42:52258 } else {
[email protected]ec7f48d2011-08-09 03:48:50259 position = GetRightSelectionModel(position, break_type);
[email protected]ff44d712011-07-25 08:42:52260 }
[email protected]ec7f48d2011-08-09 03:48:50261 if (select)
262 position.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08263 MoveCursorTo(position);
[email protected]ff44d712011-07-25 08:42:52264}
265
[email protected]f9a221b2011-12-10 20:25:38266bool RenderText::MoveCursorTo(const SelectionModel& model) {
267 SelectionModel sel(model);
[email protected]0d717602011-08-30 06:21:14268 size_t text_length = text().length();
269 // Enforce valid selection model components.
270 if (sel.selection_start() > text_length)
271 sel.set_selection_start(text_length);
272 if (sel.selection_end() > text_length)
273 sel.set_selection_end(text_length);
274 // The current model only supports caret positions at valid character indices.
275 if (text_length == 0) {
276 sel.set_caret_pos(0);
277 sel.set_caret_placement(SelectionModel::LEADING);
278 } else if (sel.caret_pos() >= text_length) {
279 SelectionModel end = GetTextDirection() == base::i18n::RIGHT_TO_LEFT ?
280 LeftEndSelectionModel() : RightEndSelectionModel();
281 sel.set_caret_pos(end.caret_pos());
282 sel.set_caret_placement(end.caret_placement());
283 }
[email protected]53c0b1b2011-09-21 20:32:29284
285 if (!IsCursorablePosition(sel.selection_start()) ||
286 !IsCursorablePosition(sel.selection_end()) ||
287 !IsCursorablePosition(sel.caret_pos()))
288 return false;
289
[email protected]0d717602011-08-30 06:21:14290 bool changed = !sel.Equals(selection_model_);
291 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52292 return changed;
293}
294
[email protected]7b3cb4b22011-08-02 18:36:40295bool RenderText::MoveCursorTo(const Point& point, bool select) {
[email protected]8e42ba22011-08-04 21:47:08296 SelectionModel selection = FindCursorPosition(point);
297 if (select)
298 selection.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08299 return MoveCursorTo(selection);
[email protected]ff44d712011-07-25 08:42:52300}
301
[email protected]67e85512011-10-12 20:03:45302bool RenderText::SelectRange(const ui::Range& range) {
303 size_t text_length = text().length();
304 size_t start = std::min(range.start(), text_length);
305 size_t end = std::min(range.end(), text_length);
306
307 if (!IsCursorablePosition(start) || !IsCursorablePosition(end))
308 return false;
309
310 size_t pos = end;
311 SelectionModel::CaretPlacement placement = SelectionModel::LEADING;
312 if (start < end) {
313 pos = GetIndexOfPreviousGrapheme(end);
314 DCHECK_LT(pos, end);
315 placement = SelectionModel::TRAILING;
316 } else if (end == text_length) {
317 SelectionModel boundary = GetTextDirection() == base::i18n::RIGHT_TO_LEFT ?
318 LeftEndSelectionModel() : RightEndSelectionModel();
319 pos = boundary.caret_pos();
320 placement = boundary.caret_placement();
321 }
322 SetSelectionModel(SelectionModel(start, end, pos, placement));
323 return true;
324}
325
[email protected]8e42ba22011-08-04 21:47:08326bool RenderText::IsPointInSelection(const Point& point) {
[email protected]0d717602011-08-30 06:21:14327 if (EmptySelection())
328 return false;
[email protected]8e42ba22011-08-04 21:47:08329 // TODO(xji): should this check whether the point is inside the visual
330 // selection bounds? In case of "abcFED", if "ED" is selected, |point| points
331 // to the right half of 'c', is the point in selection?
332 size_t pos = FindCursorPosition(point).selection_end();
333 return (pos >= MinOfSelection() && pos < MaxOfSelection());
[email protected]ff44d712011-07-25 08:42:52334}
335
336void RenderText::ClearSelection() {
[email protected]ec7f48d2011-08-09 03:48:50337 SelectionModel sel(selection_model());
338 sel.set_selection_start(GetCursorPosition());
339 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52340}
341
342void RenderText::SelectAll() {
[email protected]0d717602011-08-30 06:21:14343 SelectionModel sel(RightEndSelectionModel());
344 sel.set_selection_start(LeftEndSelectionModel().selection_start());
[email protected]8e42ba22011-08-04 21:47:08345 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52346}
347
348void RenderText::SelectWord() {
[email protected]ff44d712011-07-25 08:42:52349 size_t cursor_position = GetCursorPosition();
[email protected]ff44d712011-07-25 08:42:52350
[email protected]53c0b1b2011-09-21 20:32:29351 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
352 bool success = iter.Init();
353 DCHECK(success);
354 if (!success)
355 return;
356
357 size_t selection_start = cursor_position;
358 for (; selection_start != 0; --selection_start) {
359 if (iter.IsStartOfWord(selection_start) ||
360 iter.IsEndOfWord(selection_start))
[email protected]ff44d712011-07-25 08:42:52361 break;
362 }
363
[email protected]53c0b1b2011-09-21 20:32:29364 if (selection_start == cursor_position)
365 ++cursor_position;
366
367 for (; cursor_position < text().length(); ++cursor_position) {
368 if (iter.IsEndOfWord(cursor_position) ||
369 iter.IsStartOfWord(cursor_position))
[email protected]ff44d712011-07-25 08:42:52370 break;
371 }
372
[email protected]0d717602011-08-30 06:21:14373 MoveCursorTo(selection_start, false);
374 MoveCursorTo(cursor_position, true);
[email protected]ff44d712011-07-25 08:42:52375}
376
377const ui::Range& RenderText::GetCompositionRange() const {
378 return composition_range_;
379}
380
381void RenderText::SetCompositionRange(const ui::Range& composition_range) {
382 CHECK(!composition_range.IsValid() ||
383 ui::Range(0, text_.length()).Contains(composition_range));
384 composition_range_.set_end(composition_range.end());
385 composition_range_.set_start(composition_range.start());
[email protected]79791682011-11-09 19:11:25386 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52387}
388
389void RenderText::ApplyStyleRange(StyleRange style_range) {
390 const ui::Range& new_range = style_range.range;
391 if (!new_range.IsValid() || new_range.is_empty())
392 return;
393 CHECK(!new_range.is_reversed());
394 CHECK(ui::Range(0, text_.length()).Contains(new_range));
[email protected]8e42ba22011-08-04 21:47:08395 ApplyStyleRangeImpl(&style_ranges_, style_range);
[email protected]ff44d712011-07-25 08:42:52396#ifndef NDEBUG
397 CheckStyleRanges(style_ranges_, text_.length());
398#endif
[email protected]f6aaa0f2011-08-11 07:05:46399 // TODO(xji): only invalidate if font or underline changes.
400 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25401 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52402}
403
404void RenderText::ApplyDefaultStyle() {
405 style_ranges_.clear();
406 StyleRange style = StyleRange(default_style_);
407 style.range.set_end(text_.length());
408 style_ranges_.push_back(style);
[email protected]f6aaa0f2011-08-11 07:05:46409 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25410 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52411}
412
[email protected]d3c6b0602011-09-07 19:26:06413base::i18n::TextDirection RenderText::GetTextDirection() {
[email protected]0d717602011-08-30 06:21:14414 if (base::i18n::IsRTL())
415 return base::i18n::RIGHT_TO_LEFT;
[email protected]ff44d712011-07-25 08:42:52416 return base::i18n::LEFT_TO_RIGHT;
417}
418
[email protected]8e42ba22011-08-04 21:47:08419int RenderText::GetStringWidth() {
[email protected]f6aaa0f2011-08-11 07:05:46420 return default_style_.font.GetStringWidth(text());
[email protected]ff44d712011-07-25 08:42:52421}
422
[email protected]7b3cb4b22011-08-02 18:36:40423void RenderText::Draw(Canvas* canvas) {
[email protected]6002a4f32011-11-30 10:18:42424 EnsureLayout();
[email protected]ff44d712011-07-25 08:42:52425
[email protected]6002a4f32011-11-30 10:18:42426 if (!text().empty()) {
427 DrawSelection(canvas);
428 DrawVisualText(canvas);
[email protected]ff44d712011-07-25 08:42:52429 }
[email protected]6002a4f32011-11-30 10:18:42430 DrawCursor(canvas);
[email protected]ff44d712011-07-25 08:42:52431}
432
[email protected]8e42ba22011-08-04 21:47:08433SelectionModel RenderText::FindCursorPosition(const Point& point) {
[email protected]7b3cb4b22011-08-02 18:36:40434 const Font& font = default_style_.font;
[email protected]ff44d712011-07-25 08:42:52435 int left = 0;
436 int left_pos = 0;
437 int right = font.GetStringWidth(text());
438 int right_pos = text().length();
439
[email protected]f6aaa0f2011-08-11 07:05:46440 int x = point.x() - (display_rect_.x() + GetUpdatedDisplayOffset().x());
[email protected]8e42ba22011-08-04 21:47:08441 if (x <= left) return SelectionModel(left_pos);
442 if (x >= right) return SelectionModel(right_pos);
[email protected]ff44d712011-07-25 08:42:52443 // binary searching the cursor position.
444 // TODO(oshima): use the center of character instead of edge.
[email protected]d3c6b0602011-09-07 19:26:06445 // Binary search may not work for language like Arabic.
[email protected]f9a221b2011-12-10 20:25:38446 while (std::abs(right_pos - left_pos) > 1) {
[email protected]ff44d712011-07-25 08:42:52447 int pivot_pos = left_pos + (right_pos - left_pos) / 2;
448 int pivot = font.GetStringWidth(text().substr(0, pivot_pos));
449 if (pivot < x) {
450 left = pivot;
451 left_pos = pivot_pos;
452 } else if (pivot == x) {
[email protected]8e42ba22011-08-04 21:47:08453 return SelectionModel(pivot_pos);
[email protected]ff44d712011-07-25 08:42:52454 } else {
455 right = pivot;
456 right_pos = pivot_pos;
457 }
458 }
[email protected]8e42ba22011-08-04 21:47:08459 return SelectionModel(left_pos);
[email protected]ff44d712011-07-25 08:42:52460}
461
[email protected]f6aaa0f2011-08-11 07:05:46462const Rect& RenderText::GetUpdatedCursorBounds() {
463 UpdateCachedBoundsAndOffset();
[email protected]8e42ba22011-08-04 21:47:08464 return cursor_bounds_;
465}
466
[email protected]53c0b1b2011-09-21 20:32:29467size_t RenderText::GetIndexOfNextGrapheme(size_t position) {
468 return IndexOfAdjacentGrapheme(position, true);
469}
470
[email protected]04b7bf322011-10-03 19:08:46471SelectionModel RenderText::GetSelectionModelForSelectionStart() {
472 size_t selection_start = GetSelectionStart();
473 size_t selection_end = GetCursorPosition();
474 if (selection_start < selection_end)
475 return SelectionModel(selection_start,
476 selection_start,
477 SelectionModel::LEADING);
478 else if (selection_start > selection_end)
479 return SelectionModel(selection_start,
480 GetIndexOfPreviousGrapheme(selection_start),
481 SelectionModel::TRAILING);
482 return selection_model_;
483}
484
[email protected]8e42ba22011-08-04 21:47:08485RenderText::RenderText()
486 : text_(),
487 selection_model_(),
488 cursor_bounds_(),
[email protected]8e42ba22011-08-04 21:47:08489 cursor_visible_(false),
490 insert_mode_(true),
[email protected]d3c6b0602011-09-07 19:26:06491 composition_range_(ui::Range::InvalidRange()),
[email protected]8e42ba22011-08-04 21:47:08492 style_ranges_(),
493 default_style_(),
494 display_rect_(),
[email protected]f6aaa0f2011-08-11 07:05:46495 display_offset_(),
496 cached_bounds_and_offset_valid_(false) {
497}
498
499const Point& RenderText::GetUpdatedDisplayOffset() {
500 UpdateCachedBoundsAndOffset();
501 return display_offset_;
[email protected]8e42ba22011-08-04 21:47:08502}
503
[email protected]ec7f48d2011-08-09 03:48:50504SelectionModel RenderText::GetLeftSelectionModel(const SelectionModel& current,
505 BreakType break_type) {
506 if (break_type == LINE_BREAK)
[email protected]67e85512011-10-12 20:03:45507 return LeftEndSelectionModel();
[email protected]f9a221b2011-12-10 20:25:38508 size_t pos = std::max<int>(current.selection_end() - 1, 0);
[email protected]ec7f48d2011-08-09 03:48:50509 if (break_type == CHARACTER_BREAK)
510 return SelectionModel(pos, pos, SelectionModel::LEADING);
511
[email protected]d3c6b0602011-09-07 19:26:06512 // Notes: We always iterate words from the beginning.
[email protected]ff44d712011-07-25 08:42:52513 // This is probably fast enough for our usage, but we may
514 // want to modify WordIterator so that it can start from the
515 // middle of string and advance backwards.
516 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
517 bool success = iter.Init();
518 DCHECK(success);
[email protected]ec7f48d2011-08-09 03:48:50519 if (!success)
520 return current;
[email protected]ff44d712011-07-25 08:42:52521 while (iter.Advance()) {
522 if (iter.IsWord()) {
523 size_t begin = iter.pos() - iter.GetString().length();
[email protected]ec7f48d2011-08-09 03:48:50524 if (begin == current.selection_end()) {
[email protected]ff44d712011-07-25 08:42:52525 // The cursor is at the beginning of a word.
526 // Move to previous word.
527 break;
[email protected]ec7f48d2011-08-09 03:48:50528 } else if (iter.pos() >= current.selection_end()) {
[email protected]ff44d712011-07-25 08:42:52529 // The cursor is in the middle or at the end of a word.
530 // Move to the top of current word.
[email protected]ec7f48d2011-08-09 03:48:50531 pos = begin;
[email protected]ff44d712011-07-25 08:42:52532 break;
533 } else {
[email protected]ec7f48d2011-08-09 03:48:50534 pos = iter.pos() - iter.GetString().length();
[email protected]ff44d712011-07-25 08:42:52535 }
536 }
537 }
538
[email protected]ec7f48d2011-08-09 03:48:50539 return SelectionModel(pos, pos, SelectionModel::LEADING);
[email protected]ff44d712011-07-25 08:42:52540}
541
[email protected]ec7f48d2011-08-09 03:48:50542SelectionModel RenderText::GetRightSelectionModel(const SelectionModel& current,
543 BreakType break_type) {
[email protected]d3c6b0602011-09-07 19:26:06544 if (text_.empty())
545 return SelectionModel(0, 0, SelectionModel::LEADING);
[email protected]ec7f48d2011-08-09 03:48:50546 if (break_type == LINE_BREAK)
[email protected]67e85512011-10-12 20:03:45547 return RightEndSelectionModel();
[email protected]ec7f48d2011-08-09 03:48:50548 size_t pos = std::min(current.selection_end() + 1, text().length());
549 if (break_type == CHARACTER_BREAK)
550 return SelectionModel(pos, pos, SelectionModel::LEADING);
[email protected]8e42ba22011-08-04 21:47:08551
[email protected]ff44d712011-07-25 08:42:52552 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
553 bool success = iter.Init();
554 DCHECK(success);
[email protected]ec7f48d2011-08-09 03:48:50555 if (!success)
556 return current;
[email protected]ff44d712011-07-25 08:42:52557 while (iter.Advance()) {
558 pos = iter.pos();
[email protected]ec7f48d2011-08-09 03:48:50559 if (iter.IsWord() && pos > current.selection_end())
[email protected]ff44d712011-07-25 08:42:52560 break;
[email protected]ff44d712011-07-25 08:42:52561 }
[email protected]ec7f48d2011-08-09 03:48:50562 return SelectionModel(pos, pos, SelectionModel::LEADING);
563}
564
[email protected]0d717602011-08-30 06:21:14565SelectionModel RenderText::LeftEndSelectionModel() {
566 return SelectionModel(0, 0, SelectionModel::LEADING);
567}
568
569SelectionModel RenderText::RightEndSelectionModel() {
570 size_t cursor = text().length();
571 size_t caret_pos = GetIndexOfPreviousGrapheme(cursor);
572 SelectionModel::CaretPlacement placement = (caret_pos == cursor) ?
573 SelectionModel::LEADING : SelectionModel::TRAILING;
574 return SelectionModel(cursor, caret_pos, placement);
575}
576
[email protected]6002a4f32011-11-30 10:18:42577void RenderText::SetSelectionModel(const SelectionModel& model) {
578 DCHECK_LE(model.selection_start(), text().length());
579 selection_model_.set_selection_start(model.selection_start());
580 DCHECK_LE(model.selection_end(), text().length());
581 selection_model_.set_selection_end(model.selection_end());
[email protected]f9a221b2011-12-10 20:25:38582 DCHECK_LT(model.caret_pos(), std::max<size_t>(text().length(), 1));
[email protected]6002a4f32011-11-30 10:18:42583 selection_model_.set_caret_pos(model.caret_pos());
584 selection_model_.set_caret_placement(model.caret_placement());
585
586 cached_bounds_and_offset_valid_ = false;
[email protected]00383f32011-12-02 15:46:09587 UpdateLayout();
[email protected]0d717602011-08-30 06:21:14588}
589
[email protected]6002a4f32011-11-30 10:18:42590size_t RenderText::GetIndexOfPreviousGrapheme(size_t position) {
591 return IndexOfAdjacentGrapheme(position, false);
[email protected]ff44d712011-07-25 08:42:52592}
593
[email protected]8e42ba22011-08-04 21:47:08594void RenderText::ApplyCompositionAndSelectionStyles(
595 StyleRanges* style_ranges) const {
596 // TODO(msw): This pattern ought to be reconsidered; what about composition
597 // and selection overlaps, retain existing local style features?
598 // Apply a composition style override to a copy of the style ranges.
599 if (composition_range_.IsValid() && !composition_range_.is_empty()) {
600 StyleRange composition_style(default_style_);
601 composition_style.underline = true;
602 composition_style.range.set_start(composition_range_.start());
603 composition_style.range.set_end(composition_range_.end());
604 ApplyStyleRangeImpl(style_ranges, composition_style);
605 }
606 // Apply a selection style override to a copy of the style ranges.
607 if (!EmptySelection()) {
608 StyleRange selection_style(default_style_);
609 selection_style.foreground = kSelectedTextColor;
610 selection_style.range.set_start(MinOfSelection());
611 selection_style.range.set_end(MaxOfSelection());
612 ApplyStyleRangeImpl(style_ranges, selection_style);
613 }
[email protected]ff44d712011-07-25 08:42:52614}
615
[email protected]0d717602011-08-30 06:21:14616Point RenderText::ToTextPoint(const Point& point) {
617 Point p(point.Subtract(display_rect().origin()));
618 p = p.Subtract(GetUpdatedDisplayOffset());
619 if (base::i18n::IsRTL())
620 p.Offset(GetStringWidth() - display_rect().width() + 1, 0);
621 return p;
622}
623
624Point RenderText::ToViewPoint(const Point& point) {
625 Point p(point.Add(display_rect().origin()));
626 p = p.Add(GetUpdatedDisplayOffset());
627 if (base::i18n::IsRTL())
628 p.Offset(display_rect().width() - GetStringWidth() - 1, 0);
629 return p;
630}
631
[email protected]67b981562011-12-09 00:35:05632Point RenderText::GetOriginForSkiaDrawing() {
633 Point origin(ToViewPoint(Point()));
634 // TODO(msw): Establish a vertical baseline for strings of mixed font heights.
635 const Font& font = default_style().font;
636 size_t height = font.GetHeight();
637 // Center the text vertically in the display area.
638 origin.Offset(0, (display_rect().height() - height) / 2);
639 // Offset by the font size to account for Skia expecting y to be the bottom.
640 origin.Offset(0, font.GetFontSize());
641 return origin;
642}
643
[email protected]0d717602011-08-30 06:21:14644void RenderText::MoveCursorTo(size_t position, bool select) {
645 size_t cursor = std::min(position, text().length());
646 size_t caret_pos = GetIndexOfPreviousGrapheme(cursor);
647 SelectionModel::CaretPlacement placement = (caret_pos == cursor) ?
648 SelectionModel::LEADING : SelectionModel::TRAILING;
649 size_t selection_start = select ? GetSelectionStart() : cursor;
[email protected]53c0b1b2011-09-21 20:32:29650 if (IsCursorablePosition(cursor)) {
651 SelectionModel sel(selection_start, cursor, caret_pos, placement);
652 SetSelectionModel(sel);
653 }
[email protected]8e42ba22011-08-04 21:47:08654}
655
[email protected]f6aaa0f2011-08-11 07:05:46656void RenderText::UpdateCachedBoundsAndOffset() {
657 if (cached_bounds_and_offset_valid_)
658 return;
659 // First, set the valid flag true to calculate the current cursor bounds using
660 // the stale |display_offset_|. Applying |delta_offset| at the end of this
661 // function will set |cursor_bounds_| and |display_offset_| to correct values.
662 cached_bounds_and_offset_valid_ = true;
663 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_);
[email protected]8e42ba22011-08-04 21:47:08664 // Update |display_offset_| to ensure the current cursor is visible.
665 int display_width = display_rect_.width();
666 int string_width = GetStringWidth();
[email protected]f6aaa0f2011-08-11 07:05:46667 int delta_offset = 0;
[email protected]8e42ba22011-08-04 21:47:08668 if (string_width < display_width) {
669 // Show all text whenever the text fits to the size.
[email protected]f6aaa0f2011-08-11 07:05:46670 delta_offset = -display_offset_.x();
[email protected]65788132011-09-07 20:20:23671 } else if (cursor_bounds_.right() >= display_rect_.right()) {
[email protected]d3c6b0602011-09-07 19:26:06672 // TODO(xji): when the character overflow is a RTL character, currently, if
673 // we pan cursor at the rightmost position, the entered RTL character is not
674 // displayed. Should pan cursor to show the last logical characters.
675 //
[email protected]8e42ba22011-08-04 21:47:08676 // Pan to show the cursor when it overflows to the right,
[email protected]65788132011-09-07 20:20:23677 delta_offset = display_rect_.right() - cursor_bounds_.right() - 1;
[email protected]f6aaa0f2011-08-11 07:05:46678 } else if (cursor_bounds_.x() < display_rect_.x()) {
[email protected]d3c6b0602011-09-07 19:26:06679 // TODO(xji): have similar problem as above when overflow character is a
680 // LTR character.
681 //
[email protected]8e42ba22011-08-04 21:47:08682 // Pan to show the cursor when it overflows to the left.
[email protected]f6aaa0f2011-08-11 07:05:46683 delta_offset = display_rect_.x() - cursor_bounds_.x();
[email protected]8e42ba22011-08-04 21:47:08684 }
[email protected]f6aaa0f2011-08-11 07:05:46685 display_offset_.Offset(delta_offset, 0);
686 cursor_bounds_.Offset(delta_offset, 0);
[email protected]ff44d712011-07-25 08:42:52687}
688
[email protected]6002a4f32011-11-30 10:18:42689void RenderText::DrawSelection(Canvas* canvas) {
690 std::vector<Rect> sel;
691 GetSubstringBounds(GetSelectionStart(), GetCursorPosition(), &sel);
692 SkColor color = focused() ? kFocusedSelectionColor : kUnfocusedSelectionColor;
693 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
694 canvas->FillRect(color, *i);
695}
696
697void RenderText::DrawCursor(Canvas* canvas) {
698 // Paint cursor. Replace cursor is drawn as rectangle for now.
699 // TODO(msw): Draw a better cursor with a better indication of association.
700 if (cursor_visible() && focused()) {
701 Rect r(GetUpdatedCursorBounds());
702 canvas->DrawRectInt(kCursorColor, r.x(), r.y(), r.width(), r.height());
703 }
704}
705
[email protected]ff44d712011-07-25 08:42:52706} // namespace gfx