Chromium Code Reviews| 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 LOGFONT* font = reinterpret_cast<LOGFONT*>(log_font); | |
| 74 const EMREXTCREATEFONTINDIRECTW* create_font_record | |
| 75 = reinterpret_cast<const EMREXTCREATEFONTINDIRECTW*>(record); | |
|
sky
2011/11/14 16:30:41
= on previous line.
Alexei Svitkine (slow)
2011/11/14 16:41:38
Done.
| |
| 76 *font = create_font_record->elfw.elfLogFont; | |
| 77 } | |
| 78 return 1; | |
| 79 } | |
| 80 | |
| 81 // Finds a fallback font to use to render the specified |text| with respect to | |
| 82 // an initial |font|. Returns the resulting font via out param |result|. Returns | |
| 83 // |true| if a fallback font was found. | |
| 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) { | |
| 77 } | 136 } |
| 78 | 137 |
| 79 } // namespace internal | 138 } // namespace internal |
| 80 | 139 |
| 81 RenderTextWin::RenderTextWin() | 140 RenderTextWin::RenderTextWin() |
| 82 : RenderText(), | 141 : RenderText(), |
| 83 script_control_(), | 142 script_control_(), |
| 84 script_state_(), | 143 script_state_(), |
| 85 script_cache_(NULL), | |
| 86 string_width_(0) { | 144 string_width_(0) { |
| 87 // Omitting default constructors for script_* would leave POD uninitialized. | 145 // Omitting default constructors for script_* would leave POD uninitialized. |
| 88 HRESULT hr = 0; | 146 HRESULT hr = 0; |
| 89 | 147 |
| 90 // TODO(msw): Call ScriptRecordDigitSubstitution on WM_SETTINGCHANGE message. | 148 // TODO(msw): Call ScriptRecordDigitSubstitution on WM_SETTINGCHANGE message. |
| 91 // TODO(msw): Use Chrome/profile locale/language settings? | 149 // TODO(msw): Use Chrome/profile locale/language settings? |
| 92 hr = ScriptRecordDigitSubstitution(LOCALE_USER_DEFAULT, &digit_substitute_); | 150 hr = ScriptRecordDigitSubstitution(LOCALE_USER_DEFAULT, &digit_substitute_); |
| 93 DCHECK(SUCCEEDED(hr)); | 151 DCHECK(SUCCEEDED(hr)); |
| 94 | 152 |
| 95 hr = ScriptApplyDigitSubstitution(&digit_substitute_, | 153 hr = ScriptApplyDigitSubstitution(&digit_substitute_, |
| 96 &script_control_, | 154 &script_control_, |
| 97 &script_state_); | 155 &script_state_); |
| 98 DCHECK(SUCCEEDED(hr)); | 156 DCHECK(SUCCEEDED(hr)); |
| 99 script_control_.fMergeNeutralItems = true; | 157 script_control_.fMergeNeutralItems = true; |
| 100 | 158 |
| 101 MoveCursorTo(LeftEndSelectionModel()); | 159 MoveCursorTo(LeftEndSelectionModel()); |
| 102 } | 160 } |
| 103 | 161 |
| 104 RenderTextWin::~RenderTextWin() { | 162 RenderTextWin::~RenderTextWin() { |
| 105 ScriptFreeCache(&script_cache_); | 163 for (size_t i = 0; i < runs_.size(); ++i) |
| 164 ScriptFreeCache(&runs_[i]->script_cache); | |
| 106 STLDeleteContainerPointers(runs_.begin(), runs_.end()); | 165 STLDeleteContainerPointers(runs_.begin(), runs_.end()); |
| 107 } | 166 } |
| 108 | 167 |
| 109 int RenderTextWin::GetStringWidth() { | 168 int RenderTextWin::GetStringWidth() { |
| 110 return string_width_; | 169 return string_width_; |
| 111 } | 170 } |
| 112 | 171 |
| 113 void RenderTextWin::Draw(Canvas* canvas) { | 172 void RenderTextWin::Draw(Canvas* canvas) { |
| 114 DrawSelection(canvas); | 173 DrawSelection(canvas); |
| 115 DrawVisualText(canvas); | 174 DrawVisualText(canvas); |
| (...skipping 229 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 345 STLDeleteContainerPointers(runs_.begin(), runs_.end()); | 404 STLDeleteContainerPointers(runs_.begin(), runs_.end()); |
| 346 runs_.clear(); | 405 runs_.clear(); |
| 347 if (text().empty()) | 406 if (text().empty()) |
| 348 return; | 407 return; |
| 349 | 408 |
| 350 const wchar_t* raw_text = text().c_str(); | 409 const wchar_t* raw_text = text().c_str(); |
| 351 const int text_length = text().length(); | 410 const int text_length = text().length(); |
| 352 | 411 |
| 353 HRESULT hr = E_OUTOFMEMORY; | 412 HRESULT hr = E_OUTOFMEMORY; |
| 354 int script_items_count = 0; | 413 int script_items_count = 0; |
| 355 scoped_array<SCRIPT_ITEM> script_items; | 414 std::vector<SCRIPT_ITEM> script_items; |
| 356 for (size_t n = kGuessItems; hr == E_OUTOFMEMORY && n < kMaxItems; n *= 2) { | 415 for (size_t n = kGuessItems; hr == E_OUTOFMEMORY && n < kMaxItems; n *= 2) { |
| 357 // Derive the array of Uniscribe script items from the logical text. | 416 // 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 | 417 // 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. | 418 // last item can be derived from the terminal SCRIPT_ITEM::iCharPos. |
| 360 script_items.reset(new SCRIPT_ITEM[n]); | 419 script_items.resize(n); |
| 361 hr = ScriptItemize(raw_text, | 420 hr = ScriptItemize(raw_text, |
| 362 text_length, | 421 text_length, |
| 363 n - 1, | 422 n - 1, |
| 364 &script_control_, | 423 &script_control_, |
| 365 &script_state_, | 424 &script_state_, |
| 366 script_items.get(), | 425 &script_items[0], |
| 367 &script_items_count); | 426 &script_items_count); |
| 368 } | 427 } |
| 369 DCHECK(SUCCEEDED(hr)); | 428 DCHECK(SUCCEEDED(hr)); |
| 370 | 429 |
| 371 if (script_items_count <= 0) | 430 if (script_items_count <= 0) |
| 372 return; | 431 return; |
| 373 | 432 |
| 374 // Build the list of runs, merge font/underline styles. | 433 // Build the list of runs, merge font/underline styles. |
| 375 // TODO(msw): Only break for font changes, not color etc. See TextRun comment. | 434 // TODO(msw): Only break for font changes, not color etc. See TextRun comment. |
| 376 // TODO(msw): Apply the overriding selection and composition styles. | 435 // TODO(msw): Apply the overriding selection and composition styles. |
| 377 StyleRanges::const_iterator style = style_ranges().begin(); | 436 StyleRanges::const_iterator style = style_ranges().begin(); |
| 378 SCRIPT_ITEM* script_item = script_items.get(); | 437 SCRIPT_ITEM* script_item = &script_items[0]; |
| 379 for (int run_break = 0; run_break < text_length;) { | 438 for (int run_break = 0; run_break < text_length;) { |
| 380 internal::TextRun* run = new internal::TextRun(); | 439 internal::TextRun* run = new internal::TextRun(); |
| 381 run->range.set_start(run_break); | 440 run->range.set_start(run_break); |
| 382 run->font = style->font; | 441 run->font = style->font; |
| 383 run->foreground = style->foreground; | 442 run->foreground = style->foreground; |
| 384 run->strike = style->strike; | 443 run->strike = style->strike; |
| 385 run->underline = style->underline; | 444 run->underline = style->underline; |
| 386 run->script_analysis = script_item->a; | 445 run->script_analysis = script_item->a; |
| 387 | 446 |
| 388 // Find the range end and advance the structures as needed. | 447 // Find the range end and advance the structures as needed. |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 411 SelectObject(hdc, run->font.GetNativeFont()); | 470 SelectObject(hdc, run->font.GetNativeFont()); |
| 412 | 471 |
| 413 run->logical_clusters.reset(new WORD[run_length]); | 472 run->logical_clusters.reset(new WORD[run_length]); |
| 414 run->glyph_count = 0; | 473 run->glyph_count = 0; |
| 415 // Max glyph guess: http://msdn.microsoft.com/en-us/library/dd368564.aspx | 474 // 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); | 475 size_t max_glyphs = static_cast<size_t>(1.5 * run_length + 16); |
| 417 while (max_glyphs < kMaxGlyphs) { | 476 while (max_glyphs < kMaxGlyphs) { |
| 418 run->glyphs.reset(new WORD[max_glyphs]); | 477 run->glyphs.reset(new WORD[max_glyphs]); |
| 419 run->visible_attributes.reset(new SCRIPT_VISATTR[max_glyphs]); | 478 run->visible_attributes.reset(new SCRIPT_VISATTR[max_glyphs]); |
| 420 hr = ScriptShape(hdc, | 479 hr = ScriptShape(hdc, |
| 421 &script_cache_, | 480 &run->script_cache, |
| 422 run_text, | 481 run_text, |
| 423 run_length, | 482 run_length, |
| 424 max_glyphs, | 483 max_glyphs, |
| 425 &(run->script_analysis), | 484 &(run->script_analysis), |
| 426 run->glyphs.get(), | 485 run->glyphs.get(), |
| 427 run->logical_clusters.get(), | 486 run->logical_clusters.get(), |
| 428 run->visible_attributes.get(), | 487 run->visible_attributes.get(), |
| 429 &(run->glyph_count)); | 488 &(run->glyph_count)); |
| 430 if (hr == E_OUTOFMEMORY) { | 489 if (hr == E_OUTOFMEMORY) { |
| 431 max_glyphs *= 2; | 490 max_glyphs *= 2; |
| 432 } else if (hr == USP_E_SCRIPT_NOT_IN_FONT) { | 491 } else if (hr == USP_E_SCRIPT_NOT_IN_FONT) { |
| 433 // The run's font doesn't contain the required glyphs, use an alternate. | |
| 434 // TODO(msw): Font fallback... Don't use SCRIPT_UNDEFINED. | |
| 435 // See https://bugzilla.mozilla.org/show_bug.cgi?id=341500 | |
| 436 // And http://maxradi.us/documents/uniscribe/ | |
| 437 if (run->script_analysis.eScript == SCRIPT_UNDEFINED) | 492 if (run->script_analysis.eScript == SCRIPT_UNDEFINED) |
| 438 break; | 493 break; |
| 494 | |
| 495 // The run's font doesn't contain the required glyphs, use an alternate. | |
| 496 if (ChooseFallbackFont(hdc, run->font, run_text, run_length, | |
| 497 &run->font)) { | |
| 498 ScriptFreeCache(&run->script_cache); | |
| 499 SelectObject(hdc, run->font.GetNativeFont()); | |
|
sky
2011/11/14 16:30:41
Do we need to worry about selecting the old font o
Alexei Svitkine (slow)
2011/11/14 16:41:38
It's not an issue since this is a temporary dc.
| |
| 500 } | |
| 501 | |
| 439 run->script_analysis.eScript = SCRIPT_UNDEFINED; | 502 run->script_analysis.eScript = SCRIPT_UNDEFINED; |
| 440 } else { | 503 } else { |
| 441 break; | 504 break; |
| 442 } | 505 } |
| 443 } | 506 } |
| 444 DCHECK(SUCCEEDED(hr)); | 507 DCHECK(SUCCEEDED(hr)); |
| 445 | 508 |
| 446 if (run->glyph_count > 0) { | 509 if (run->glyph_count > 0) { |
| 447 run->advance_widths.reset(new int[run->glyph_count]); | 510 run->advance_widths.reset(new int[run->glyph_count]); |
| 448 run->offsets.reset(new GOFFSET[run->glyph_count]); | 511 run->offsets.reset(new GOFFSET[run->glyph_count]); |
| 449 hr = ScriptPlace(hdc, | 512 hr = ScriptPlace(hdc, |
| 450 &script_cache_, | 513 &run->script_cache, |
| 451 run->glyphs.get(), | 514 run->glyphs.get(), |
| 452 run->glyph_count, | 515 run->glyph_count, |
| 453 run->visible_attributes.get(), | 516 run->visible_attributes.get(), |
| 454 &(run->script_analysis), | 517 &(run->script_analysis), |
| 455 run->advance_widths.get(), | 518 run->advance_widths.get(), |
| 456 run->offsets.get(), | 519 run->offsets.get(), |
| 457 &(run->abc_widths)); | 520 &(run->abc_widths)); |
| 458 DCHECK(SUCCEEDED(hr)); | 521 DCHECK(SUCCEEDED(hr)); |
| 459 } | 522 } |
| 460 } | 523 } |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 500 size_t RenderTextWin::GetRunContainingPoint(const Point& point) const { | 563 size_t RenderTextWin::GetRunContainingPoint(const Point& point) const { |
| 501 // Find the text run containing the argument point (assumed already offset). | 564 // Find the text run containing the argument point (assumed already offset). |
| 502 size_t run = 0; | 565 size_t run = 0; |
| 503 for (; run < runs_.size(); ++run) | 566 for (; run < runs_.size(); ++run) |
| 504 if (runs_[run]->preceding_run_widths <= point.x() && | 567 if (runs_[run]->preceding_run_widths <= point.x() && |
| 505 runs_[run]->preceding_run_widths + runs_[run]->width > point.x()) | 568 runs_[run]->preceding_run_widths + runs_[run]->width > point.x()) |
| 506 break; | 569 break; |
| 507 return run; | 570 return run; |
| 508 } | 571 } |
| 509 | 572 |
| 510 | |
| 511 SelectionModel RenderTextWin::FirstSelectionModelInsideRun( | 573 SelectionModel RenderTextWin::FirstSelectionModelInsideRun( |
| 512 internal::TextRun* run) { | 574 internal::TextRun* run) { |
| 513 size_t caret = run->range.start(); | 575 size_t caret = run->range.start(); |
| 514 size_t cursor = IndexOfAdjacentGrapheme(caret, true); | 576 size_t cursor = IndexOfAdjacentGrapheme(caret, true); |
| 515 return SelectionModel(cursor, caret, SelectionModel::TRAILING); | 577 return SelectionModel(cursor, caret, SelectionModel::TRAILING); |
| 516 } | 578 } |
| 517 | 579 |
| 518 SelectionModel RenderTextWin::LastSelectionModelInsideRun( | 580 SelectionModel RenderTextWin::LastSelectionModelInsideRun( |
| 519 internal::TextRun* run) { | 581 internal::TextRun* run) { |
| 520 size_t caret = IndexOfAdjacentGrapheme(run->range.end(), false); | 582 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()); | 729 Rect r(GetUpdatedCursorBounds()); |
| 668 canvas->DrawRectInt(kCursorColor, r.x(), r.y(), r.width(), r.height()); | 730 canvas->DrawRectInt(kCursorColor, r.x(), r.y(), r.width(), r.height()); |
| 669 } | 731 } |
| 670 } | 732 } |
| 671 | 733 |
| 672 RenderText* RenderText::CreateRenderText() { | 734 RenderText* RenderText::CreateRenderText() { |
| 673 return new RenderTextWin; | 735 return new RenderTextWin; |
| 674 } | 736 } |
| 675 | 737 |
| 676 } // namespace gfx | 738 } // namespace gfx |
| OLD | NEW |