blob: 22e189c7b8ac33712c2648abe46048f3b94b0740 [file] [log] [blame]
// Copyright 2015 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/browser/renderer_host/dwrite_font_proxy_message_filter_win.h"
#include <dwrite.h>
#include <shlobj.h>
#include <stddef.h>
#include <stdint.h>
#include <set>
#include <utility>
#include "base/callback_helpers.h"
#include "base/i18n/case_conversion.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string16.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "content/common/dwrite_font_proxy_messages.h"
#include "content/common/dwrite_text_analysis_source_win.h"
#include "ipc/ipc_message_macros.h"
#include "ui/gfx/win/direct_write.h"
namespace mswr = Microsoft::WRL;
namespace content {
namespace {
// 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 DirectWriteFontLoaderType {
FILE_SYSTEM_FONT_DIR = 0,
FILE_OUTSIDE_SANDBOX = 1,
OTHER_LOADER = 2,
FONT_LOADER_TYPE_MAX_VALUE
};
void LogLoaderType(DirectWriteFontLoaderType loader_type) {
UMA_HISTOGRAM_ENUMERATION("DirectWrite.Fonts.Proxy.LoaderType", loader_type,
FONT_LOADER_TYPE_MAX_VALUE);
}
const wchar_t* kFontsToIgnore[] = {
// "Gill Sans Ultra Bold" turns into an Ultra Bold weight "Gill Sans" in
// DirectWrite, but most users don't have any other weights. The regular
// weight font is named "Gill Sans MT", but that ends up in a different
// family with that name. On Mac, there's a "Gill Sans" with various
// weights, so CSS authors use { 'font-family': 'Gill Sans',
// 'Gill Sans MT', ... } and because of the DirectWrite family futzing,
// they end up with an Ultra Bold font, when they just wanted "Gill Sans".
// Mozilla implemented a more complicated hack where they effectively
// rename the Ultra Bold font to "Gill Sans MT Ultra Bold", but because the
// Ultra Bold font is so ugly anyway, we simply ignore it. See
// http://www.microsoft.com/typography/fonts/font.aspx?FMID=978 for a
// picture of the font, and the file name. We also ignore "Gill Sans Ultra
// Bold Condensed".
L"gilsanub.ttf", L"gillubcd.ttf",
};
base::string16 GetWindowsFontsPath() {
std::vector<base::char16> font_path_chars;
// SHGetSpecialFolderPath requires at least MAX_PATH characters.
font_path_chars.resize(MAX_PATH);
BOOL result = SHGetSpecialFolderPath(nullptr /* hwndOwner - reserved */,
font_path_chars.data(), CSIDL_FONTS,
FALSE /* fCreate */);
DCHECK(result);
return base::i18n::FoldCase(font_path_chars.data());
}
} // namespace
DWriteFontProxyMessageFilter::DWriteFontProxyMessageFilter()
: BrowserMessageFilter(DWriteFontProxyMsgStart),
windows_fonts_path_(GetWindowsFontsPath()) {}
DWriteFontProxyMessageFilter::~DWriteFontProxyMessageFilter() = default;
bool DWriteFontProxyMessageFilter::OnMessageReceived(
const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(DWriteFontProxyMessageFilter, message)
IPC_MESSAGE_HANDLER(DWriteFontProxyMsg_FindFamily, OnFindFamily)
IPC_MESSAGE_HANDLER(DWriteFontProxyMsg_GetFamilyCount, OnGetFamilyCount)
IPC_MESSAGE_HANDLER(DWriteFontProxyMsg_GetFamilyNames, OnGetFamilyNames)
IPC_MESSAGE_HANDLER(DWriteFontProxyMsg_GetFontFiles, OnGetFontFiles)
IPC_MESSAGE_HANDLER(DWriteFontProxyMsg_MapCharacters, OnMapCharacters)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
void DWriteFontProxyMessageFilter::OverrideThreadForMessage(
const IPC::Message& message,
content::BrowserThread::ID* thread) {
if (IPC_MESSAGE_CLASS(message) == DWriteFontProxyMsgStart)
*thread = BrowserThread::FILE;
}
void DWriteFontProxyMessageFilter::OnFindFamily(
const base::string16& family_name,
UINT32* family_index) {
InitializeDirectWrite();
TRACE_EVENT0("dwrite", "FontProxyHost::OnFindFamily");
DCHECK(collection_);
*family_index = UINT32_MAX;
if (collection_) {
BOOL exists = FALSE;
UINT32 index = UINT32_MAX;
HRESULT hr =
collection_->FindFamilyName(family_name.data(), &index, &exists);
if (SUCCEEDED(hr) && exists)
*family_index = index;
}
}
void DWriteFontProxyMessageFilter::OnGetFamilyCount(UINT32* count) {
InitializeDirectWrite();
TRACE_EVENT0("dwrite", "FontProxyHost::OnGetFamilyCount");
DCHECK(collection_);
if (!collection_)
*count = 0;
else
*count = collection_->GetFontFamilyCount();
}
void DWriteFontProxyMessageFilter::OnGetFamilyNames(
UINT32 family_index,
std::vector<DWriteStringPair>* family_names) {
InitializeDirectWrite();
TRACE_EVENT0("dwrite", "FontProxyHost::OnGetFamilyNames");
DCHECK(collection_);
if (!collection_)
return;
TRACE_EVENT0("dwrite", "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<base::char16> locale;
std::vector<base::char16> name;
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]);
// Would be great to use emplace_back instead.
family_names->push_back(std::pair<base::string16, base::string16>(
base::string16(locale.data()), base::string16(name.data())));
}
}
void DWriteFontProxyMessageFilter::OnGetFontFiles(
uint32_t family_index,
std::vector<base::string16>* file_paths) {
InitializeDirectWrite();
TRACE_EVENT0("dwrite", "FontProxyHost::OnGetFontFiles");
DCHECK(collection_);
if (!collection_)
return;
mswr::ComPtr<IDWriteFontFamily> family;
HRESULT hr = collection_->GetFontFamily(family_index, &family);
if (FAILED(hr)) {
return;
}
UINT32 font_count = family->GetFontCount();
std::set<base::string16> 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;
}
AddFilesForFont(&path_set, font.Get());
}
file_paths->assign(path_set.begin(), path_set.end());
}
void DWriteFontProxyMessageFilter::OnMapCharacters(
const base::string16& text,
const DWriteFontStyle& font_style,
const base::string16& locale_name,
uint32_t reading_direction,
const base::string16& base_family_name,
MapCharactersResult* result) {
InitializeDirectWrite();
result->family_index = UINT32_MAX;
result->mapped_length = text.length();
result->family_name.clear();
result->scale = 0.0;
result->font_style.font_slant = DWRITE_FONT_STYLE_NORMAL;
result->font_style.font_stretch = DWRITE_FONT_STRETCH_NORMAL;
result->font_style.font_weight = DWRITE_FONT_WEIGHT_NORMAL;
if (factory2_ == nullptr || collection_ == nullptr)
return;
if (font_fallback_ == nullptr) {
if (FAILED(factory2_->GetSystemFontFallback(&font_fallback_)))
return;
}
UINT32 length;
mswr::ComPtr<IDWriteFont> mapped_font;
mswr::ComPtr<IDWriteNumberSubstitution> number_substitution;
if (FAILED(factory2_->CreateNumberSubstitution(
DWRITE_NUMBER_SUBSTITUTION_METHOD_NONE, locale_name.c_str(),
TRUE /* ignoreUserOverride */, &number_substitution))) {
DCHECK(false);
return;
}
mswr::ComPtr<IDWriteTextAnalysisSource> analysis_source;
if (FAILED(mswr::MakeAndInitialize<TextAnalysisSource>(
&analysis_source, text, locale_name, number_substitution.Get(),
static_cast<DWRITE_READING_DIRECTION>(reading_direction)))) {
DCHECK(false);
return;
}
if (FAILED(font_fallback_->MapCharacters(
analysis_source.Get(), 0, text.length(), collection_.Get(),
base_family_name.c_str(),
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), &length,
&mapped_font, &result->scale))) {
DCHECK(false);
return;
}
result->mapped_length = length;
if (mapped_font == nullptr)
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<base::char16> 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 = name.data();
return;
}
// Could not find a matching family
// TODO(kulshin): log UMA that we matched a font, but could not locate the
// family
DCHECK_EQ(result->family_index, UINT32_MAX);
DCHECK_GT(result->mapped_length, 0u);
}
void DWriteFontProxyMessageFilter::InitializeDirectWrite() {
DCHECK_CURRENTLY_ON(BrowserThread::FILE);
if (direct_write_initialized_)
return;
direct_write_initialized_ = true;
mswr::ComPtr<IDWriteFactory> factory;
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. It's ok for this to fail if we are
// running an older version of DirectWrite (earlier than Win8.1).
factory.As<IDWriteFactory2>(&factory2_);
HRESULT hr = factory->GetSystemFontCollection(&collection_);
DCHECK(SUCCEEDED(hr));
}
bool DWriteFontProxyMessageFilter::AddFilesForFont(
std::set<base::string16>* path_set,
IDWriteFont* font) {
mswr::ComPtr<IDWriteFontFace> font_face;
HRESULT hr;
hr = font->CreateFontFace(&font_face);
if (FAILED(hr)) {
return false;
}
UINT32 file_count;
hr = font_face->GetFiles(&file_count, nullptr);
if (FAILED(hr)) {
return false;
}
std::vector<mswr::ComPtr<IDWriteFontFile>> font_files;
font_files.resize(file_count);
hr = font_face->GetFiles(
&file_count, reinterpret_cast<IDWriteFontFile**>(font_files.data()));
if (FAILED(hr)) {
return false;
}
for (unsigned int file_index = 0; file_index < file_count; ++file_index) {
mswr::ComPtr<IDWriteFontFileLoader> loader;
hr = font_files[file_index]->GetLoader(&loader);
if (FAILED(hr)) {
return false;
}
mswr::ComPtr<IDWriteLocalFontFileLoader> local_loader;
hr = loader.CopyTo(local_loader.GetAddressOf()); // QueryInterface.
if (hr == E_NOINTERFACE) {
// We could get here if the system font collection contains fonts that
// are backed by something other than files in the system fonts folder.
// I don't think that is actually possible, so for now we'll just
// ignore it (result will be that we'll be unable to match any styles
// for this font, forcing blink/skia to fall back to whatever font is
// next). If we get telemetry indicating that this case actually
// happens, we can implement this by exposing the loader via ipc. That
// will likely be by loading the font data into shared memory, although
// we could proxy the stream reads directly instead.
LogLoaderType(OTHER_LOADER);
DCHECK(false);
return false;
} else if (FAILED(hr)) {
return false;
}
if (!AddLocalFile(path_set, local_loader.Get(),
font_files[file_index].Get())) {
return false;
}
}
return true;
}
bool DWriteFontProxyMessageFilter::AddLocalFile(
std::set<base::string16>* path_set,
IDWriteLocalFontFileLoader* local_loader,
IDWriteFontFile* font_file) {
HRESULT hr;
const void* key;
UINT32 key_size;
hr = font_file->GetReferenceKey(&key, &key_size);
if (FAILED(hr)) {
return false;
}
UINT32 path_length = 0;
hr = local_loader->GetFilePathLengthFromKey(key, key_size, &path_length);
if (FAILED(hr)) {
return false;
}
++path_length; // Reserve space for the null terminator.
std::vector<base::char16> file_path_chars;
file_path_chars.resize(path_length);
hr = local_loader->GetFilePathFromKey(key, key_size, file_path_chars.data(),
path_length);
if (FAILED(hr)) {
return false;
}
base::string16 file_path = base::i18n::FoldCase(file_path_chars.data());
if (!base::StartsWith(file_path, windows_fonts_path_,
base::CompareCase::SENSITIVE)) {
// Skip loading fonts from outside the system fonts directory, since
// these families will not be accessible to the renderer process. If
// this turns out to be a common case, we can either grant the renderer
// access to these files (not sure if this is actually possible), or
// load the file data ourselves and hand it to the renderer.
// Really, really, really want to know what families hit this. Current
// data indicates about 0.09% of families fall into this case. Nothing to
// worry about if it's random obscure fonts noone has ever heard of, but
// could be a problem if it's common fonts.
LogLoaderType(FILE_OUTSIDE_SANDBOX);
NOTREACHED(); // Not yet implemented.
return false;
}
// Refer to comments in kFontsToIgnore for this block.
for (const auto& file_to_ignore : kFontsToIgnore) {
// Ok to do ascii comparison since the strings we are looking for are
// all ascii.
if (base::EndsWith(file_path, file_to_ignore,
base::CompareCase::INSENSITIVE_ASCII)) {
// Unlike most other cases in this function, we do not abort loading
// the entire family, since we want to specifically ignore particular
// font styles and load the rest of the family if it exists. The
// renderer can deal with a family with zero files if that ends up
// being the case.
return true;
}
}
LogLoaderType(FILE_SYSTEM_FONT_DIR);
path_set->insert(file_path);
return true;
}
} // namespace content