// Copyright 2015 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/browser/renderer_host/dwrite_font_proxy_impl_win.h"
#include <shlobj.h>
#include <stddef.h>
#include <stdint.h>
#include <algorithm>
#include <memory>
#include <set>
#include <string>
#include <tuple>
#include <utility>
#include "base/check_op.h"
#include "base/feature_list.h"
#include "base/functional/callback_helpers.h"
#include "base/i18n/case_conversion.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/threading/platform_thread.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/trace_event/trace_event.h"
#include "content/browser/renderer_host/dwrite_font_file_util_win.h"
#include "content/common/features.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "third_party/abseil-cpp/absl/utility/utility.h"
#include "third_party/blink/public/common/font_unique_name_lookup/font_unique_name_table.pb.h"
#include "third_party/blink/public/common/font_unique_name_lookup/icu_fold_case_util.h"
#include "ui/gfx/win/direct_write.h"
#include "ui/gfx/win/text_analysis_source.h"
namespace mswr = Microsoft::WRL;
namespace content {
namespace {
// These are the fonts that Blink tries to load in getLastResortFallbackFont,
// and will crash if none can be loaded.
const auto kLastResortFontNames = std::to_array<const wchar_t*>(
{L"Sans", L"Arial", L"MS UI Gothic", L"Microsoft Sans Serif", L"Segoe UI",
L"Calibri", L"Times New Roman", L"Courier New"});
struct RequiredFontStyle {
const char16_t* family_name;
DWRITE_FONT_WEIGHT required_weight;
DWRITE_FONT_STRETCH required_stretch;
DWRITE_FONT_STYLE required_style;
};
// Used in tests to allow a known font to masquerade as a locally installed
// font. Usually this is the Ahem.ttf font. Leaked at shutdown.
std::vector<base::FilePath>* g_sideloaded_fonts = nullptr;
const RequiredFontStyle kRequiredStyles[] = {
// The regular version of Gill Sans is actually in the Gill Sans MT family,
// and the Gill Sans family typically contains just the ultra-bold styles.
{u"gill sans", DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
DWRITE_FONT_STYLE_NORMAL},
{u"helvetica", DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
DWRITE_FONT_STYLE_NORMAL},
{u"open sans", DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STRETCH_NORMAL,
DWRITE_FONT_STYLE_NORMAL},
};
// As a workaround for crbug.com/635932, refuse to load some common fonts that
// do not contain certain styles. We found that sometimes these fonts are
// installed only in specialized styles ('Open Sans' might only be available in
// the condensed light variant, or Helvetica might only be available in bold).
// That results in a poor user experience because websites that use those fonts
// usually expect them to be rendered in the regular variant.
bool CheckRequiredStylesPresent(IDWriteFontCollection* collection,
const std::u16string& family_name,
uint32_t family_index) {
for (const auto& font_style : kRequiredStyles) {
if (base::EqualsCaseInsensitiveASCII(family_name, font_style.family_name)) {
mswr::ComPtr<IDWriteFontFamily> family;
if (FAILED(collection->GetFontFamily(family_index, &family))) {
DCHECK(false);
return true;
}
mswr::ComPtr<IDWriteFont> font;
if (FAILED(family->GetFirstMatchingFont(
font_style.required_weight, font_style.required_stretch,
font_style.required_style, &font))) {
DCHECK(false);
return true;
}
// GetFirstMatchingFont doesn't require strict style matching, so check
// the actual font that we got.
if (font->GetWeight() != font_style.required_weight ||
font->GetStretch() != font_style.required_stretch ||
font->GetStyle() != font_style.required_style) {
return false;
}
break;
}
}
return true;
}
HRESULT GetLocalFontCollection(mswr::ComPtr<IDWriteFactory3>& factory,
IDWriteFontCollection** collection) {
if (!g_sideloaded_fonts) {
// Normal path - use the system's font collection with no sideloading.
return factory->GetSystemFontCollection(collection);
}
// If sideloading - build a font set with sideloads then add the system font
// collection.
mswr::ComPtr<IDWriteFontSetBuilder> font_set_builder;
HRESULT hr = factory->CreateFontSetBuilder(&font_set_builder);
if (!SUCCEEDED(hr)) {
return hr;
}
for (auto& path : *g_sideloaded_fonts) {
mswr::ComPtr<IDWriteFontFile> font_file;
hr = factory->CreateFontFileReference(path.value().c_str(), nullptr,
&font_file);
if (!SUCCEEDED(hr)) {
return hr;
}
BOOL supported;
DWRITE_FONT_FILE_TYPE file_type;
UINT32 n_fonts;
hr = font_file->Analyze(&supported, &file_type, nullptr, &n_fonts);
if (!SUCCEEDED(hr)) {
return hr;
}
for (UINT32 font_index = 0; font_index < n_fonts; ++font_index) {
mswr::ComPtr<IDWriteFontFaceReference> font_face;
hr = factory->CreateFontFaceReference(font_file.Get(), font_index,
DWRITE_FONT_SIMULATIONS_NONE,
&font_face);
if (!SUCCEEDED(hr)) {
return hr;
}
hr = font_set_builder->AddFontFaceReference(font_face.Get());
if (!SUCCEEDED(hr)) {
return hr;
}
}
}
// Now add the system fonts.
mswr::ComPtr<IDWriteFontSet> system_font_set;
hr = factory->GetSystemFontSet(&system_font_set);
if (!SUCCEEDED(hr)) {
return hr;
}
hr = font_set_builder->AddFontSet(system_font_set.Get());
if (!SUCCEEDED(hr)) {
return hr;
}
// Make the set.
mswr::ComPtr<IDWriteFontSet> font_set;
hr = font_set_builder->CreateFontSet(&font_set);
if (!SUCCEEDED(hr)) {
return hr;
}
// Make the collection.
mswr::ComPtr<IDWriteFontCollection1> collection1;
hr = factory->CreateFontCollectionFromFontSet(font_set.Get(), &collection1);
if (!SUCCEEDED(hr)) {
return hr;
}
hr = collection1->QueryInterface(collection);
return hr;
}
} // namespace
DWriteFontProxyImpl::DWriteFontProxyImpl()
: windows_fonts_path_(GetWindowsFontsPath()) {}
DWriteFontProxyImpl::~DWriteFontProxyImpl() = default;
// static
void DWriteFontProxyImpl::Create(
mojo::PendingReceiver<blink::mojom::DWriteFontProxy> receiver) {
mojo::MakeSelfOwnedReceiver(std::make_unique<DWriteFontProxyImpl>(),
std::move(receiver));
}
// static
void DWriteFontProxyImpl::SideLoadFontForTesting(base::FilePath path) {
if (!g_sideloaded_fonts) {
// Note: this list is leaked.
g_sideloaded_fonts = new std::vector<base::FilePath>();
}
g_sideloaded_fonts->push_back(path);
}
void DWriteFontProxyImpl::SetWindowsFontsPathForTesting(std::u16string path) {
windows_fonts_path_.swap(path);
}
void DWriteFontProxyImpl::FindFamily(const std::u16string& family_name,
FindFamilyCallback callback) {
InitializeDirectWrite();
TRACE_EVENT0("dwrite,fonts", "FontProxyHost::OnFindFamily");
UINT32 family_index = UINT32_MAX;
if (collection_) {
BOOL exists = FALSE;
UINT32 index = UINT32_MAX;
HRESULT hr = collection_->FindFamilyName(base::as_wcstr(family_name),
&index, &exists);
if (SUCCEEDED(hr) && exists &&
CheckRequiredStylesPresent(collection_.Get(), family_name, index)) {
family_index = index;
}
}
std::move(callback).Run(family_index);
}
void DWriteFontProxyImpl::GetFamilyCount(GetFamilyCountCallback callback) {
InitializeDirectWrite();
TRACE_EVENT0("dwrite,fonts", "FontProxyHost::OnGetFamilyCount");
std::move(callback).Run(collection_ ? collection_->GetFontFamilyCount() : 0);
}
void DWriteFontProxyImpl::GetFamilyNames(UINT32 family_index,
GetFamilyNamesCallback callback) {
InitializeDirectWrite();
TRACE_EVENT0("dwrite,fonts", "FontProxyHost::OnGetFamilyNames");
callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback), std::vector<blink::mojom::DWriteStringPairPtr>());
if (!collection_)
return;
TRACE_EVENT0("dwrite,fonts", "FontProxyHost::DoGetFamilyNames");
mswr::ComPtr<IDWriteFontFamily> family;
HRESULT hr = collection_->GetFontFamily(family_index, &family);
if (FAILED(hr)) {
return;
}
mswr::ComPtr<IDWriteLocalizedStrings> localized_names;
hr = family->GetFamilyNames(&localized_names);
if (FAILED(hr)) {
return;
}
size_t string_count = localized_names->GetCount();
std::vector<wchar_t> locale;
std::vector<wchar_t> name;
std::vector<blink::mojom::DWriteStringPairPtr> family_names;
for (size_t index = 0; index < string_count; ++index) {
UINT32 length = 0;
hr = localized_names->GetLocaleNameLength(index, &length);
if (FAILED(hr)) {
return;
}
++length; // Reserve space for the null terminator.
locale.resize(length);
hr = localized_names->GetLocaleName(index, locale.data(), length);
if (FAILED(hr)) {
return;
}
CHECK_EQ(L'\0', locale[length - 1]);
length = 0;
hr = localized_names->GetStringLength(index, &length);
if (FAILED(hr)) {
return;
}
++length; // Reserve space for the null terminator.
name.resize(length);
hr = localized_names->GetString(index, name.data(), length);
if (FAILED(hr)) {
return;
}
CHECK_EQ(L'\0', name[length - 1]);
family_names.emplace_back(std::in_place, base::WideToUTF16(locale.data()),
base::WideToUTF16(name.data()));
}
std::move(callback).Run(std::move(family_names));
}
void DWriteFontProxyImpl::GetFontFileHandles(
uint32_t family_index,
GetFontFileHandlesCallback callback) {
InitializeDirectWrite();
TRACE_EVENT0("dwrite,fonts", "FontProxyHost::OnGetFontFiles");
callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback), std::vector<base::File>());
if (!collection_)
return;
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
mswr::ComPtr<IDWriteFontFamily> family;
HRESULT hr = collection_->GetFontFamily(family_index, &family);
if (FAILED(hr)) {
return;
}
UINT32 font_count = family->GetFontCount();
std::set<std::wstring> path_set;
// Iterate through all the fonts in the family, and all the files for those
// fonts. If anything goes wrong, bail on the entire family to avoid having
// a partially-loaded font family.
for (UINT32 font_index = 0; font_index < font_count; ++font_index) {
mswr::ComPtr<IDWriteFont> font;
hr = family->GetFont(font_index, &font);
if (FAILED(hr)) {
return;
}
std::ignore = AddFilesForFont(font.Get(), windows_fonts_path_, &path_set);
}
std::vector<base::File> file_handles;
// We pass handles for every path as the sandbox blocks direct access to font
// files in the renderer.
for (const auto& font_path : path_set) {
// Specify FLAG_WIN_EXCLUSIVE_WRITE to prevent base::File from opening the
// file with FILE_SHARE_WRITE access. FLAG_WIN_EXCLUSIVE_WRITE doesn't
// actually open the file for write access.
base::File file(base::FilePath(font_path),
base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_WIN_EXCLUSIVE_WRITE);
if (file.IsValid()) {
file_handles.push_back(std::move(file));
}
}
std::move(callback).Run(std::move(file_handles));
}
void DWriteFontProxyImpl::MapCharacters(
const std::u16string& text,
blink::mojom::DWriteFontStylePtr font_style,
const std::u16string& locale_name,
uint32_t reading_direction,
const std::u16string& base_family_name,
MapCharactersCallback callback) {
InitializeDirectWrite();
callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback),
blink::mojom::MapCharactersResult::New(
UINT32_MAX, u"", text.length(), 0.0,
blink::mojom::DWriteFontStyle::New(DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
DWRITE_FONT_WEIGHT_NORMAL)));
if (factory2_ == nullptr || collection_ == nullptr)
return;
if (font_fallback_ == nullptr) {
if (FAILED(factory2_->GetSystemFontFallback(&font_fallback_))) {
return;
}
}
mswr::ComPtr<IDWriteFont> mapped_font;
mswr::ComPtr<IDWriteNumberSubstitution> number_substitution;
if (FAILED(factory2_->CreateNumberSubstitution(
DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE, base::as_wcstr(locale_name),
TRUE /* ignoreUserOverride */, &number_substitution))) {
DCHECK(false);
return;
}
mswr::ComPtr<IDWriteTextAnalysisSource> analysis_source;
if (FAILED(gfx::win::TextAnalysisSource::Create(
&analysis_source, base::UTF16ToWide(text),
base::UTF16ToWide(locale_name), number_substitution.Get(),
static_cast<DWRITE_READING_DIRECTION>(reading_direction)))) {
DCHECK(false);
return;
}
auto result = blink::mojom::MapCharactersResult::New(
UINT32_MAX, u"", text.length(), 0.0,
blink::mojom::DWriteFontStyle::New(DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL,
DWRITE_FONT_WEIGHT_NORMAL));
if (FAILED(font_fallback_->MapCharacters(
analysis_source.Get(), 0, text.length(), collection_.Get(),
base::as_wcstr(base_family_name),
static_cast<DWRITE_FONT_WEIGHT>(font_style->font_weight),
static_cast<DWRITE_FONT_STYLE>(font_style->font_slant),
static_cast<DWRITE_FONT_STRETCH>(font_style->font_stretch),
&result->mapped_length, &mapped_font, &result->scale))) {
DCHECK(false);
return;
}
if (mapped_font == nullptr) {
std::move(callback).Run(std::move(result));
return;
}
mswr::ComPtr<IDWriteFontFamily> mapped_family;
if (FAILED(mapped_font->GetFontFamily(&mapped_family))) {
DCHECK(false);
return;
}
mswr::ComPtr<IDWriteLocalizedStrings> family_names;
if (FAILED(mapped_family->GetFamilyNames(&family_names))) {
DCHECK(false);
return;
}
result->font_style->font_slant = mapped_font->GetStyle();
result->font_style->font_stretch = mapped_font->GetStretch();
result->font_style->font_weight = mapped_font->GetWeight();
std::vector<wchar_t> name;
size_t name_count = family_names->GetCount();
for (size_t name_index = 0; name_index < name_count; name_index++) {
UINT32 name_length = 0;
if (FAILED(family_names->GetStringLength(name_index, &name_length)))
continue; // Keep trying other names
++name_length; // Reserve space for the null terminator.
name.resize(name_length);
if (FAILED(family_names->GetString(name_index, name.data(), name_length)))
continue;
UINT32 index = UINT32_MAX;
BOOL exists = false;
if (FAILED(collection_->FindFamilyName(name.data(), &index, &exists)) ||
!exists)
continue;
// Found a matching family!
result->family_index = index;
result->family_name = base::as_u16cstr(name.data());
std::move(callback).Run(std::move(result));
return;
}
// Could not find a matching family
DCHECK_EQ(result->family_index, UINT32_MAX);
DCHECK_GT(result->mapped_length, 0u);
}
void DWriteFontProxyImpl::MatchUniqueFont(
const std::u16string& unique_font_name,
MatchUniqueFontCallback callback) {
TRACE_EVENT0("dwrite,fonts", "DWriteFontProxyImpl::MatchUniqueFont");
DCHECK(base::FeatureList::IsEnabled(features::kFontSrcLocalMatching));
callback = mojo::WrapCallbackWithDefaultInvokeIfNotRun(std::move(callback),
base::File(), 0);
InitializeDirectWrite();
// We must not get here if this version of DWrite can't handle performing the
// search.
DCHECK(factory3_.Get());
DCHECK(collection_);
Microsoft::WRL::ComPtr<IDWriteFontCollection1> collection1;
HRESULT hr = collection_.As(&collection1);
if (FAILED(hr)) {
return;
}
// In non-testing cases this is identical to factory3_->GetSystemFontSet().
mswr::ComPtr<IDWriteFontSet> system_font_set;
hr = collection1->GetFontSet(&system_font_set);
if (FAILED(hr))
return;
DCHECK_GT(system_font_set->GetFontCount(), 0U);
mswr::ComPtr<IDWriteFontSet> filtered_set;
auto filter_set = [&system_font_set, &filtered_set,
&unique_font_name](DWRITE_FONT_PROPERTY_ID property_id) {
TRACE_EVENT0("dwrite,fonts",
"DWriteFontProxyImpl::MatchUniqueFont::filter_set");
std::wstring unique_font_name_wide = base::UTF16ToWide(unique_font_name);
DWRITE_FONT_PROPERTY search_property = {property_id,
unique_font_name_wide.c_str(), L""};
// GetMatchingFonts() matches all languages according to:
// https://docs.microsoft.com/en-us/windows/desktop/api/dwrite_3/ns-dwrite_3-dwrite_font_property
HRESULT hr =
system_font_set->GetMatchingFonts(&search_property, 1, &filtered_set);
return SUCCEEDED(hr);
};
// Search PostScript name first, otherwise try searching for full font name.
// Return if filtering failed.
if (!filter_set(DWRITE_FONT_PROPERTY_ID_POSTSCRIPT_NAME))
return;
if (!filtered_set->GetFontCount() &&
!filter_set(DWRITE_FONT_PROPERTY_ID_FULL_NAME)) {
return;
}
if (!filtered_set->GetFontCount())
return;
mswr::ComPtr<IDWriteFontFaceReference> first_font;
hr = filtered_set->GetFontFaceReference(0, &first_font);
if (FAILED(hr))
return;
mswr::ComPtr<IDWriteFontFace3> first_font_face_3;
hr = first_font->CreateFontFace(&first_font_face_3);
if (FAILED(hr))
return;
mswr::ComPtr<IDWriteFontFace> first_font_face;
hr = first_font_face_3.As<IDWriteFontFace>(&first_font_face);
if (FAILED(hr))
return;
std::wstring font_file_pathname;
uint32_t ttc_index;
if (FAILED(FontFilePathAndTtcIndex(first_font_face.Get(), font_file_pathname,
ttc_index))) {
return;
}
base::FilePath path(font_file_pathname);
// Have the Browser process open the font file and send the handle to the
// Renderer Process to access the font. Otherwise, user-installed local font
// files outside of Windows fonts system directory wouldn't be accessible by
// Renderer due to Windows sandboxing rules.
// Specify FLAG_WIN_EXCLUSIVE_WRITE to prevent base::File from opening the
// file with FILE_SHARE_WRITE access. FLAG_WIN_EXCLUSIVE_WRITE doesn't
// actually open the file for write access.
base::File font_file(path, base::File::FLAG_OPEN | base::File::FLAG_READ |
base::File::FLAG_WIN_EXCLUSIVE_WRITE);
if (!font_file.IsValid() || !font_file.GetLength()) {
return;
}
std::move(callback).Run(std::move(font_file), ttc_index);
}
void DWriteFontProxyImpl::InitializeDirectWrite() {
if (direct_write_initialized_)
return;
direct_write_initialized_ = true;
TRACE_EVENT0("dwrite,fonts", "DWriteFontProxyImpl::InitializeDirectWrite");
gfx::win::CreateDWriteFactory(&factory_);
if (factory_ == nullptr) {
// We won't be able to load fonts, but we should still return messages so
// renderers don't hang if they for some reason send us a font message.
return;
}
// QueryInterface for IDWriteFactory2. This should succeed since we only
// support >= Win10.
factory_.As<IDWriteFactory2>(&factory2_);
DCHECK(factory2_);
// QueryInterface for IDwriteFactory3, needed for MatchUniqueFont on Windows.
// This should succeed since we only support >= Win10.
factory_.As<IDWriteFactory3>(&factory3_);
DCHECK(factory3_);
// Normally identical to factory_->GetSystemFontCollection() unless a
// sideloaded font has been added using SideLoadFontForTesting().
HRESULT hr = GetLocalFontCollection(factory3_, &collection_);
DCHECK(SUCCEEDED(hr));
if (!collection_) {
return;
}
// Temp code to help track down crbug.com/561873
for (const wchar_t* font : kLastResortFontNames) {
uint32_t font_index = 0;
BOOL exists = FALSE;
if (SUCCEEDED(collection_->FindFamilyName(font, &font_index, &exists)) &&
exists && font_index != UINT32_MAX) {
last_resort_fonts_.push_back(font_index);
}
}
}
bool DWriteFontProxyImpl::IsLastResortFallbackFont(uint32_t font_index) {
for (auto iter = last_resort_fonts_.begin(); iter != last_resort_fonts_.end();
++iter) {
if (*iter == font_index)
return true;
}
return false;
}
} // namespace content