blob: 94bfd232ab4c5b94d9bd02e05bdcddecdcffa1ef [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
[email protected]cd8e5522011-12-15 00:24:1099void SkiaTextRenderer::SetTypeface(SkTypeface* typeface) {
100 paint_.setTypeface(typeface);
101}
102
103void SkiaTextRenderer::SetTextSize(int size) {
104 paint_.setTextSize(size);
105}
106
[email protected]67b981562011-12-09 00:35:05107void SkiaTextRenderer::SetFont(const gfx::Font& font) {
108 SkAutoTUnref<SkTypeface> typeface(
109 SkTypeface::CreateFromName(font.GetFontName().c_str(),
110 SkTypeface::kNormal));
111 if (typeface.get()) {
112 // |paint_| adds its own ref. So don't |release()| it from the ref ptr here.
[email protected]cd8e5522011-12-15 00:24:10113 SetTypeface(typeface.get());
[email protected]67b981562011-12-09 00:35:05114 }
[email protected]cd8e5522011-12-15 00:24:10115 SetTextSize(font.GetFontSize());
[email protected]67b981562011-12-09 00:35:05116}
117
118void SkiaTextRenderer::SetForegroundColor(SkColor foreground) {
119 paint_.setColor(foreground);
120}
121
122void SkiaTextRenderer::DrawPosText(const SkPoint* pos,
123 const uint16* glyphs,
124 size_t glyph_count) {
125 size_t byte_length = glyph_count * sizeof(glyphs[0]);
126 canvas_skia_->drawPosText(&glyphs[0], byte_length, &pos[0], paint_);
127}
128
129// Draw underline and strike through text decorations.
130// Based on |SkCanvas::DrawTextDecorations()| and constants from:
131// third_party/skia/src/core/SkTextFormatParams.h
132void SkiaTextRenderer::DrawDecorations(int x, int y, int width,
133 bool underline, bool strike) {
134 // Fraction of the text size to lower a strike through below the baseline.
135 const SkScalar kStrikeThroughOffset = (-SK_Scalar1 * 6 / 21);
136 // Fraction of the text size to lower an underline below the baseline.
137 const SkScalar kUnderlineOffset = (SK_Scalar1 / 9);
138 // Fraction of the text size to use for a strike through or under-line.
139 const SkScalar kLineThickness = (SK_Scalar1 / 18);
140
141 SkScalar text_size = paint_.getTextSize();
142 SkScalar height = SkScalarMul(text_size, kLineThickness);
143 SkRect r;
144
145 r.fLeft = x;
146 r.fRight = x + width;
147
148 if (underline) {
149 SkScalar offset = SkScalarMulAdd(text_size, kUnderlineOffset, y);
150 r.fTop = offset;
151 r.fBottom = offset + height;
152 canvas_skia_->drawRect(r, paint_);
153 }
154 if (strike) {
155 SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y);
156 r.fTop = offset;
157 r.fBottom = offset + height;
158 canvas_skia_->drawRect(r, paint_);
159 }
160}
161
162} // namespace internal
163
164
[email protected]ff44d712011-07-25 08:42:52165StyleRange::StyleRange()
[email protected]2bb1b7e2011-12-20 20:53:29166 : font(),
167 foreground(SK_ColorBLACK),
[email protected]ff44d712011-07-25 08:42:52168 strike(false),
[email protected]2bb1b7e2011-12-20 20:53:29169 underline(false),
170 range() {
[email protected]ff44d712011-07-25 08:42:52171}
172
[email protected]8e42ba22011-08-04 21:47:08173RenderText::~RenderText() {
174}
175
[email protected]ff44d712011-07-25 08:42:52176void RenderText::SetText(const string16& text) {
[email protected]d3c6b0602011-09-07 19:26:06177 DCHECK(!composition_range_.IsValid());
[email protected]ff44d712011-07-25 08:42:52178 size_t old_text_length = text_.length();
179 text_ = text;
180
181 // Update the style ranges as needed.
182 if (text_.empty()) {
183 style_ranges_.clear();
184 } else if (style_ranges_.empty()) {
185 ApplyDefaultStyle();
186 } else if (text_.length() > old_text_length) {
187 style_ranges_.back().range.set_end(text_.length());
188 } else if (text_.length() < old_text_length) {
189 StyleRanges::iterator i;
190 for (i = style_ranges_.begin(); i != style_ranges_.end(); i++) {
191 if (i->range.start() >= text_.length()) {
[email protected]052ce6bb2011-10-14 19:48:20192 // Style ranges are sorted and non-overlapping, so all the subsequent
193 // style ranges should be out of text_.length() as well.
194 style_ranges_.erase(i, style_ranges_.end());
195 break;
[email protected]ff44d712011-07-25 08:42:52196 }
197 }
[email protected]052ce6bb2011-10-14 19:48:20198 // Since style ranges are sorted and non-overlapping, if there is a style
199 // range ends beyond text_.length, it must be the last one.
[email protected]ff44d712011-07-25 08:42:52200 style_ranges_.back().range.set_end(text_.length());
201 }
202#ifndef NDEBUG
203 CheckStyleRanges(style_ranges_, text_.length());
204#endif
[email protected]f6aaa0f2011-08-11 07:05:46205 cached_bounds_and_offset_valid_ = false;
[email protected]d3c6b0602011-09-07 19:26:06206
207 // Reset selection model. SetText should always followed by SetSelectionModel
208 // or SetCursorPosition in upper layer.
209 SetSelectionModel(SelectionModel(0, 0, SelectionModel::LEADING));
[email protected]6002a4f32011-11-30 10:18:42210
211 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52212}
213
[email protected]0d717602011-08-30 06:21:14214void RenderText::ToggleInsertMode() {
215 insert_mode_ = !insert_mode_;
[email protected]f6aaa0f2011-08-11 07:05:46216 cached_bounds_and_offset_valid_ = false;
217}
218
219void RenderText::SetDisplayRect(const Rect& r) {
220 display_rect_ = r;
221 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25222 UpdateLayout();
[email protected]8e42ba22011-08-04 21:47:08223}
224
[email protected]ff44d712011-07-25 08:42:52225size_t RenderText::GetCursorPosition() const {
[email protected]8e42ba22011-08-04 21:47:08226 return selection_model_.selection_end();
[email protected]ff44d712011-07-25 08:42:52227}
228
[email protected]0d717602011-08-30 06:21:14229void RenderText::SetCursorPosition(size_t position) {
230 MoveCursorTo(position, false);
[email protected]ff44d712011-07-25 08:42:52231}
232
233void RenderText::MoveCursorLeft(BreakType break_type, bool select) {
[email protected]ec7f48d2011-08-09 03:48:50234 SelectionModel position(selection_model());
235 position.set_selection_start(GetCursorPosition());
[email protected]ff44d712011-07-25 08:42:52236 // Cancelling a selection moves to the edge of the selection.
[email protected]ec7f48d2011-08-09 03:48:50237 if (break_type != LINE_BREAK && !EmptySelection() && !select) {
[email protected]ff44d712011-07-25 08:42:52238 // Use the selection start if it is left of the selection end.
[email protected]d3c6b0602011-09-07 19:26:06239 SelectionModel selection_start = GetSelectionModelForSelectionStart();
240 if (GetCursorBounds(selection_start, true).x() <
241 GetCursorBounds(position, true).x())
[email protected]8e42ba22011-08-04 21:47:08242 position = selection_start;
[email protected]ec7f48d2011-08-09 03:48:50243 // For word breaks, use the nearest word boundary left of the selection.
[email protected]ff44d712011-07-25 08:42:52244 if (break_type == WORD_BREAK)
[email protected]ec7f48d2011-08-09 03:48:50245 position = GetLeftSelectionModel(position, break_type);
[email protected]ff44d712011-07-25 08:42:52246 } else {
[email protected]ec7f48d2011-08-09 03:48:50247 position = GetLeftSelectionModel(position, break_type);
[email protected]ff44d712011-07-25 08:42:52248 }
[email protected]ec7f48d2011-08-09 03:48:50249 if (select)
250 position.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08251 MoveCursorTo(position);
[email protected]ff44d712011-07-25 08:42:52252}
253
254void RenderText::MoveCursorRight(BreakType break_type, bool select) {
[email protected]ec7f48d2011-08-09 03:48:50255 SelectionModel position(selection_model());
256 position.set_selection_start(GetCursorPosition());
[email protected]ff44d712011-07-25 08:42:52257 // Cancelling a selection moves to the edge of the selection.
[email protected]ec7f48d2011-08-09 03:48:50258 if (break_type != LINE_BREAK && !EmptySelection() && !select) {
[email protected]ff44d712011-07-25 08:42:52259 // Use the selection start if it is right of the selection end.
[email protected]d3c6b0602011-09-07 19:26:06260 SelectionModel selection_start = GetSelectionModelForSelectionStart();
261 if (GetCursorBounds(selection_start, true).x() >
262 GetCursorBounds(position, true).x())
[email protected]8e42ba22011-08-04 21:47:08263 position = selection_start;
[email protected]ec7f48d2011-08-09 03:48:50264 // For word breaks, use the nearest word boundary right of the selection.
[email protected]ff44d712011-07-25 08:42:52265 if (break_type == WORD_BREAK)
[email protected]ec7f48d2011-08-09 03:48:50266 position = GetRightSelectionModel(position, break_type);
[email protected]ff44d712011-07-25 08:42:52267 } else {
[email protected]ec7f48d2011-08-09 03:48:50268 position = GetRightSelectionModel(position, break_type);
[email protected]ff44d712011-07-25 08:42:52269 }
[email protected]ec7f48d2011-08-09 03:48:50270 if (select)
271 position.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08272 MoveCursorTo(position);
[email protected]ff44d712011-07-25 08:42:52273}
274
[email protected]f9a221b2011-12-10 20:25:38275bool RenderText::MoveCursorTo(const SelectionModel& model) {
276 SelectionModel sel(model);
[email protected]0d717602011-08-30 06:21:14277 size_t text_length = text().length();
278 // Enforce valid selection model components.
279 if (sel.selection_start() > text_length)
280 sel.set_selection_start(text_length);
281 if (sel.selection_end() > text_length)
282 sel.set_selection_end(text_length);
283 // The current model only supports caret positions at valid character indices.
284 if (text_length == 0) {
285 sel.set_caret_pos(0);
286 sel.set_caret_placement(SelectionModel::LEADING);
287 } else if (sel.caret_pos() >= text_length) {
288 SelectionModel end = GetTextDirection() == base::i18n::RIGHT_TO_LEFT ?
289 LeftEndSelectionModel() : RightEndSelectionModel();
290 sel.set_caret_pos(end.caret_pos());
291 sel.set_caret_placement(end.caret_placement());
292 }
[email protected]53c0b1b2011-09-21 20:32:29293
294 if (!IsCursorablePosition(sel.selection_start()) ||
295 !IsCursorablePosition(sel.selection_end()) ||
296 !IsCursorablePosition(sel.caret_pos()))
297 return false;
298
[email protected]0d717602011-08-30 06:21:14299 bool changed = !sel.Equals(selection_model_);
300 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52301 return changed;
302}
303
[email protected]7b3cb4b22011-08-02 18:36:40304bool RenderText::MoveCursorTo(const Point& point, bool select) {
[email protected]8e42ba22011-08-04 21:47:08305 SelectionModel selection = FindCursorPosition(point);
306 if (select)
307 selection.set_selection_start(GetSelectionStart());
[email protected]8e42ba22011-08-04 21:47:08308 return MoveCursorTo(selection);
[email protected]ff44d712011-07-25 08:42:52309}
310
[email protected]67e85512011-10-12 20:03:45311bool RenderText::SelectRange(const ui::Range& range) {
312 size_t text_length = text().length();
313 size_t start = std::min(range.start(), text_length);
314 size_t end = std::min(range.end(), text_length);
315
316 if (!IsCursorablePosition(start) || !IsCursorablePosition(end))
317 return false;
318
319 size_t pos = end;
320 SelectionModel::CaretPlacement placement = SelectionModel::LEADING;
321 if (start < end) {
322 pos = GetIndexOfPreviousGrapheme(end);
323 DCHECK_LT(pos, end);
324 placement = SelectionModel::TRAILING;
325 } else if (end == text_length) {
326 SelectionModel boundary = GetTextDirection() == base::i18n::RIGHT_TO_LEFT ?
327 LeftEndSelectionModel() : RightEndSelectionModel();
328 pos = boundary.caret_pos();
329 placement = boundary.caret_placement();
330 }
331 SetSelectionModel(SelectionModel(start, end, pos, placement));
332 return true;
333}
334
[email protected]8e42ba22011-08-04 21:47:08335bool RenderText::IsPointInSelection(const Point& point) {
[email protected]0d717602011-08-30 06:21:14336 if (EmptySelection())
337 return false;
[email protected]8e42ba22011-08-04 21:47:08338 // TODO(xji): should this check whether the point is inside the visual
339 // selection bounds? In case of "abcFED", if "ED" is selected, |point| points
340 // to the right half of 'c', is the point in selection?
341 size_t pos = FindCursorPosition(point).selection_end();
342 return (pos >= MinOfSelection() && pos < MaxOfSelection());
[email protected]ff44d712011-07-25 08:42:52343}
344
345void RenderText::ClearSelection() {
[email protected]ec7f48d2011-08-09 03:48:50346 SelectionModel sel(selection_model());
347 sel.set_selection_start(GetCursorPosition());
348 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52349}
350
351void RenderText::SelectAll() {
[email protected]0d717602011-08-30 06:21:14352 SelectionModel sel(RightEndSelectionModel());
353 sel.set_selection_start(LeftEndSelectionModel().selection_start());
[email protected]8e42ba22011-08-04 21:47:08354 SetSelectionModel(sel);
[email protected]ff44d712011-07-25 08:42:52355}
356
357void RenderText::SelectWord() {
[email protected]ff44d712011-07-25 08:42:52358 size_t cursor_position = GetCursorPosition();
[email protected]ff44d712011-07-25 08:42:52359
[email protected]53c0b1b2011-09-21 20:32:29360 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
361 bool success = iter.Init();
362 DCHECK(success);
363 if (!success)
364 return;
365
366 size_t selection_start = cursor_position;
367 for (; selection_start != 0; --selection_start) {
368 if (iter.IsStartOfWord(selection_start) ||
369 iter.IsEndOfWord(selection_start))
[email protected]ff44d712011-07-25 08:42:52370 break;
371 }
372
[email protected]53c0b1b2011-09-21 20:32:29373 if (selection_start == cursor_position)
374 ++cursor_position;
375
376 for (; cursor_position < text().length(); ++cursor_position) {
377 if (iter.IsEndOfWord(cursor_position) ||
378 iter.IsStartOfWord(cursor_position))
[email protected]ff44d712011-07-25 08:42:52379 break;
380 }
381
[email protected]0d717602011-08-30 06:21:14382 MoveCursorTo(selection_start, false);
383 MoveCursorTo(cursor_position, true);
[email protected]ff44d712011-07-25 08:42:52384}
385
386const ui::Range& RenderText::GetCompositionRange() const {
387 return composition_range_;
388}
389
390void RenderText::SetCompositionRange(const ui::Range& composition_range) {
391 CHECK(!composition_range.IsValid() ||
392 ui::Range(0, text_.length()).Contains(composition_range));
393 composition_range_.set_end(composition_range.end());
394 composition_range_.set_start(composition_range.start());
[email protected]79791682011-11-09 19:11:25395 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52396}
397
398void RenderText::ApplyStyleRange(StyleRange style_range) {
399 const ui::Range& new_range = style_range.range;
400 if (!new_range.IsValid() || new_range.is_empty())
401 return;
402 CHECK(!new_range.is_reversed());
403 CHECK(ui::Range(0, text_.length()).Contains(new_range));
[email protected]8e42ba22011-08-04 21:47:08404 ApplyStyleRangeImpl(&style_ranges_, style_range);
[email protected]ff44d712011-07-25 08:42:52405#ifndef NDEBUG
406 CheckStyleRanges(style_ranges_, text_.length());
407#endif
[email protected]f6aaa0f2011-08-11 07:05:46408 // TODO(xji): only invalidate if font or underline changes.
409 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25410 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52411}
412
413void RenderText::ApplyDefaultStyle() {
414 style_ranges_.clear();
415 StyleRange style = StyleRange(default_style_);
416 style.range.set_end(text_.length());
417 style_ranges_.push_back(style);
[email protected]f6aaa0f2011-08-11 07:05:46418 cached_bounds_and_offset_valid_ = false;
[email protected]79791682011-11-09 19:11:25419 UpdateLayout();
[email protected]ff44d712011-07-25 08:42:52420}
421
[email protected]d3c6b0602011-09-07 19:26:06422base::i18n::TextDirection RenderText::GetTextDirection() {
[email protected]0d717602011-08-30 06:21:14423 if (base::i18n::IsRTL())
424 return base::i18n::RIGHT_TO_LEFT;
[email protected]ff44d712011-07-25 08:42:52425 return base::i18n::LEFT_TO_RIGHT;
426}
427
[email protected]2bb1b7e2011-12-20 20:53:29428int RenderText::GetStringWidth() {
429 return default_style_.font.GetStringWidth(text());
430}
431
[email protected]7b3cb4b22011-08-02 18:36:40432void RenderText::Draw(Canvas* canvas) {
[email protected]592a7ad2011-12-14 05:57:53433 TRACE_EVENT0("gfx", "RenderText::Draw");
434 {
435 TRACE_EVENT0("gfx", "RenderText::EnsureLayout");
436 EnsureLayout();
437 }
[email protected]ff44d712011-07-25 08:42:52438
[email protected]6002a4f32011-11-30 10:18:42439 if (!text().empty()) {
[email protected]592a7ad2011-12-14 05:57:53440 TRACE_EVENT0("gfx", "RenderText::Draw draw text");
[email protected]6002a4f32011-11-30 10:18:42441 DrawSelection(canvas);
442 DrawVisualText(canvas);
[email protected]ff44d712011-07-25 08:42:52443 }
[email protected]6002a4f32011-11-30 10:18:42444 DrawCursor(canvas);
[email protected]ff44d712011-07-25 08:42:52445}
446
[email protected]2bb1b7e2011-12-20 20:53:29447SelectionModel RenderText::FindCursorPosition(const Point& point) {
448 const Font& font = default_style_.font;
449 int left = 0;
450 int left_pos = 0;
451 int right = font.GetStringWidth(text());
452 int right_pos = text().length();
453
454 int x = point.x() - (display_rect_.x() + GetUpdatedDisplayOffset().x());
455 if (x <= left) return SelectionModel(left_pos);
456 if (x >= right) return SelectionModel(right_pos);
457 // binary searching the cursor position.
458 // TODO(oshima): use the center of character instead of edge.
459 // Binary search may not work for language like Arabic.
460 while (std::abs(right_pos - left_pos) > 1) {
461 int pivot_pos = left_pos + (right_pos - left_pos) / 2;
462 int pivot = font.GetStringWidth(text().substr(0, pivot_pos));
463 if (pivot < x) {
464 left = pivot;
465 left_pos = pivot_pos;
466 } else if (pivot == x) {
467 return SelectionModel(pivot_pos);
468 } else {
469 right = pivot;
470 right_pos = pivot_pos;
471 }
472 }
473 return SelectionModel(left_pos);
474}
475
[email protected]f6aaa0f2011-08-11 07:05:46476const Rect& RenderText::GetUpdatedCursorBounds() {
477 UpdateCachedBoundsAndOffset();
[email protected]8e42ba22011-08-04 21:47:08478 return cursor_bounds_;
479}
480
[email protected]53c0b1b2011-09-21 20:32:29481size_t RenderText::GetIndexOfNextGrapheme(size_t position) {
482 return IndexOfAdjacentGrapheme(position, true);
483}
484
[email protected]04b7bf322011-10-03 19:08:46485SelectionModel RenderText::GetSelectionModelForSelectionStart() {
486 size_t selection_start = GetSelectionStart();
487 size_t selection_end = GetCursorPosition();
488 if (selection_start < selection_end)
489 return SelectionModel(selection_start,
490 selection_start,
491 SelectionModel::LEADING);
492 else if (selection_start > selection_end)
493 return SelectionModel(selection_start,
494 GetIndexOfPreviousGrapheme(selection_start),
495 SelectionModel::TRAILING);
496 return selection_model_;
497}
498
[email protected]8e42ba22011-08-04 21:47:08499RenderText::RenderText()
500 : text_(),
501 selection_model_(),
502 cursor_bounds_(),
[email protected]8e42ba22011-08-04 21:47:08503 cursor_visible_(false),
504 insert_mode_(true),
[email protected]5abe6302011-12-20 23:44:32505 focused_(false),
[email protected]d3c6b0602011-09-07 19:26:06506 composition_range_(ui::Range::InvalidRange()),
[email protected]8e42ba22011-08-04 21:47:08507 style_ranges_(),
508 default_style_(),
509 display_rect_(),
[email protected]f6aaa0f2011-08-11 07:05:46510 display_offset_(),
511 cached_bounds_and_offset_valid_(false) {
512}
513
514const Point& RenderText::GetUpdatedDisplayOffset() {
515 UpdateCachedBoundsAndOffset();
516 return display_offset_;
[email protected]8e42ba22011-08-04 21:47:08517}
518
[email protected]ec7f48d2011-08-09 03:48:50519SelectionModel RenderText::GetLeftSelectionModel(const SelectionModel& current,
520 BreakType break_type) {
521 if (break_type == LINE_BREAK)
[email protected]67e85512011-10-12 20:03:45522 return LeftEndSelectionModel();
[email protected]f9a221b2011-12-10 20:25:38523 size_t pos = std::max<int>(current.selection_end() - 1, 0);
[email protected]ec7f48d2011-08-09 03:48:50524 if (break_type == CHARACTER_BREAK)
525 return SelectionModel(pos, pos, SelectionModel::LEADING);
526
[email protected]d3c6b0602011-09-07 19:26:06527 // Notes: We always iterate words from the beginning.
[email protected]ff44d712011-07-25 08:42:52528 // This is probably fast enough for our usage, but we may
529 // want to modify WordIterator so that it can start from the
530 // middle of string and advance backwards.
531 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
532 bool success = iter.Init();
533 DCHECK(success);
[email protected]ec7f48d2011-08-09 03:48:50534 if (!success)
535 return current;
[email protected]ff44d712011-07-25 08:42:52536 while (iter.Advance()) {
537 if (iter.IsWord()) {
538 size_t begin = iter.pos() - iter.GetString().length();
[email protected]ec7f48d2011-08-09 03:48:50539 if (begin == current.selection_end()) {
[email protected]ff44d712011-07-25 08:42:52540 // The cursor is at the beginning of a word.
541 // Move to previous word.
542 break;
[email protected]ec7f48d2011-08-09 03:48:50543 } else if (iter.pos() >= current.selection_end()) {
[email protected]ff44d712011-07-25 08:42:52544 // The cursor is in the middle or at the end of a word.
545 // Move to the top of current word.
[email protected]ec7f48d2011-08-09 03:48:50546 pos = begin;
[email protected]ff44d712011-07-25 08:42:52547 break;
548 } else {
[email protected]ec7f48d2011-08-09 03:48:50549 pos = iter.pos() - iter.GetString().length();
[email protected]ff44d712011-07-25 08:42:52550 }
551 }
552 }
553
[email protected]ec7f48d2011-08-09 03:48:50554 return SelectionModel(pos, pos, SelectionModel::LEADING);
[email protected]ff44d712011-07-25 08:42:52555}
556
[email protected]ec7f48d2011-08-09 03:48:50557SelectionModel RenderText::GetRightSelectionModel(const SelectionModel& current,
558 BreakType break_type) {
[email protected]d3c6b0602011-09-07 19:26:06559 if (text_.empty())
560 return SelectionModel(0, 0, SelectionModel::LEADING);
[email protected]ec7f48d2011-08-09 03:48:50561 if (break_type == LINE_BREAK)
[email protected]67e85512011-10-12 20:03:45562 return RightEndSelectionModel();
[email protected]ec7f48d2011-08-09 03:48:50563 size_t pos = std::min(current.selection_end() + 1, text().length());
564 if (break_type == CHARACTER_BREAK)
565 return SelectionModel(pos, pos, SelectionModel::LEADING);
[email protected]8e42ba22011-08-04 21:47:08566
[email protected]ff44d712011-07-25 08:42:52567 base::i18n::BreakIterator iter(text(), base::i18n::BreakIterator::BREAK_WORD);
568 bool success = iter.Init();
569 DCHECK(success);
[email protected]ec7f48d2011-08-09 03:48:50570 if (!success)
571 return current;
[email protected]ff44d712011-07-25 08:42:52572 while (iter.Advance()) {
573 pos = iter.pos();
[email protected]ec7f48d2011-08-09 03:48:50574 if (iter.IsWord() && pos > current.selection_end())
[email protected]ff44d712011-07-25 08:42:52575 break;
[email protected]ff44d712011-07-25 08:42:52576 }
[email protected]ec7f48d2011-08-09 03:48:50577 return SelectionModel(pos, pos, SelectionModel::LEADING);
578}
579
[email protected]0d717602011-08-30 06:21:14580SelectionModel RenderText::LeftEndSelectionModel() {
581 return SelectionModel(0, 0, SelectionModel::LEADING);
582}
583
584SelectionModel RenderText::RightEndSelectionModel() {
585 size_t cursor = text().length();
586 size_t caret_pos = GetIndexOfPreviousGrapheme(cursor);
587 SelectionModel::CaretPlacement placement = (caret_pos == cursor) ?
588 SelectionModel::LEADING : SelectionModel::TRAILING;
589 return SelectionModel(cursor, caret_pos, placement);
590}
591
[email protected]6002a4f32011-11-30 10:18:42592void RenderText::SetSelectionModel(const SelectionModel& model) {
593 DCHECK_LE(model.selection_start(), text().length());
594 selection_model_.set_selection_start(model.selection_start());
595 DCHECK_LE(model.selection_end(), text().length());
596 selection_model_.set_selection_end(model.selection_end());
[email protected]f9a221b2011-12-10 20:25:38597 DCHECK_LT(model.caret_pos(), std::max<size_t>(text().length(), 1));
[email protected]6002a4f32011-11-30 10:18:42598 selection_model_.set_caret_pos(model.caret_pos());
599 selection_model_.set_caret_placement(model.caret_placement());
600
601 cached_bounds_and_offset_valid_ = false;
[email protected]0d717602011-08-30 06:21:14602}
603
[email protected]6002a4f32011-11-30 10:18:42604size_t RenderText::GetIndexOfPreviousGrapheme(size_t position) {
605 return IndexOfAdjacentGrapheme(position, false);
[email protected]ff44d712011-07-25 08:42:52606}
607
[email protected]8e42ba22011-08-04 21:47:08608void RenderText::ApplyCompositionAndSelectionStyles(
609 StyleRanges* style_ranges) const {
610 // TODO(msw): This pattern ought to be reconsidered; what about composition
611 // and selection overlaps, retain existing local style features?
612 // Apply a composition style override to a copy of the style ranges.
613 if (composition_range_.IsValid() && !composition_range_.is_empty()) {
614 StyleRange composition_style(default_style_);
615 composition_style.underline = true;
616 composition_style.range.set_start(composition_range_.start());
617 composition_style.range.set_end(composition_range_.end());
618 ApplyStyleRangeImpl(style_ranges, composition_style);
619 }
620 // Apply a selection style override to a copy of the style ranges.
621 if (!EmptySelection()) {
622 StyleRange selection_style(default_style_);
623 selection_style.foreground = kSelectedTextColor;
624 selection_style.range.set_start(MinOfSelection());
625 selection_style.range.set_end(MaxOfSelection());
626 ApplyStyleRangeImpl(style_ranges, selection_style);
627 }
[email protected]ff44d712011-07-25 08:42:52628}
629
[email protected]0d717602011-08-30 06:21:14630Point RenderText::ToTextPoint(const Point& point) {
631 Point p(point.Subtract(display_rect().origin()));
632 p = p.Subtract(GetUpdatedDisplayOffset());
633 if (base::i18n::IsRTL())
634 p.Offset(GetStringWidth() - display_rect().width() + 1, 0);
635 return p;
636}
637
638Point RenderText::ToViewPoint(const Point& point) {
639 Point p(point.Add(display_rect().origin()));
640 p = p.Add(GetUpdatedDisplayOffset());
641 if (base::i18n::IsRTL())
642 p.Offset(display_rect().width() - GetStringWidth() - 1, 0);
643 return p;
644}
645
[email protected]67b981562011-12-09 00:35:05646Point RenderText::GetOriginForSkiaDrawing() {
647 Point origin(ToViewPoint(Point()));
648 // TODO(msw): Establish a vertical baseline for strings of mixed font heights.
[email protected]2bb1b7e2011-12-20 20:53:29649 const Font& font = default_style().font;
[email protected]67b981562011-12-09 00:35:05650 size_t height = font.GetHeight();
651 // Center the text vertically in the display area.
652 origin.Offset(0, (display_rect().height() - height) / 2);
653 // Offset by the font size to account for Skia expecting y to be the bottom.
654 origin.Offset(0, font.GetFontSize());
655 return origin;
656}
657
[email protected]0d717602011-08-30 06:21:14658void RenderText::MoveCursorTo(size_t position, bool select) {
659 size_t cursor = std::min(position, text().length());
660 size_t caret_pos = GetIndexOfPreviousGrapheme(cursor);
661 SelectionModel::CaretPlacement placement = (caret_pos == cursor) ?
662 SelectionModel::LEADING : SelectionModel::TRAILING;
663 size_t selection_start = select ? GetSelectionStart() : cursor;
[email protected]53c0b1b2011-09-21 20:32:29664 if (IsCursorablePosition(cursor)) {
665 SelectionModel sel(selection_start, cursor, caret_pos, placement);
666 SetSelectionModel(sel);
667 }
[email protected]8e42ba22011-08-04 21:47:08668}
669
[email protected]f6aaa0f2011-08-11 07:05:46670void RenderText::UpdateCachedBoundsAndOffset() {
671 if (cached_bounds_and_offset_valid_)
672 return;
673 // First, set the valid flag true to calculate the current cursor bounds using
674 // the stale |display_offset_|. Applying |delta_offset| at the end of this
675 // function will set |cursor_bounds_| and |display_offset_| to correct values.
676 cached_bounds_and_offset_valid_ = true;
677 cursor_bounds_ = GetCursorBounds(selection_model_, insert_mode_);
[email protected]8e42ba22011-08-04 21:47:08678 // Update |display_offset_| to ensure the current cursor is visible.
679 int display_width = display_rect_.width();
680 int string_width = GetStringWidth();
[email protected]f6aaa0f2011-08-11 07:05:46681 int delta_offset = 0;
[email protected]8e42ba22011-08-04 21:47:08682 if (string_width < display_width) {
683 // Show all text whenever the text fits to the size.
[email protected]f6aaa0f2011-08-11 07:05:46684 delta_offset = -display_offset_.x();
[email protected]65788132011-09-07 20:20:23685 } else if (cursor_bounds_.right() >= display_rect_.right()) {
[email protected]d3c6b0602011-09-07 19:26:06686 // TODO(xji): when the character overflow is a RTL character, currently, if
687 // we pan cursor at the rightmost position, the entered RTL character is not
688 // displayed. Should pan cursor to show the last logical characters.
689 //
[email protected]8e42ba22011-08-04 21:47:08690 // Pan to show the cursor when it overflows to the right,
[email protected]65788132011-09-07 20:20:23691 delta_offset = display_rect_.right() - cursor_bounds_.right() - 1;
[email protected]f6aaa0f2011-08-11 07:05:46692 } else if (cursor_bounds_.x() < display_rect_.x()) {
[email protected]d3c6b0602011-09-07 19:26:06693 // TODO(xji): have similar problem as above when overflow character is a
694 // LTR character.
695 //
[email protected]8e42ba22011-08-04 21:47:08696 // Pan to show the cursor when it overflows to the left.
[email protected]f6aaa0f2011-08-11 07:05:46697 delta_offset = display_rect_.x() - cursor_bounds_.x();
[email protected]8e42ba22011-08-04 21:47:08698 }
[email protected]f6aaa0f2011-08-11 07:05:46699 display_offset_.Offset(delta_offset, 0);
700 cursor_bounds_.Offset(delta_offset, 0);
[email protected]ff44d712011-07-25 08:42:52701}
702
[email protected]6002a4f32011-11-30 10:18:42703void RenderText::DrawSelection(Canvas* canvas) {
[email protected]592a7ad2011-12-14 05:57:53704 TRACE_EVENT0("gfx", "RenderText::DrawSelection");
[email protected]6002a4f32011-11-30 10:18:42705 std::vector<Rect> sel;
706 GetSubstringBounds(GetSelectionStart(), GetCursorPosition(), &sel);
707 SkColor color = focused() ? kFocusedSelectionColor : kUnfocusedSelectionColor;
708 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i)
709 canvas->FillRect(color, *i);
710}
711
712void RenderText::DrawCursor(Canvas* canvas) {
[email protected]592a7ad2011-12-14 05:57:53713 TRACE_EVENT0("gfx", "RenderText::DrawCursor");
[email protected]6002a4f32011-11-30 10:18:42714 // Paint cursor. Replace cursor is drawn as rectangle for now.
715 // TODO(msw): Draw a better cursor with a better indication of association.
[email protected]3acc6422011-12-17 16:31:31716 if (cursor_visible() && focused())
717 canvas->DrawRect(GetUpdatedCursorBounds(), kCursorColor);
[email protected]6002a4f32011-11-30 10:18:42718}
719
[email protected]ff44d712011-07-25 08:42:52720} // namespace gfx