Fixes crash when specifying invalid font (#2153)
* Stop the crash with fonts by trying a few fallback/backup fonts if we can't find what was selected. * Create fallback pattern for finding a font. Resolve and pass the locale name. Retrieve the font name while retrieving the font object. Use retrieved data in the _GetProposedFont methods instead of re-resolving it. * Add details to schema about fallback. Finish comment explaining fallback pattern to doc comment on method.
This commit is contained in:
parent
3f62c8b470
commit
56589c0aac
|
@ -25,7 +25,7 @@ Properties listed below are specific to each unique profile.
|
|||
| `commandline` | _Required_ | String | `powershell.exe` | Executable used in the profile. |
|
||||
| `cursorColor` | _Required_ | String | `#FFFFFF` | Sets the cursor color for the profile. Uses hex color format: `"#rrggbb"`. |
|
||||
| `cursorShape` | _Required_ | String | `bar` | Sets the cursor shape for the profile. Possible values: `"vintage"` ( ▃ ), `"bar"` ( ┃ ), `"underscore"` ( ▁ ), `"filledBox"` ( █ ), `"emptyBox"` ( ▯ ) |
|
||||
| `fontFace` | _Required_ | String | `Consolas` | Name of the font face used in the profile. |
|
||||
| `fontFace` | _Required_ | String | `Consolas` | Name of the font face used in the profile. We will try to fallback to Consolas if this can't be found or is invalid. |
|
||||
| `fontSize` | _Required_ | Integer | `10` | Sets the font size. |
|
||||
| `guid` | _Required_ | String | | Unique identifier of the profile. Written in registry format: `"{00000000-0000-0000-0000-000000000000}"`. |
|
||||
| `historySize` | _Required_ | Integer | `9001` | The number of lines above the ones displayed in the window you can scroll back to. |
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
#pragma hdrstop
|
||||
|
||||
static constexpr float POINTS_PER_INCH = 72.0f;
|
||||
static std::wstring FALLBACK_FONT_FACE = L"Consolas";
|
||||
static constexpr std::wstring_view FALLBACK_LOCALE = L"en-us";
|
||||
|
||||
using namespace Microsoft::Console::Render;
|
||||
using namespace Microsoft::Console::Types;
|
||||
|
@ -133,14 +135,14 @@ DxEngine::~DxEngine()
|
|||
|
||||
const DWORD DeviceFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT |
|
||||
// clang-format off
|
||||
// This causes problems for folks who do not have the whole DirectX SDK installed
|
||||
// when they try to run the rest of the project in debug mode.
|
||||
// As such, I'm leaving this flag here for people doing DX-specific work to toggle it
|
||||
// only when they need it and shutting it off otherwise.
|
||||
// Find out more about the debug layer here:
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/direct3d11/overviews-direct3d-11-devices-layers
|
||||
// You can find out how to install it here:
|
||||
// https://docs.microsoft.com/en-us/windows/uwp/gaming/use-the-directx-runtime-and-visual-studio-graphics-diagnostic-features
|
||||
// This causes problems for folks who do not have the whole DirectX SDK installed
|
||||
// when they try to run the rest of the project in debug mode.
|
||||
// As such, I'm leaving this flag here for people doing DX-specific work to toggle it
|
||||
// only when they need it and shutting it off otherwise.
|
||||
// Find out more about the debug layer here:
|
||||
// https://docs.microsoft.com/en-us/windows/desktop/direct3d11/overviews-direct3d-11-devices-layers
|
||||
// You can find out how to install it here:
|
||||
// https://docs.microsoft.com/en-us/windows/uwp/gaming/use-the-directx-runtime-and-visual-studio-graphics-diagnostic-features
|
||||
// clang-format on
|
||||
// D3D11_CREATE_DEVICE_DEBUG |
|
||||
D3D11_CREATE_DEVICE_SINGLETHREADED;
|
||||
|
@ -1354,6 +1356,46 @@ float DxEngine::GetScaling() const noexcept
|
|||
return PostMessageW(_hwndTarget, CM_UPDATE_TITLE, 0, (LPARAM) nullptr) ? S_OK : E_FAIL;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Attempts to locate the font given, but then begins falling back if we cannot find it.
|
||||
// - We'll try to fall back to Consolas with the given weight/stretch/style first,
|
||||
// then try Consolas again with normal weight/stretch/style,
|
||||
// and if nothing works, then we'll throw an error.
|
||||
// Arguments:
|
||||
// - familyName - The font name we should be looking for
|
||||
// - weight - The weight (bold, light, etc.)
|
||||
// - stretch - The stretch of the font is the spacing between each letter
|
||||
// - style - Normal, italic, etc.
|
||||
// Return Value:
|
||||
// - Smart pointer holding interface reference for queryable font data.
|
||||
[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteFontFace1> DxEngine::_ResolveFontFaceWithFallback(std::wstring& familyName,
|
||||
DWRITE_FONT_WEIGHT& weight,
|
||||
DWRITE_FONT_STRETCH& stretch,
|
||||
DWRITE_FONT_STYLE& style,
|
||||
std::wstring& localeName) const
|
||||
{
|
||||
auto face = _FindFontFace(familyName, weight, stretch, style, localeName);
|
||||
|
||||
if (!face)
|
||||
{
|
||||
familyName = FALLBACK_FONT_FACE;
|
||||
face = _FindFontFace(familyName, weight, stretch, style, localeName);
|
||||
}
|
||||
|
||||
if (!face)
|
||||
{
|
||||
familyName = FALLBACK_FONT_FACE;
|
||||
weight = DWRITE_FONT_WEIGHT_NORMAL;
|
||||
stretch = DWRITE_FONT_STRETCH_NORMAL;
|
||||
style = DWRITE_FONT_STYLE_NORMAL;
|
||||
face = _FindFontFace(familyName, weight, stretch, style, localeName);
|
||||
}
|
||||
|
||||
THROW_IF_NULL_ALLOC(face);
|
||||
|
||||
return face;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Locates a suitable font face from the given information
|
||||
// Arguments:
|
||||
|
@ -1363,10 +1405,11 @@ float DxEngine::GetScaling() const noexcept
|
|||
// - style - Normal, italic, etc.
|
||||
// Return Value:
|
||||
// - Smart pointer holding interface reference for queryable font data.
|
||||
[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteFontFace1> DxEngine::_FindFontFace(const std::wstring& familyName,
|
||||
DWRITE_FONT_WEIGHT weight,
|
||||
DWRITE_FONT_STRETCH stretch,
|
||||
DWRITE_FONT_STYLE style) const
|
||||
[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteFontFace1> DxEngine::_FindFontFace(std::wstring& familyName,
|
||||
DWRITE_FONT_WEIGHT& weight,
|
||||
DWRITE_FONT_STRETCH& stretch,
|
||||
DWRITE_FONT_STYLE& style,
|
||||
std::wstring& localeName) const
|
||||
{
|
||||
Microsoft::WRL::ComPtr<IDWriteFontFace1> fontFace;
|
||||
|
||||
|
@ -1375,7 +1418,7 @@ float DxEngine::GetScaling() const noexcept
|
|||
|
||||
UINT32 familyIndex;
|
||||
BOOL familyExists;
|
||||
THROW_IF_FAILED(fontCollection->FindFamilyName(familyName.c_str(), &familyIndex, &familyExists));
|
||||
THROW_IF_FAILED(fontCollection->FindFamilyName(familyName.data(), &familyIndex, &familyExists));
|
||||
|
||||
if (familyExists)
|
||||
{
|
||||
|
@ -1389,11 +1432,107 @@ float DxEngine::GetScaling() const noexcept
|
|||
THROW_IF_FAILED(font->CreateFontFace(&fontFace0));
|
||||
|
||||
THROW_IF_FAILED(fontFace0.As(&fontFace));
|
||||
|
||||
// Dig the family name out at the end to return it.
|
||||
familyName = _GetFontFamilyName(fontFamily.Get(), localeName);
|
||||
}
|
||||
|
||||
return fontFace;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Helper to retrieve the user's locale preference or fallback to the default.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - A locale that can be used on construction of assorted DX objects that want to know one.
|
||||
[[nodiscard]] std::wstring DxEngine::_GetLocaleName() const
|
||||
{
|
||||
std::array<wchar_t, LOCALE_NAME_MAX_LENGTH> localeName;
|
||||
|
||||
const auto returnCode = GetUserDefaultLocaleName(localeName.data(), gsl::narrow<int>(localeName.size()));
|
||||
if (returnCode)
|
||||
{
|
||||
return { localeName.data() };
|
||||
}
|
||||
else
|
||||
{
|
||||
return { FALLBACK_LOCALE.data(), FALLBACK_LOCALE.size() };
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Retrieves the font family name out of the given object in the given locale.
|
||||
// - If we can't find a valid name for the given locale, we'll fallback and report it back.
|
||||
// Arguments:
|
||||
// - fontFamily - DirectWrite font family object
|
||||
// - localeName - The locale in which the name should be retrieved.
|
||||
// - If fallback occurred, this is updated to what we retrieved instead.
|
||||
// Return Value:
|
||||
// - Localized string name of the font family
|
||||
[[nodiscard]] std::wstring DxEngine::_GetFontFamilyName(IDWriteFontFamily* const fontFamily,
|
||||
std::wstring& localeName) const
|
||||
{
|
||||
// See: https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontcollection
|
||||
Microsoft::WRL::ComPtr<IDWriteLocalizedStrings> familyNames;
|
||||
THROW_IF_FAILED(fontFamily->GetFamilyNames(&familyNames));
|
||||
|
||||
// First we have to find the right family name for the locale. We're going to bias toward what the caller
|
||||
// requested, but fallback if we need to and reply with the locale we ended up choosing.
|
||||
UINT32 index = 0;
|
||||
BOOL exists = false;
|
||||
|
||||
// This returns S_OK whether or not it finds a locale name. Check exists field instead.
|
||||
// If it returns an error, it's a real problem, not an absence of this locale name.
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-findlocalename
|
||||
THROW_IF_FAILED(familyNames->FindLocaleName(localeName.data(), &index, &exists));
|
||||
|
||||
// If we tried and it still doesn't exist, try with the fallback locale.
|
||||
if (!exists)
|
||||
{
|
||||
localeName = FALLBACK_LOCALE;
|
||||
THROW_IF_FAILED(familyNames->FindLocaleName(localeName.data(), &index, &exists));
|
||||
}
|
||||
|
||||
// If it still doesn't exist, we're going to try index 0.
|
||||
if (!exists)
|
||||
{
|
||||
index = 0;
|
||||
|
||||
// Get the locale name out so at least the caller knows what locale this name goes with.
|
||||
UINT32 length = 0;
|
||||
THROW_IF_FAILED(familyNames->GetLocaleNameLength(index, &length));
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-getlocalenamelength
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-getlocalename
|
||||
// GetLocaleNameLength does not include space for null terminator, but GetLocaleName needs it so add one.
|
||||
length++;
|
||||
|
||||
localeName.resize(length);
|
||||
|
||||
THROW_IF_FAILED(familyNames->GetLocaleName(index, localeName.data(), length));
|
||||
}
|
||||
|
||||
// OK, now that we've decided which family name and the locale that it's in... let's go get it.
|
||||
UINT32 length = 0;
|
||||
THROW_IF_FAILED(familyNames->GetStringLength(index, &length));
|
||||
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-getstringlength
|
||||
// https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritelocalizedstrings-getstring
|
||||
// Once again, GetStringLength is without the null, but GetString needs the null. So add one.
|
||||
length++;
|
||||
|
||||
// Make our output buffer and resize it so it is allocated.
|
||||
std::wstring retVal;
|
||||
retVal.resize(length);
|
||||
|
||||
// FINALLY, go fetch the string name.
|
||||
THROW_IF_FAILED(familyNames->GetString(index, retVal.data(), length));
|
||||
|
||||
// and return it.
|
||||
return retVal;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Updates the font used for drawing
|
||||
// Arguments:
|
||||
|
@ -1411,13 +1550,13 @@ float DxEngine::GetScaling() const noexcept
|
|||
{
|
||||
try
|
||||
{
|
||||
const std::wstring fontName(desired.GetFaceName());
|
||||
const DWRITE_FONT_WEIGHT weight = DWRITE_FONT_WEIGHT_NORMAL;
|
||||
const DWRITE_FONT_STYLE style = DWRITE_FONT_STYLE_NORMAL;
|
||||
const DWRITE_FONT_STRETCH stretch = DWRITE_FONT_STRETCH_NORMAL;
|
||||
std::wstring fontName(desired.GetFaceName());
|
||||
DWRITE_FONT_WEIGHT weight = DWRITE_FONT_WEIGHT_NORMAL;
|
||||
DWRITE_FONT_STYLE style = DWRITE_FONT_STYLE_NORMAL;
|
||||
DWRITE_FONT_STRETCH stretch = DWRITE_FONT_STRETCH_NORMAL;
|
||||
std::wstring localeName = _GetLocaleName();
|
||||
|
||||
const auto face = _FindFontFace(fontName, weight, stretch, style);
|
||||
THROW_IF_NULL_ALLOC_MSG(face, "Failed to find the requested font");
|
||||
const auto face = _ResolveFontFaceWithFallback(fontName, weight, stretch, style, localeName);
|
||||
|
||||
DWRITE_FONT_METRICS1 fontMetrics;
|
||||
face->GetMetrics(&fontMetrics);
|
||||
|
@ -1508,7 +1647,7 @@ float DxEngine::GetScaling() const noexcept
|
|||
style,
|
||||
stretch,
|
||||
fontSize,
|
||||
L"",
|
||||
localeName.data(),
|
||||
&format));
|
||||
|
||||
THROW_IF_FAILED(format.As(&textFormat));
|
||||
|
@ -1529,10 +1668,6 @@ float DxEngine::GetScaling() const noexcept
|
|||
coordSize.X = gsl::narrow<SHORT>(widthExact);
|
||||
coordSize.Y = gsl::narrow<SHORT>(lineSpacing.height);
|
||||
|
||||
const auto familyNameLength = textFormat->GetFontFamilyNameLength() + 1; // 1 for space for null
|
||||
const auto familyNameBuffer = std::make_unique<wchar_t[]>(familyNameLength);
|
||||
THROW_IF_FAILED(textFormat->GetFontFamilyName(familyNameBuffer.get(), familyNameLength));
|
||||
|
||||
const DWORD weightDword = static_cast<DWORD>(textFormat->GetFontWeight());
|
||||
|
||||
// Unscaled is for the purposes of re-communicating this font back to the renderer again later.
|
||||
|
@ -1542,7 +1677,7 @@ float DxEngine::GetScaling() const noexcept
|
|||
|
||||
COORD scaled = coordSize;
|
||||
|
||||
actual.SetFromEngine(familyNameBuffer.get(),
|
||||
actual.SetFromEngine(fontName.data(),
|
||||
desired.GetFamily(),
|
||||
weightDword,
|
||||
false,
|
||||
|
|
|
@ -178,10 +178,22 @@ namespace Microsoft::Console::Render
|
|||
|
||||
[[nodiscard]] HRESULT _EnableDisplayAccess(const bool outputEnabled) noexcept;
|
||||
|
||||
[[nodiscard]] ::Microsoft::WRL::ComPtr<IDWriteFontFace1> _FindFontFace(const std::wstring& familyName,
|
||||
DWRITE_FONT_WEIGHT weight,
|
||||
DWRITE_FONT_STRETCH stretch,
|
||||
DWRITE_FONT_STYLE style) const;
|
||||
[[nodiscard]] ::Microsoft::WRL::ComPtr<IDWriteFontFace1> _ResolveFontFaceWithFallback(std::wstring& familyName,
|
||||
DWRITE_FONT_WEIGHT& weight,
|
||||
DWRITE_FONT_STRETCH& stretch,
|
||||
DWRITE_FONT_STYLE& style,
|
||||
std::wstring& localeName) const;
|
||||
|
||||
[[nodiscard]] ::Microsoft::WRL::ComPtr<IDWriteFontFace1> _FindFontFace(std::wstring& familyName,
|
||||
DWRITE_FONT_WEIGHT& weight,
|
||||
DWRITE_FONT_STRETCH& stretch,
|
||||
DWRITE_FONT_STYLE& style,
|
||||
std::wstring& localeName) const;
|
||||
|
||||
[[nodiscard]] std::wstring _GetLocaleName() const;
|
||||
|
||||
[[nodiscard]] std::wstring _GetFontFamilyName(IDWriteFontFamily* const fontFamily,
|
||||
std::wstring& localeName) const;
|
||||
|
||||
[[nodiscard]] HRESULT _GetProposedFont(const FontInfoDesired& desired,
|
||||
FontInfo& actual,
|
||||
|
|
Loading…
Reference in a new issue