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