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