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