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); | |
xji
2011/08/26 17:59:26
DCHECK_GE()
msw
2011/08/26 20:10:43
Done.
| |
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 if (break_type == CHARACTER_BREAK) | |
195 return LeftSelectionModel(selection); | |
196 // TODO(msw): Implement word breaking. | |
197 return RenderText::GetLeftSelectionModel(selection, break_type); | |
198 } | |
199 | |
200 SelectionModel RenderTextWin::GetRightSelectionModel( | |
201 const SelectionModel& selection, | |
202 BreakType break_type) { | |
203 if (break_type == LINE_BREAK || text().empty()) | |
204 return RightEndSelectionModel(); | |
205 if (break_type == CHARACTER_BREAK) | |
206 return RightSelectionModel(selection); | |
207 // TODO(msw): Implement word breaking. | |
208 return RenderText::GetRightSelectionModel(selection, break_type); | |
209 } | |
210 | |
211 SelectionModel RenderTextWin::LeftEndSelectionModel() { | |
212 if (text().empty()) | |
213 return SelectionModel(0, 0, SelectionModel::LEADING); | |
214 size_t cursor = base::i18n::IsRTL() ? text().length() : 0; | |
215 internal::TextRun* run = runs_[visual_to_logical_[0]]; | |
216 bool rtl = run->script_analysis.fRTL; | |
217 size_t caret = rtl ? run->range.end() - 1 : run->range.start(); | |
218 SelectionModel::CaretPlacement placement = | |
219 rtl ? SelectionModel::TRAILING : SelectionModel::LEADING; | |
220 return SelectionModel(cursor, caret, placement); | |
221 } | |
222 | |
223 SelectionModel RenderTextWin::RightEndSelectionModel() { | |
224 if (text().empty()) | |
225 return SelectionModel(0, 0, SelectionModel::LEADING); | |
226 size_t cursor = base::i18n::IsRTL() ? 0 : text().length(); | |
227 internal::TextRun* run = runs_[visual_to_logical_[runs_.size() - 1]]; | |
228 bool rtl = run->script_analysis.fRTL; | |
229 size_t caret = rtl ? run->range.start() : run->range.end() - 1; | |
230 SelectionModel::CaretPlacement placement = | |
231 rtl ? SelectionModel::LEADING : SelectionModel::TRAILING; | |
232 return SelectionModel(cursor, caret, placement); | |
233 } | |
234 | |
235 size_t RenderTextWin::GetIndexOfPreviousGrapheme(size_t position) { | |
236 return IndexOfAdjacentGrapheme(position, false); | |
237 } | |
238 | |
239 std::vector<Rect> RenderTextWin::GetSubstringBounds(size_t from, size_t to) { | |
240 ui::Range range(from, to); | |
241 DCHECK(ui::Range(0, text().length()).Contains(range)); | |
242 Point display_offset(GetUpdatedDisplayOffset()); | |
243 std::vector<Rect> bounds; | |
244 HRESULT hr = 0; | |
245 | |
246 // Add a Rect for each run/selection intersection. | |
247 // TODO(msw): The bounds should probably not always be leading the range ends. | |
248 for (size_t i = 0; i < runs_.size(); ++i) { | |
249 internal::TextRun* run = runs_[visual_to_logical_[i]]; | |
250 ui::Range intersection = run->range.Intersect(range); | |
251 if (intersection.IsValid()) { | |
252 DCHECK(!intersection.is_reversed()); | |
253 int start_offset = 0; | |
254 hr = ScriptCPtoX(intersection.start() - run->range.start(), | |
255 false, | |
256 run->range.length(), | |
257 run->glyph_count, | |
258 run->logical_clusters.get(), | |
259 run->visible_attributes.get(), | |
260 run->advance_widths.get(), | |
261 &(run->script_analysis), | |
262 &start_offset); | |
263 DCHECK(SUCCEEDED(hr)); | |
264 int end_offset = 0; | |
265 hr = ScriptCPtoX(intersection.end() - run->range.start(), | |
266 false, | |
267 run->range.length(), | |
268 run->glyph_count, | |
269 run->logical_clusters.get(), | |
270 run->visible_attributes.get(), | |
271 run->advance_widths.get(), | |
272 &(run->script_analysis), | |
273 &end_offset); | |
274 DCHECK(SUCCEEDED(hr)); | |
275 if (start_offset > end_offset) | |
276 std::swap(start_offset, end_offset); | |
277 Rect rect(run->preceding_run_widths + start_offset, 0, | |
278 end_offset - start_offset, run->font.GetHeight()); | |
279 // Center the rect vertically in the display area. | |
280 rect.Offset(0, (display_rect().height() - rect.height()) / 2); | |
281 rect.set_origin(ToViewPoint(rect.origin())); | |
282 // Union this with the last rect if they're adjacent. | |
283 if (!bounds.empty() && rect.SharesEdgeWith(bounds.back())) { | |
284 rect = rect.Union(bounds.back()); | |
285 bounds.pop_back(); | |
286 } | |
287 bounds.push_back(rect); | |
288 } | |
289 } | |
290 return bounds; | |
291 } | |
292 | |
293 void RenderTextWin::ItemizeLogicalText() { | |
294 text_is_dirty_ = false; | |
295 STLDeleteContainerPointers(runs_.begin(), runs_.end()); | |
296 runs_.clear(); | |
297 if (text().empty()) | |
298 return; | |
299 | |
300 const wchar_t* raw_text = text().c_str(); | |
301 const int text_length = text().length(); | |
302 | |
303 HRESULT hr = E_OUTOFMEMORY; | |
304 int script_items_count = 0; | |
305 scoped_array<SCRIPT_ITEM> script_items; | |
306 for (size_t n = kGuessItems; hr == E_OUTOFMEMORY && n < kMaxItems; n *= 2) { | |
307 // Derive the array of Uniscribe script items from the logical text. | |
308 // ScriptItemize always adds a terminal array item so that the length of the | |
309 // last item can be derived from the terminal SCRIPT_ITEM::iCharPos. | |
310 script_items.reset(new SCRIPT_ITEM[n]); | |
311 hr = ScriptItemize(raw_text, | |
312 text_length, | |
313 n - 1, | |
314 &script_control_, | |
315 &script_state_, | |
316 script_items.get(), | |
317 &script_items_count); | |
318 } | |
319 DCHECK(SUCCEEDED(hr)); | |
320 | |
321 if (script_items_count <= 0) | |
322 return; | |
323 | |
324 // Build the list of runs, merge font/underline styles. | |
325 // TODO(msw): Only break for font changes, not color etc. See TextRun comment. | |
326 // TODO(msw): Apply the overriding selection and composition styles. | |
327 StyleRanges::const_iterator style = style_ranges().begin(); | |
328 SCRIPT_ITEM* script_item = script_items.get(); | |
329 for (int run_break = 0; run_break < text_length;) { | |
330 internal::TextRun* run = new internal::TextRun(); | |
331 run->range.set_start(run_break); | |
332 run->font = !style->underline ? style->font : | |
333 style->font.DeriveFont(0, style->font.GetStyle() | Font::UNDERLINED); | |
334 run->foreground = style->foreground; | |
335 run->strike = style->strike; | |
336 run->script_analysis = script_item->a; | |
337 | |
338 // Find the range end and advance the structures as needed. | |
339 int script_item_end = (script_item + 1)->iCharPos; | |
340 int style_range_end = style->range.end(); | |
341 run_break = std::min(script_item_end, style_range_end); | |
342 if (script_item_end <= style_range_end) | |
343 script_item++; | |
344 if (script_item_end >= style_range_end) | |
345 style++; | |
346 run->range.set_end(run_break); | |
347 runs_.push_back(run); | |
348 } | |
349 } | |
350 | |
351 void RenderTextWin::LayoutVisualText(HDC hdc) { | |
352 HRESULT hr = 0; | |
353 std::vector<internal::TextRun*>::const_iterator run_iter; | |
354 for (run_iter = runs_.begin(); run_iter < runs_.end(); ++run_iter) { | |
355 internal::TextRun* run = *run_iter; | |
356 int run_length = run->range.length(); | |
357 string16 run_string(text().substr(run->range.start(), run_length)); | |
358 const wchar_t* run_text = run_string.c_str(); | |
359 // Select the font desired for glyph generation | |
360 SelectObject(hdc, run->font.GetNativeFont()); | |
361 | |
362 run->logical_clusters.reset(new WORD[run_length]); | |
363 run->glyph_count = 0; | |
364 hr = E_OUTOFMEMORY; | |
365 // kGuess comes from: http://msdn.microsoft.com/en-us/library/dd368564.aspx | |
366 const int kGuess = static_cast<int>(1.5 * run_length + 16); | |
367 for (size_t n = kGuess; hr == E_OUTOFMEMORY && n < kMaxGlyphs; n *= 2) { | |
368 run->glyphs.reset(new WORD[n]); | |
369 run->visible_attributes.reset(new SCRIPT_VISATTR[n]); | |
370 hr = ScriptShape(hdc, | |
371 &script_cache_, | |
372 run_text, | |
373 run_length, | |
374 n, | |
375 &(run->script_analysis), | |
376 run->glyphs.get(), | |
377 run->logical_clusters.get(), | |
378 run->visible_attributes.get(), | |
379 &(run->glyph_count)); | |
380 } | |
381 DCHECK(SUCCEEDED(hr)); | |
382 | |
383 if (run->glyph_count > 0) { | |
384 run->advance_widths.reset(new int[run->glyph_count]); | |
385 run->offsets.reset(new GOFFSET[run->glyph_count]); | |
386 hr = ScriptPlace(hdc, | |
387 &script_cache_, | |
388 run->glyphs.get(), | |
389 run->glyph_count, | |
390 run->visible_attributes.get(), | |
391 &(run->script_analysis), | |
392 run->advance_widths.get(), | |
393 run->offsets.get(), | |
394 &(run->abc_widths)); | |
395 DCHECK(SUCCEEDED(hr)); | |
396 } | |
397 } | |
398 | |
399 if (runs_.size() > 0) { | |
400 // Build the array of bidirectional embedding levels. | |
401 scoped_array<BYTE> levels(new BYTE[runs_.size()]); | |
402 for (size_t i = 0; i < runs_.size(); ++i) | |
403 levels[i] = runs_[i]->script_analysis.s.uBidiLevel; | |
404 | |
405 // Get the maps between visual and logical run indices. | |
406 visual_to_logical_.reset(new int[runs_.size()]); | |
407 logical_to_visual_.reset(new int[runs_.size()]); | |
408 hr = ScriptLayout(runs_.size(), | |
409 levels.get(), | |
410 visual_to_logical_.get(), | |
411 logical_to_visual_.get()); | |
412 DCHECK(SUCCEEDED(hr)); | |
413 } | |
414 | |
415 // Precalculate run width information. | |
416 size_t preceding_run_widths = 0; | |
417 for (size_t i = 0; i < runs_.size(); ++i) { | |
418 internal::TextRun* run = runs_[visual_to_logical_[i]]; | |
419 run->preceding_run_widths = preceding_run_widths; | |
420 const ABC& abc = run->abc_widths; | |
421 run->width = abc.abcA + abc.abcB + abc.abcC; | |
422 preceding_run_widths += run->width; | |
423 } | |
424 string_width_ = preceding_run_widths; | |
425 } | |
426 | |
427 size_t RenderTextWin::GetRunContainingPosition(size_t position) const { | |
428 // Find the text run containing the argument position. | |
429 size_t run = 0; | |
430 for (; run < runs_.size(); ++run) | |
431 if (runs_[run]->range.start() <= position && | |
432 runs_[run]->range.end() > position) | |
433 break; | |
434 return run; | |
435 } | |
436 | |
437 size_t RenderTextWin::GetRunContainingPoint(const Point& point) const { | |
438 // Find the text run containing the argument point (assumed already offset). | |
439 size_t run = 0; | |
440 for (; run < runs_.size(); ++run) | |
441 if (runs_[run]->preceding_run_widths <= point.x() && | |
442 runs_[run]->preceding_run_widths + runs_[run]->width > point.x()) | |
443 break; | |
444 return run; | |
445 } | |
446 | |
447 size_t RenderTextWin::IndexOfAdjacentGrapheme(size_t index, bool next) const { | |
448 size_t run_index = GetRunContainingPosition(index); | |
449 internal::TextRun* run = run_index < runs_.size() ? runs_[run_index] : NULL; | |
450 long start = run ? run->range.start() : 0; | |
451 long length = run ? run->range.length() : text().length(); | |
452 long ch = index - start; | |
453 WORD cluster = run ? run->logical_clusters[ch] : 0; | |
454 | |
455 if (!next) { | |
456 do { | |
457 ch--; | |
458 } while (ch >= 0 && run && run->logical_clusters[ch] == cluster); | |
459 } else { | |
460 while (ch < length && run && run->logical_clusters[ch] == cluster) | |
461 ch++; | |
462 } | |
463 return std::max(static_cast<long>(std::min(ch, length) + start), 0L); | |
464 } | |
465 | |
466 SelectionModel RenderTextWin::FirstSelectionModelInsideRun( | |
467 internal::TextRun* run) const { | |
468 size_t caret = run->range.start(); | |
469 size_t cursor = IndexOfAdjacentGrapheme(caret, true); | |
470 return SelectionModel(cursor, caret, SelectionModel::TRAILING); | |
471 } | |
472 | |
473 SelectionModel RenderTextWin::LastSelectionModelInsideRun( | |
474 internal::TextRun* run) const { | |
475 size_t caret = IndexOfAdjacentGrapheme(run->range.end(), false); | |
476 return SelectionModel(caret, caret, SelectionModel::LEADING); | |
477 } | |
478 | |
479 SelectionModel RenderTextWin::LeftSelectionModel( | |
480 const SelectionModel& selection) { | |
481 size_t caret = selection.caret_pos(); | |
482 SelectionModel::CaretPlacement caret_placement = selection.caret_placement(); | |
483 size_t run_index = GetRunContainingPosition(caret); | |
484 DCHECK(run_index < runs_.size()); | |
485 internal::TextRun* run = runs_[run_index]; | |
486 | |
487 // If the caret's associated character is in a LTR run. | |
488 if (!run->script_analysis.fRTL) { | |
489 if (caret_placement == SelectionModel::TRAILING) | |
490 return SelectionModel(caret, caret, SelectionModel::LEADING); | |
491 else if (caret > run->range.start()) { | |
492 caret = IndexOfAdjacentGrapheme(caret, false); | |
493 return SelectionModel(caret, caret, SelectionModel::LEADING); | |
494 } | |
495 } else { // The caret's associated character is in a RTL run. | |
496 if (caret_placement == SelectionModel::LEADING) { | |
497 size_t cursor = IndexOfAdjacentGrapheme(caret, true); | |
498 return SelectionModel(cursor, caret, SelectionModel::TRAILING); | |
499 } else if (selection.selection_end() < run->range.end()) { | |
500 caret = IndexOfAdjacentGrapheme(caret, true); | |
501 size_t cursor = IndexOfAdjacentGrapheme(caret, true); | |
502 return SelectionModel(cursor, caret, SelectionModel::TRAILING); | |
503 } | |
504 } | |
505 | |
506 // The character is at the begin of its run; go to the previous visual run. | |
507 size_t visual_index = logical_to_visual_[run_index]; | |
508 if (visual_index == 0) | |
509 return LeftEndSelectionModel(); | |
510 internal::TextRun* prev = runs_[visual_to_logical_[visual_index - 1]]; | |
511 return prev->script_analysis.fRTL ? FirstSelectionModelInsideRun(prev) : | |
512 LastSelectionModelInsideRun(prev); | |
513 } | |
514 | |
515 SelectionModel RenderTextWin::RightSelectionModel( | |
516 const SelectionModel& selection) { | |
517 size_t caret = selection.caret_pos(); | |
518 SelectionModel::CaretPlacement caret_placement = selection.caret_placement(); | |
519 size_t run_index = GetRunContainingPosition(caret); | |
520 DCHECK(run_index < runs_.size()); | |
521 internal::TextRun* run = runs_[run_index]; | |
522 | |
523 // If the caret's associated character is in a LTR run. | |
524 if (!run->script_analysis.fRTL) { | |
525 if (caret_placement == SelectionModel::LEADING) { | |
526 size_t cursor = IndexOfAdjacentGrapheme(caret, true); | |
527 return SelectionModel(cursor, caret, SelectionModel::TRAILING); | |
528 } else if (selection.selection_end() < run->range.end()) { | |
529 caret = IndexOfAdjacentGrapheme(caret, true); | |
530 size_t cursor = IndexOfAdjacentGrapheme(caret, true); | |
531 return SelectionModel(cursor, caret, SelectionModel::TRAILING); | |
532 } | |
533 } else { // The caret's associated character is in a RTL run. | |
534 if (caret_placement == SelectionModel::TRAILING) | |
535 return SelectionModel(caret, caret, SelectionModel::LEADING); | |
536 else if (caret > run->range.start()) { | |
537 caret = IndexOfAdjacentGrapheme(caret, false); | |
538 return SelectionModel(caret, caret, SelectionModel::LEADING); | |
539 } | |
540 } | |
541 | |
542 // The character is at the end of its run; go to the next visual run. | |
543 size_t visual_index = logical_to_visual_[run_index]; | |
544 if (visual_index == runs_.size() - 1) | |
545 return RightEndSelectionModel(); | |
546 internal::TextRun* next = runs_[visual_to_logical_[visual_index + 1]]; | |
547 return next->script_analysis.fRTL ? LastSelectionModelInsideRun(next) : | |
548 FirstSelectionModelInsideRun(next); | |
549 } | |
550 | |
551 void RenderTextWin::DrawSelection(Canvas* canvas) { | |
552 std::vector<Rect> sel( | |
553 GetSubstringBounds(GetSelectionStart(), GetCursorPosition())); | |
554 SkColor color = focused() ? kFocusedSelectionColor : kUnfocusedSelectionColor; | |
555 for (std::vector<Rect>::const_iterator i = sel.begin(); i < sel.end(); ++i) | |
556 canvas->FillRectInt(color, i->x(), i->y(), i->width(), i->height()); | |
557 } | |
558 | |
559 void RenderTextWin::DrawVisualText(Canvas* canvas) { | |
560 if (text().empty()) | |
561 return; | |
562 | |
563 skia::ScopedPlatformPaint scoped_platform_paint(canvas->AsCanvasSkia()); | |
564 HDC hdc = scoped_platform_paint.GetPlatformSurface(); | |
565 | |
566 // Set the background mix mode to transparent. | |
567 int previous_background_mode = SetBkMode(hdc, TRANSPARENT); | |
568 | |
569 Point offset(ToViewPoint(Point())); | |
570 // TODO(msw): Establish a vertical baseline for strings of mixed font heights. | |
571 size_t height = default_style().font.GetHeight(); | |
572 // Center the text vertically in the display area. | |
573 offset.Offset(0, (display_rect().height() - height) / 2); | |
574 | |
575 HRESULT hr = 0; | |
576 RECT rect = display_rect().ToRECT(); | |
577 for (size_t i = 0; i < runs_.size(); ++i) { | |
578 // Get the run specified by the visual-to-logical map. | |
579 internal::TextRun* run = runs_[visual_to_logical_[i]]; | |
580 | |
581 // Set the font and color. | |
582 SelectObject(hdc, run->font.GetNativeFont()); | |
583 SetTextColor(hdc, skia::SkColorToCOLORREF(run->foreground)); | |
584 | |
585 hr = ScriptTextOut(hdc, | |
586 &script_cache_, | |
587 offset.x(), | |
588 offset.y(), | |
589 0, | |
590 &rect, | |
591 &(run->script_analysis), | |
592 NULL, | |
593 0, | |
594 run->glyphs.get(), | |
595 run->glyph_count, | |
596 run->advance_widths.get(), | |
597 NULL, | |
598 run->offsets.get()); | |
599 DCHECK(SUCCEEDED(hr)); | |
600 | |
601 // Draw the strikethrough. | |
602 if (run->strike) { | |
603 Rect bounds(offset, Size(run->width, run->font.GetHeight())); | |
604 SkPaint paint; | |
605 paint.setAntiAlias(true); | |
606 paint.setStyle(SkPaint::kFill_Style); | |
607 paint.setColor(run->foreground); | |
608 paint.setStrokeWidth(kStrikeWidth); | |
609 canvas->AsCanvasSkia()->drawLine(SkIntToScalar(bounds.x()), | |
610 SkIntToScalar(bounds.bottom()), | |
611 SkIntToScalar(bounds.right()), | |
612 SkIntToScalar(bounds.y()), | |
613 paint); | |
614 } | |
615 | |
616 offset.Offset(run->width, 0); | |
617 } | |
618 | |
619 // Restore the previous background mix mode. | |
620 SetBkMode(hdc, previous_background_mode); | |
621 } | |
622 | |
623 void RenderTextWin::DrawCursor(Canvas* canvas) { | |
624 // Paint cursor. Replace cursor is drawn as rectangle for now. | |
625 // TODO(msw): Draw a better cursor with a better indication of association. | |
626 if (cursor_visible() && focused()) { | |
627 Rect r(GetUpdatedCursorBounds()); | |
628 canvas->DrawRectInt(kCursorColor, r.x(), r.y(), r.width(), r.height()); | |
629 } | |
14 } | 630 } |
15 | 631 |
16 RenderText* RenderText::CreateRenderText() { | 632 RenderText* RenderText::CreateRenderText() { |
17 return new RenderTextWin; | 633 return new RenderTextWin; |
18 } | 634 } |
19 | 635 |
20 } // namespace gfx | 636 } // namespace gfx |
OLD | NEW |