Implement font fallback in RenderTextWin, try #2.

The original change (http://codereview.chromium.org/8565011/) was
reverted due to failing NativeTextfieldViewsTest.HitInsideTextAreaTest.

The test was too strict when comparing cursor bounds and failed due
to font fallback causing height differences in cursor bounds. This
updated CL makes the test only check the x value of cursor bounds.

Original CL description:

This is done by using a metafile to capture the font
that Uniscribe would use to render the text (since
there is no API to get this from Uniscribe itself).

Makes SCRIPT_CACHE be per-run, since different runs
may have different fonts and the SCRIPT_CACHE cannot
be re-used between these.

This is similar to what is done in WebKit in FontCacheWin.cpp

BUG=90426,104234
TEST=Run chrome.exe --use-pure-views and paste some Hebrew
text into the omnibox. It should show up properly.
Review URL: http://codereview.chromium.org/8570003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@110749 0039d316-1c4b-4281-b951-d872f2087c98
parent 1c43656e
...@@ -5,15 +5,16 @@ ...@@ -5,15 +5,16 @@
#include "ui/gfx/render_text_win.h" #include "ui/gfx/render_text_win.h"
#include <algorithm> #include <algorithm>
#include <map>
#include "base/logging.h" #include "base/logging.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/string_util.h" #include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "base/win/scoped_hdc.h" #include "base/win/scoped_hdc.h"
#include "third_party/skia/include/core/SkTypeface.h" #include "third_party/skia/include/core/SkTypeface.h"
#include "ui/gfx/canvas.h" #include "ui/gfx/canvas.h"
#include "ui/gfx/canvas_skia.h" #include "ui/gfx/canvas_skia.h"
#include "ui/gfx/platform_font.h"
namespace { namespace {
...@@ -62,6 +63,63 @@ void DrawTextRunDecorations(SkCanvas* canvas_skia, ...@@ -62,6 +63,63 @@ void DrawTextRunDecorations(SkCanvas* canvas_skia,
} }
} }
// Callback to |EnumEnhMetaFile()| to intercept font creation.
int CALLBACK MetaFileEnumProc(HDC hdc,
HANDLETABLE* table,
CONST ENHMETARECORD* record,
int table_entries,
LPARAM log_font) {
if (record->iType == EMR_EXTCREATEFONTINDIRECTW) {
const EMREXTCREATEFONTINDIRECTW* create_font_record =
reinterpret_cast<const EMREXTCREATEFONTINDIRECTW*>(record);
*reinterpret_cast<LOGFONT*>(log_font) = create_font_record->elfw.elfLogFont;
}
return 1;
}
// Finds a fallback font to use to render the specified |text| with respect to
// an initial |font|. Returns the resulting font via out param |result|. Returns
// |true| if a fallback font was found.
// Adapted from WebKit's |FontCache::GetFontDataForCharacters()|.
bool ChooseFallbackFont(HDC hdc,
const gfx::Font& font,
const wchar_t* text,
int text_length,
gfx::Font* result) {
// Use a meta file to intercept the fallback font chosen by Uniscribe.
HDC meta_file_dc = CreateEnhMetaFile(hdc, NULL, NULL, NULL);
if (!meta_file_dc)
return false;
SelectObject(meta_file_dc, font.GetNativeFont());
SCRIPT_STRING_ANALYSIS script_analysis;
HRESULT hresult =
ScriptStringAnalyse(meta_file_dc, text, text_length, 0, -1,
SSA_METAFILE | SSA_FALLBACK | SSA_GLYPHS | SSA_LINK,
0, NULL, NULL, NULL, NULL, NULL, &script_analysis);
if (SUCCEEDED(hresult)) {
hresult = ScriptStringOut(script_analysis, 0, 0, 0, NULL, 0, 0, FALSE);
ScriptStringFree(&script_analysis);
}
bool found_fallback = false;
HENHMETAFILE meta_file = CloseEnhMetaFile(meta_file_dc);
if (SUCCEEDED(hresult)) {
LOGFONT log_font;
log_font.lfFaceName[0] = 0;
EnumEnhMetaFile(0, meta_file, MetaFileEnumProc, &log_font, NULL);
if (log_font.lfFaceName[0]) {
*result = gfx::Font(UTF16ToUTF8(log_font.lfFaceName), font.GetFontSize());
found_fallback = true;
}
}
DeleteEnhMetaFile(meta_file);
return found_fallback;
}
} // namespace } // namespace
namespace gfx { namespace gfx {
...@@ -73,7 +131,12 @@ TextRun::TextRun() ...@@ -73,7 +131,12 @@ TextRun::TextRun()
underline(false), underline(false),
width(0), width(0),
preceding_run_widths(0), preceding_run_widths(0),
glyph_count(0) { glyph_count(0),
script_cache(NULL) {
}
TextRun::~TextRun() {
ScriptFreeCache(&script_cache);
} }
} // namespace internal } // namespace internal
...@@ -82,7 +145,6 @@ RenderTextWin::RenderTextWin() ...@@ -82,7 +145,6 @@ RenderTextWin::RenderTextWin()
: RenderText(), : RenderText(),
script_control_(), script_control_(),
script_state_(), script_state_(),
script_cache_(NULL),
string_width_(0) { string_width_(0) {
// Omitting default constructors for script_* would leave POD uninitialized. // Omitting default constructors for script_* would leave POD uninitialized.
HRESULT hr = 0; HRESULT hr = 0;
...@@ -102,7 +164,6 @@ RenderTextWin::RenderTextWin() ...@@ -102,7 +164,6 @@ RenderTextWin::RenderTextWin()
} }
RenderTextWin::~RenderTextWin() { RenderTextWin::~RenderTextWin() {
ScriptFreeCache(&script_cache_);
STLDeleteContainerPointers(runs_.begin(), runs_.end()); STLDeleteContainerPointers(runs_.begin(), runs_.end());
} }
...@@ -341,7 +402,6 @@ size_t RenderTextWin::IndexOfAdjacentGrapheme(size_t index, bool next) { ...@@ -341,7 +402,6 @@ size_t RenderTextWin::IndexOfAdjacentGrapheme(size_t index, bool next) {
} }
void RenderTextWin::ItemizeLogicalText() { void RenderTextWin::ItemizeLogicalText() {
text_is_dirty_ = false;
STLDeleteContainerPointers(runs_.begin(), runs_.end()); STLDeleteContainerPointers(runs_.begin(), runs_.end());
runs_.clear(); runs_.clear();
if (text().empty()) if (text().empty())
...@@ -352,18 +412,18 @@ void RenderTextWin::ItemizeLogicalText() { ...@@ -352,18 +412,18 @@ void RenderTextWin::ItemizeLogicalText() {
HRESULT hr = E_OUTOFMEMORY; HRESULT hr = E_OUTOFMEMORY;
int script_items_count = 0; int script_items_count = 0;
scoped_array<SCRIPT_ITEM> script_items; std::vector<SCRIPT_ITEM> script_items;
for (size_t n = kGuessItems; hr == E_OUTOFMEMORY && n < kMaxItems; n *= 2) { for (size_t n = kGuessItems; hr == E_OUTOFMEMORY && n < kMaxItems; n *= 2) {
// Derive the array of Uniscribe script items from the logical text. // Derive the array of Uniscribe script items from the logical text.
// ScriptItemize always adds a terminal array item so that the length of the // ScriptItemize always adds a terminal array item so that the length of the
// last item can be derived from the terminal SCRIPT_ITEM::iCharPos. // last item can be derived from the terminal SCRIPT_ITEM::iCharPos.
script_items.reset(new SCRIPT_ITEM[n]); script_items.resize(n);
hr = ScriptItemize(raw_text, hr = ScriptItemize(raw_text,
text_length, text_length,
n - 1, n - 1,
&script_control_, &script_control_,
&script_state_, &script_state_,
script_items.get(), &script_items[0],
&script_items_count); &script_items_count);
} }
DCHECK(SUCCEEDED(hr)); DCHECK(SUCCEEDED(hr));
...@@ -375,7 +435,7 @@ void RenderTextWin::ItemizeLogicalText() { ...@@ -375,7 +435,7 @@ void RenderTextWin::ItemizeLogicalText() {
// TODO(msw): Only break for font changes, not color etc. See TextRun comment. // TODO(msw): Only break for font changes, not color etc. See TextRun comment.
// TODO(msw): Apply the overriding selection and composition styles. // TODO(msw): Apply the overriding selection and composition styles.
StyleRanges::const_iterator style = style_ranges().begin(); StyleRanges::const_iterator style = style_ranges().begin();
SCRIPT_ITEM* script_item = script_items.get(); SCRIPT_ITEM* script_item = &script_items[0];
for (int run_break = 0; run_break < text_length;) { for (int run_break = 0; run_break < text_length;) {
internal::TextRun* run = new internal::TextRun(); internal::TextRun* run = new internal::TextRun();
run->range.set_start(run_break); run->range.set_start(run_break);
...@@ -418,7 +478,7 @@ void RenderTextWin::LayoutVisualText() { ...@@ -418,7 +478,7 @@ void RenderTextWin::LayoutVisualText() {
run->glyphs.reset(new WORD[max_glyphs]); run->glyphs.reset(new WORD[max_glyphs]);
run->visible_attributes.reset(new SCRIPT_VISATTR[max_glyphs]); run->visible_attributes.reset(new SCRIPT_VISATTR[max_glyphs]);
hr = ScriptShape(hdc, hr = ScriptShape(hdc,
&script_cache_, &run->script_cache,
run_text, run_text,
run_length, run_length,
max_glyphs, max_glyphs,
...@@ -430,12 +490,20 @@ void RenderTextWin::LayoutVisualText() { ...@@ -430,12 +490,20 @@ void RenderTextWin::LayoutVisualText() {
if (hr == E_OUTOFMEMORY) { if (hr == E_OUTOFMEMORY) {
max_glyphs *= 2; max_glyphs *= 2;
} else if (hr == USP_E_SCRIPT_NOT_IN_FONT) { } else if (hr == USP_E_SCRIPT_NOT_IN_FONT) {
// The run's font doesn't contain the required glyphs, use an alternate. // TODO(msw): Don't use SCRIPT_UNDEFINED. Apparently Uniscribe can crash
// TODO(msw): Font fallback... Don't use SCRIPT_UNDEFINED. // on certain surrogate pairs with SCRIPT_UNDEFINED.
// See https://bugzilla.mozilla.org/show_bug.cgi?id=341500 // See https://bugzilla.mozilla.org/show_bug.cgi?id=341500
// And http://maxradi.us/documents/uniscribe/ // And http://maxradi.us/documents/uniscribe/
if (run->script_analysis.eScript == SCRIPT_UNDEFINED) if (run->script_analysis.eScript == SCRIPT_UNDEFINED)
break; break;
// The run's font doesn't contain the required glyphs, use an alternate.
if (ChooseFallbackFont(hdc, run->font, run_text, run_length,
&run->font)) {
ScriptFreeCache(&run->script_cache);
SelectObject(hdc, run->font.GetNativeFont());
}
run->script_analysis.eScript = SCRIPT_UNDEFINED; run->script_analysis.eScript = SCRIPT_UNDEFINED;
} else { } else {
break; break;
...@@ -447,7 +515,7 @@ void RenderTextWin::LayoutVisualText() { ...@@ -447,7 +515,7 @@ void RenderTextWin::LayoutVisualText() {
run->advance_widths.reset(new int[run->glyph_count]); run->advance_widths.reset(new int[run->glyph_count]);
run->offsets.reset(new GOFFSET[run->glyph_count]); run->offsets.reset(new GOFFSET[run->glyph_count]);
hr = ScriptPlace(hdc, hr = ScriptPlace(hdc,
&script_cache_, &run->script_cache,
run->glyphs.get(), run->glyphs.get(),
run->glyph_count, run->glyph_count,
run->visible_attributes.get(), run->visible_attributes.get(),
...@@ -507,7 +575,6 @@ size_t RenderTextWin::GetRunContainingPoint(const Point& point) const { ...@@ -507,7 +575,6 @@ size_t RenderTextWin::GetRunContainingPoint(const Point& point) const {
return run; return run;
} }
SelectionModel RenderTextWin::FirstSelectionModelInsideRun( SelectionModel RenderTextWin::FirstSelectionModelInsideRun(
internal::TextRun* run) { internal::TextRun* run) {
size_t caret = run->range.start(); size_t caret = run->range.start();
......
...@@ -19,6 +19,7 @@ namespace internal { ...@@ -19,6 +19,7 @@ namespace internal {
struct TextRun { struct TextRun {
TextRun(); TextRun();
~TextRun();
ui::Range range; ui::Range range;
Font font; Font font;
...@@ -43,6 +44,7 @@ struct TextRun { ...@@ -43,6 +44,7 @@ struct TextRun {
scoped_array<int> advance_widths; scoped_array<int> advance_widths;
scoped_array<GOFFSET> offsets; scoped_array<GOFFSET> offsets;
ABC abc_widths; ABC abc_widths;
SCRIPT_CACHE script_cache;
private: private:
DISALLOW_COPY_AND_ASSIGN(TextRun); DISALLOW_COPY_AND_ASSIGN(TextRun);
...@@ -89,8 +91,8 @@ class RenderTextWin : public RenderText { ...@@ -89,8 +91,8 @@ class RenderTextWin : public RenderText {
// Given a |run|, returns the SelectionModel that contains the logical first // Given a |run|, returns the SelectionModel that contains the logical first
// or last caret position inside (not at a boundary of) the run. // or last caret position inside (not at a boundary of) the run.
// The returned value represents a cursor/caret position without a selection. // The returned value represents a cursor/caret position without a selection.
SelectionModel FirstSelectionModelInsideRun(internal::TextRun*); SelectionModel FirstSelectionModelInsideRun(internal::TextRun* run);
SelectionModel LastSelectionModelInsideRun(internal::TextRun*); SelectionModel LastSelectionModelInsideRun(internal::TextRun* run);
// Get the selection model visually left/right of |selection| by one grapheme. // Get the selection model visually left/right of |selection| by one grapheme.
// The returned value represents a cursor/caret position without a selection. // The returned value represents a cursor/caret position without a selection.
...@@ -102,17 +104,12 @@ class RenderTextWin : public RenderText { ...@@ -102,17 +104,12 @@ class RenderTextWin : public RenderText {
void DrawVisualText(Canvas* canvas); void DrawVisualText(Canvas* canvas);
void DrawCursor(Canvas* canvas); void DrawCursor(Canvas* canvas);
bool text_is_dirty_;
bool style_is_dirty_;
// National Language Support native digit and digit substitution settings. // National Language Support native digit and digit substitution settings.
SCRIPT_DIGITSUBSTITUTE digit_substitute_; SCRIPT_DIGITSUBSTITUTE digit_substitute_;
SCRIPT_CONTROL script_control_; SCRIPT_CONTROL script_control_;
SCRIPT_STATE script_state_; SCRIPT_STATE script_state_;
SCRIPT_CACHE script_cache_;
std::vector<internal::TextRun*> runs_; std::vector<internal::TextRun*> runs_;
int string_width_; int string_width_;
......
...@@ -1354,25 +1354,27 @@ TEST_F(NativeTextfieldViewsTest, HitInsideTextAreaTest) { ...@@ -1354,25 +1354,27 @@ TEST_F(NativeTextfieldViewsTest, HitInsideTextAreaTest) {
sel = gfx::SelectionModel(1, 0, gfx::SelectionModel::TRAILING); sel = gfx::SelectionModel(1, 0, gfx::SelectionModel::TRAILING);
gfx::Rect bound = GetCursorBounds(sel); gfx::Rect bound = GetCursorBounds(sel);
sel = gfx::SelectionModel(1, 1, gfx::SelectionModel::LEADING); sel = gfx::SelectionModel(1, 1, gfx::SelectionModel::LEADING);
EXPECT_EQ(bound, GetCursorBounds(sel)); EXPECT_EQ(bound.x(), GetCursorBounds(sel).x());
cursor_bounds.push_back(bound); cursor_bounds.push_back(bound);
// Check that a cursor at the end of the Latin portion of the text is at the
// same position as a cursor placed at the end of the RTL Hebrew portion.
sel = gfx::SelectionModel(2, 1, gfx::SelectionModel::TRAILING); sel = gfx::SelectionModel(2, 1, gfx::SelectionModel::TRAILING);
bound = GetCursorBounds(sel); bound = GetCursorBounds(sel);
sel = gfx::SelectionModel(4, 3, gfx::SelectionModel::TRAILING); sel = gfx::SelectionModel(4, 3, gfx::SelectionModel::TRAILING);
EXPECT_EQ(bound, GetCursorBounds(sel)); EXPECT_EQ(bound.x(), GetCursorBounds(sel).x());
cursor_bounds.push_back(bound); cursor_bounds.push_back(bound);
sel = gfx::SelectionModel(3, 2, gfx::SelectionModel::TRAILING); sel = gfx::SelectionModel(3, 2, gfx::SelectionModel::TRAILING);
bound = GetCursorBounds(sel); bound = GetCursorBounds(sel);
sel = gfx::SelectionModel(3, 3, gfx::SelectionModel::LEADING); sel = gfx::SelectionModel(3, 3, gfx::SelectionModel::LEADING);
EXPECT_EQ(bound, GetCursorBounds(sel)); EXPECT_EQ(bound.x(), GetCursorBounds(sel).x());
cursor_bounds.push_back(bound); cursor_bounds.push_back(bound);
sel = gfx::SelectionModel(2, 2, gfx::SelectionModel::LEADING); sel = gfx::SelectionModel(2, 2, gfx::SelectionModel::LEADING);
bound = GetCursorBounds(sel); bound = GetCursorBounds(sel);
sel = gfx::SelectionModel(4, 2, gfx::SelectionModel::LEADING); sel = gfx::SelectionModel(4, 2, gfx::SelectionModel::LEADING);
EXPECT_EQ(bound, GetCursorBounds(sel)); EXPECT_EQ(bound.x(), GetCursorBounds(sel).x());
cursor_bounds.push_back(bound); cursor_bounds.push_back(bound);
// Expected cursor position when clicking left and right of each character. // Expected cursor position when clicking left and right of each character.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment