| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/child/dwrite_font_proxy/font_fallback_win.h" |
| |
| #include <math.h> |
| |
| #include <algorithm> |
| |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/utf_string_conversion_utils.h" |
| #include "content/child/dwrite_font_proxy/dwrite_font_proxy_win.h" |
| #include "content/common/dwrite_font_proxy_messages.h" |
| #include "content/public/child/child_thread.h" |
| #include "ipc/ipc_sender.h" |
| |
| namespace mswr = Microsoft::WRL; |
| |
| namespace content { |
| |
| namespace { |
| |
| const size_t kMaxFamilyCacheSize = 10; |
| |
| // This enum is used to define the buckets for an enumerated UMA histogram. |
| // Hence, |
| // (a) existing enumerated constants should never be deleted or reordered, and |
| // (b) new constants should only be appended at the end of the enumeration. |
| enum DirectWriteFontFallbackResult { |
| FAILED_NO_FONT = 0, |
| SUCCESS_CACHE = 1, |
| SUCCESS_IPC = 2, |
| |
| FONT_FALLBACK_RESULT_MAX_VALUE |
| }; |
| |
| void LogFallbackResult(DirectWriteFontFallbackResult fallback_result) { |
| UMA_HISTOGRAM_ENUMERATION("DirectWrite.Fonts.Proxy.FallbackResult", |
| fallback_result, FONT_FALLBACK_RESULT_MAX_VALUE); |
| } |
| |
| } // namespace |
| |
| FontFallback::FontFallback() = default; |
| FontFallback::~FontFallback() = default; |
| |
| HRESULT FontFallback::MapCharacters(IDWriteTextAnalysisSource* source, |
| UINT32 text_position, |
| UINT32 text_length, |
| IDWriteFontCollection* base_font_collection, |
| const wchar_t* base_family_name, |
| DWRITE_FONT_WEIGHT base_weight, |
| DWRITE_FONT_STYLE base_style, |
| DWRITE_FONT_STRETCH base_stretch, |
| UINT32* mapped_length, |
| IDWriteFont** mapped_font, |
| FLOAT* scale) { |
| *mapped_font = nullptr; |
| *mapped_length = 1; |
| *scale = 1.0; |
| |
| const WCHAR* text = nullptr; |
| UINT32 chunk_length = 0; |
| if (FAILED(source->GetTextAtPosition(text_position, &text, &chunk_length))) { |
| DCHECK(false); |
| return E_FAIL; |
| } |
| base::string16 text_chunk(text, std::min(chunk_length, text_length)); |
| |
| if (text_chunk.size() == 0) { |
| DCHECK(false); |
| return E_INVALIDARG; |
| } |
| |
| base_family_name = base_family_name ? base_family_name : L""; |
| |
| if (GetCachedFont(text_chunk, base_family_name, base_weight, base_style, |
| base_stretch, mapped_font, mapped_length)) { |
| DCHECK(*mapped_font); |
| DCHECK_GT(*mapped_length, 0u); |
| LogFallbackResult(SUCCESS_CACHE); |
| return S_OK; |
| } |
| |
| TRACE_EVENT0("dwrite", "FontFallback::MapCharacters (IPC)"); |
| |
| const WCHAR* locale = nullptr; |
| // |locale_text_length| is actually the length of text with the locale, not |
| // the length of the locale string itself. |
| UINT32 locale_text_length = 0; |
| source->GetLocaleName(text_position /*textPosition*/, &locale_text_length, |
| &locale); |
| |
| locale = locale ? locale : L""; |
| |
| DWriteFontStyle style; |
| style.font_weight = base_weight; |
| style.font_slant = base_style; |
| style.font_stretch = base_stretch; |
| |
| MapCharactersResult result; |
| |
| IPC::Sender* sender = |
| sender_override_ ? sender_override_ : ChildThread::Get(); |
| if (!sender->Send(new DWriteFontProxyMsg_MapCharacters( |
| text_chunk, style, locale, source->GetParagraphReadingDirection(), |
| base_family_name, &result))) { |
| DCHECK(false); |
| return E_FAIL; |
| } |
| |
| // We don't cache scale in the fallback cache, and Skia ignores scale anyway. |
| // If we ever get a result that's significantly different from 1 we may need |
| // to consider whether it's worth doing the work to plumb it through. |
| DCHECK(fabs(*scale - 1.0f) < 0.00001); |
| |
| *mapped_length = result.mapped_length; |
| *scale = result.scale; |
| |
| if (result.family_index == UINT32_MAX) { |
| LogFallbackResult(FAILED_NO_FONT); |
| return S_OK; |
| } |
| |
| mswr::ComPtr<IDWriteFontFamily> family; |
| // It would be nice to find a way to determine at runtime if |collection_| is |
| // a proxy collection, or just a generic IDWriteFontCollection. Unfortunately |
| // I can't find a way to get QueryInterface to return the actual class when |
| // using mswr::RuntimeClass. If we could use QI, we can fallback on |
| // FindFontFamily if the proxy is not available. |
| if (!collection_->GetFontFamily(result.family_index, result.family_name, |
| &family)) { |
| DCHECK(false); |
| return E_FAIL; |
| } |
| |
| if (FAILED(family->GetFirstMatchingFont( |
| static_cast<DWRITE_FONT_WEIGHT>(result.font_style.font_weight), |
| static_cast<DWRITE_FONT_STRETCH>(result.font_style.font_stretch), |
| static_cast<DWRITE_FONT_STYLE>(result.font_style.font_slant), |
| mapped_font))) { |
| DCHECK(false); |
| return E_FAIL; |
| } |
| |
| DCHECK(*mapped_font); |
| AddCachedFamily(std::move(family), base_family_name); |
| LogFallbackResult(SUCCESS_IPC); |
| return S_OK; |
| } |
| |
| HRESULT STDMETHODCALLTYPE |
| FontFallback::RuntimeClassInitialize(DWriteFontCollectionProxy* collection, |
| IPC::Sender* sender_override) { |
| sender_override_ = sender_override; |
| collection_ = collection; |
| return S_OK; |
| } |
| |
| bool FontFallback::GetCachedFont(const base::string16& text, |
| const wchar_t* base_family_name, |
| DWRITE_FONT_WEIGHT base_weight, |
| DWRITE_FONT_STYLE base_style, |
| DWRITE_FONT_STRETCH base_stretch, |
| IDWriteFont** font, |
| uint32_t* mapped_length) { |
| std::map<base::string16, std::list<mswr::ComPtr<IDWriteFontFamily>>>::iterator |
| it = fallback_family_cache_.find(base_family_name); |
| if (it == fallback_family_cache_.end()) |
| return false; |
| |
| TRACE_EVENT0("dwrite", "FontFallback::GetCachedFont"); |
| |
| std::list<mswr::ComPtr<IDWriteFontFamily>>& family_list = it->second; |
| std::list<mswr::ComPtr<IDWriteFontFamily>>::iterator family_iterator; |
| for (family_iterator = family_list.begin(); |
| family_iterator != family_list.end(); ++family_iterator) { |
| mswr::ComPtr<IDWriteFont> matched_font; |
| (*family_iterator)->GetFirstMatchingFont(base_weight, base_stretch, |
| base_style, &matched_font); |
| |
| // |character_index| tracks how much of the string we have read. This is |
| // different from |mapped_length| because ReadUnicodeCharacter can advance |
| // |character_index| even if the character cannot be mapped (invalid |
| // surrogate pair or font does not contain a matching glyph). |
| int32_t character_index = 0; |
| uint32_t length = 0; // How much of the text can actually be mapped. |
| while (static_cast<uint32_t>(character_index) < text.length()) { |
| BOOL exists = false; |
| uint32_t character = 0; |
| if (!base::ReadUnicodeCharacter(text.c_str(), text.length(), |
| &character_index, &character)) |
| break; |
| if (FAILED(matched_font->HasCharacter(character, &exists)) || !exists) |
| break; |
| character_index++; |
| length = character_index; |
| } |
| |
| if (length > 0) { |
| // Move the current family to the front of the list |
| family_list.splice(family_list.begin(), family_list, family_iterator); |
| |
| matched_font.CopyTo(font); |
| *mapped_length = length; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| void FontFallback::AddCachedFamily( |
| Microsoft::WRL::ComPtr<IDWriteFontFamily> family, |
| const wchar_t* base_family_name) { |
| std::list<mswr::ComPtr<IDWriteFontFamily>>& family_list = |
| fallback_family_cache_[base_family_name]; |
| family_list.push_front(std::move(family)); |
| |
| UMA_HISTOGRAM_COUNTS_100("DirectWrite.Fonts.Proxy.Fallback.CacheSize", |
| family_list.size()); |
| |
| while (family_list.size() > kMaxFamilyCacheSize) |
| family_list.pop_back(); |
| } |
| |
| } // namespace content |