OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "ui/gfx/render_text_win.h" | 5 #include "ui/gfx/render_text_win.h" |
6 | 6 |
7 #include "base/i18n/break_iterator.h" | |
8 #include "base/logging.h" | |
9 #include "base/stl_util.h" | |
10 #include "skia/ext/skia_utils_win.h" | |
11 #include "ui/gfx/canvas.h" | |
12 #include "ui/gfx/canvas_skia.h" | |
13 | |
14 namespace { | |
15 | |
16 // The maximum supported number of Uniscribe runs; a SCRIPT_ITEM is 8 bytes. | |
17 // TODO(msw): Review memory use/failure? Max string length? Alternate approach? | |
18 const int kGuessItems = 100; | |
19 const int kMaxItems = 10000; | |
20 | |
21 // The maximum supported number of Uniscribe glyphs; a glyph is 1 word. | |
22 // TODO(msw): Review memory use/failure? Max string length? Alternate approach? | |
23 const int kMaxGlyphs = 100000; | |
24 | |
25 } // namespace | |
26 | |
7 namespace gfx { | 27 namespace gfx { |
8 | 28 |
29 namespace internal { | |
30 | |
31 TextRun::TextRun() | |
32 : strike(false), | |
33 width(0), | |
34 preceding_run_widths(0), | |
35 glyph_count(0) { | |
36 } | |
37 | |
38 } // namespace internal | |
39 | |
9 RenderTextWin::RenderTextWin() | 40 RenderTextWin::RenderTextWin() |
10 : RenderText() { | 41 : RenderText(), |
42 script_control_(), | |
43 script_state_(), | |
44 script_cache_(NULL), | |
45 string_width_(0) { | |
46 // Omitting default constructors for script_* would leave POD uninitialized. | |
47 HRESULT hr = 0; | |
48 | |
49 // TODO(msw): Call ScriptRecordDigitSubstitution on WM_SETTINGCHANGE message. | |
50 // TODO(msw): Use Chrome/profile locale/language settings? | |
51 hr = ScriptRecordDigitSubstitution(LOCALE_USER_DEFAULT, &digit_substitute_); | |
52 DCHECK(SUCCEEDED(hr)); | |
53 | |
54 hr = ScriptApplyDigitSubstitution(&digit_substitute_, | |
55 &script_control_, | |
56 &script_state_); | |
57 DCHECK(SUCCEEDED(hr)); | |
58 script_control_.fMergeNeutralItems = true; | |
59 | |
60 MoveCursorTo(LeftEndSelectionModel()); | |
11 } | 61 } |
12 | 62 |
13 RenderTextWin::~RenderTextWin() { | 63 RenderTextWin::~RenderTextWin() { |
64 ScriptFreeCache(&script_cache_); | |
65 STLDeleteContainerPointers(runs_.begin(), runs_.end()); | |
66 } | |
67 | |
68 void RenderTextWin::SetText(const string16& text) { | |
69 // TODO(msw): Skip complex processing if ScriptIsComplex returns false. | |
70 RenderText::SetText(text); | |
71 ItemizeLogicalText(); | |
72 LayoutVisualText(CreateCompatibleDC(NULL)); | |
73 } | |
74 | |
75 void RenderTextWin::SetDisplayRect(const Rect& r) { | |
76 RenderText::SetDisplayRect(r); | |
77 ItemizeLogicalText(); | |
78 LayoutVisualText(CreateCompatibleDC(NULL)); | |
79 } | |
80 | |
81 void RenderTextWin::ApplyStyleRange(StyleRange style_range) { | |
82 RenderText::ApplyStyleRange(style_range); | |
83 ItemizeLogicalText(); | |
84 LayoutVisualText(CreateCompatibleDC(NULL)); | |
85 } | |
86 | |
87 void RenderTextWin::ApplyDefaultStyle() { | |
88 RenderText::ApplyDefaultStyle(); | |
89 ItemizeLogicalText(); | |
90 LayoutVisualText(CreateCompatibleDC(NULL)); | |
91 } | |
92 | |
93 int RenderTextWin::GetStringWidth() { | |
94 return string_width_; | |
95 } | |
96 | |
97 void RenderTextWin::Draw(Canvas* canvas) { | |
98 skia::ScopedPlatformPaint scoped_platform_paint(canvas->AsCanvasSkia()); | |
99 HDC hdc = scoped_platform_paint.GetPlatformSurface(); | |
100 int saved_dc = SaveDC(hdc); | |
101 DrawSelection(canvas); | |
102 DrawVisualText(canvas); | |
103 DrawCursor(canvas); | |
104 RestoreDC(hdc, saved_dc); | |
105 } | |
106 | |
107 SelectionModel RenderTextWin::FindCursorPosition(const Point& point) { | |
108 if (text().empty()) | |
109 return SelectionModel(); | |
110 | |
111 // Find the run that contains the point and adjust the argument location. | |
112 Point p(ToTextPoint(point)); | |
113 size_t run_index = GetRunContainingPoint(p); | |
114 if (run_index == runs_.size()) | |
115 return (p.x() < 0) ? LeftEndSelectionModel() : RightEndSelectionModel(); | |
116 internal::TextRun* run = runs_[run_index]; | |
117 | |
118 int position = 0, trailing = 0; | |
119 HRESULT hr = ScriptXtoCP(p.x() - run->preceding_run_widths, | |
120 run->range.length(), | |
121 run->glyph_count, | |
122 run->logical_clusters.get(), | |
123 run->visible_attributes.get(), | |
124 run->advance_widths.get(), | |
125 &(run->script_analysis), | |
126 &position, | |
127 &trailing); | |
128 DCHECK(SUCCEEDED(hr)); | |
129 position += run->range.start(); | |
130 | |
131 size_t cursor = position + trailing; | |
132 DCHECK_GT(cursor, 0U); | |
133 DCHECK_LE(cursor, text().length()); | |
134 return SelectionModel(cursor, position, | |
135 (trailing > 0) ? SelectionModel::TRAILING : SelectionModel::LEADING); | |
136 } | |
137 | |
138 Rect RenderTextWin::GetCursorBounds(const SelectionModel& selection, | |
139 bool insert_mode) { | |
140 // Highlight the logical cursor (selection end) when not in insert mode. | |
141 size_t pos = insert_mode ? selection.caret_pos() : selection.selection_end(); | |
142 size_t run_index = GetRunContainingPosition(pos); | |
143 internal::TextRun* run = run_index == runs_.size() ? NULL : runs_[run_index]; | |
144 | |
145 int start_x = 0, end_x = 0; | |
146 if (run) { | |
147 HRESULT hr = 0; | |
148 hr = ScriptCPtoX(pos - run->range.start(), | |
149 false, | |
150 run->range.length(), | |
151 run->glyph_count, | |
152 run->logical_clusters.get(), | |
153 run->visible_attributes.get(), | |
154 run->advance_widths.get(), | |
155 &(run->script_analysis), | |
156 &start_x); | |
157 DCHECK(SUCCEEDED(hr)); | |
158 hr = ScriptCPtoX(pos - run->range.start(), | |
159 true, | |
160 run->range.length(), | |
161 run->glyph_count, | |
162 run->logical_clusters.get(), | |
163 run->visible_attributes.get(), | |
164 run->advance_widths.get(), | |
165 &(run->script_analysis), | |
166 &end_x); | |
167 DCHECK(SUCCEEDED(hr)); | |
168 } | |
169 // TODO(msw): Use the last visual run's font instead of the default font? | |
170 int height = run ? run->font.GetHeight() : default_style().font.GetHeight(); | |
171 Rect rect(std::min(start_x, end_x), 0, std::abs(end_x - start_x), height); | |
172 // Offset to the run start or the right/left end for an out of bounds index. | |
173 // Also center the rect vertically in the display area. | |
174 int text_end_offset = base::i18n::IsRTL() ? 0 : GetStringWidth(); | |
175 rect.Offset((run ? run->preceding_run_widths : text_end_offset), | |
176 (display_rect().height() - rect.height()) / 2); | |
177 // Adjust for leading/trailing in insert mode. | |
178 if (insert_mode && run) { | |
179 bool leading = selection.caret_placement() == SelectionModel::LEADING; | |
180 // Adjust the x value for right-side placement. | |
181 if (run->script_analysis.fRTL == leading) | |
182 rect.set_x(rect.right()); | |
183 rect.set_width(0); | |
184 } | |
185 rect.set_origin(ToViewPoint(rect.origin())); | |
186 return rect; | |
187 } | |
188 | |
189 SelectionModel RenderTextWin::GetLeftSelectionModel( | |
190 const SelectionModel& selection, | |
191 BreakType break_type) { | |
192 if (break_type == LINE_BREAK || text().empty()) | |
193 return LeftEndSelectionModel(); | |
194 | |
195 scoped_ptr<base::i18n::BreakIterator> iter; | |
196 if (break_type == WORD_BREAK) { | |
197 iter.reset(new base::i18n::BreakIterator(text(), | |
198 base::i18n::BreakIterator::BREAK_WORD)); | |
199 bool success = iter->Init(); | |
200 DCHECK(success); | |
201 if (!success) | |
202 return selection; | |
203 } | |
204 | |
205 bool character_break = break_type == CHARACTER_BREAK; | |
206 bool at_word_break = false; | |
207 SelectionModel left(selection); | |
208 SCRIPT_LOGATTR attributes; | |
209 do { | |
210 left = LeftSelectionModel(left); | |
211 size_t position = left.caret_pos(); | |
212 // TODO(msw): Implement GetIndexOfNextGrapheme and fix word breaking logic. | |
213 if (left.caret_placement() == SelectionModel::TRAILING) | |
214 position++; | |
215 size_t run_index = GetRunContainingPosition(position); | |
216 if (run_index == runs_.size() || left.Equals(LeftEndSelectionModel())) | |
xji
2011/08/25 05:58:38
the changed logic actually has the same functional
msw
2011/08/26 16:26:25
I removed the word break code and rewrote this to
| |
217 return left; | |
218 size_t char_index = position - runs_[run_index]->range.start(); | |
219 attributes = runs_[run_index]->logical_attributes[char_index]; | |
220 at_word_break = iter.get() && iter->IsWordBreakAt(position); | |
221 } while (!(character_break && attributes.fCharStop) && !at_word_break); | |
222 return left; | |
223 } | |
224 | |
225 SelectionModel RenderTextWin::GetRightSelectionModel( | |
226 const SelectionModel& selection, | |
227 BreakType break_type) { | |
228 if (break_type == LINE_BREAK || text().empty()) | |
229 return RightEndSelectionModel(); | |
230 | |
231 scoped_ptr<base::i18n::BreakIterator> iter; | |
232 if (break_type == WORD_BREAK) { | |
233 iter.reset(new base::i18n::BreakIterator(text(), | |
234 base::i18n::BreakIterator::BREAK_WORD)); | |
235 bool success = iter->Init(); | |
236 DCHECK(success); | |
237 if (!success) | |
238 return selection; | |
239 } | |
240 | |
241 bool character_break = break_type == CHARACTER_BREAK; | |
242 bool at_word_break = false; | |
243 SelectionModel right(selection); | |
244 SCRIPT_LOGATTR attributes; | |
245 do { | |
246 right = RightSelectionModel(right); | |
247 size_t position = right.caret_pos(); | |
248 // TODO(msw): Implement GetIndexOfNextGrapheme and fix word breaking logic. | |
249 if (right.caret_placement() == SelectionModel::TRAILING) | |
250 position++; | |
251 size_t run_index = GetRunContainingPosition(position); | |
xji
2011/08/25 05:58:38
ditto
msw
2011/08/26 16:26:25
See above.
| |
252 if (run_index == runs_.size() || right.Equals(RightEndSelectionModel())) | |
253 return right; | |
254 size_t char_index = position - runs_[run_index]->range.start(); | |
255 attributes = runs_[run_index]->logical_attributes[char_index]; | |
256 at_word_break = iter.get() && iter->IsWordBreakAt(position); | |
257 } while (!(character_break && attributes.fCharStop) && !at_word_break); | |
258 return right; | |
259 } | |
260 | |
261 SelectionModel RenderTextWin::LeftEndSelectionModel() { | |
262 if (text().empty()) | |
263 return SelectionModel(0, 0, SelectionModel::LEADING); | |
264 size_t cursor = base::i18n::IsRTL() ? text().length() : 0; | |
265 internal::TextRun* run = runs_[visual_to_logical_[0]]; | |
266 bool rtl = run->script_analysis.fRTL; | |
267 size_t caret = rtl ? run->range.end() - 1 : run->range.start(); | |
268 SelectionModel::CaretPlacement placement = | |
269 rtl ? SelectionModel::TRAILING : SelectionModel::LEADING; | |
270 return SelectionModel(cursor, caret, placement); | |
271 } | |
272 | |
273 SelectionModel RenderTextWin::RightEndSelectionModel() { | |
274 if (text().empty()) | |
275 return SelectionModel(0, 0, SelectionModel::LEADING); | |
276 size_t cursor = base::i18n::IsRTL() ? 0 : text().length(); | |
277 internal::TextRun* run = runs_[visual_to_logical_[runs_.size() - 1]]; | |
278 bool rtl = run->script_analysis.fRTL; | |
279 size_t caret = rtl ? run->range.start() : run->range.end() - 1; | |
280 SelectionModel::CaretPlacement placement = | |
281 rtl ? SelectionModel::LEADING : SelectionModel::TRAILING; | |
282 return SelectionModel(cursor, caret, placement); | |
283 } | |
284 | |
285 std::vector<Rect> RenderTextWin::GetSubstringBounds(size_t from, size_t to) { | |
286 ui::Range range(from, to); | |
287 DCHECK(ui::Range(0, text().length()).Contains(range)); | |
288 Point display_offset(GetUpdatedDisplayOffset()); | |
289 std::vector<Rect> bounds; | |
290 HRESULT hr = 0; | |
291 | |
292 // Add a Rect for each run/selection intersection. | |
293 // TODO(msw): The bounds should probably not always be leading the range ends. | |
294 for (size_t i = 0; i < runs_.size(); ++i) { | |
295 internal::TextRun* run = runs_[visual_to_logical_[i]]; | |
296 ui::Range intersection = run->range.Intersect(range); | |
297 if (intersection.IsValid()) { | |
298 DCHECK(!intersection.is_reversed()); | |
299 int start_offset = 0; | |
300 hr = ScriptCPtoX(intersection.start() - run->range.start(), | |
301 false, | |
302 run->range.length(), | |
303 run->glyph_count, | |
304 run->logical_clusters.get(), | |
305 run->visible_attributes.get(), | |
306 run->advance_widths.get(), | |
307 &(run->script_analysis), | |
308 &start_offset); | |
309 DCHECK(SUCCEEDED(hr)); | |
310 int end_offset = 0; | |
311 hr = ScriptCPtoX(intersection.end() - run->range.start(), | |
312 false, | |
313 run->range.length(), | |
314 run->glyph_count, | |
315 run->logical_clusters.get(), | |
316 run->visible_attributes.get(), | |
317 run->advance_widths.get(), | |
318 &(run->script_analysis), | |
319 &end_offset); | |
320 DCHECK(SUCCEEDED(hr)); | |
321 if (start_offset > end_offset) | |
322 std::swap(start_offset, end_offset); | |
323 Rect rect(run->preceding_run_widths + start_offset, 0, | |
324 end_offset - start_offset, run->font.GetHeight()); | |
325 // Center the rect vertically in the display area. | |
326 rect.Offset(0, (display_rect().height() - rect.height()) / 2); | |
327 rect.set_origin(ToViewPoint(rect.origin())); | |
328 // Union this with the last rect if they're adjacent. | |
329 if (!bounds.empty() && rect.SharesEdgeWith(bounds.back())) { | |
330 rect = rect.Union(bounds.back()); | |
331 bounds.pop_back(); | |
332 } | |
333 bounds.push_back(rect); | |
334 } | |
335 } | |
336 return bounds; | |
337 } | |
338 | |
339 void RenderTextWin::ItemizeLogicalText() { | |
340 text_is_dirty_ = false; | |
341 STLDeleteContainerPointers(runs_.begin(), runs_.end()); | |
342 runs_.clear(); | |
343 if (text().empty()) | |
344 return; | |
345 | |
346 const wchar_t* raw_text = text().c_str(); | |
347 const int text_length = text().length(); | |
348 | |
349 HRESULT hr = E_OUTOFMEMORY; | |
350 int script_items_count = 0; | |
351 scoped_array<SCRIPT_ITEM> script_items; | |
352 for (size_t n = kGuessItems; hr == E_OUTOFMEMORY && n < kMaxItems; n *= 2) { | |
353 // Derive the array of Uniscribe script items from the logical text. | |
354 // ScriptItemize always adds a terminal array item so that the length of the | |
355 // last item can be derived from the terminal SCRIPT_ITEM::iCharPos. | |
356 script_items.reset(new SCRIPT_ITEM[n]); | |
357 hr = ScriptItemize(raw_text, | |
358 text_length, | |
359 n - 1, | |
360 &script_control_, | |
361 &script_state_, | |
362 script_items.get(), | |
363 &script_items_count); | |
364 } | |
365 DCHECK(SUCCEEDED(hr)); | |
366 | |
367 if (script_items_count <= 0) | |
368 return; | |
369 | |
370 // Build the list of runs, merge font/underline styles. | |
371 // TODO(msw): Only break for font changes, not color etc. See TextRun comment. | |
372 // TODO(msw): Apply the overriding selection and composition styles. | |
373 StyleRanges::const_iterator style = style_ranges().begin(); | |
374 SCRIPT_ITEM* script_item = script_items.get(); | |
375 for (int run_break = 0; run_break < text_length;) { | |
376 internal::TextRun* run = new internal::TextRun(); | |
377 run->range.set_start(run_break); | |
378 run->font = !style->underline ? style->font : | |
379 style->font.DeriveFont(0, style->font.GetStyle() | Font::UNDERLINED); | |
380 run->foreground = style->foreground; | |
381 run->strike = style->strike; | |
382 run->script_analysis = script_item->a; | |
383 | |
384 // Find the range end and advance the structures as needed. | |
385 int script_item_end = (script_item + 1)->iCharPos; | |
386 int style_range_end = style->range.end(); | |
387 run_break = std::min(script_item_end, style_range_end); | |
388 if (script_item_end <= style_range_end) | |
389 script_item++; | |
390 if (script_item_end >= style_range_end) | |
391 style++; | |
392 run->range.set_end(run_break); | |
393 runs_.push_back(run); | |
394 } | |
395 } | |
396 | |
397 void RenderTextWin::LayoutVisualText(HDC hdc) { | |
398 HRESULT hr = 0; | |
399 std::vector<internal::TextRun*>::const_iterator run_iter; | |
400 for (run_iter = runs_.begin(); run_iter < runs_.end(); ++run_iter) { | |
401 internal::TextRun* run = *run_iter; | |
402 int run_length = run->range.length(); | |
403 string16 run_string(text().substr(run->range.start(), run_length)); | |
404 const wchar_t* run_text = run_string.c_str(); | |
405 // Select the font desired for glyph generation | |
406 SelectObject(hdc, run->font.GetNativeFont()); | |
407 | |
408 run->logical_clusters.reset(new WORD[run_length]); | |
409 run->glyph_count = 0; | |
410 hr = E_OUTOFMEMORY; | |
411 // kGuess comes from: http://msdn.microsoft.com/en-us/library/dd368564.aspx | |
412 const int kGuess = static_cast<int>(1.5 * run_length + 16); | |
413 for (size_t n = kGuess; hr == E_OUTOFMEMORY && n < kMaxGlyphs; n *= 2) { | |
414 run->glyphs.reset(new WORD[n]); | |
415 run->visible_attributes.reset(new SCRIPT_VISATTR[n]); | |
416 hr = ScriptShape(hdc, | |
417 &script_cache_, | |
418 run_text, | |
419 run_length, | |
420 n, | |
421 &(run->script_analysis), | |
422 run->glyphs.get(), | |
423 run->logical_clusters.get(), | |
424 run->visible_attributes.get(), | |
425 &(run->glyph_count)); | |
426 } | |
427 DCHECK(SUCCEEDED(hr)); | |
428 | |
429 if (run->glyph_count > 0) { | |
430 run->advance_widths.reset(new int[run->glyph_count]); | |
431 run->offsets.reset(new GOFFSET[run->glyph_count]); | |
432 hr = ScriptPlace(hdc, | |
433 &script_cache_, | |
434 run->glyphs.get(), | |
435 run->glyph_count, | |
436 run->visible_attributes.get(), | |
437 &(run->script_analysis), | |
438 run->advance_widths.get(), | |
439 run->offsets.get(), | |
440 &(run->abc_widths)); | |
441 DCHECK(SUCCEEDED(hr)); | |
442 | |
443 // TODO(msw): The character break information can also be derived from the | |
444 // run's logical clusters structure, so ScriptBreak may not be needed. | |
445 run->logical_attributes.reset(new SCRIPT_LOGATTR[run_length]); | |
446 hr = ScriptBreak(run_text, | |
447 run_length, | |
448 &(run->script_analysis), | |
449 run->logical_attributes.get()); | |
450 DCHECK(SUCCEEDED(hr)); | |
451 } | |
452 } | |
453 | |
454 if (runs_.size() > 0) { | |
455 // Build the array of bidirectional embedding levels. | |
456 scoped_array<BYTE> levels(new BYTE[runs_.size()]); | |
457 for (size_t i = 0; i < runs_.size(); ++i) | |
458 levels[i] = runs_[i]->script_analysis.s.uBidiLevel; | |
459 | |
460 // Get the maps between visual and logical run indices. | |
461 visual_to_logical_.reset(new int[runs_.size()]); | |
462 logical_to_visual_.reset(new int[runs_.size()]); | |
463 hr = ScriptLayout(runs_.size(), | |
464 levels.get(), | |
465 visual_to_logical_.get(), | |
466 logical_to_visual_.get()); | |
467 DCHECK(SUCCEEDED(hr)); | |
468 } | |
469 | |
470 // Precalculate run width information. | |
471 size_t preceding_run_widths = 0; | |
472 for (size_t i = 0; i < runs_.size(); ++i) { | |
473 internal::TextRun* run = runs_[visual_to_logical_[i]]; | |
474 run->preceding_run_widths = preceding_run_widths; | |
475 const ABC& abc = run->abc_widths; | |
476 run->width = abc.abcA + abc.abcB + abc.abcC; | |
477 preceding_run_widths += run->width; | |
478 } | |
479 string_width_ = preceding_run_widths; | |
480 } | |
481 | |
482 size_t RenderTextWin::GetRunContainingPosition(size_t position) const { | |
483 // Find the text run containing the argument position. | |
484 size_t run = 0; | |
485 for (; run < runs_.size(); ++run) | |
486 if (runs_[run]->range.start() <= position && | |
487 runs_[run]->range.end() > position) | |
488 break; | |
489 return run; | |
490 } | |
491 | |
492 size_t RenderTextWin::GetRunContainingPoint(const Point& point) const { | |
493 // Find the text run containing the argument point (assumed already offset). | |
494 size_t run = 0; | |
495 for (; run < runs_.size(); ++run) | |
496 if (runs_[run]->preceding_run_widths <= point.x() && | |
497 runs_[run]->preceding_run_widths + runs_[run]->width > point.x()) | |
498 break; | |
499 return run; | |
500 } | |
501 | |
502 SelectionModel RenderTextWin::LeftSelectionModel( | |
503 const SelectionModel& selection) { | |
504 size_t caret = selection.caret_pos(); | |
505 SelectionModel::CaretPlacement caret_placement = selection.caret_placement(); | |
506 size_t run_index = GetRunContainingPosition(caret); | |
507 DCHECK(run_index < runs_.size()); | |
508 internal::TextRun* run = runs_[run_index]; | |
509 | |
510 // If the caret's associated character is in a LTR run. | |
511 if (!run->script_analysis.fRTL) { | |
512 if (caret_placement == SelectionModel::TRAILING) | |
513 return SelectionModel(caret, caret, SelectionModel::LEADING); | |
514 else if (caret > run->range.start()) | |
515 return SelectionModel(caret - 1, caret - 1, SelectionModel::LEADING); | |
516 } else { // The caret's associated character is in a RTL run. | |
517 if (caret_placement == SelectionModel::LEADING) | |
518 return SelectionModel(caret + 1, caret, SelectionModel::TRAILING); | |
519 else if (caret < run->range.end() - 1) | |
520 return SelectionModel(caret + 2, caret + 1, SelectionModel::TRAILING); | |
521 } | |
522 | |
523 // The character is at the begin of its run; go to the previous visual run. | |
524 size_t visual_index = logical_to_visual_[run_index]; | |
525 if (visual_index == 0) | |
526 return LeftEndSelectionModel(); | |
527 internal::TextRun* previous_run = runs_[visual_to_logical_[visual_index - 1]]; | |
528 if (!previous_run->script_analysis.fRTL) { | |
529 size_t pos = previous_run->range.end() - 1; | |
530 return SelectionModel(pos, pos, SelectionModel::LEADING); | |
531 } | |
532 size_t pos = previous_run->range.start(); | |
533 return SelectionModel(pos + 1, pos, SelectionModel::TRAILING); | |
534 } | |
535 | |
536 SelectionModel RenderTextWin::RightSelectionModel( | |
537 const SelectionModel& selection) { | |
538 size_t caret = selection.caret_pos(); | |
539 SelectionModel::CaretPlacement caret_placement = selection.caret_placement(); | |
540 size_t run_index = GetRunContainingPosition(caret); | |
541 DCHECK(run_index < runs_.size()); | |
542 internal::TextRun* run = runs_[run_index]; | |
543 | |
544 // If the caret's associated character is in a LTR run. | |
545 if (!run->script_analysis.fRTL) { | |
546 if (caret_placement == SelectionModel::LEADING) | |
547 return SelectionModel(caret + 1, caret, SelectionModel::TRAILING); | |
548 else if (caret < run->range.end() - 1) | |
549 return SelectionModel(caret + 2, caret + 1, SelectionModel::TRAILING); | |
550 } else { // The caret's associated character is in a RTL run. | |
551 if (caret_placement == SelectionModel::TRAILING) | |
552 return SelectionModel(caret, caret, SelectionModel::LEADING); | |
553 else if (caret > run->range.start()) | |
554 return SelectionModel(caret - 1, caret - 1, SelectionModel::LEADING); | |
555 } | |
556 | |
557 // The character is at the end of its run; go to the next visual run. | |
558 size_t visual_index = logical_to_visual_[run_index]; | |
559 if (visual_index == runs_.size() - 1) | |
560 return RightEndSelectionModel(); | |
561 internal::TextRun* next_run = runs_[visual_to_logical_[visual_index + 1]]; | |
562 if (!next_run->script_analysis.fRTL) { | |
563 size_t pos = next_run->range.start(); | |
564 return SelectionModel(pos + 1, pos, SelectionModel::TRAILING); | |
565 } | |
566 size_t pos = next_run->range.end() - 1; | |
567 return SelectionModel(pos, pos, SelectionModel::LEADING); | |
568 } | |
569 | |
570 void RenderTextWin::DrawSelection(Canvas* canvas) { | |
571 std::vector<Rect> sel( | |
572 GetSubstringBounds(GetSelectionStart(), GetCursorPosition())); | |
573 SkColor color = focused() ? kFocusedSelectionColor : kUnfocusedSelectionColor; | |
574 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i) | |
575 canvas->FillRectInt(color, i->x(), i->y(), i->width(), i->height()); | |
576 } | |
577 | |
578 void RenderTextWin::DrawVisualText(Canvas* canvas) { | |
579 if (text().empty()) | |
580 return; | |
581 | |
582 skia::ScopedPlatformPaint scoped_platform_paint(canvas->AsCanvasSkia()); | |
583 HDC hdc = scoped_platform_paint.GetPlatformSurface(); | |
584 | |
585 // Set the background mix mode to transparent. | |
586 int previous_background_mode = SetBkMode(hdc, TRANSPARENT); | |
587 | |
588 Point offset(ToViewPoint(Point())); | |
589 // TODO(msw): Establish a vertical baseline for strings of mixed font heights. | |
590 size_t height = default_style().font.GetHeight(); | |
591 // Center the text vertically in the display area. | |
592 offset.Offset(0, (display_rect().height() - height) / 2); | |
593 | |
594 HRESULT hr = 0; | |
595 RECT rect = display_rect().ToRECT(); | |
596 for (size_t i = 0; i < runs_.size(); ++i) { | |
597 // Get the run specified by the visual-to-logical map. | |
598 internal::TextRun* run = runs_[visual_to_logical_[i]]; | |
599 | |
600 // Set the font and color. | |
601 SelectObject(hdc, run->font.GetNativeFont()); | |
602 SetTextColor(hdc, skia::SkColorToCOLORREF(run->foreground)); | |
603 | |
604 hr = ScriptTextOut(hdc, | |
605 &script_cache_, | |
606 offset.x(), | |
607 offset.y(), | |
608 0, | |
609 &rect, | |
610 &(run->script_analysis), | |
611 NULL, | |
612 0, | |
613 run->glyphs.get(), | |
614 run->glyph_count, | |
615 run->advance_widths.get(), | |
616 NULL, | |
617 run->offsets.get()); | |
618 DCHECK(SUCCEEDED(hr)); | |
619 | |
620 // Draw the strikethrough. | |
621 if (run->strike) { | |
622 Rect bounds(offset, Size(run->width, run->font.GetHeight())); | |
623 SkPaint paint; | |
624 paint.setAntiAlias(true); | |
625 paint.setStyle(SkPaint::kFill_Style); | |
626 paint.setColor(run->foreground); | |
627 paint.setStrokeWidth(kStrikeWidth); | |
628 canvas->AsCanvasSkia()->drawLine(SkIntToScalar(bounds.x()), | |
629 SkIntToScalar(bounds.bottom()), | |
630 SkIntToScalar(bounds.right()), | |
631 SkIntToScalar(bounds.y()), | |
632 paint); | |
633 } | |
634 | |
635 offset.Offset(run->width, 0); | |
636 } | |
637 | |
638 // Restore the previous background mix mode. | |
639 SetBkMode(hdc, previous_background_mode); | |
640 } | |
641 | |
642 void RenderTextWin::DrawCursor(Canvas* canvas) { | |
643 // Paint cursor. Replace cursor is drawn as rectangle for now. | |
644 // TODO(msw): Draw a better cursor with a better indication of association. | |
645 if (cursor_visible() && focused()) { | |
646 Rect r(GetUpdatedCursorBounds()); | |
647 canvas->DrawRectInt(kCursorColor, r.x(), r.y(), r.width(), r.height()); | |
648 } | |
14 } | 649 } |
15 | 650 |
16 RenderText* RenderText::CreateRenderText() { | 651 RenderText* RenderText::CreateRenderText() { |
17 return new RenderTextWin; | 652 return new RenderTextWin; |
18 } | 653 } |
19 | 654 |
20 } // namespace gfx | 655 } // namespace gfx |
OLD | NEW |