Avoid finding a font with simulations

When requesting a font match for fonts like Sylfaen or Gabriola, which
absolutely do not have a bold or italic version, Windows silently
produces DirectWrite simulations which synthetic bolding or synthetic
italic or both.

From Chromium's perspective, we clearly do not want Windows to produce
silent simulations for us and this makes it impossible for us to
determine what the font's actual weight or slant is. As a result, we
are blocked from implementing the font-synthesis CSS property
correctly. For the latter, we need to be able to programmatically
enable setEmbolden() or setSkew() on SkFont and have Skia apply
artifical synthesis, instead of Windows instantiating such a font
silently.

Addresses TODO to avoid simulations in
SkFontStyleSet_DirectWrite::matchStyle.

Bug: skia:12455
Cq-Include-Trybots: luci.skia.skia.primary:Test-Win10-Clang-NUC5i7RYH-CPU-AVX2-x86_64-Debug-All-NativeFonts,Test-Win10-Clang-NUC6i5SYK-GPU-IntelIris540-x86_64-Release-All-NativeFonts,Test-Win7-Clang-Golo-CPU-AVX512-x86_64-Debug-All-NativeFonts,Test-Win7-Clang-Golo-CPU-AVX512-x86_64-Debug-All-NativeFonts_GDI
Change-Id: If965d2501dff45ee7881544c6473b3fb36a8d90d
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/450676
Commit-Queue: Ben Wagner <bungeman@google.com>
Reviewed-by: Ben Wagner <bungeman@google.com>
This commit is contained in:
Dominik Röttsches 2021-10-04 16:38:16 +03:00 committed by SkCQ
parent a106472197
commit 76a22af02a

View File

