Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(208)

Side by Side Diff: ui/gfx/render_text_win.cc

Issue 8570003: Implement font fallback in RenderTextWin, try #2. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 9 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « ui/gfx/render_text_win.h ('k') | views/controls/textfield/native_textfield_views_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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
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
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
OLDNEW
« no previous file with comments | « ui/gfx/render_text_win.h ('k') | views/controls/textfield/native_textfield_views_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698