Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reduce instances of font fallback dialog #9734

Merged
9 commits merged into from
Apr 8, 2021
4 changes: 2 additions & 2 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2113,8 +2113,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// actually fail. We need a way to gracefully fallback.
_renderer->TriggerFontChange(newDpi, _desiredFont, _actualFont);

// If the actual font isn't what was requested...
if (_actualFont.GetFaceName() != _desiredFont.GetFaceName())
// If the actual font went through the last-chance fallback routines...
if (_actualFont.GetFallback())
{
// Then warn the user that we picked something because we couldn't find their font.

Expand Down
13 changes: 12 additions & 1 deletion src/renderer/base/fontinfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ FontInfo::FontInfo(const std::wstring_view faceName,
const bool fSetDefaultRasterFont /* = false */) :
FontInfoBase(faceName, family, weight, fSetDefaultRasterFont, codePage),
_coordSize(coordSize),
_coordSizeUnscaled(coordSize)
_coordSizeUnscaled(coordSize),
_didFallback(false)
{
ValidateFont();
}
Expand Down Expand Up @@ -60,6 +61,16 @@ void FontInfo::SetFromEngine(const std::wstring_view faceName,
_ValidateCoordSize();
}

bool FontInfo::GetFallback() const noexcept
{
return _didFallback;
}

void FontInfo::SetFallback(const bool didFallback) noexcept
{
_didFallback = didFallback;
}

void FontInfo::ValidateFont()
{
_ValidateCoordSize();
Expand Down
165 changes: 152 additions & 13 deletions src/renderer/dx/DxFontRenderData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@

#include "DxFontRenderData.h"

#include "unicode.hpp"

#include <VersionHelpers.h>

static constexpr float POINTS_PER_INCH = 72.0f;
static constexpr std::wstring_view FALLBACK_FONT_FACES[] = { L"Consolas", L"Lucida Console", L"Courier New" };
static constexpr std::wstring_view FALLBACK_LOCALE = L"en-us";
Expand Down Expand Up @@ -36,6 +40,51 @@ DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr<IDWriteFactory1> dwr
return _systemFontFallback;
}

// Routine Description:
// - Creates a DirectWrite font collection of font files that are sitting next to the running
// binary (in the same directory as the EXE).
// Arguments:
// - <none>
// Return Value:
// - DirectWrite font collection. May be null if one cannot be created.
[[nodiscard]] const Microsoft::WRL::ComPtr<IDWriteFontCollection1>& DxFontRenderData::NearbyCollection() const
{
// Magic static so we only attempt to grovel the hard disk once no matter how many instances
// of the font collection itself we require.
static const auto knownPaths = s_GetNearbyFonts();

// The convenience interfaces for loading fonts from files
// are only available on Windows 10+.
// Don't try to look up if below that OS version.
static const bool s_isWindows10OrGreater = IsWindows10OrGreater();

if (s_isWindows10OrGreater && !_nearbyCollection)
{
// Factory3 has a convenience to get us a font set builder.
::Microsoft::WRL::ComPtr<IDWriteFactory3> factory3;
THROW_IF_FAILED(_dwriteFactory.As(&factory3));

::Microsoft::WRL::ComPtr<IDWriteFontSetBuilder> fontSetBuilder;
THROW_IF_FAILED(factory3->CreateFontSetBuilder(&fontSetBuilder));

// Builder2 has a convenience to just feed in paths to font files.
::Microsoft::WRL::ComPtr<IDWriteFontSetBuilder2> fontSetBuilder2;
THROW_IF_FAILED(fontSetBuilder.As(&fontSetBuilder2));

for (auto& p : knownPaths)
{
fontSetBuilder2->AddFontFile(p.c_str());
}

::Microsoft::WRL::ComPtr<IDWriteFontSet> fontSet;
THROW_IF_FAILED(fontSetBuilder2->CreateFontSet(&fontSet));

THROW_IF_FAILED(factory3->CreateFontCollectionFromFontSet(fontSet.Get(), &_nearbyCollection));
}

return _nearbyCollection;
}

[[nodiscard]] til::size DxFontRenderData::GlyphCell() noexcept
{
return _glyphCell;
Expand Down Expand Up @@ -94,7 +143,9 @@ DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr<IDWriteFactory1> dwr
// _ResolveFontFaceWithFallback overrides the last argument with the locale name of the font,
// but we should use the system's locale to render the text.
std::wstring fontLocaleName = localeName;
const auto face = _ResolveFontFaceWithFallback(fontName, weight, stretch, style, fontLocaleName);

bool didFallback = false;
const auto face = _ResolveFontFaceWithFallback(fontName, weight, stretch, style, fontLocaleName, didFallback);

DWRITE_FONT_METRICS1 fontMetrics;
face->GetMetrics(&fontMetrics);
Expand Down Expand Up @@ -221,8 +272,9 @@ DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr<IDWriteFactory1> dwr
DWRITE_FONT_WEIGHT weightItalic = weight;
DWRITE_FONT_STYLE styleItalic = DWRITE_FONT_STYLE_ITALIC;
DWRITE_FONT_STRETCH stretchItalic = stretch;
bool didItalicFallback = false;

const auto faceItalic = _ResolveFontFaceWithFallback(fontNameItalic, weightItalic, stretchItalic, styleItalic, fontLocaleName);
const auto faceItalic = _ResolveFontFaceWithFallback(fontNameItalic, weightItalic, stretchItalic, styleItalic, fontLocaleName, didItalicFallback);

Microsoft::WRL::ComPtr<IDWriteTextFormat> formatItalic;
THROW_IF_FAILED(_dwriteFactory->CreateTextFormat(fontNameItalic.data(),
Expand Down Expand Up @@ -266,6 +318,7 @@ DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr<IDWriteFactory1> dwr
false,
scaled,
unscaled);
actual.SetFallback(didFallback);

LineMetrics lineMetrics;
// There is no font metric for the grid line width, so we use a small
Expand Down Expand Up @@ -578,37 +631,76 @@ CATCH_RETURN()
// - weight - The weight (bold, light, etc.)
// - stretch - The stretch of the font is the spacing between each letter
// - style - Normal, italic, etc.
// - localeName - Locale to search for appropriate fonts
// - didFallback - Indicates whether we couldn't match the user request and had to choose from a hardcoded default list.
// Return Value:
// - Smart pointer holding interface reference for queryable font data.
[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteFontFace1> DxFontRenderData::_ResolveFontFaceWithFallback(std::wstring& familyName,
DWRITE_FONT_WEIGHT& weight,
DWRITE_FONT_STRETCH& stretch,
DWRITE_FONT_STYLE& style,
std::wstring& localeName) const
std::wstring& localeName,
bool& didFallback) const
{
// First attempt to find exactly what the user asked for.
didFallback = false;
auto face = _FindFontFace(familyName, weight, stretch, style, localeName);

if (!face)
{
for (const auto fallbackFace : FALLBACK_FONT_FACES)
// If we missed, try looking a little more by trimming the last word off the requested family name a few times.
// Quite often, folks are specifying weights or something in the familyName and it causes failed resolution and
// an unexpected error dialog. We theoretically could detect the weight words and convert them, but this
// is the quick fix for the majority scenario.
// The long/full fix is backlogged to GH#9744
// Also this doesn't count as a fallback because we don't want to annoy folks with the warning dialog over
// this resolution.
while (!face && !familyName.empty())
{
familyName = fallbackFace;
face = _FindFontFace(familyName, weight, stretch, style, localeName);
const auto lastSpace = familyName.find_last_of(UNICODE_SPACE);

if (face)
// value is unsigned and npos will be greater than size.
// if we didn't find anything to trim, leave.
if (lastSpace >= familyName.size())
{
break;
}

familyName = fallbackFace;
weight = DWRITE_FONT_WEIGHT_NORMAL;
stretch = DWRITE_FONT_STRETCH_NORMAL;
style = DWRITE_FONT_STYLE_NORMAL;
// trim string down to just before the found space
// (space found at 6... trim from 0 for 6 length will give us 0-5 as the new string)
familyName = familyName.substr(0, lastSpace);

// Try to find it with the shortened family name
face = _FindFontFace(familyName, weight, stretch, style, localeName);
}

if (face)
// Alright, if our quick shot at trimming didn't work either...
// move onto looking up a font from our hardcoded list of fonts
// that should really always be available.
if (!face)
{
for (const auto fallbackFace : FALLBACK_FONT_FACES)
{
break;
familyName = fallbackFace;
face = _FindFontFace(familyName, weight, stretch, style, localeName);

if (face)
{
didFallback = true;
break;
}

familyName = fallbackFace;
weight = DWRITE_FONT_WEIGHT_NORMAL;
stretch = DWRITE_FONT_STRETCH_NORMAL;
style = DWRITE_FONT_STYLE_NORMAL;
face = _FindFontFace(familyName, weight, stretch, style, localeName);

if (face)
{
didFallback = true;
break;
}
}
}
}
Expand Down Expand Up @@ -642,6 +734,19 @@ CATCH_RETURN()
BOOL familyExists;
THROW_IF_FAILED(fontCollection->FindFamilyName(familyName.data(), &familyIndex, &familyExists));

// If the system collection missed, try the files sitting next to our binary.
if (!familyExists)
{
auto&& nearbyCollection = NearbyCollection();

// May be null on OS below Windows 10. If null, just skip the attempt.
if (nearbyCollection)
{
nearbyCollection.As(&fontCollection);
THROW_IF_FAILED(fontCollection->FindFamilyName(familyName.data(), &familyIndex, &familyExists));
}
}

if (familyExists)
{
Microsoft::WRL::ComPtr<IDWriteFontFamily> fontFamily;
Expand Down Expand Up @@ -753,3 +858,37 @@ CATCH_RETURN()

return _userLocaleName;
}

// Routine Description:
// - Digs through the directory that the current executable is running within to find
// any TTF files sitting next to it.
// Arguments:
// - <none>
// Return Value:
// - Iterable collection of filesystem paths, one per font file that was found
[[nodiscard]] std::vector<std::filesystem::path> DxFontRenderData::s_GetNearbyFonts()
{
std::vector<std::filesystem::path> paths;

// Find the directory we're running from then enumerate all the TTF files
// sitting next to us.
const std::filesystem::path module{ wil::GetModuleFileNameW<std::wstring>(nullptr) };
const auto folder{ module.parent_path() };

for (auto& p : std::filesystem::directory_iterator(folder))
{
if (p.is_regular_file())
{
auto extension = p.path().extension().wstring();
std::transform(extension.begin(), extension.end(), extension.begin(), std::towlower);

static constexpr std::wstring_view ttfExtension{ L".ttf" };
if (ttfExtension == extension)
{
paths.push_back(p);
}
}
}

return paths;
}
8 changes: 7 additions & 1 deletion src/renderer/dx/DxFontRenderData.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ namespace Microsoft::Console::Render

[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteFontFallback> SystemFontFallback();

[[nodiscard]] const Microsoft::WRL::ComPtr<IDWriteFontCollection1>& NearbyCollection() const;

[[nodiscard]] til::size GlyphCell() noexcept;
[[nodiscard]] LineMetrics GetLineMetrics() noexcept;

Expand Down Expand Up @@ -62,7 +64,8 @@ namespace Microsoft::Console::Render
DWRITE_FONT_WEIGHT& weight,
DWRITE_FONT_STRETCH& stretch,
DWRITE_FONT_STYLE& style,
std::wstring& localeName) const;
std::wstring& localeName,
bool& didFallback) const;

[[nodiscard]] ::Microsoft::WRL::ComPtr<IDWriteFontFace1> _FindFontFace(std::wstring& familyName,
DWRITE_FONT_WEIGHT& weight,
Expand All @@ -76,6 +79,8 @@ namespace Microsoft::Console::Render
// A locale that can be used on construction of assorted DX objects that want to know one.
[[nodiscard]] std::wstring _GetUserLocaleName();

[[nodiscard]] static std::vector<std::filesystem::path> s_GetNearbyFonts();

::Microsoft::WRL::ComPtr<IDWriteFactory1> _dwriteFactory;

::Microsoft::WRL::ComPtr<IDWriteTextAnalyzer1> _dwriteTextAnalyzer;
Expand All @@ -87,6 +92,7 @@ namespace Microsoft::Console::Render
::Microsoft::WRL::ComPtr<IBoxDrawingEffect> _boxDrawingEffect;

::Microsoft::WRL::ComPtr<IDWriteFontFallback> _systemFontFallback;
mutable ::Microsoft::WRL::ComPtr<IDWriteFontCollection1> _nearbyCollection;
miniksa marked this conversation as resolved.
Show resolved Hide resolved
std::wstring _userLocaleName;

til::size _glyphCell;
Expand Down
4 changes: 4 additions & 0 deletions src/renderer/inc/FontInfo.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ class FontInfo : public FontInfoBase
const COORD coordSize,
const COORD coordSizeUnscaled);

bool GetFallback() const noexcept;
void SetFallback(const bool didFallback) noexcept;

void ValidateFont();

friend bool operator==(const FontInfo& a, const FontInfo& b);
Expand All @@ -56,6 +59,7 @@ class FontInfo : public FontInfoBase

COORD _coordSize;
COORD _coordSizeUnscaled;
bool _didFallback;
};

bool operator==(const FontInfo& a, const FontInfo& b);
Expand Down