Reduce instances of font fallback dialog (#9734)
Reduce instances of font fallback dialog through package font loading, basic name trimming, and revised fallback test - Adjusts the font dialog to only show when we attempt last-chance resolution from our hardcoded list of font names with a flag instead of with a string comparison by name - Adds a resolution step to trim the font name by word from the end and retry to attempt to resolve a proper font that just has a weight suffix - Adds a second font collection to font loading that will attempt to locate all TTF files sitting next to our binary, like in our package - [x] Wrote my font preference in the JSON as `Cascadia Code Heavy` and watched it quietly resolve to just `Cascadia Code` without the dialog. - [x] Put a font that isn't registered with the system into the layout directory for the package, set it as my desired font in Terminal, and watched it load just fine. - [x] Try a font name with different casing and see if dialog doesn't pop anymore - [x] Try a font with different (localized) names like MS ゴシック and see if dialog doesn't pop anymore - [x] Check Win7 with WPF target Closes #9375
This commit is contained in:
parent
ed1cd32f1f
commit
7f5a19b627
|
@ -2116,8 +2116,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.
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
@ -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();
|
||||
|
|
|
@ -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";
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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(),
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
face = _FindFontFace(familyName, weight, stretch, style, localeName);
|
||||
// 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);
|
||||
|
||||
if (face)
|
||||
// Try to find it with the shortened family name
|
||||
face = _FindFontFace(familyName, weight, stretch, style, localeName);
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -87,6 +92,7 @@ namespace Microsoft::Console::Render
|
|||
::Microsoft::WRL::ComPtr<IBoxDrawingEffect> _boxDrawingEffect;
|
||||
|
||||
::Microsoft::WRL::ComPtr<IDWriteFontFallback> _systemFontFallback;
|
||||
mutable ::Microsoft::WRL::ComPtr<IDWriteFontCollection1> _nearbyCollection;
|
||||
std::wstring _userLocaleName;
|
||||
|
||||
til::size _glyphCell;
|
||||
|
|
|
@ -47,6 +47,9 @@ public:
|
|||
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);
|
||||
|
@ -56,6 +59,7 @@ private:
|
|||
|
||||
COORD _coordSize;
|
||||
COORD _coordSizeUnscaled;
|
||||
bool _didFallback;
|
||||
};
|
||||
|
||||
bool operator==(const FontInfo& a, const FontInfo& b);
|
||||
|
|
Loading…
Reference in a new issue