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 |