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:
Michael Niksa 2019-07-30 14:32:23 -07:00 committed by GitHub
parent 3f62c8b470
commit 56589c0aac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 177 additions and 30 deletions

View file

@ -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. |

View file

@ -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,

View file

@ -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,