blob: fc7faa8ed0aa406b57048037e3aab8494a13ffe6 [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
[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]ff44d712011-07-25 08:42:5214#include "ui/gfx/canvas.h"
15#include "ui/gfx/canvas_skia.h"
[email protected]8e42ba22011-08-04 21:47:0816#include "unicode/uchar.h"
[email protected]ff44d712011-07-25 08:42:5217
18namespace {
19
20#ifndef NDEBUG
21// Check StyleRanges invariant conditions: sorted and non-overlapping ranges.
22void CheckStyleRanges(const gfx::StyleRanges& style_ranges, size_t length) {
23 if (length == 0) {
24 DCHECK(style_ranges.empty()) << "Style ranges exist for empty text.";
25 return;
26 }
27 for (gfx::StyleRanges::size_type i = 0; i < style_ranges.size() - 1; i++) {
28 const ui::Range& former = style_ranges[i].range;
29 const ui::Range& latter = style_ranges[i + 1].range;
30 DCHECK(!former.is_empty()) << "Empty range at " << i << ":" << former;
31 DCHECK(former.IsValid()) << "Invalid range at " << i << ":" << former;
32 DCHECK(!former.is_reversed()) << "Reversed range at " << i << ":" << former;
33 DCHECK(former.end() == latter.start()) << "Ranges gap/overlap/unsorted." <<
34 "former:" << former << ", latter:" << latter;
35 }
36 const gfx::StyleRange& end_style = *style_ranges.rbegin();
37 DCHECK(!end_style.range.is_empty()) << "Empty range at end.";
38 DCHECK(end_style.range.IsValid()) << "Invalid range at end.";
39 DCHECK(!end_style.range.is_reversed()) << "Reversed range at end.";
40 DCHECK(end_style.range.end() == length) << "Style and text length mismatch.";
41}
42#endif
43
[email protected]8e42ba22011-08-04 21:47:0844void ApplyStyleRangeImpl(gfx::StyleRanges* style_ranges,
[email protected]ff44d712011-07-25 08:42:5245 gfx::StyleRange style_range) {
46 const ui::Range& new_range = style_range.range;
47 // Follow StyleRanges invariant conditions: sorted and non-overlapping ranges.
48 gfx::StyleRanges::iterator i;
[email protected]8e42ba22011-08-04 21:47:0849 for (i = style_ranges->begin(); i != style_ranges->end();) {
[email protected]ff44d712011-07-25 08:42:5250 if (i->range.end() < new_range.start()) {
51 i++;
52 } else if (i->range.start() == new_range.end()) {
53 break;
54 } else if (new_range.Contains(i->range)) {
[email protected]8e42ba22011-08-04 21:47:0855 i = style_ranges->erase(i);
56 if (i == style_ranges->end())
[email protected]ff44d712011-07-25 08:42:5257 break;
58 } else if (i->range.start() < new_range.start() &&
59 i->range.end() > new_range.end()) {
60 // Split the current style into two styles.
61 gfx::StyleRange split_style = gfx::StyleRange(*i);
62 split_style.range.set_end(new_range.start());
[email protected]8e42ba22011-08-04 21:47:0863 i = style_ranges->insert(i, split_style) + 1;
[email protected]ff44d712011-07-25 08:42:5264 i->range.set_start(new_range.end());
65 break;
66 } else if (i->range.start() < new_range.start()) {
67 i->range.set_end(new_range.start());
68 i++;
69 } else if (i->range.end() > new_range.end()) {
70 i->range.set_start(new_range.end());
71 break;
[email protected]f9a221b2011-12-10 20:25:3872 } else {
[email protected]ff44d712011-07-25 08:42:5273 NOTREACHED();
[email protected]f9a221b2011-12-10 20:25:3874 }
[email protected]ff44d712011-07-25 08:42:5275 }
76 // Add the new range in its sorted location.
[email protected]8e42ba22011-08-04 21:47:0877 style_ranges->insert(i, style_range);
[email protected]ff44d712011-07-25 08:42:5278}
79
80} // namespace
81
82namespace gfx {
83
[email protected]67b981562011-12-09 00:35:0584namespace internal {
85
86SkiaTextRenderer::SkiaTextRenderer(Canvas* canvas)
87 : canvas_skia_(canvas->GetSkCanvas()) {
88 DCHECK(canvas_skia_);
89 paint_.setTextEncoding(SkPaint::kGlyphID_TextEncoding);
90 paint_.setStyle(SkPaint::kFill_Style);
91 paint_.setAntiAlias(true);
92 paint_.setSubpixelText(true);
93 paint_.setLCDRenderText(true);
94}
95
96SkiaTextRenderer::~SkiaTextRenderer() {
97}
98
99void SkiaTextRenderer::SetFont(const gfx::Font& font) {
100 SkAutoTUnref<SkTypeface> typeface(
101 SkTypeface::CreateFromName(font.GetFontName().c_str(),
102 SkTypeface::kNormal));
103 if (typeface.get()) {
104 // |paint_| adds its own ref. So don't |release()| it from the ref ptr here.
105 paint_.setTypeface(typeface.get());
106 }
107 paint_.setTextSize(font.GetFontSize());
108}
109
110void SkiaTextRenderer::SetForegroundColor(SkColor foreground) {
111 paint_.setColor(foreground);
112}
113
114void SkiaTextRenderer::DrawPosText(const SkPoint* pos,
115 const uint16* glyphs,
116 size_t glyph_count) {
117 size_t byte_length = glyph_count * sizeof(glyphs[0]);
118 canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_);
119}
120
121// Draw underline and strike through text decorations.
122// Based on |SkCanvas::DrawTextDecorations()| and constants from:
123// third_party/skia/src/core/SkTextFormatParams.h
124void SkiaTextRenderer::DrawDecorations(int x, int y, int width,
125 bool underline, bool strike) {
126 // Fraction of the text size to lower a strike through below the baseline.
127 const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21);
128 // Fraction of the text size to lower an underline below the baseline.
129 const SkScalar kUnderlineOffset = (SK_Scalar1 / 9);
130 // Fraction of the text size to use for a strike through or under-line.
131 const SkScalar kLineThickness = (SK_Scalar1 / 18);
132
133 SkScalar text_size = paint_.getTextSize();
134 SkScalar height = SkScalarMul(text_size, kLineThickness);
135 SkRect r;
136
137 r.fLeft = x;
138 r.fRight = x + width;
139
140 if (underline) {
141 SkScalar offset = SkScalarMulAdd(text_size, kUnderlineOffset, y);
142 r.fTop = offset;
143 r.fBottom = offset + height;
144 canvas_skia_->drawRect(r, paint_);
145 }
146 if (strike) {
147 SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y);
148 r.fTop = offset;
149 r.fBottom = offset + height;
150 canvas_skia_->drawRect(r, paint_);
151 }
152}
153
154} // namespace internal
155
156
[email protected]ff44d712011-07-25 08:42:52157StyleRange::StyleRange()
158 : font(),
159 foreground(SK_ColorBLACK),
160 strike(false),
161 underline(false),
162 range() {
163}
164
[email protected]8e42ba22011-08-04 21:47:08165RenderText::~RenderText() {
166}
167
[email protected]ff44d712011-07-25 08:42:52168void RenderText::SetText(const string16& text) {
[email protected]d3c6b0602011-09-07 19:26:06169 DCHECK(!composition_range_.IsValid());
[email protected]ff44d712011-07-25 08:42:52170 size_t old_text_length = text_.length();
171 text_ = text;
172
173 // Update the style ranges as needed.
174 if (text_.empty()) {
175 style_ranges_.clear();
176 } else if (style_ranges_.empty()) {
177 ApplyDefaultStyle();
178 } else if (text_.length() > old_text_length) {
179 style_ranges_.back().range.set_end(text_.length());
180 } else if (text_.length() < old_text_length) {
181 StyleRanges::iterator i;
182 for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) {
183 if (i->range.start() >= text_.length()) {
[email protected]052ce6bb2011-10-14 19:48:20184 // Style ranges are sorted and non-overlapping, so all the subsequent
185 // style ranges should be out of text_.length() as well.
186 style_ranges_.erase(i, style_ranges_.end());
187 break;
[email protected]ff44d712011-07-25 08:42:52188 }
189 }
[email protected]052ce6bb2011-10-14 19:48:20190 // Since style ranges are sorted and non-overlapping, if there is a style
191 // range ends beyond text_.length, it must be the last one.
[email protected]ff44d712011-07-25 08:42:52192 style_ranges_.back().range.set_end(text_.length());
193 }
194#ifndef NDEBUG
195 CheckStyleRanges(style_ranges_, text_.length());
196#endif
[email protected]f6aaa0f2011-08-11 07:05:46197 cached_bounds_and_offset_valid_ = false;
[email protected]d3c6b0602011-09-07 19:26:06198
199 // Reset selection model. SetText should always followed by SetSelectionModel
200 // or SetCursorPosition in upper layer.
201 SetSelectionModel(SelectionModel(0, 0, SelectionModel::LEADING));
[email protected]6002a4f32011-11-30 10:18:42202
203 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52204}
205
[email protected]0d717602011-08-30 06:21:14206void RenderText::ToggleInsertMode() {
207 insert_mode_ = !insert_mode_;
[email protected]f6aaa0f2011-08-11 07:05:46208 cached_bounds_and_offset_valid_ = false;
209}
210
211void RenderText::SetDisplayRect(const Rect& r) {
212 display_rect_ = r;
213 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25214 UpdateLayout();
[email protected]8e42ba22011-08-04 21:47:08215}
216
[email protected]ff44d712011-07-25 08:42:52217size_t RenderText::GetCursorPosition() const {
[email protected]8e42ba22011-08-04 21:47:08218 return selection_model_.selection_end();
[email protected]ff44d712011-07-25 08:42:52219}
220
[email protected]0d717602011-08-30 06:21:14221void RenderText::SetCursorPosition(size_t position) {
222 MoveCursorTo(position, false);
[email protected]ff44d712011-07-25 08:42:52223}
224
225void RenderText::MoveCursorLeft(BreakType break_type, bool select) {
[email protected]ec7f48d2011-08-09 03:48:50226 SelectionModel position(selection_model());
227 position.set_selection_start(GetCursorPosition());
[email protected]ff44d712011-07-25 08:42:52228 // Cancelling a selection moves to the edge of the selection.
[email protected]ec7f48d2011-08-09 03:48:50229 if (break_type != LINE_BREAK && !EmptySelection() && !select) {
[email protected]ff44d712011-07-25 08:42:52230 // Use the selection start if it is left of the selection end.
[email protected]d3c6b0602011-09-07 19:26:06231 SelectionModel selection_start = GetSelectionModelForSelectionStart();
232 if (GetCursorBounds(selection_start, true).x() <
233 GetCursorBounds(position, true).x())
[email protected]8e42ba22011-08-04 21:47:08234 position = selection_start;
[email protected]ec7f48d2011-08-09 03:48:50235 // For word breaks, use the nearest word boundary left of the selection.
[email protected]ff44d712011-07-25 08:42:52236 if (break_type == WORD_BREAK)
[email protected]ec7f48d2011-08-09 03:48:50237 position = GetLeftSelectionModel(position, break_type);
[email protected]ff44d712011-07-25 08:42:52238 } else {
[email protected]ec7f48d2011-08-09 03:48:50239 position = GetLeftSelectionModel(position, break_type);
[email protected]ff44d712011-07-25 08:42:52240 }
[email protected]ec7f48d2011-08-09 03:48:50241 if (select)
242 position.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08243 MoveCursorTo(position);
[email protected]ff44d712011-07-25 08:42:52244}
245
246void RenderText::MoveCursorRight(BreakType break_type, bool select) {
[email protected]ec7f48d2011-08-09 03:48:50247 SelectionModel position(selection_model());
248 position.set_selection_start(GetCursorPosition());
[email protected]ff44d712011-07-25 08:42:52249 // Cancelling a selection moves to the edge of the selection.
[email protected]ec7f48d2011-08-09 03:48:50250 if (break_type != LINE_BREAK && !EmptySelection() && !select) {
[email protected]ff44d712011-07-25 08:42:52251 // Use the selection start if it is right of the selection end.
[email protected]d3c6b0602011-09-07 19:26:06252 SelectionModel selection_start = GetSelectionModelForSelectionStart();
253 if (GetCursorBounds(selection_start, true).x() >
254 GetCursorBounds(position, true).x())
[email protected]8e42ba22011-08-04 21:47:08255 position = selection_start;
[email protected]ec7f48d2011-08-09 03:48:50256 // For word breaks, use the nearest word boundary right of the selection.
[email protected]ff44d712011-07-25 08:42:52257 if (break_type == WORD_BREAK)
[email protected]ec7f48d2011-08-09 03:48:50258 position = GetRightSelectionModel(position, break_type);
[email protected]ff44d712011-07-25 08:42:52259 } else {
[email protected]ec7f48d2011-08-09 03:48:50260 position = GetRightSelectionModel(position, break_type);
[email protected]ff44d712011-07-25 08:42:52261 }
[email protected]ec7f48d2011-08-09 03:48:50262 if (select)
263 position.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08264 MoveCursorTo(position);
[email protected]ff44d712011-07-25 08:42:52265}
266
[email protected]f9a221b2011-12-10 20:25:38267bool RenderText::MoveCursorTo(const SelectionModel& model) {
268 SelectionModel sel(model);
[email protected]0d717602011-08-30 06:21:14269 size_t text_length = text().length();
270 // Enforce valid selection model components.
271 if (sel.selection_start() > text_length)
272 sel.set_selection_start(text_length);
273 if (sel.selection_end() > text_length)
274 sel.set_selection_end(text_length);
275 // The current model only supports caret positions at valid character indices.
276 if (text_length == 0) {
277 sel.set_caret_pos(0);
278 sel.set_caret_placement(SelectionModel::LEADING);
279 } else if (sel.caret_pos() >= text_length) {
280 SelectionModel end = GetTextDirection() == base::i18n::RIGHT_TO_LEFT ?
281 LeftEndSelectionModel() : RightEndSelectionModel();
282 sel.set_caret_pos(end.caret_pos());
283 sel.set_caret_placement(end.caret_placement());
284 }
[email protected]53c0b1b2011-09-21 20:32:29285
286 if (!IsCursorablePosition(sel.selection_start()) ||
287 !IsCursorablePosition(sel.selection_end()) ||
288 !IsCursorablePosition(sel.caret_pos()))
289 return false;
290
[email protected]0d717602011-08-30 06:21:14291 bool changed = !sel.Equals(selection_model_);
292 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52293 return changed;
294}
295
[email protected]7b3cb4b22011-08-02 18:36:40296bool RenderText::MoveCursorTo(const Point& point, bool select) {
[email protected]8e42ba22011-08-04 21:47:08297 SelectionModel selection = FindCursorPosition(point);
298 if (select)
299 selection.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08300 return MoveCursorTo(selection);
[email protected]ff44d712011-07-25 08:42:52301}
302
[email protected]67e85512011-10-12 20:03:45303bool RenderText::SelectRange(const ui::Range& range) {
304 size_t text_length = text().length();
305 size_t start = std::min(range.start(), text_length);
306 size_t end = std::min(range.end(), text_length);
307
308 if (!IsCursorablePosition(start) || !IsCursorablePosition(end))
309 return false;
310
311 size_t pos = end;
312 SelectionModel::CaretPlacement placement = SelectionModel::LEADING;
313 if (start < end) {
314 pos = GetIndexOfPreviousGrapheme(end);
315 DCHECK_LT(pos, end);
316 placement = SelectionModel::TRAILING;
317 } else if (end == text_length) {
318 SelectionModel boundary = GetTextDirection() == base::i18n::RIGHT_TO_LEFT ?
319 LeftEndSelectionModel() : RightEndSelectionModel();
320 pos = boundary.caret_pos();
321 placement = boundary.caret_placement();
322 }
323 SetSelectionModel(SelectionModel(start, end, pos, placement));
324 return true;
325}
326
[email protected]8e42ba22011-08-04 21:47:08327bool RenderText::IsPointInSelection(const Point& point) {
[email protected]0d717602011-08-30 06:21:14328 if (EmptySelection())
329 return false;
[email protected]8e42ba22011-08-04 21:47:08330 // TODO(xji): should this check whether the point is inside the visual
331 // selection bounds? In case of "abcFED", if "ED" is selected, |point| points
332 // to the right half of 'c', is the point in selection?
333 size_t pos = FindCursorPosition(point).selection_end();
334 return (pos >= MinOfSelection() && pos < MaxOfSelection());
[email protected]ff44d712011-07-25 08:42:52335}
336
337void RenderText::ClearSelection() {
[email protected]ec7f48d2011-08-09 03:48:50338 SelectionModel sel(selection_model());
339 sel.set_selection_start(GetCursorPosition());
340 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52341}
342
343void RenderText::SelectAll() {
[email protected]0d717602011-08-30 06:21:14344 SelectionModel sel(RightEndSelectionModel());
345 sel.set_selection_start(LeftEndSelectionModel().selection_start());
[email protected]8e42ba22011-08-04 21:47:08346 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52347}
348
349void RenderText::SelectWord() {
[email protected]ff44d712011-07-25 08:42:52350 size_t cursor_position = GetCursorPosition();
[email protected]ff44d712011-07-25 08:42:52351
[email protected]53c0b1b2011-09-21 20:32:29352 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
353 bool success = iter.Init();
354 DCHECK(success);
355 if (!success)
356 return;
357
358 size_t selection_start = cursor_position;
359 for (; selection_start != 0; --selection_start) {
360 if (iter.IsStartOfWord(selection_start) ||
361 iter.IsEndOfWord(selection_start))
[email protected]ff44d712011-07-25 08:42:52362 break;
363 }
364
[email protected]53c0b1b2011-09-21 20:32:29365 if (selection_start == cursor_position)
366 ++cursor_position;
367
368 for (; cursor_position < text().length(); ++cursor_position) {
369 if (iter.IsEndOfWord(cursor_position) ||
370 iter.IsStartOfWord(cursor_position))
[email protected]ff44d712011-07-25 08:42:52371 break;
372 }
373
[email protected]0d717602011-08-30 06:21:14374 MoveCursorTo(selection_start, false);
375 MoveCursorTo(cursor_position, true);
[email protected]ff44d712011-07-25 08:42:52376}
377
378const ui::Range& RenderText::GetCompositionRange() const {
379 return composition_range_;
380}
381
382void RenderText::SetCompositionRange(const ui::Range& composition_range) {
383 CHECK(!composition_range.IsValid() ||
384 ui::Range(0, text_.length()).Contains(composition_range));
385 composition_range_.set_end(composition_range.end());
386 composition_range_.set_start(composition_range.start());
[email protected]79791682011-11-09 19:11:25387 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52388}
389
390void RenderText::ApplyStyleRange(StyleRange style_range) {
391 const ui::Range& new_range = style_range.range;
392 if (!new_range.IsValid() || new_range.is_empty())
393 return;
394 CHECK(!new_range.is_reversed());
395 CHECK(ui::Range(0, text_.length()).Contains(new_range));
[email protected]8e42ba22011-08-04 21:47:08396 ApplyStyleRangeImpl(&style_ranges_, style_range);
[email protected]ff44d712011-07-25 08:42:52397#ifndef NDEBUG
398 CheckStyleRanges(style_ranges_, text_.length());
399#endif
[email protected]f6aaa0f2011-08-11 07:05:46400 // TODO(xji): only invalidate if font or underline changes.
401 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25402 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52403}
404
405void RenderText::ApplyDefaultStyle() {
406 style_ranges_.clear();
407 StyleRange style = StyleRange(default_style_);
408 style.range.set_end(text_.length());
409 style_ranges_.push_back(style);
[email protected]f6aaa0f2011-08-11 07:05:46410 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25411 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52412}
413
[email protected]d3c6b0602011-09-07 19:26:06414base::i18n::TextDirection RenderText::GetTextDirection() {
[email protected]0d717602011-08-30 06:21:14415 if (base::i18n::IsRTL())
416 return base::i18n::RIGHT_TO_LEFT;
[email protected]ff44d712011-07-25 08:42:52417 return base::i18n::LEFT_TO_RIGHT;
418}
419
[email protected]8e42ba22011-08-04 21:47:08420int RenderText::GetStringWidth() {
[email protected]f6aaa0f2011-08-11 07:05:46421 return default_style_.font.GetStringWidth(text());
[email protected]ff44d712011-07-25 08:42:52422}
423
[email protected]7b3cb4b22011-08-02 18:36:40424void RenderText::Draw(Canvas* canvas) {
[email protected]592a7ad2011-12-14 05:57:53425 TRACE_EVENT0("gfx", "RenderText::Draw");
426 {
427 TRACE_EVENT0("gfx", "RenderText::EnsureLayout");
428 EnsureLayout();
429 }
[email protected]ff44d712011-07-25 08:42:52430
[email protected]6002a4f32011-11-30 10:18:42431 if (!text().empty()) {
[email protected]592a7ad2011-12-14 05:57:53432 TRACE_EVENT0("gfx", "RenderText::Draw draw text");
[email protected]6002a4f32011-11-30 10:18:42433 DrawSelection(canvas);
434 DrawVisualText(canvas);
[email protected]ff44d712011-07-25 08:42:52435 }
[email protected]6002a4f32011-11-30 10:18:42436 DrawCursor(canvas);
[email protected]ff44d712011-07-25 08:42:52437}
438
[email protected]8e42ba22011-08-04 21:47:08439SelectionModel RenderText::FindCursorPosition(const Point& point) {
[email protected]7b3cb4b22011-08-02 18:36:40440 const Font& font = default_style_.font;
[email protected]ff44d712011-07-25 08:42:52441 int left = 0;
442 int left_pos = 0;
443 int right = font.GetStringWidth(text());
444 int right_pos = text().length();
445
[email protected]f6aaa0f2011-08-11 07:05:46446 int x = point.x() - (display_rect_.x() + GetUpdatedDisplayOffset().x());
[email protected]8e42ba22011-08-04 21:47:08447 if (x <= left) return SelectionModel(left_pos);
448 if (x >= right) return SelectionModel(right_pos);
[email protected]ff44d712011-07-25 08:42:52449 // binary searching the cursor position.
450 // TODO(oshima): use the center of character instead of edge.
[email protected]d3c6b0602011-09-07 19:26:06451 // Binary search may not work for language like Arabic.
[email protected]f9a221b2011-12-10 20:25:38452 while (std::abs(right_pos - left_pos) > 1) {
[email protected]ff44d712011-07-25 08:42:52453 int pivot_pos = left_pos + (right_pos - left_pos) / 2;
454 int pivot = font.GetStringWidth(text().substr(0, pivot_pos));
455 if (pivot < x) {
456 left = pivot;
457 left_pos = pivot_pos;
458 } else if (pivot == x) {
[email protected]8e42ba22011-08-04 21:47:08459 return SelectionModel(pivot_pos);
[email protected]ff44d712011-07-25 08:42:52460 } else {
461 right = pivot;
462 right_pos = pivot_pos;
463 }
464 }
[email protected]8e42ba22011-08-04 21:47:08465 return SelectionModel(left_pos);
[email protected]ff44d712011-07-25 08:42:52466}
467
[email protected]f6aaa0f2011-08-11 07:05:46468const Rect& RenderText::GetUpdatedCursorBounds() {
469 UpdateCachedBoundsAndOffset();
[email protected]8e42ba22011-08-04 21:47:08470 return cursor_bounds_;
471}
472
[email protected]53c0b1b2011-09-21 20:32:29473size_t RenderText::GetIndexOfNextGrapheme(size_t position) {
474 return IndexOfAdjacentGrapheme(position, true);
475}
476
[email protected]04b7bf322011-10-03 19:08:46477SelectionModel RenderText::GetSelectionModelForSelectionStart() {
478 size_t selection_start = GetSelectionStart();
479 size_t selection_end = GetCursorPosition();
480 if (selection_start < selection_end)
481 return SelectionModel(selection_start,
482 selection_start,
483 SelectionModel::LEADING);
484 else if (selection_start > selection_end)
485 return SelectionModel(selection_start,
486 GetIndexOfPreviousGrapheme(selection_start),
487 SelectionModel::TRAILING);
488 return selection_model_;
489}
490
[email protected]8e42ba22011-08-04 21:47:08491RenderText::RenderText()
492 : text_(),
493 selection_model_(),
494 cursor_bounds_(),
[email protected]8e42ba22011-08-04 21:47:08495 cursor_visible_(false),
496 insert_mode_(true),
[email protected]d3c6b0602011-09-07 19:26:06497 composition_range_(ui::Range::InvalidRange()),
[email protected]8e42ba22011-08-04 21:47:08498 style_ranges_(),
499 default_style_(),
500 display_rect_(),
[email protected]f6aaa0f2011-08-11 07:05:46501 display_offset_(),
502 cached_bounds_and_offset_valid_(false) {
503}
504
505const Point& RenderText::GetUpdatedDisplayOffset() {
506 UpdateCachedBoundsAndOffset();
507 return display_offset_;
[email protected]8e42ba22011-08-04 21:47:08508}
509
[email protected]ec7f48d2011-08-09 03:48:50510SelectionModel RenderText::GetLeftSelectionModel(const SelectionModel& current,
511 BreakType break_type) {
512 if (break_type == LINE_BREAK)
[email protected]67e85512011-10-12 20:03:45513 return LeftEndSelectionModel();
[email protected]f9a221b2011-12-10 20:25:38514 size_t pos = std::max<int>(current.selection_end() - 1, 0);
[email protected]ec7f48d2011-08-09 03:48:50515 if (break_type == CHARACTER_BREAK)
516 return SelectionModel(pos, pos, SelectionModel::LEADING);
517
[email protected]d3c6b0602011-09-07 19:26:06518 // Notes: We always iterate words from the beginning.
[email protected]ff44d712011-07-25 08:42:52519 // This is probably fast enough for our usage, but we may
520 // want to modify WordIterator so that it can start from the
521 // middle of string and advance backwards.
522 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
523 bool success = iter.Init();
524 DCHECK(success);
[email protected]ec7f48d2011-08-09 03:48:50525 if (!success)
526 return current;
[email protected]ff44d712011-07-25 08:42:52527 while (iter.Advance()) {
528 if (iter.IsWord()) {
529 size_t begin = iter.pos() - iter.GetString().length();
[email protected]ec7f48d2011-08-09 03:48:50530 if (begin == current.selection_end()) {
[email protected]ff44d712011-07-25 08:42:52531 // The cursor is at the beginning of a word.
532 // Move to previous word.
533 break;
[email protected]ec7f48d2011-08-09 03:48:50534 } else if (iter.pos() >= current.selection_end()) {
[email protected]ff44d712011-07-25 08:42:52535 // The cursor is in the middle or at the end of a word.
536 // Move to the top of current word.
[email protected]ec7f48d2011-08-09 03:48:50537 pos = begin;
[email protected]ff44d712011-07-25 08:42:52538 break;
539 } else {
[email protected]ec7f48d2011-08-09 03:48:50540 pos = iter.pos() - iter.GetString().length();
[email protected]ff44d712011-07-25 08:42:52541 }
542 }
543 }
544
[email protected]ec7f48d2011-08-09 03:48:50545 return SelectionModel(pos, pos, SelectionModel::LEADING);
[email protected]ff44d712011-07-25 08:42:52546}
547
[email protected]ec7f48d2011-08-09 03:48:50548SelectionModel RenderText::GetRightSelectionModel(const SelectionModel& current,
549 BreakType break_type) {
[email protected]d3c6b0602011-09-07 19:26:06550 if (text_.empty())
551 return SelectionModel(0, 0, SelectionModel::LEADING);
[email protected]ec7f48d2011-08-09 03:48:50552 if (break_type == LINE_BREAK)
[email protected]67e85512011-10-12 20:03:45553 return RightEndSelectionModel();
[email protected]ec7f48d2011-08-09 03:48:50554 size_t pos = std::min(current.selection_end() + 1, text().length());
555 if (break_type == CHARACTER_BREAK)
556 return SelectionModel(pos, pos, SelectionModel::LEADING);
[email protected]8e42ba22011-08-04 21:47:08557
[email protected]ff44d712011-07-25 08:42:52558 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
559 bool success = iter.Init();
560 DCHECK(success);
[email protected]ec7f48d2011-08-09 03:48:50561 if (!success)
562 return current;
[email protected]ff44d712011-07-25 08:42:52563 while (iter.Advance()) {
564 pos = iter.pos();
[email protected]ec7f48d2011-08-09 03:48:50565 if (iter.IsWord() && pos > current.selection_end())
[email protected]ff44d712011-07-25 08:42:52566 break;
[email protected]ff44d712011-07-25 08:42:52567 }
[email protected]ec7f48d2011-08-09 03:48:50568 return SelectionModel(pos, pos, SelectionModel::LEADING);
569}
570
[email protected]0d717602011-08-30 06:21:14571SelectionModel RenderText::LeftEndSelectionModel() {
572 return SelectionModel(0, 0, SelectionModel::LEADING);
573}
574
575SelectionModel RenderText::RightEndSelectionModel() {
576 size_t cursor = text().length();
577 size_t caret_pos = GetIndexOfPreviousGrapheme(cursor);
578 SelectionModel::CaretPlacement placement = (caret_pos == cursor) ?
579 SelectionModel::LEADING : SelectionModel::TRAILING;
580 return SelectionModel(cursor, caret_pos, placement);
581}
582
[email protected]6002a4f32011-11-30 10:18:42583void RenderText::SetSelectionModel(const SelectionModel& model) {
584 DCHECK_LE(model.selection_start(), text().length());
585 selection_model_.set_selection_start(model.selection_start());
586 DCHECK_LE(model.selection_end(), text().length());
587 selection_model_.set_selection_end(model.selection_end());
[email protected]f9a221b2011-12-10 20:25:38588 DCHECK_LT(model.caret_pos(), std::max<size_t>(text().length(), 1));
[email protected]6002a4f32011-11-30 10:18:42589 selection_model_.set_caret_pos(model.caret_pos());
590 selection_model_.set_caret_placement(model.caret_placement());
591
592 cached_bounds_and_offset_valid_ = false;
[email protected]0d717602011-08-30 06:21:14593}
594
[email protected]6002a4f32011-11-30 10:18:42595size_t RenderText::GetIndexOfPreviousGrapheme(size_t position) {
596 return IndexOfAdjacentGrapheme(position, false);
[email protected]ff44d712011-07-25 08:42:52597}
598
[email protected]8e42ba22011-08-04 21:47:08599void RenderText::ApplyCompositionAndSelectionStyles(
600 StyleRanges* style_ranges) const {
601 // TODO(msw): This pattern ought to be reconsidered; what about composition
602 // and selection overlaps, retain existing local style features?
603 // Apply a composition style override to a copy of the style ranges.
604 if (composition_range_.IsValid() && !composition_range_.is_empty()) {
605 StyleRange composition_style(default_style_);
606 composition_style.underline = true;
607 composition_style.range.set_start(composition_range_.start());
608 composition_style.range.set_end(composition_range_.end());
609 ApplyStyleRangeImpl(style_ranges, composition_style);
610 }
611 // Apply a selection style override to a copy of the style ranges.
612 if (!EmptySelection()) {
613 StyleRange selection_style(default_style_);
614 selection_style.foreground = kSelectedTextColor;
615 selection_style.range.set_start(MinOfSelection());
616 selection_style.range.set_end(MaxOfSelection());
617 ApplyStyleRangeImpl(style_ranges, selection_style);
618 }
[email protected]ff44d712011-07-25 08:42:52619}
620
[email protected]0d717602011-08-30 06:21:14621Point RenderText::ToTextPoint(const Point& point) {
622 Point p(point.Subtract(display_rect().origin()));
623 p = p.Subtract(GetUpdatedDisplayOffset());
624 if (base::i18n::IsRTL())
625 p.Offset(GetStringWidth() - display_rect().width() + 1, 0);
626 return p;
627}
628
629Point RenderText::ToViewPoint(const Point& point) {
630 Point p(point.Add(display_rect().origin()));
631 p = p.Add(GetUpdatedDisplayOffset());
632 if (base::i18n::IsRTL())
633 p.Offset(display_rect().width() - GetStringWidth() - 1, 0);
634 return p;
635}
636
[email protected]67b981562011-12-09 00:35:05637Point RenderText::GetOriginForSkiaDrawing() {
638 Point origin(ToViewPoint(Point()));
639 // TODO(msw): Establish a vertical baseline for strings of mixed font heights.
640 const Font& font = default_style().font;
641 size_t height = font.GetHeight();
642 // Center the text vertically in the display area.
643 origin.Offset(0, (display_rect().height() - height) / 2);
644 // Offset by the font size to account for Skia expecting y to be the bottom.
645 origin.Offset(0, font.GetFontSize());
646 return origin;
647}
648
[email protected]0d717602011-08-30 06:21:14649void RenderText::MoveCursorTo(size_t position, bool select) {
650 size_t cursor = std::min(position, text().length());
651 size_t caret_pos = GetIndexOfPreviousGrapheme(cursor);
652 SelectionModel::CaretPlacement placement = (caret_pos == cursor) ?
653 SelectionModel::LEADING : SelectionModel::TRAILING;
654 size_t selection_start = select ? GetSelectionStart() : cursor;
[email protected]53c0b1b2011-09-21 20:32:29655 if (IsCursorablePosition(cursor)) {
656 SelectionModel sel(selection_start, cursor, caret_pos, placement);
657 SetSelectionModel(sel);
658 }
[email protected]8e42ba22011-08-04 21:47:08659}
660
[email protected]f6aaa0f2011-08-11 07:05:46661void RenderText::UpdateCachedBoundsAndOffset() {
662 if (cached_bounds_and_offset_valid_)
663 return;
664 // First, set the valid flag true to calculate the current cursor bounds using
665 // the stale |display_offset_|. Applying |delta_offset| at the end of this
666 // function will set |cursor_bounds_| and |display_offset_| to correct values.
667 cached_bounds_and_offset_valid_ = true;
668 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_);
[email protected]8e42ba22011-08-04 21:47:08669 // Update |display_offset_| to ensure the current cursor is visible.
670 int display_width = display_rect_.width();
671 int string_width = GetStringWidth();
[email protected]f6aaa0f2011-08-11 07:05:46672 int delta_offset = 0;
[email protected]8e42ba22011-08-04 21:47:08673 if (string_width < display_width) {
674 // Show all text whenever the text fits to the size.
[email protected]f6aaa0f2011-08-11 07:05:46675 delta_offset = -display_offset_.x();
[email protected]65788132011-09-07 20:20:23676 } else if (cursor_bounds_.right() >= display_rect_.right()) {
[email protected]d3c6b0602011-09-07 19:26:06677 // TODO(xji): when the character overflow is a RTL character, currently, if
678 // we pan cursor at the rightmost position, the entered RTL character is not
679 // displayed. Should pan cursor to show the last logical characters.
680 //
[email protected]8e42ba22011-08-04 21:47:08681 // Pan to show the cursor when it overflows to the right,
[email protected]65788132011-09-07 20:20:23682 delta_offset = display_rect_.right() - cursor_bounds_.right() - 1;
[email protected]f6aaa0f2011-08-11 07:05:46683 } else if (cursor_bounds_.x() < display_rect_.x()) {
[email protected]d3c6b0602011-09-07 19:26:06684 // TODO(xji): have similar problem as above when overflow character is a
685 // LTR character.
686 //
[email protected]8e42ba22011-08-04 21:47:08687 // Pan to show the cursor when it overflows to the left.
[email protected]f6aaa0f2011-08-11 07:05:46688 delta_offset = display_rect_.x() - cursor_bounds_.x();
[email protected]8e42ba22011-08-04 21:47:08689 }
[email protected]f6aaa0f2011-08-11 07:05:46690 display_offset_.Offset(delta_offset, 0);
691 cursor_bounds_.Offset(delta_offset, 0);
[email protected]ff44d712011-07-25 08:42:52692}
693
[email protected]6002a4f32011-11-30 10:18:42694void RenderText::DrawSelection(Canvas* canvas) {
[email protected]592a7ad2011-12-14 05:57:53695 TRACE_EVENT0("gfx", "RenderText::DrawSelection");
[email protected]6002a4f32011-11-30 10:18:42696 std::vector<Rect> sel;
697 GetSubstringBounds(GetSelectionStart(), GetCursorPosition(), &sel);
698 SkColor color = focused() ? kFocusedSelectionColor : kUnfocusedSelectionColor;
699 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
700 canvas->FillRect(color, *i);
701}
702
703void RenderText::DrawCursor(Canvas* canvas) {
[email protected]592a7ad2011-12-14 05:57:53704 TRACE_EVENT0("gfx", "RenderText::DrawCursor");
[email protected]6002a4f32011-11-30 10:18:42705 // Paint cursor. Replace cursor is drawn as rectangle for now.
706 // TODO(msw): Draw a better cursor with a better indication of association.
707 if (cursor_visible() && focused()) {
708 Rect r(GetUpdatedCursorBounds());
709 canvas->DrawRectInt(kCursorColor, r.x(), r.y(), r.width(), r.height());
710 }
711}
712
[email protected]ff44d712011-07-25 08:42:52713} // namespace gfx