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 <algorithm> | 7 #include <algorithm> |
| 8 #include <map> |
8 | 9 |
9 #include "base/logging.h" | 10 #include "base/logging.h" |
10 #include "base/stl_util.h" | 11 #include "base/stl_util.h" |
11 #include "base/string_util.h" | 12 #include "base/string_util.h" |
12 #include "base/utf_string_conversions.h" | |
13 #include "base/win/scoped_hdc.h" | 13 #include "base/win/scoped_hdc.h" |
14 #include "third_party/skia/include/core/SkTypeface.h" | 14 #include "third_party/skia/include/core/SkTypeface.h" |
15 #include "ui/gfx/canvas.h" | 15 #include "ui/gfx/canvas.h" |
16 #include "ui/gfx/canvas_skia.h" | 16 #include "ui/gfx/canvas_skia.h" |
17 #include "ui/gfx/platform_font.h" | |
18 | 17 |
19 namespace { | 18 namespace { |
20 | 19 |
21 // The maximum supported number of Uniscribe runs; a SCRIPT_ITEM is 8 bytes. | 20 // The maximum supported number of Uniscribe runs; a SCRIPT_ITEM is 8 bytes. |
22 // TODO(msw): Review memory use/failure? Max string length? Alternate approach? | 21 // TODO(msw): Review memory use/failure? Max string length? Alternate approach? |
23 const int kGuessItems = 100; | 22 const int kGuessItems = 100; |
24 const int kMaxItems = 10000; | 23 const int kMaxItems = 10000; |
25 | 24 |
26 // The maximum supported number of Uniscribe glyphs; a glyph is 1 word. | 25 // The maximum supported number of Uniscribe glyphs; a glyph is 1 word. |
27 // TODO(msw): Review memory use/failure? Max string length? Alternate approach? | 26 // TODO(msw): Review memory use/failure? Max string length? Alternate approach? |
(...skipping 28 matching lines...) Expand all Loading... |
56 canvas_skia->drawRect(r, paint); | 55 canvas_skia->drawRect(r, paint); |
57 } | 56 } |
58 if (run.strike) { | 57 if (run.strike) { |
59 SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y); | 58 SkScalar offset = SkScalarMulAdd(text_size, kStrikeThroughOffset, y); |
60 r.fTop = offset; | 59 r.fTop = offset; |
61 r.fBottom = offset + height; | 60 r.fBottom = offset + height; |
62 canvas_skia->drawRect(r, paint); | 61 canvas_skia->drawRect(r, paint); |
63 } | 62 } |
64 } | 63 } |
65 | 64 |
66 // Callback to |EnumEnhMetaFile()| to intercept font creation. | |
67 int CALLBACK MetaFileEnumProc(HDC hdc, | |
68 HANDLETABLE* table, | |
69 CONST ENHMETARECORD* record, | |
70 int table_entries, | |
71 LPARAM log_font) { | |
72 if (record->iType == EMR_EXTCREATEFONTINDIRECTW) { | |
73 const EMREXTCREATEFONTINDIRECTW* create_font_record = | |
74 reinterpret_cast<const EMREXTCREATEFONTINDIRECTW*>(record); | |
75 *reinterpret_cast<LOGFONT*>(log_font) = create_font_record->elfw.elfLogFont; | |
76 } | |
77 return 1; | |
78 } | |
79 | |
80 // Finds a fallback font to use to render the specified |text| with respect to | |
81 // an initial |font|. Returns the resulting font via out param |result|. Returns | |
82 // |true| if a fallback font was found. | |
83 // Adapted from WebKit's |FontCache::GetFontDataForCharacters()|. | |
84 bool ChooseFallbackFont(HDC hdc, | |
85 const gfx::Font& font, | |
86 const wchar_t* text, | |
87 int text_length, | |
88 gfx::Font* result) { | |
89 // Use a meta file to intercept the fallback font chosen by Uniscribe. | |
90 HDC meta_file_dc = CreateEnhMetaFile(hdc, NULL, NULL, NULL); | |
91 if (!meta_file_dc) | |
92 return false; | |
93 | |
94 SelectObject(meta_file_dc, font.GetNativeFont()); | |
95 | |
96 SCRIPT_STRING_ANALYSIS script_analysis; | |
97 HRESULT hresult = | |
98 ScriptStringAnalyse(meta_file_dc, text, text_length, 0, -1, | |
99 SSA_METAFILE | SSA_FALLBACK | SSA_GLYPHS | SSA_LINK, | |
100 0, NULL, NULL, NULL, NULL, NULL, &script_analysis); | |
101 | |
102 if (SUCCEEDED(hresult)) { | |
103 hresult = ScriptStringOut(script_analysis, 0, 0, 0, NULL, 0, 0, FALSE); | |
104 ScriptStringFree(&script_analysis); | |
105 } | |
106 | |
107 bool found_fallback = false; | |
108 HENHMETAFILE meta_file = CloseEnhMetaFile(meta_file_dc); | |
109 if (SUCCEEDED(hresult)) { | |
110 LOGFONT log_font; | |
111 log_font.lfFaceName[0] = 0; | |
112 EnumEnhMetaFile(0, meta_file, MetaFileEnumProc, &log_font, NULL); | |
113 if (log_font.lfFaceName[0]) { | |
114 *result = gfx::Font(UTF16ToUTF8(log_font.lfFaceName), font.GetFontSize()); | |
115 found_fallback = true; | |
116 } | |
117 } | |
118 DeleteEnhMetaFile(meta_file); | |
119 | |
120 return found_fallback; | |
121 } | |
122 | |
123 } // namespace | 65 } // namespace |
124 | 66 |
125 namespace gfx { | 67 namespace gfx { |
126 | 68 |
127 namespace internal { | 69 namespace internal { |
128 | 70 |
129 TextRun::TextRun() | 71 TextRun::TextRun() |
130 : strike(false), | 72 : strike(false), |
131 underline(false), | 73 underline(false), |
132 width(0), | 74 width(0), |
133 preceding_run_widths(0), | 75 preceding_run_widths(0), |
134 glyph_count(0), | 76 glyph_count(0) { |
135 script_cache(NULL) { | |
136 } | 77 } |
137 | 78 |
138 } // namespace internal | 79 } // namespace internal |
139 | 80 |
140 RenderTextWin::RenderTextWin() | 81 RenderTextWin::RenderTextWin() |
141 : RenderText(), | 82 : RenderText(), |
142 script_control_(), | 83 script_control_(), |
143 script_state_(), | 84 script_state_(), |
| 85 script_cache_(NULL), |
144 string_width_(0) { | 86 string_width_(0) { |
145 // Omitting default constructors for script_* would leave POD uninitialized. | 87 // Omitting default constructors for script_* would leave POD uninitialized. |
146 HRESULT hr = 0; | 88 HRESULT hr = 0; |
147 | 89 |
148 // TODO(msw): Call ScriptRecordDigitSubstitution on WM_SETTINGCHANGE message. | 90 // TODO(msw): Call ScriptRecordDigitSubstitution on WM_SETTINGCHANGE message. |
149 // TODO(msw): Use Chrome/profile locale/language settings? | 91 // TODO(msw): Use Chrome/profile locale/language settings? |
150 hr = ScriptRecordDigitSubstitution(LOCALE_USER_DEFAULT, &digit_substitute_); | 92 hr = ScriptRecordDigitSubstitution(LOCALE_USER_DEFAULT, &digit_substitute_); |
151 DCHECK(SUCCEEDED(hr)); | 93 DCHECK(SUCCEEDED(hr)); |
152 | 94 |
153 hr = ScriptApplyDigitSubstitution(&digit_substitute_, | 95 hr = ScriptApplyDigitSubstitution(&digit_substitute_, |
154 &script_control_, | 96 &script_control_, |
155 &script_state_); | 97 &script_state_); |
156 DCHECK(SUCCEEDED(hr)); | 98 DCHECK(SUCCEEDED(hr)); |
157 script_control_.fMergeNeutralItems = true; | 99 script_control_.fMergeNeutralItems = true; |
158 | 100 |
159 MoveCursorTo(LeftEndSelectionModel()); | 101 MoveCursorTo(LeftEndSelectionModel()); |
160 } | 102 } |
161 | 103 |
162 RenderTextWin::~RenderTextWin() { | 104 RenderTextWin::~RenderTextWin() { |
163 for (size_t i = 0; i < runs_.size(); ++i) | 105 ScriptFreeCache(&script_cache_); |
164 ScriptFreeCache(&runs_[i]->script_cache); | |
165 STLDeleteContainerPointers(runs_.begin(), runs_.end()); | 106 STLDeleteContainerPointers(runs_.begin(), runs_.end()); |
166 } | 107 } |
167 | 108 |
168 int RenderTextWin::GetStringWidth() { | 109 int RenderTextWin::GetStringWidth() { |
169 return string_width_; | 110 return string_width_; |
170 } | 111 } |
171 | 112 |
172 void RenderTextWin::Draw(Canvas* canvas) { | 113 void RenderTextWin::Draw(Canvas* canvas) { |
173 DrawSelection(canvas); | 114 DrawSelection(canvas); |
174 DrawVisualText(canvas); | 115 DrawVisualText(canvas); |
(...skipping 229 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
404 STLDeleteContainerPointers(runs_.begin(), runs_.end()); | 345 STLDeleteContainerPointers(runs_.begin(), runs_.end()); |
405 runs_.clear(); | 346 runs_.clear(); |
406 if (text().empty()) | 347 if (text().empty()) |
407 return; | 348 return; |
408 | 349 |
409 const wchar_t* raw_text = text().c_str(); | 350 const wchar_t* raw_text = text().c_str(); |
410 const int text_length = text().length(); | 351 const int text_length = text().length(); |
411 | 352 |
412 HRESULT hr = E_OUTOFMEMORY; | 353 HRESULT hr = E_OUTOFMEMORY; |
413 int script_items_count = 0; | 354 int script_items_count = 0; |
414 std::vector<SCRIPT_ITEM> script_items; | 355 scoped_array<SCRIPT_ITEM> script_items; |
415 for (size_t n = kGuessItems; hr == E_OUTOFMEMORY && n < kMaxItems; n *= 2) { | 356 for (size_t n = kGuessItems; hr == E_OUTOFMEMORY && n < kMaxItems; n *= 2) { |
416 // Derive the array of Uniscribe script items from the logical text. | 357 // Derive the array of Uniscribe script items from the logical text. |
417 // ScriptItemize always adds a terminal array item so that the length of the | 358 // ScriptItemize always adds a terminal array item so that the length of the |
418 // last item can be derived from the terminal SCRIPT_ITEM::iCharPos. | 359 // last item can be derived from the terminal SCRIPT_ITEM::iCharPos. |
419 script_items.resize(n); | 360 script_items.reset(new SCRIPT_ITEM[n]); |
420 hr = ScriptItemize(raw_text, | 361 hr = ScriptItemize(raw_text, |
421 text_length, | 362 text_length, |
422 n - 1, | 363 n - 1, |
423 &script_control_, | 364 &script_control_, |
424 &script_state_, | 365 &script_state_, |
425 &script_items[0], | 366 script_items.get(), |
426 &script_items_count); | 367 &script_items_count); |
427 } | 368 } |
428 DCHECK(SUCCEEDED(hr)); | 369 DCHECK(SUCCEEDED(hr)); |
429 | 370 |
430 if (script_items_count <= 0) | 371 if (script_items_count <= 0) |
431 return; | 372 return; |
432 | 373 |
433 // Build the list of runs, merge font/underline styles. | 374 // Build the list of runs, merge font/underline styles. |
434 // TODO(msw): Only break for font changes, not color etc. See TextRun comment. | 375 // TODO(msw): Only break for font changes, not color etc. See TextRun comment. |
435 // TODO(msw): Apply the overriding selection and composition styles. | 376 // TODO(msw): Apply the overriding selection and composition styles. |
436 StyleRanges::const_iterator style = style_ranges().begin(); | 377 StyleRanges::const_iterator style = style_ranges().begin(); |
437 SCRIPT_ITEM* script_item = &script_items[0]; | 378 SCRIPT_ITEM* script_item = script_items.get(); |
438 for (int run_break = 0; run_break < text_length;) { | 379 for (int run_break = 0; run_break < text_length;) { |
439 internal::TextRun* run = new internal::TextRun(); | 380 internal::TextRun* run = new internal::TextRun(); |
440 run->range.set_start(run_break); | 381 run->range.set_start(run_break); |
441 run->font = style->font; | 382 run->font = style->font; |
442 run->foreground = style->foreground; | 383 run->foreground = style->foreground; |
443 run->strike = style->strike; | 384 run->strike = style->strike; |
444 run->underline = style->underline; | 385 run->underline = style->underline; |
445 run->script_analysis = script_item->a; | 386 run->script_analysis = script_item->a; |
446 | 387 |
447 // Find the range end and advance the structures as needed. | 388 // Find the range end and advance the structures as needed. |
(...skipping 22 matching lines...) Expand all Loading... |
470 SelectObject(hdc, run->font.GetNativeFont()); | 411 SelectObject(hdc, run->font.GetNativeFont()); |
471 | 412 |
472 run->logical_clusters.reset(new WORD[run_length]); | 413 run->logical_clusters.reset(new WORD[run_length]); |
473 run->glyph_count = 0; | 414 run->glyph_count = 0; |
474 // Max glyph guess: http://msdn.microsoft.com/en-us/library/dd368564.aspx | 415 // Max glyph guess: http://msdn.microsoft.com/en-us/library/dd368564.aspx |
475 size_t max_glyphs = static_cast<size_t>(1.5 * run_length + 16); | 416 size_t max_glyphs = static_cast<size_t>(1.5 * run_length + 16); |
476 while (max_glyphs < kMaxGlyphs) { | 417 while (max_glyphs < kMaxGlyphs) { |
477 run->glyphs.reset(new WORD[max_glyphs]); | 418 run->glyphs.reset(new WORD[max_glyphs]); |
478 run->visible_attributes.reset(new SCRIPT_VISATTR[max_glyphs]); | 419 run->visible_attributes.reset(new SCRIPT_VISATTR[max_glyphs]); |
479 hr = ScriptShape(hdc, | 420 hr = ScriptShape(hdc, |
480 &run->script_cache, | 421 &script_cache_, |
481 run_text, | 422 run_text, |
482 run_length, | 423 run_length, |
483 max_glyphs, | 424 max_glyphs, |
484 &(run->script_analysis), | 425 &(run->script_analysis), |
485 run->glyphs.get(), | 426 run->glyphs.get(), |
486 run->logical_clusters.get(), | 427 run->logical_clusters.get(), |
487 run->visible_attributes.get(), | 428 run->visible_attributes.get(), |
488 &(run->glyph_count)); | 429 &(run->glyph_count)); |
489 if (hr == E_OUTOFMEMORY) { | 430 if (hr == E_OUTOFMEMORY) { |
490 max_glyphs *= 2; | 431 max_glyphs *= 2; |
491 } else if (hr == USP_E_SCRIPT_NOT_IN_FONT) { | 432 } else if (hr == USP_E_SCRIPT_NOT_IN_FONT) { |
492 // TODO(msw): Don't use SCRIPT_UNDEFINED. Apparently Uniscribe can crash | 433 // The run's font doesn't contain the required glyphs, use an alternate. |
493 // on certain surrogate pairs when using SCRIPT_UNDEFINED. | 434 // TODO(msw): Font fallback... Don't use SCRIPT_UNDEFINED. |
494 // See https://bugzilla.mozilla.org/show_bug.cgi?id=341500 | 435 // See https://bugzilla.mozilla.org/show_bug.cgi?id=341500 |
495 // And http://maxradi.us/documents/uniscribe/ | 436 // And http://maxradi.us/documents/uniscribe/ |
496 if (run->script_analysis.eScript == SCRIPT_UNDEFINED) | 437 if (run->script_analysis.eScript == SCRIPT_UNDEFINED) |
497 break; | 438 break; |
498 | |
499 // The run's font doesn't contain the required glyphs, use an alternate. | |
500 if (ChooseFallbackFont(hdc, run->font, run_text, run_length, | |
501 &run->font)) { | |
502 ScriptFreeCache(&run->script_cache); | |
503 SelectObject(hdc, run->font.GetNativeFont()); | |
504 } | |
505 | |
506 run->script_analysis.eScript = SCRIPT_UNDEFINED; | 439 run->script_analysis.eScript = SCRIPT_UNDEFINED; |
507 } else { | 440 } else { |
508 break; | 441 break; |
509 } | 442 } |
510 } | 443 } |
511 DCHECK(SUCCEEDED(hr)); | 444 DCHECK(SUCCEEDED(hr)); |
512 | 445 |
513 if (run->glyph_count > 0) { | 446 if (run->glyph_count > 0) { |
514 run->advance_widths.reset(new int[run->glyph_count]); | 447 run->advance_widths.reset(new int[run->glyph_count]); |
515 run->offsets.reset(new GOFFSET[run->glyph_count]); | 448 run->offsets.reset(new GOFFSET[run->glyph_count]); |
516 hr = ScriptPlace(hdc, | 449 hr = ScriptPlace(hdc, |
517 &run->script_cache, | 450 &script_cache_, |
518 run->glyphs.get(), | 451 run->glyphs.get(), |
519 run->glyph_count, | 452 run->glyph_count, |
520 run->visible_attributes.get(), | 453 run->visible_attributes.get(), |
521 &(run->script_analysis), | 454 &(run->script_analysis), |
522 run->advance_widths.get(), | 455 run->advance_widths.get(), |
523 run->offsets.get(), | 456 run->offsets.get(), |
524 &(run->abc_widths)); | 457 &(run->abc_widths)); |
525 DCHECK(SUCCEEDED(hr)); | 458 DCHECK(SUCCEEDED(hr)); |
526 } | 459 } |
527 } | 460 } |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
567 size_t RenderTextWin::GetRunContainingPoint(const Point& point) const { | 500 size_t RenderTextWin::GetRunContainingPoint(const Point& point) const { |
568 // Find the text run containing the argument point (assumed already offset). | 501 // Find the text run containing the argument point (assumed already offset). |
569 size_t run = 0; | 502 size_t run = 0; |
570 for (; run < runs_.size(); ++run) | 503 for (; run < runs_.size(); ++run) |
571 if (runs_[run]->preceding_run_widths <= point.x() && | 504 if (runs_[run]->preceding_run_widths <= point.x() && |
572 runs_[run]->preceding_run_widths + runs_[run]->width > point.x()) | 505 runs_[run]->preceding_run_widths + runs_[run]->width > point.x()) |
573 break; | 506 break; |
574 return run; | 507 return run; |
575 } | 508 } |
576 | 509 |
| 510 |
577 SelectionModel RenderTextWin::FirstSelectionModelInsideRun( | 511 SelectionModel RenderTextWin::FirstSelectionModelInsideRun( |
578 internal::TextRun* run) { | 512 internal::TextRun* run) { |
579 size_t caret = run->range.start(); | 513 size_t caret = run->range.start(); |
580 size_t cursor = IndexOfAdjacentGrapheme(caret, true); | 514 size_t cursor = IndexOfAdjacentGrapheme(caret, true); |
581 return SelectionModel(cursor, caret, SelectionModel::TRAILING); | 515 return SelectionModel(cursor, caret, SelectionModel::TRAILING); |
582 } | 516 } |
583 | 517 |
584 SelectionModel RenderTextWin::LastSelectionModelInsideRun( | 518 SelectionModel RenderTextWin::LastSelectionModelInsideRun( |
585 internal::TextRun* run) { | 519 internal::TextRun* run) { |
586 size_t caret = IndexOfAdjacentGrapheme(run->range.end(), false); | 520 size_t caret = IndexOfAdjacentGrapheme(run->range.end(), false); |
(...skipping 146 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
733 Rect r(GetUpdatedCursorBounds()); | 667 Rect r(GetUpdatedCursorBounds()); |
734 canvas->DrawRectInt(kCursorColor, r.x(), r.y(), r.width(), r.height()); | 668 canvas->DrawRectInt(kCursorColor, r.x(), r.y(), r.width(), r.height()); |
735 } | 669 } |
736 } | 670 } |
737 | 671 |
738 RenderText* RenderText::CreateRenderText() { | 672 RenderText* RenderText::CreateRenderText() { |
739 return new RenderTextWin; | 673 return new RenderTextWin; |
740 } | 674 } |
741 | 675 |
742 } // namespace gfx | 676 } // namespace gfx |
OLD | NEW |