Chromium Code Reviews| 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 |