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