blob: ea99d1e0ce6537764280a80a13b6186dd15f0768 [file] [log] [blame]
// Copyright 2020 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/font_access/font_enumeration_cache_win.h"
#include <dwrite.h>
#include <wrl/client.h>
#include <limits>
#include <string>
#include <vector>
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/sequence_checker.h"
#include "base/task/task_traits.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/threading/thread_checker.h"
#include "content/browser/font_access/font_enumeration_cache.h"
#include "third_party/abseil-cpp/absl/types/optional.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.
absl::optional<std::string> GetLocalizedString(IDWriteLocalizedStrings* names,
const std::string& locale) {
absl::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.
absl::optional<std::string> GetFamilyName(IDWriteFontFamily* family) {
absl::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.
absl::optional<std::string> GetFontPostScriptName(IDWriteFont* font) {
absl::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.
absl::optional<std::string> GetFontFullName(IDWriteFont* font,
const std::string& locale) {
absl::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.
absl::optional<std::string> GetFontStyleName(IDWriteFont* font) {
absl::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;
}
// Map DWRITE_FONT_STYLE to a boolean for italic/oblique.
bool DWriteStyleToWebItalic(DWRITE_FONT_STYLE style) {
return (style & (DWRITE_FONT_STYLE_ITALIC | DWRITE_FONT_STYLE_OBLIQUE)) != 0;
}
// Map DWRITE_FONT_WEIGHT to a font-weight (number in [1,1000]).
// https://drafts.csswg.org/css-fonts-4/#font-weight-prop
float DWriteWeightToWebWeight(DWRITE_FONT_WEIGHT weight) {
// DirectWrite values already correspond to the web definition of
// numbers in the range [1,1000] with 400 as normal.
return weight;
}
// Map DWRITE_FONT_STRETCH to a font-stretch value (percentage).
// https://drafts.csswg.org/css-fonts-4/#propdef-font-stretch
float DWriteStretchToWebStretch(DWRITE_FONT_STRETCH stretch) {
// DWRITE_FONT_STRETCH is an enumeration, so a more complex mapping or
// interpolation is not necessary.
switch (stretch) {
case DWRITE_FONT_STRETCH_ULTRA_CONDENSED:
return 0.5;
case DWRITE_FONT_STRETCH_EXTRA_CONDENSED:
return 0.625;
case DWRITE_FONT_STRETCH_CONDENSED:
return 0.75;
case DWRITE_FONT_STRETCH_SEMI_CONDENSED:
return 0.875;
case DWRITE_FONT_STRETCH_UNDEFINED:
case DWRITE_FONT_STRETCH_NORMAL:
return 1.0f;
case DWRITE_FONT_STRETCH_SEMI_EXPANDED:
return 1.125f;
case DWRITE_FONT_STRETCH_EXPANDED:
return 1.25f;
case DWRITE_FONT_STRETCH_EXTRA_EXPANDED:
return 1.5f;
case DWRITE_FONT_STRETCH_ULTRA_EXPANDED:
return 2.0f;
}
NOTREACHED();
return 1.0f;
}
} // namespace
// static
base::SequenceBound<FontEnumerationCache>
FontEnumerationCache::CreateForTesting(
scoped_refptr<base::SequencedTaskRunner> task_runner,
absl::optional<std::string> locale_override) {
return base::SequenceBound<FontEnumerationCacheWin>(
std::move(task_runner), std::move(locale_override),
base::PassKey<FontEnumerationCache>());
}
FontEnumerationCacheWin::FontEnumerationCacheWin(
absl::optional<std::string> locale_override,
base::PassKey<FontEnumerationCache>)
: FontEnumerationCache(std::move(locale_override)) {}
FontEnumerationCacheWin::~FontEnumerationCacheWin() = default;
FontEnumerationCacheWin::FamilyDataResult::~FamilyDataResult() = default;
FontEnumerationCacheWin::FamilyDataResult::FamilyDataResult() = default;
blink::FontEnumerationTable FontEnumerationCacheWin::ComputeFontEnumerationData(
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;
absl::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;
absl::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;
}
absl::optional<std::string> localized_full_name =
GetFontFullName(font.Get(), locale);
if (!localized_full_name)
localized_full_name = postscript_name;
absl::optional<std::string> style_name = GetFontStyleName(font.Get());
if (!style_name)
continue;
blink::FontEnumerationTable_FontMetadata* metadata =
font_enumeration_table.add_fonts();
metadata->set_postscript_name(std::move(postscript_name).value());
metadata->set_full_name(std::move(localized_full_name).value());
metadata->set_family(family_name.value());
metadata->set_style(style_name ? std::move(style_name.value())
: std::string());
metadata->set_italic(DWriteStyleToWebItalic(font->GetStyle()));
metadata->set_weight(DWriteWeightToWebWeight(font->GetWeight()));
metadata->set_stretch(DWriteStretchToWebStretch(font->GetStretch()));
}
}
return font_enumeration_table;
}
} // namespace content