@ -265,6 +265,52 @@ SK_STDMETHODIMP StreamFontCollectionLoader::CreateEnumeratorFromKey(
////////////////////////////////////////////////////////////////////////////////
namespace {
// Iterate calls to GetFirstMatchingFont incrementally removing bold or italic
// styling that can trigger the simulations. Implementing it this way gets us a
// IDWriteFont that can be used as before and has the correct information on its
// own style. Stripping simulations from IDWriteFontFace is possible via
// IDWriteFontList1, IDWriteFontFaceReference and CreateFontFace, but this way
// we won't have a matching IDWriteFont which is still used in get_style().
HRESULT FirstMatchingFontWithoutSimulations(const SkTScopedComPtr<IDWriteFontFamily>& family,
DWriteStyle dwStyle,
SkTScopedComPtr<IDWriteFont>& font) {
bool noSimulations = false;
while (!noSimulations) {
SkTScopedComPtr<IDWriteFont> searchFont;
HR(family->GetFirstMatchingFont(
dwStyle.fWeight, dwStyle.fWidth, dwStyle.fSlant, &searchFont));
DWRITE_FONT_SIMULATIONS simulations = searchFont->GetSimulations();
// If we still get simulations even though we're not asking for bold or
// italic, we can't help it and exit the loop.
#ifdef SK_WIN_FONTMGR_NO_SIMULATIONS
noSimulations = simulations == DWRITE_FONT_SIMULATIONS_NONE ||
(dwStyle.fWeight == DWRITE_FONT_WEIGHT_REGULAR &&
dwStyle.fSlant == DWRITE_FONT_STYLE_NORMAL);
#else
noSimulations = true;
#endif
if (noSimulations) {
font = std::move(searchFont);
break;
}
if (simulations & DWRITE_FONT_SIMULATIONS_BOLD) {
dwStyle.fWeight = DWRITE_FONT_WEIGHT_REGULAR;
continue;
}
if (simulations & DWRITE_FONT_SIMULATIONS_OBLIQUE) {
dwStyle.fSlant = DWRITE_FONT_STYLE_NORMAL;
continue;
}
}
return S_OK;
}
}
////////////////////////////////////////////////////////////////////////////////
class SkFontMgr_DirectWrite : public SkFontMgr {
public:
/** localeNameLength and defaultFamilyNameLength must include the null terminator. */
@ -585,6 +631,7 @@ public:
fResolvedTypeface = fOuter->makeTypefaceFromDWriteFont(glyphRun->fontFace,
font.get(),
fontFamily.get());
fHasSimulations = font->GetSimulations() != DWRITE_FONT_SIMULATIONS_NONE;
}
return S_OK;
@ -644,6 +691,8 @@ public:
sk_sp<SkTypeface> ConsumeFallbackTypeface() { return std::move(fResolvedTypeface); }
bool FallbackTypefaceHasSimulations() { return fHasSimulations; }
private:
virtual ~FontFallbackRenderer() { }
@ -651,6 +700,7 @@ private:
sk_sp<const SkFontMgr_DirectWrite> fOuter;
UINT32 fCharacter;
sk_sp<SkTypeface> fResolvedTypeface;
bool fHasSimulations{false};
};
class FontFallbackSource : public IDWriteTextAnalysisSource {
@ -756,9 +806,8 @@ private:
SkTypeface* SkFontMgr_DirectWrite::onMatchFamilyStyleCharacter(const char familyName[],
const SkFontStyle& style,
const char* bcp47[], int bcp47Count,
SkUnichar character) const
{
const DWriteStyle dwStyle(style);
SkUnichar character) const {
DWriteStyle dwStyle(style);
const WCHAR* dwFamilyName = nullptr;
SkSMallocWCHAR dwFamilyNameLocal;
@ -790,8 +839,7 @@ SkTypeface* SkFontMgr_DirectWrite::onMatchFamilyStyleCharacter(const char family
sk_sp<SkTypeface> SkFontMgr_DirectWrite::fallback(const WCHAR* dwFamilyName,
DWriteStyle dwStyle,
const WCHAR* dwBcp47,
UINT32 character) const
{
UINT32 character) const {
WCHAR str[16];
UINT32 strLen = SkTo<UINT32>(SkUTF::ToUTF16(character, reinterpret_cast<uint16_t*>(str)));
@ -809,20 +857,43 @@ sk_sp<SkTypeface> SkFontMgr_DirectWrite::fallback(const WCHAR* dwFamilyName,
UINT32 mappedLength;
SkTScopedComPtr<IDWriteFont> font;
FLOAT scale;
HRNM(fFontFallback->MapCharacters(fontFallbackSource.get(),
0, // textPosition,
strLen,
fFontCollection.get(),
dwFamilyName,
dwStyle.fWeight,
dwStyle.fSlant,
dwStyle.fWidth,
&mappedLength,
&font,
&scale),
"Could not map characters");
if (!font.get()) {
return nullptr;
bool noSimulations = false;
while (!noSimulations) {
font.reset();
HRNM(fFontFallback->MapCharacters(fontFallbackSource.get(),
0, // textPosition,
strLen,
fFontCollection.get(),
dwFamilyName,
dwStyle.fWeight,
dwStyle.fSlant,
dwStyle.fWidth,
&mappedLength,
&font,
&scale),
"Could not map characters");
if (!font.get()) {
return nullptr;
}
DWRITE_FONT_SIMULATIONS simulations = font->GetSimulations();
#ifdef SK_WIN_FONTMGR_NO_SIMULATIONS
noSimulations = simulations == DWRITE_FONT_SIMULATIONS_NONE;
#else
noSimulations = true;
#endif
if (simulations & DWRITE_FONT_SIMULATIONS_BOLD) {
dwStyle.fWeight = DWRITE_FONT_WEIGHT_REGULAR;
continue;
}
if (simulations & DWRITE_FONT_SIMULATIONS_OBLIQUE) {
dwStyle.fSlant = DWRITE_FONT_STYLE_NORMAL;
continue;
}
}
SkTScopedComPtr<IDWriteFontFace> fontFace;
@ -841,34 +912,57 @@ sk_sp<SkTypeface> SkFontMgr_DirectWrite::layoutFallback(const WCHAR* dwFamilyNam
WCHAR str[16];
UINT32 strLen = SkTo<UINT32>(SkUTF::ToUTF16(character, reinterpret_cast<uint16_t*>(str)));
SkTScopedComPtr<IDWriteTextFormat> fallbackFormat;
HRNM(fFactory->CreateTextFormat(dwFamilyName ? dwFamilyName : L"",
fFontCollection.get(),
dwStyle.fWeight,
dwStyle.fSlant,
dwStyle.fWidth,
72.0f,
dwBcp47,
&fallbackFormat),
"Could not create text format.");
bool noSimulations = false;
sk_sp<SkTypeface> returnTypeface(nullptr);
while (!noSimulations) {
SkTScopedComPtr<IDWriteTextFormat> fallbackFormat;
HRNM(fFactory->CreateTextFormat(dwFamilyName ? dwFamilyName : L"",
fFontCollection.get(),
dwStyle.fWeight,
dwStyle.fSlant,
dwStyle.fWidth,
72.0f,
dwBcp47,
&fallbackFormat),
"Could not create text format.");
// No matter how the font collection is set on this IDWriteTextLayout, it is not possible to
// disable use of the system font collection in fallback.
SkTScopedComPtr<IDWriteTextLayout> fallbackLayout;
HRNM(fFactory->CreateTextLayout(str, strLen, fallbackFormat.get(),
200.0f, 200.0f,
&fallbackLayout),
"Could not create text layout.");
// No matter how the font collection is set on this IDWriteTextLayout, it is not possible to
// disable use of the system font collection in fallback.
SkTScopedComPtr<IDWriteTextLayout> fallbackLayout;
HRNM(fFactory->CreateTextLayout(
str, strLen, fallbackFormat.get(), 200.0f, 200.0f, &fallbackLayout),
"Could not create text layout.");
SkTScopedComPtr<FontFallbackRenderer> fontFallbackRenderer(
new FontFallbackRenderer(this, character));
SkTScopedComPtr<FontFallbackRenderer> fontFallbackRenderer(
new FontFallbackRenderer(this, character));
HRNM(fallbackLayout->SetFontCollection(fFontCollection.get(), { 0, strLen }),
"Could not set layout font collection.");
HRNM(fallbackLayout->Draw(nullptr, fontFallbackRenderer.get(), 50.0f, 50.0f),
"Could not draw layout with renderer.");
HRNM(fallbackLayout->SetFontCollection(fFontCollection.get(), {0, strLen}),
"Could not set layout font collection.");
HRNM(fallbackLayout->Draw(nullptr, fontFallbackRenderer.get(), 50.0f, 50.0f),
"Could not draw layout with renderer.");
return fontFallbackRenderer->ConsumeFallbackTypeface();
#ifdef SK_WIN_FONTMGR_NO_SIMULATIONS
noSimulations = !fontFallbackRenderer->FallbackTypefaceHasSimulations();
#else
noSimulations = true;
#endif
if (noSimulations) {
returnTypeface = fontFallbackRenderer->ConsumeFallbackTypeface();
}
if (dwStyle.fWeight != DWRITE_FONT_WEIGHT_REGULAR) {
dwStyle.fWeight = DWRITE_FONT_WEIGHT_REGULAR;
continue;
}
if (dwStyle.fSlant != DWRITE_FONT_STYLE_NORMAL) {
dwStyle.fSlant = DWRITE_FONT_STYLE_NORMAL;
continue;
}
}
return returnTypeface;
}
template <typename T> class SkAutoIDWriteUnregister {
@ -1032,13 +1126,14 @@ HRESULT SkFontMgr_DirectWrite::getByFamilyName(const WCHAR wideFamilyName[],
sk_sp<SkTypeface> SkFontMgr_DirectWrite::onLegacyMakeTypeface(const char familyName[],
SkFontStyle style) const {
SkTScopedComPtr<IDWriteFontFamily> fontFamily;
const DWriteStyle dwStyle(style);
DWriteStyle dwStyle(style);
if (familyName) {
SkSMallocWCHAR dwFamilyName;
if (SUCCEEDED(sk_cstring_to_wchar(familyName, &dwFamilyName))) {
this->getByFamilyName(dwFamilyName, &fontFamily);
if (!fontFamily && fFontFallback) {
return this->fallback(dwFamilyName, dwStyle, fLocaleName.get(), 32);
return this->fallback(
dwFamilyName, dwStyle, fLocaleName.get(), 32);
}
}
}
@ -1060,8 +1155,8 @@ sk_sp<SkTypeface> SkFontMgr_DirectWrite::onLegacyMakeTypeface(const char familyN
}
SkTScopedComPtr<IDWriteFont> font;
HRNM(fontFamily->GetFirstMatchingFont(dwStyle.fWeight, dwStyle.fWidth, dwStyle.fSlant, &font),
"Could not get matching font.");
HRNM(FirstMatchingFontWithoutSimulations(fontFamily, dwStyle, font),
"No font found from family.");
SkTScopedComPtr<IDWriteFontFace> fontFace;
HRNM(font->CreateFontFace(&fontFace), "Could not create font face.");
@ -1104,9 +1199,9 @@ void SkFontStyleSet_DirectWrite::getStyle(int index, SkFontStyle* fs, SkString*
SkTypeface* SkFontStyleSet_DirectWrite::matchStyle(const SkFontStyle& pattern) {
SkTScopedComPtr<IDWriteFont> font;
DWriteStyle dwStyle(pattern);
// TODO: perhaps use GetMatchingFonts and get the least simulated?
HRNM(fFontFamily->GetFirstMatchingFont(dwStyle.fWeight, dwStyle.fWidth, dwStyle.fSlant, &font),
"Could not match font in family.");
HRNM(FirstMatchingFontWithoutSimulations(fFontFamily, dwStyle, font),
"No font found from family.");
SkTScopedComPtr<IDWriteFontFace> fontFace;
HRNM(font->CreateFontFace(&fontFace), "Could not create font face.");