// Copyright 2016 The Chromium Authors
// 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 <string>
#include "base/metrics/histogram_macros.h"
#include "base/strings/utf_string_conversion_utils.h"
#include "base/strings/utf_string_conversions.h"
#include "base/trace_event/trace_event.h"
#include "content/child/dwrite_font_proxy/dwrite_font_proxy_win.h"
namespace mswr = Microsoft::WRL;
namespace content {
namespace {
const size_t kMaxFamilyCacheSize = 10;
std::wstring MakeCacheKey(const wchar_t* base_family_name,
const wchar_t* locale) {
std::wstring cache_key(base_family_name);
return cache_key + L"_" + locale;
}
} // namespace
HRESULT FontFallback::Create(FontFallback** font_fallback_out,
DWriteFontCollectionProxy* collection) {
return Microsoft::WRL::MakeAndInitialize<FontFallback>(font_fallback_out,
collection);
}
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;
}
std::u16string text_chunk;
base::WideToUTF16(text, std::min(chunk_length, text_length), &text_chunk);
if (text_chunk.size() == 0) {
DCHECK(false);
return E_INVALIDARG;
}
base_family_name = base_family_name ? base_family_name : L"";
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"";
size_t mapped_length_size_t = *mapped_length;
if (GetCachedFont(text_chunk, base_family_name, locale, base_weight,
base_style, base_stretch, mapped_font,
&mapped_length_size_t)) {
DCHECK(*mapped_font);
DCHECK_GT(mapped_length_size_t, 0u);
*mapped_length = base::checked_cast<UINT32>(mapped_length_size_t);
return S_OK;
}
TRACE_EVENT0("dwrite,fonts", "FontFallback::MapCharacters (IPC)");
blink::mojom::MapCharactersResultPtr result;
if (!GetFontProxy().MapCharacters(
text_chunk,
blink::mojom::DWriteFontStyle::New(base_weight, base_style,
base_stretch),
base::WideToUTF16(locale), source->GetParagraphReadingDirection(),
base::WideToUTF16(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) {
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, locale);
return S_OK;
}
HRESULT STDMETHODCALLTYPE
FontFallback::RuntimeClassInitialize(DWriteFontCollectionProxy* collection) {
collection_ = collection;
return S_OK;
}
bool FontFallback::GetCachedFont(const std::u16string& text,
const wchar_t* base_family_name,
const wchar_t* locale,
DWRITE_FONT_WEIGHT base_weight,
DWRITE_FONT_STYLE base_style,
DWRITE_FONT_STRETCH base_stretch,
IDWriteFont** font,
size_t* mapped_length) {
base::AutoLock guard(lock_);
std::map<std::wstring, std::list<mswr::ComPtr<IDWriteFontFamily>>>::iterator
it = fallback_family_cache_.find(MakeCacheKey(base_family_name, locale));
if (it == fallback_family_cache_.end())
return false;
TRACE_EVENT0("dwrite,fonts", "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;
if (FAILED((*family_iterator)
->GetFirstMatchingFont(base_weight, base_stretch, base_style,
&matched_font))) {
continue;
}
// |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).
size_t character_index = 0;
size_t length = 0; // How much of the text can actually be mapped.
while (character_index < text.length()) {
BOOL exists = false;
base_icu::UChar32 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,
const wchar_t* locale) {
base::AutoLock guard(lock_);
// Note: If the requested locale does not disambiguate Han ideographs, caching
// by locale may prime the cache with one CJK font for the first request,
// which may be unsuitable for the next request. For example: While specifying
// an ambiguous locale, requesting certain Chinese characters first, DWrite
// will give us a simplified Chinese font, then requesting a Korean character
// later may return a Chinese font for the Korean character. This is prevented
// on the Blink side by passing a disambiguating locale.
std::list<mswr::ComPtr<IDWriteFontFamily>>& family_list =
fallback_family_cache_[MakeCacheKey(base_family_name, locale)];
family_list.push_front(std::move(family));
while (family_list.size() > kMaxFamilyCacheSize)
family_list.pop_back();
}
blink::mojom::DWriteFontProxy& FontFallback::GetFontProxy() {
return collection_->GetFontProxy();
}
} // namespace content