Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 <algorithm> | 7 #include <algorithm> |
| 8 | 8 |
| 9 #include "base/i18n/break_iterator.h" | 9 #include "base/i18n/break_iterator.h" |
| 10 #include "base/i18n/rtl.h" | 10 #include "base/i18n/rtl.h" |
| 11 #include "base/logging.h" | 11 #include "base/logging.h" |
| 12 #include "base/strings/string_util.h" | 12 #include "base/strings/string_util.h" |
| 13 #include "base/strings/utf_string_conversions.h" | 13 #include "base/strings/utf_string_conversions.h" |
| 14 #include "base/win/windows_version.h" | 14 #include "base/win/windows_version.h" |
| 15 #include "ui/base/text/utf16_indexing.h" | 15 #include "ui/base/text/utf16_indexing.h" |
| 16 #include "ui/gfx/canvas.h" | 16 #include "ui/gfx/canvas.h" |
| 17 #include "ui/gfx/font_fallback_win.h" | 17 #include "ui/gfx/font_fallback_win.h" |
| 18 #include "ui/gfx/font_smoothing_win.h" | 18 #include "ui/gfx/font_smoothing_win.h" |
| 19 #include "ui/gfx/platform_font_win.h" | 19 #include "ui/gfx/platform_font_win.h" |
| 20 | 20 |
| 21 namespace gfx { | 21 namespace gfx { |
| 22 | 22 |
| 23 namespace { | 23 namespace { |
| 24 | 24 |
| 25 // The maximum supported number of Uniscribe runs; a SCRIPT_ITEM is 8 bytes. | 25 // The maximum length of text supported for Uniscribe layout and display. |
| 26 // TODO(msw): Review memory use/failure? Max string length? Alternate approach? | 26 // This empirically chosen value should prevent major performance degradations. |
| 27 const int kGuessItems = 100; | 27 // TODO(msw): Support longer text, partial layout/painting, etc. |
| 28 const int kMaxItems = 10000; | 28 const size_t kMaxUniscribeTextLength = 10000; |
| 29 | 29 |
| 30 // The maximum supported number of Uniscribe glyphs; a glyph is 1 word. | 30 // The maximum supported number of text runs; an arbitrarily chosen value. |
| 31 // TODO(msw): Review memory use/failure? Max string length? Alternate approach? | 31 // TODO(msw): Support more runs; see https://codereview.chromium.org/17745005 |
| 32 const int kMaxGlyphs = 100000; | 32 const size_t kMaxRuns = 10000; |
| 33 | |
| 34 // The maximum number of glyphs per run; ScriptShape fails on larger values. | |
| 35 const size_t kMaxGlyphs = 65535; | |
| 33 | 36 |
| 34 // Callback to |EnumEnhMetaFile()| to intercept font creation. | 37 // Callback to |EnumEnhMetaFile()| to intercept font creation. |
| 35 int CALLBACK MetaFileEnumProc(HDC hdc, | 38 int CALLBACK MetaFileEnumProc(HDC hdc, |
| 36 HANDLETABLE* table, | 39 HANDLETABLE* table, |
| 37 CONST ENHMETARECORD* record, | 40 CONST ENHMETARECORD* record, |
| 38 int table_entries, | 41 int table_entries, |
| 39 LPARAM log_font) { | 42 LPARAM log_font) { |
| 40 if (record->iType == EMR_EXTCREATEFONTINDIRECTW) { | 43 if (record->iType == EMR_EXTCREATEFONTINDIRECTW) { |
| 41 const EMREXTCREATEFONTINDIRECTW* create_font_record = | 44 const EMREXTCREATEFONTINDIRECTW* create_font_record = |
| 42 reinterpret_cast<const EMREXTCREATEFONTINDIRECTW*>(record); | 45 reinterpret_cast<const EMREXTCREATEFONTINDIRECTW*>(record); |
| (...skipping 131 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 174 // static | 177 // static |
| 175 HDC RenderTextWin::cached_hdc_ = NULL; | 178 HDC RenderTextWin::cached_hdc_ = NULL; |
| 176 | 179 |
| 177 // static | 180 // static |
| 178 std::map<std::string, Font> RenderTextWin::successful_substitute_fonts_; | 181 std::map<std::string, Font> RenderTextWin::successful_substitute_fonts_; |
| 179 | 182 |
| 180 RenderTextWin::RenderTextWin() | 183 RenderTextWin::RenderTextWin() |
| 181 : RenderText(), | 184 : RenderText(), |
| 182 common_baseline_(0), | 185 common_baseline_(0), |
| 183 needs_layout_(false) { | 186 needs_layout_(false) { |
| 187 set_truncate_length(kMaxUniscribeTextLength); | |
| 188 | |
| 184 memset(&script_control_, 0, sizeof(script_control_)); | 189 memset(&script_control_, 0, sizeof(script_control_)); |
| 185 memset(&script_state_, 0, sizeof(script_state_)); | 190 memset(&script_state_, 0, sizeof(script_state_)); |
| 186 | 191 |
| 187 MoveCursorTo(EdgeSelectionModel(CURSOR_LEFT)); | 192 MoveCursorTo(EdgeSelectionModel(CURSOR_LEFT)); |
| 188 } | 193 } |
| 189 | 194 |
| 190 RenderTextWin::~RenderTextWin() { | 195 RenderTextWin::~RenderTextWin() { |
| 191 } | 196 } |
| 192 | 197 |
| 193 Size RenderTextWin::GetStringSize() { | 198 Size RenderTextWin::GetStringSize() { |
| 194 EnsureLayout(); | 199 EnsureLayout(); |
| 195 return string_size_; | 200 return string_size_; |
| 196 } | 201 } |
| 197 | 202 |
| 198 int RenderTextWin::GetBaseline() { | 203 int RenderTextWin::GetBaseline() { |
| 199 EnsureLayout(); | 204 EnsureLayout(); |
| 200 return common_baseline_; | 205 return common_baseline_; |
| 201 } | 206 } |
| 202 | 207 |
| 203 SelectionModel RenderTextWin::FindCursorPosition(const Point& point) { | 208 SelectionModel RenderTextWin::FindCursorPosition(const Point& point) { |
| 204 if (text().empty()) | 209 if (text().empty()) |
| 205 return SelectionModel(); | 210 return SelectionModel(); |
| 206 | 211 |
| 207 EnsureLayout(); | 212 EnsureLayout(); |
| 208 // Find the run that contains the point and adjust the argument location. | 213 // Find the run that contains the point and adjust the argument location. |
| 209 int x = ToTextPoint(point).x(); | 214 int x = ToTextPoint(point).x(); |
| 210 size_t run_index = GetRunContainingXCoord(x); | 215 size_t run_index = GetRunContainingXCoord(x); |
| 211 if (run_index == runs_.size()) | 216 if (run_index >= runs_.size()) |
| 212 return EdgeSelectionModel((x < 0) ? CURSOR_LEFT : CURSOR_RIGHT); | 217 return EdgeSelectionModel((x < 0) ? CURSOR_LEFT : CURSOR_RIGHT); |
| 213 internal::TextRun* run = runs_[run_index]; | 218 internal::TextRun* run = runs_[run_index]; |
| 214 | 219 |
| 215 int position = 0, trailing = 0; | 220 int position = 0, trailing = 0; |
| 216 HRESULT hr = ScriptXtoCP(x - run->preceding_run_widths, | 221 HRESULT hr = ScriptXtoCP(x - run->preceding_run_widths, |
| 217 run->range.length(), | 222 run->range.length(), |
| 218 run->glyph_count, | 223 run->glyph_count, |
| 219 run->logical_clusters.get(), | 224 run->logical_clusters.get(), |
| 220 run->visible_attributes.get(), | 225 run->visible_attributes.get(), |
| 221 run->advance_widths.get(), | 226 run->advance_widths.get(), |
| (...skipping 116 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 338 RenderText::SetSelectionModel(model); | 343 RenderText::SetSelectionModel(model); |
| 339 // TODO(xji|msw): The text selection color is applied in ItemizeLogicalText(). | 344 // TODO(xji|msw): The text selection color is applied in ItemizeLogicalText(). |
| 340 // So, the layout must be updated in order to draw the proper selection range. | 345 // So, the layout must be updated in order to draw the proper selection range. |
| 341 // Colors should be applied in DrawVisualText(), as done by RenderTextLinux. | 346 // Colors should be applied in DrawVisualText(), as done by RenderTextLinux. |
| 342 ResetLayout(); | 347 ResetLayout(); |
| 343 } | 348 } |
| 344 | 349 |
| 345 ui::Range RenderTextWin::GetGlyphBounds(size_t index) { | 350 ui::Range RenderTextWin::GetGlyphBounds(size_t index) { |
| 346 const size_t run_index = | 351 const size_t run_index = |
| 347 GetRunContainingCaret(SelectionModel(index, CURSOR_FORWARD)); | 352 GetRunContainingCaret(SelectionModel(index, CURSOR_FORWARD)); |
| 348 DCHECK_LT(run_index, runs_.size()); | 353 // Return edge bounds if the index is invalid or beyond the layout text size. |
| 354 if (run_index >= runs_.size()) | |
| 355 return ui::Range(string_size_.width()); | |
| 349 internal::TextRun* run = runs_[run_index]; | 356 internal::TextRun* run = runs_[run_index]; |
| 350 const size_t layout_index = TextIndexToLayoutIndex(index); | 357 const size_t layout_index = TextIndexToLayoutIndex(index); |
| 351 return ui::Range(GetGlyphXBoundary(run, layout_index, false), | 358 return ui::Range(GetGlyphXBoundary(run, layout_index, false), |
| 352 GetGlyphXBoundary(run, layout_index, true)); | 359 GetGlyphXBoundary(run, layout_index, true)); |
| 353 } | 360 } |
| 354 | 361 |
| 355 std::vector<Rect> RenderTextWin::GetSubstringBounds(const ui::Range& range) { | 362 std::vector<Rect> RenderTextWin::GetSubstringBounds(const ui::Range& range) { |
| 356 DCHECK(!needs_layout_); | 363 DCHECK(!needs_layout_); |
| 357 DCHECK(ui::Range(0, text().length()).Contains(range)); | 364 DCHECK(ui::Range(0, text().length()).Contains(range)); |
| 358 ui::Range layout_range(TextIndexToLayoutIndex(range.start()), | 365 ui::Range layout_range(TextIndexToLayoutIndex(range.start()), |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 379 rect.Union(bounds.back()); | 386 rect.Union(bounds.back()); |
| 380 bounds.pop_back(); | 387 bounds.pop_back(); |
| 381 } | 388 } |
| 382 bounds.push_back(rect); | 389 bounds.push_back(rect); |
| 383 } | 390 } |
| 384 } | 391 } |
| 385 return bounds; | 392 return bounds; |
| 386 } | 393 } |
| 387 | 394 |
| 388 size_t RenderTextWin::TextIndexToLayoutIndex(size_t index) const { | 395 size_t RenderTextWin::TextIndexToLayoutIndex(size_t index) const { |
| 389 if (!obscured()) | |
| 390 return index; | |
| 391 | |
| 392 DCHECK_LE(index, text().length()); | 396 DCHECK_LE(index, text().length()); |
| 393 const ptrdiff_t offset = ui::UTF16IndexToOffset(text(), 0, index); | 397 ptrdiff_t i = obscured() ? ui::UTF16IndexToOffset(text(), 0, index) : index; |
| 394 DCHECK_GE(offset, 0); | 398 CHECK_GE(i, 0); |
| 395 DCHECK_LE(static_cast<size_t>(offset), GetLayoutText().length()); | 399 // Clamp layout indices to the length of the text actually used for layout. |
| 396 return static_cast<size_t>(offset); | 400 return std::min<size_t>(GetLayoutText().length(), i); |
| 397 } | 401 } |
| 398 | 402 |
| 399 size_t RenderTextWin::LayoutIndexToTextIndex(size_t index) const { | 403 size_t RenderTextWin::LayoutIndexToTextIndex(size_t index) const { |
| 400 if (!obscured()) | 404 if (!obscured()) |
| 401 return index; | 405 return index; |
| 402 | 406 |
| 403 DCHECK_LE(index, GetLayoutText().length()); | 407 DCHECK_LE(index, GetLayoutText().length()); |
| 404 const size_t text_index = ui::UTF16OffsetToIndex(text(), 0, index); | 408 const size_t text_index = ui::UTF16OffsetToIndex(text(), 0, index); |
| 405 DCHECK_LE(text_index, text().length()); | 409 DCHECK_LE(text_index, text().length()); |
| 406 return text_index; | 410 return text_index; |
| (...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 489 // Set Uniscribe's base text direction. | 493 // Set Uniscribe's base text direction. |
| 490 script_state_.uBidiLevel = | 494 script_state_.uBidiLevel = |
| 491 (GetTextDirection() == base::i18n::RIGHT_TO_LEFT) ? 1 : 0; | 495 (GetTextDirection() == base::i18n::RIGHT_TO_LEFT) ? 1 : 0; |
| 492 | 496 |
| 493 if (text().empty()) | 497 if (text().empty()) |
| 494 return; | 498 return; |
| 495 | 499 |
| 496 HRESULT hr = E_OUTOFMEMORY; | 500 HRESULT hr = E_OUTOFMEMORY; |
| 497 int script_items_count = 0; | 501 int script_items_count = 0; |
| 498 std::vector<SCRIPT_ITEM> script_items; | 502 std::vector<SCRIPT_ITEM> script_items; |
| 499 const size_t text_length = GetLayoutText().length(); | 503 |
| 500 for (size_t n = kGuessItems; hr == E_OUTOFMEMORY && n < kMaxItems; n *= 2) { | 504 // Guess the run count based on string sizes; Uniscribe requires 3 at minimum. |
| 505 const size_t layout_text_length = GetLayoutText().length(); | |
| 506 size_t run_guess = std::min(kMaxRuns, std::max(3U, layout_text_length / 10)); | |
|
Alexei Svitkine (slow)
2013/06/28 15:37:02
The old code would start at 100 whereas your new c
msw
2013/06/28 16:31:23
Done.
| |
| 507 while (hr == E_OUTOFMEMORY && run_guess <= kMaxRuns) { | |
| 501 // Derive the array of Uniscribe script items from the logical text. | 508 // Derive the array of Uniscribe script items from the logical text. |
| 502 // ScriptItemize always adds a terminal array item so that the length of the | 509 // ScriptItemize always adds a terminal array item so that the length of |
| 503 // last item can be derived from the terminal SCRIPT_ITEM::iCharPos. | 510 // the last item can be derived from the terminal SCRIPT_ITEM::iCharPos. |
| 504 script_items.resize(n); | 511 script_items.resize(run_guess); |
| 505 hr = ScriptItemize(GetLayoutText().c_str(), | 512 hr = ScriptItemize(GetLayoutText().c_str(), layout_text_length, |
| 506 text_length, | 513 run_guess - 1, &script_control_, &script_state_, |
| 507 n - 1, | 514 &script_items[0], &script_items_count); |
| 508 &script_control_, | 515 // Ensure that |kMaxRuns| is attempted and the loop terminates afterward. |
| 509 &script_state_, | 516 run_guess = std::max(run_guess + 1, std::min(run_guess * 2, kMaxRuns)); |
| 510 &script_items[0], | |
| 511 &script_items_count); | |
| 512 } | 517 } |
| 513 DCHECK(SUCCEEDED(hr)); | 518 DCHECK(SUCCEEDED(hr)); |
| 514 | 519 if (!SUCCEEDED(hr) || script_items_count <= 0) |
| 515 if (script_items_count <= 0) | |
| 516 return; | 520 return; |
| 517 | 521 |
| 518 // Temporarily apply composition underlines and selection colors. | 522 // Temporarily apply composition underlines and selection colors. |
| 519 ApplyCompositionAndSelectionStyles(); | 523 ApplyCompositionAndSelectionStyles(); |
| 520 | 524 |
| 521 // Build the list of runs from the script items and ranged colors/styles. | 525 // Build the list of runs from the script items and ranged colors/styles. |
| 522 // TODO(msw): Only break for bold/italic, not color etc. See TextRun comment. | 526 // TODO(msw): Only break for bold/italic, not color etc. See TextRun comment. |
| 523 internal::StyleIterator style(colors(), styles()); | 527 internal::StyleIterator style(colors(), styles()); |
| 524 SCRIPT_ITEM* script_item = &script_items[0]; | 528 SCRIPT_ITEM* script_item = &script_items[0]; |
| 525 const size_t layout_text_length = GetLayoutText().length(); | 529 const size_t max_run_length = kMaxGlyphs / 2; |
| 526 for (size_t run_break = 0; run_break < layout_text_length;) { | 530 for (size_t run_break = 0; run_break < layout_text_length;) { |
| 527 internal::TextRun* run = new internal::TextRun(); | 531 internal::TextRun* run = new internal::TextRun(); |
| 528 run->range.set_start(run_break); | 532 run->range.set_start(run_break); |
| 529 run->font = GetFont(); | 533 run->font = GetFont(); |
| 530 run->font_style = (style.style(BOLD) ? Font::BOLD : 0) | | 534 run->font_style = (style.style(BOLD) ? Font::BOLD : 0) | |
| 531 (style.style(ITALIC) ? Font::ITALIC : 0); | 535 (style.style(ITALIC) ? Font::ITALIC : 0); |
| 532 DeriveFontIfNecessary(run->font.GetFontSize(), run->font.GetHeight(), | 536 DeriveFontIfNecessary(run->font.GetFontSize(), run->font.GetHeight(), |
| 533 run->font_style, &run->font); | 537 run->font_style, &run->font); |
| 534 run->foreground = style.color(); | 538 run->foreground = style.color(); |
| 535 run->strike = style.style(STRIKE); | 539 run->strike = style.style(STRIKE); |
| 536 run->diagonal_strike = style.style(DIAGONAL_STRIKE); | 540 run->diagonal_strike = style.style(DIAGONAL_STRIKE); |
| 537 run->underline = style.style(UNDERLINE); | 541 run->underline = style.style(UNDERLINE); |
| 538 run->script_analysis = script_item->a; | 542 run->script_analysis = script_item->a; |
| 539 | 543 |
| 540 // Find the next break and advance the iterators as needed. | 544 // Find the next break and advance the iterators as needed. |
| 541 const size_t script_item_break = (script_item + 1)->iCharPos; | 545 const size_t script_item_break = (script_item + 1)->iCharPos; |
| 542 run_break = std::min(script_item_break, | 546 run_break = std::min(script_item_break, |
| 543 TextIndexToLayoutIndex(style.GetRange().end())); | 547 TextIndexToLayoutIndex(style.GetRange().end())); |
| 548 // Clamp run lengths to avoid exceeding the maximum supported glyph count. | |
| 549 if ((run_break - run->range.start()) > max_run_length) | |
| 550 run_break = run->range.start() + max_run_length; | |
| 544 style.UpdatePosition(LayoutIndexToTextIndex(run_break)); | 551 style.UpdatePosition(LayoutIndexToTextIndex(run_break)); |
| 545 if (script_item_break == run_break) | 552 if (script_item_break == run_break) |
| 546 script_item++; | 553 script_item++; |
| 547 run->range.set_end(run_break); | 554 run->range.set_end(run_break); |
| 548 runs_.push_back(run); | 555 runs_.push_back(run); |
| 549 } | 556 } |
| 550 | 557 |
| 551 // Undo the temporarily applied composition underlines and selection colors. | 558 // Undo the temporarily applied composition underlines and selection colors. |
| 552 UndoCompositionAndSelectionStyles(); | 559 UndoCompositionAndSelectionStyles(); |
| 553 } | 560 } |
| (...skipping 179 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 733 DeriveFontIfNecessary(font_size, font_height, run->font_style, &run->font); | 740 DeriveFontIfNecessary(font_size, font_height, run->font_style, &run->font); |
| 734 ScriptFreeCache(&run->script_cache); | 741 ScriptFreeCache(&run->script_cache); |
| 735 } | 742 } |
| 736 | 743 |
| 737 // Select the font desired for glyph generation. | 744 // Select the font desired for glyph generation. |
| 738 SelectObject(cached_hdc_, run->font.GetNativeFont()); | 745 SelectObject(cached_hdc_, run->font.GetNativeFont()); |
| 739 | 746 |
| 740 HRESULT hr = E_OUTOFMEMORY; | 747 HRESULT hr = E_OUTOFMEMORY; |
| 741 const size_t run_length = run->range.length(); | 748 const size_t run_length = run->range.length(); |
| 742 const wchar_t* run_text = &(GetLayoutText()[run->range.start()]); | 749 const wchar_t* run_text = &(GetLayoutText()[run->range.start()]); |
| 743 // Max glyph guess: http://msdn.microsoft.com/en-us/library/dd368564.aspx | 750 // Guess the expected number of glyphs from the length of the run. |
| 751 // MSDN suggests this at http://msdn.microsoft.com/en-us/library/dd368564.aspx | |
| 744 size_t max_glyphs = static_cast<size_t>(1.5 * run_length + 16); | 752 size_t max_glyphs = static_cast<size_t>(1.5 * run_length + 16); |
| 745 while (hr == E_OUTOFMEMORY && max_glyphs < kMaxGlyphs) { | 753 while (hr == E_OUTOFMEMORY && max_glyphs <= kMaxGlyphs) { |
| 746 run->glyph_count = 0; | 754 run->glyph_count = 0; |
| 747 run->glyphs.reset(new WORD[max_glyphs]); | 755 run->glyphs.reset(new WORD[max_glyphs]); |
| 748 run->visible_attributes.reset(new SCRIPT_VISATTR[max_glyphs]); | 756 run->visible_attributes.reset(new SCRIPT_VISATTR[max_glyphs]); |
| 749 hr = ScriptShape(cached_hdc_, | 757 hr = ScriptShape(cached_hdc_, &run->script_cache, run_text, run_length, |
| 750 &run->script_cache, | 758 max_glyphs, &run->script_analysis, run->glyphs.get(), |
| 751 run_text, | 759 run->logical_clusters.get(), run->visible_attributes.get(), |
| 752 run_length, | |
| 753 max_glyphs, | |
| 754 &run->script_analysis, | |
| 755 run->glyphs.get(), | |
| 756 run->logical_clusters.get(), | |
| 757 run->visible_attributes.get(), | |
| 758 &run->glyph_count); | 760 &run->glyph_count); |
| 759 max_glyphs *= 2; | 761 // Ensure that |kMaxGlyphs| is attempted and the loop terminates afterward. |
| 762 max_glyphs = std::max(max_glyphs + 1, std::min(max_glyphs * 2, kMaxGlyphs)); | |
|
Alexei Svitkine (slow)
2013/06/28 15:37:02
How about making a helper function for this constr
msw
2013/06/28 16:31:23
I'll pass; 3 uses or real complexity constitutes a
| |
| 760 } | 763 } |
| 761 return hr; | 764 return hr; |
| 762 } | 765 } |
| 763 | 766 |
| 764 int RenderTextWin::CountCharsWithMissingGlyphs(internal::TextRun* run) const { | 767 int RenderTextWin::CountCharsWithMissingGlyphs(internal::TextRun* run) const { |
| 765 int chars_not_missing_glyphs = 0; | 768 int chars_not_missing_glyphs = 0; |
| 766 SCRIPT_FONTPROPERTIES properties; | 769 SCRIPT_FONTPROPERTIES properties; |
| 767 memset(&properties, 0, sizeof(properties)); | 770 memset(&properties, 0, sizeof(properties)); |
| 768 properties.cBytes = sizeof(properties); | 771 properties.cBytes = sizeof(properties); |
| 769 ScriptGetFontProperties(cached_hdc_, &run->script_cache, &properties); | 772 ScriptGetFontProperties(cached_hdc_, &run->script_cache, &properties); |
| (...skipping 58 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 828 size_t position = LayoutIndexToTextIndex(run->range.end()); | 831 size_t position = LayoutIndexToTextIndex(run->range.end()); |
| 829 position = IndexOfAdjacentGrapheme(position, CURSOR_BACKWARD); | 832 position = IndexOfAdjacentGrapheme(position, CURSOR_BACKWARD); |
| 830 return SelectionModel(position, CURSOR_FORWARD); | 833 return SelectionModel(position, CURSOR_FORWARD); |
| 831 } | 834 } |
| 832 | 835 |
| 833 RenderText* RenderText::CreateInstance() { | 836 RenderText* RenderText::CreateInstance() { |
| 834 return new RenderTextWin; | 837 return new RenderTextWin; |
| 835 } | 838 } |
| 836 | 839 |
| 837 } // namespace gfx | 840 } // namespace gfx |
| OLD | NEW |