// Copyright 2020 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/font_access/font_enumeration_data_source_win.h"
#include <dwrite.h>
#include <stdint.h>
#include <wrl/client.h>
#include <optional>
#include <string>
#include "base/location.h"
#include "base/notreached.h"
#include "base/sequence_checker.h"
#include "base/threading/scoped_blocking_call.h"
#include "content/browser/font_access/font_enumeration_cache.h"
#include "third_party/blink/public/common/font_access/font_enumeration_table.pb.h"
#include "ui/gfx/win/direct_write.h"
namespace content {
namespace {
// Retrieves a DirectWrite font collection for all fonts on the system.
//
// This operation may be expensive, and its result should be cached.
//
// Returns nullptr in case of failure.
Microsoft::WRL::ComPtr<IDWriteFontCollection> GetSystemFonts() {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
// Returned from all code paths, to enable return value optimization.
Microsoft::WRL::ComPtr<IDWriteFontCollection> collection = nullptr;
Microsoft::WRL::ComPtr<IDWriteFactory> factory;
gfx::win::CreateDWriteFactory(&factory);
if (!factory)
return collection;
HRESULT hr = factory->GetSystemFontCollection(&collection);
if (FAILED(hr))
collection = nullptr;
return collection;
}
// Retrieves a string from a font's information table.
//
// Returns nullptr in case of failure.
Microsoft::WRL::ComPtr<IDWriteLocalizedStrings> GetFontInformation(
IDWriteFont* font,
DWRITE_INFORMATIONAL_STRING_ID string_id) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
// Returned from all code paths, to enable return value optimization.
Microsoft::WRL::ComPtr<IDWriteLocalizedStrings> localized_strings;
BOOL font_has_info;
HRESULT hr = font->GetInformationalStrings(string_id, &localized_strings,
&font_has_info);
if (FAILED(hr) || !font_has_info)
localized_strings = nullptr;
return localized_strings;
}
// Retrieves a string matching a locale from a DirectWrite string collection.
//
// If a string in the given locale does not exist, falls back to retrieving the
// first string in the collection.
//
// Returns nullopt in case the string does not exist.
std::optional<std::string> GetLocalizedString(IDWriteLocalizedStrings* names,
const std::string& locale) {
std::optional<std::string> localized_name =
gfx::win::RetrieveLocalizedString(names, locale);
if (!localized_name.has_value()) {
// Fall back to returning the first string in the collection.
localized_name = gfx::win::RetrieveLocalizedString(names, std::string());
}
return localized_name;
}
// Retrieves a font family name that can be reported by the Fonts Access API.
//
// Returns nullopt in case of failure.
std::optional<std::string> GetFamilyName(IDWriteFontFamily* family) {
std::optional<std::string> family_name;
Microsoft::WRL::ComPtr<IDWriteLocalizedStrings> family_names;
HRESULT hr = family->GetFamilyNames(&family_names);
if (FAILED(hr))
return family_name;
family_name = GetLocalizedString(family_names.Get(), "en-us");
return family_name;
}
// Retrieves a font's PostScript name, to be reported by the Fonts Access API.
//
// Returns nullopt in case of failure.
std::optional<std::string> GetFontPostScriptName(IDWriteFont* font) {
std::optional<std::string> postscript_name;
// DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME and
// DWRITE_INFORMATIONAL_STRING_FULL_NAME are only supported on Windows 7 with
// KB2670838 (https://support.microsoft.com/en-us/kb/2670838) installed. It is
// possible to use a fallback as can be observed in Firefox:
// https://bugzilla.mozilla.org/show_bug.cgi?id=947812 However, this might not
// be worth the effort.
Microsoft::WRL::ComPtr<IDWriteLocalizedStrings> postscript_names =
GetFontInformation(font, DWRITE_INFORMATIONAL_STRING_POSTSCRIPT_NAME);
if (!postscript_names)
return postscript_name;
postscript_name = GetLocalizedString(postscript_names.Get(), "en-us");
return postscript_name;
}
// Retrieves a font's full name, to be reported by the Fonts Access API.
//
// Returns nullopt in case of failure.
std::optional<std::string> GetFontFullName(IDWriteFont* font,
const std::string& locale) {
std::optional<std::string> full_name;
Microsoft::WRL::ComPtr<IDWriteLocalizedStrings> full_names =
GetFontInformation(font, DWRITE_INFORMATIONAL_STRING_FULL_NAME);
if (!full_names)
return full_name;
full_name = GetLocalizedString(full_names.Get(), locale);
return full_name;
}
// Returns a font's style name, to be reported by the Fonts Access API.
//
// Returns nullopt in case of failure.
std::optional<std::string> GetFontStyleName(IDWriteFont* font) {
std::optional<std::string> style_name;
// All fonts should have a subfamily name compatible with Windows GDI,
// available as the string DWRITE_INFORMATIONAL_STRING_WIN32_SUBFAMILY_NAMES.
//
// In some cases, the family / sub-family names preferred by designer wouldn't
// be compatible with Windows GDI, and the desiner. In these cases, the
// designer-preferred subfamily name is availabe as the string
// DWRITE_INFORMATIONAL_STRING_PREFERRED_SUBFAMILY_NAMES.
//
// More details at
// https://docs.microsoft.com/en-us/windows/win32/api/dwrite/ne-dwrite-dwrite_informational_string_id
Microsoft::WRL::ComPtr<IDWriteLocalizedStrings> style_names =
GetFontInformation(font,
DWRITE_INFORMATIONAL_STRING_PREFERRED_SUBFAMILY_NAMES);
if (!style_names) {
style_names = GetFontInformation(
font, DWRITE_INFORMATIONAL_STRING_WIN32_SUBFAMILY_NAMES);
if (!style_names)
return style_name;
}
style_name = GetLocalizedString(style_names.Get(), "en-us");
return style_name;
}
} // namespace
FontEnumerationDataSourceWin::FontEnumerationDataSourceWin() {
DETACH_FROM_SEQUENCE(sequence_checker_);
}
FontEnumerationDataSourceWin::~FontEnumerationDataSourceWin() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
blink::FontEnumerationTable FontEnumerationDataSourceWin::GetFonts(
const std::string& locale) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
blink::FontEnumerationTable font_enumeration_table;
Microsoft::WRL::ComPtr<IDWriteFontCollection> collection = GetSystemFonts();
uint32_t family_count;
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::MAY_BLOCK);
family_count = collection->GetFontFamilyCount();
}
// Used to filter duplicates.
std::set<std::string> fonts_seen;
for (uint32_t family_index = 0; family_index < family_count; ++family_index) {
Microsoft::WRL::ComPtr<IDWriteFontFamily> family;
HRESULT hr = collection->GetFontFamily(family_index, &family);
if (FAILED(hr))
continue;
std::optional<std::string> family_name = GetFamilyName(family.Get());
uint32_t font_count = family->GetFontCount();
for (uint32_t font_index = 0; font_index < font_count; ++font_index) {
Microsoft::WRL::ComPtr<IDWriteFont> font;
{
base::ScopedBlockingCall scoped_blocking_call(
FROM_HERE, base::BlockingType::MAY_BLOCK);
hr = family->GetFont(font_index, &font);
}
if (FAILED(hr))
continue;
// Skip this font if it's a simulation.
if (font->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE)
continue;
std::optional<std::string> postscript_name =
GetFontPostScriptName(font.Get());
if (!postscript_name)
continue;
auto it_and_success = fonts_seen.emplace(postscript_name.value());
if (!it_and_success.second) {
// Skip duplicates.
continue;
}
std::optional<std::string> localized_full_name =
GetFontFullName(font.Get(), locale);
if (!localized_full_name)
localized_full_name = postscript_name;
std::optional<std::string> style_name = GetFontStyleName(font.Get());
if (!style_name)
continue;
blink::FontEnumerationTable_FontData* data =
font_enumeration_table.add_fonts();
data->set_postscript_name(std::move(postscript_name).value());
data->set_full_name(std::move(localized_full_name).value());
data->set_family(family_name.value());
data->set_style(style_name ? std::move(style_name.value())
: std::string());
}
}
return font_enumeration_table;
}
} // namespace content