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
155 changes: 142 additions & 13 deletions src/renderer/dx/DxFontRenderData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,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
[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteFontCollection1> DxFontRenderData::NearbyCollection() const
miniksa marked this conversation as resolved.
Show resolved Hide resolved
{
// 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();

if (!_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));

// 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();
miniksa marked this conversation as resolved.
Show resolved Hide resolved

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 +139,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 +268,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 +314,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 +627,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#xxxx
miniksa marked this conversation as resolved.
Show resolved Hide resolved
// 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(L'\x20');
miniksa marked this conversation as resolved.
Show resolved Hide resolved

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 +730,13 @@ 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)
{
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 +848,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();
miniksa marked this conversation as resolved.
Show resolved Hide resolved

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);

const std::wstring_view ttfExtension{ L".ttf" };
miniksa marked this conversation as resolved.
Show resolved Hide resolved
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]] 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