diff --git a/src/cascadia/TerminalSettingsEditor/Profiles.cpp b/src/cascadia/TerminalSettingsEditor/Profiles.cpp index dc3773842..b4fceee45 100644 --- a/src/cascadia/TerminalSettingsEditor/Profiles.cpp +++ b/src/cascadia/TerminalSettingsEditor/Profiles.cpp @@ -171,7 +171,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation THROW_IF_FAILED(fontFamily->GetFamilyNames(localizedFamilyNames.put())); // construct a font entry for tracking - if (const auto fontEntry{ _GetFont(localizedFamilyNames) }) + if (auto fontEntry{ _GetFont(localizedFamilyNames) }) { // check if the font is monospaced try diff --git a/src/renderer/base/fontinfo.cpp b/src/renderer/base/fontinfo.cpp index c73326c08..93ca11c77 100644 --- a/src/renderer/base/fontinfo.cpp +++ b/src/renderer/base/fontinfo.cpp @@ -13,8 +13,7 @@ FontInfo::FontInfo(const std::wstring_view& faceName, const bool fSetDefaultRasterFont /* = false */) noexcept : FontInfoBase(faceName, family, weight, fSetDefaultRasterFont, codePage), _coordSize(coordSize), - _coordSizeUnscaled(coordSize), - _didFallback(false) + _coordSizeUnscaled(coordSize) { ValidateFont(); } @@ -52,16 +51,6 @@ 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() noexcept { _ValidateCoordSize(); diff --git a/src/renderer/dx/CustomTextLayout.cpp b/src/renderer/dx/CustomTextLayout.cpp index a0c0804c4..f617109ff 100644 --- a/src/renderer/dx/CustomTextLayout.cpp +++ b/src/renderer/dx/CustomTextLayout.cpp @@ -20,8 +20,8 @@ using namespace Microsoft::Console::Render; // - dxFontRenderData - The DirectWrite font render data for our layout CustomTextLayout::CustomTextLayout(gsl::not_null const fontRenderData) : _fontRenderData{ fontRenderData }, - _formatInUse{ fontRenderData->DefaultTextFormat().Get() }, - _fontInUse{ fontRenderData->DefaultFontFace().Get() }, + _formatInUse{ fontRenderData->DefaultTextFormat() }, + _fontInUse{ fontRenderData->DefaultFontFace() }, _numberSubstitution{}, _readingDirection{ DWRITE_READING_DIRECTION_LEFT_TO_RIGHT }, _runs{}, @@ -100,8 +100,8 @@ CATCH_RETURN() RETURN_HR_IF_NULL(E_INVALIDARG, columns); *columns = 0; - _formatInUse = _fontRenderData->DefaultTextFormat().Get(); - _fontInUse = _fontRenderData->DefaultFontFace().Get(); + _formatInUse = _fontRenderData->DefaultTextFormat(); + _fontInUse = _fontRenderData->DefaultFontFace(); RETURN_IF_FAILED(_AnalyzeTextComplexity()); RETURN_IF_FAILED(_AnalyzeRuns()); @@ -139,28 +139,19 @@ try DWRITE_FONT_WEIGHT weight = _fontRenderData->DefaultFontWeight(); DWRITE_FONT_STYLE style = _fontRenderData->DefaultFontStyle(); - const DWRITE_FONT_STRETCH stretch = _fontRenderData->DefaultFontStretch(); if (drawingContext->useBoldFont) { // TODO: "relative" bold? weight = DWRITE_FONT_WEIGHT_BOLD; - // Since we are setting the font weight according to the text attribute, - // make sure to tell the text format to ignore the user set font weight - _fontRenderData->InhibitUserWeight(true); } - else - { - _fontRenderData->InhibitUserWeight(false); - } - - if (drawingContext->useItalicFont || _fontRenderData->DidUserSetItalic()) + if (drawingContext->useItalicFont) { style = DWRITE_FONT_STYLE_ITALIC; } - _formatInUse = _fontRenderData->TextFormatWithAttribute(weight, style, stretch).Get(); - _fontInUse = _fontRenderData->FontFaceWithAttribute(weight, style, stretch).Get(); + _formatInUse = _fontRenderData->TextFormatWithAttribute(weight, style); + _fontInUse = _fontRenderData->FontFaceWithAttribute(weight, style); RETURN_IF_FAILED(_AnalyzeTextComplexity()); RETURN_IF_FAILED(_AnalyzeRuns()); @@ -403,12 +394,25 @@ CATCH_RETURN() std::vector textProps(textLength); std::vector glyphProps(maxGlyphCount); - // Get the features to apply to the font - const auto& features = _fontRenderData->DefaultFontFeatures(); -#pragma warning(suppress : 26492) // Don't use const_cast to cast away const or volatile (type.3). - DWRITE_TYPOGRAPHIC_FEATURES typographicFeatures = { const_cast(features.data()), gsl::narrow(features.size()) }; - DWRITE_TYPOGRAPHIC_FEATURES const* typographicFeaturesPointer = &typographicFeatures; - const uint32_t fontFeatureLengths[] = { textLength }; +#pragma warning(push) +#pragma warning(disable : 26494) // Variable '...' is uninitialized. Always initialize an object (type.5). + // None of these variables need to be initialized. + // features/featureRangeLengths are marked _In_reads_opt_(featureRanges). + // featureRanges is only > 0 when we also initialize all these variables. + DWRITE_TYPOGRAPHIC_FEATURES typographicFeatures; + const DWRITE_TYPOGRAPHIC_FEATURES* typographicFeaturesPointer; + UINT32 fontFeatureLengths; +#pragma warning(pop) + UINT32 featureRanges = 0; + + if (const auto& features = _fontRenderData->DefaultFontFeatures(); !features.empty()) + { + typographicFeatures.features = const_cast(features.data()); + typographicFeatures.featureCount = gsl::narrow_cast(features.size()); + typographicFeaturesPointer = &typographicFeatures; + fontFeatureLengths = textLength; + featureRanges = 1; + } // Get the glyphs from the text, retrying if needed. @@ -428,8 +432,8 @@ CATCH_RETURN() _localeName.data(), (run.isNumberSubstituted) ? _numberSubstitution.Get() : nullptr, &typographicFeaturesPointer, // features - &fontFeatureLengths[0], // featureLengths - 1, // featureCount + &fontFeatureLengths, + featureRanges, maxGlyphCount, // maxGlyphCount &_glyphClusters.at(textStart), &textProps.at(0), @@ -478,8 +482,8 @@ CATCH_RETURN() &run.script, _localeName.data(), &typographicFeaturesPointer, // features - &fontFeatureLengths[0], // featureLengths - 1, // featureCount + &fontFeatureLengths, + featureRanges, &_glyphAdvances.at(glyphStart), &_glyphOffsets.at(glyphStart)); @@ -1286,7 +1290,7 @@ CATCH_RETURN(); // newer MapCharacters to apply axes of variation to the font if (!FAILED(_formatInUse->QueryInterface(IID_PPV_ARGS(&format3))) && !FAILED(fallback->QueryInterface(IID_PPV_ARGS(&fallback1)))) { - const auto axesVector = _fontRenderData->GetAxisVector(weight, stretch, style, format3.Get()); + const auto& axesVector = _fontRenderData->GetAxisVector(weight, style); // Walk through and analyze the entire string while (textLength > 0) { @@ -1513,7 +1517,7 @@ try { auto& run = _FetchNextRun(textLength); - if (run.fontFace == _fontRenderData->DefaultFontFace()) + if (run.fontFace.Get() == _fontRenderData->DefaultFontFace()) { run.drawingEffect = _fontRenderData->DefaultBoxDrawingEffect(); } diff --git a/src/renderer/dx/DxFontInfo.cpp b/src/renderer/dx/DxFontInfo.cpp deleted file mode 100644 index e5c6749f4..000000000 --- a/src/renderer/dx/DxFontInfo.cpp +++ /dev/null @@ -1,407 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "precomp.h" - -#include "DxFontInfo.h" - -#include "unicode.hpp" - -#include - -static constexpr std::wstring_view FALLBACK_FONT_FACES[] = { L"Consolas", L"Lucida Console", L"Courier New" }; - -using namespace Microsoft::Console::Render; - -DxFontInfo::DxFontInfo() noexcept : - _familyName(), - _weight(DWRITE_FONT_WEIGHT_NORMAL), - _style(DWRITE_FONT_STYLE_NORMAL), - _stretch(DWRITE_FONT_STRETCH_NORMAL), - _didFallback(false) -{ -} - -DxFontInfo::DxFontInfo(std::wstring_view familyName, - unsigned int weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch) : - DxFontInfo(familyName, static_cast(weight), style, stretch) -{ -} - -DxFontInfo::DxFontInfo(std::wstring_view familyName, - DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch) : - _familyName(familyName), - _weight(weight), - _style(style), - _stretch(stretch), - _didFallback(false) -{ -} - -bool DxFontInfo::operator==(const DxFontInfo& other) const noexcept -{ - return (_familyName == other._familyName && - _weight == other._weight && - _style == other._style && - _stretch == other._stretch && - _didFallback == other._didFallback); -} - -std::wstring_view DxFontInfo::GetFamilyName() const noexcept -{ - return _familyName; -} - -void DxFontInfo::SetFamilyName(const std::wstring_view familyName) -{ - _familyName = familyName; -} - -DWRITE_FONT_WEIGHT DxFontInfo::GetWeight() const noexcept -{ - return _weight; -} - -void DxFontInfo::SetWeight(const DWRITE_FONT_WEIGHT weight) noexcept -{ - _weight = weight; -} - -DWRITE_FONT_STYLE DxFontInfo::GetStyle() const noexcept -{ - return _style; -} - -void DxFontInfo::SetStyle(const DWRITE_FONT_STYLE style) noexcept -{ - _style = style; -} - -DWRITE_FONT_STRETCH DxFontInfo::GetStretch() const noexcept -{ - return _stretch; -} - -void DxFontInfo::SetStretch(const DWRITE_FONT_STRETCH stretch) noexcept -{ - _stretch = stretch; -} - -bool DxFontInfo::GetFallback() const noexcept -{ - return _didFallback; -} - -IDWriteFontCollection* DxFontInfo::GetNearbyCollection() const noexcept -{ - return _nearbyCollection.Get(); -} - -void DxFontInfo::SetFromEngine(const std::wstring_view familyName, - const DWRITE_FONT_WEIGHT weight, - const DWRITE_FONT_STYLE style, - const DWRITE_FONT_STRETCH stretch) -{ - _familyName = familyName; - _weight = weight; - _style = style; - _stretch = stretch; -} - -// 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: -// - dwriteFactory - The DWrite factory to use -// - localeName - Locale to search for appropriate fonts -// Return Value: -// - Smart pointer holding interface reference for queryable font data. -[[nodiscard]] Microsoft::WRL::ComPtr DxFontInfo::ResolveFontFaceWithFallback(gsl::not_null dwriteFactory, - std::wstring& localeName) -{ - // First attempt to find exactly what the user asked for. - _didFallback = false; - Microsoft::WRL::ComPtr face{ nullptr }; - - // GH#10211 - wrap this all up in a try/catch. If the nearby fonts are - // corrupted, then we don't want to throw out of this top half of this - // method. We still want to fall back to a font that's reasonable, below. - try - { - face = _FindFontFace(dwriteFactory, localeName, true); - - if (!face) - { - // 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()) - { - const auto lastSpace = _familyName.find_last_of(UNICODE_SPACE); - - // value is unsigned and npos will be greater than size. - // if we didn't find anything to trim, leave. - if (lastSpace >= _familyName.size()) - { - break; - } - - // 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(dwriteFactory, localeName, true); - } - } - } - CATCH_LOG(); - - // 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) - { - _familyName = fallbackFace; - // With these fonts, don't attempt the nearby lookup. We're looking - // for system fonts only. If one of the nearby fonts is causing us - // problems (like in GH#10211), then we don't want to go anywhere - - // near it in this part. - face = _FindFontFace(dwriteFactory, localeName, false); - - if (face) - { - _didFallback = true; - break; - } - - _familyName = fallbackFace; - _weight = DWRITE_FONT_WEIGHT_NORMAL; - _stretch = DWRITE_FONT_STRETCH_NORMAL; - _style = DWRITE_FONT_STYLE_NORMAL; - face = _FindFontFace(dwriteFactory, localeName, false); - - if (face) - { - _didFallback = true; - break; - } - } - } - - THROW_HR_IF_NULL(E_FAIL, face); - - return face; -} - -// Routine Description: -// - Locates a suitable font face from the given information -// Arguments: -// - dwriteFactory - The DWrite factory to use -// - localeName - Locale to search for appropriate fonts -// Return Value: -// - Smart pointer holding interface reference for queryable font data. -[[nodiscard]] Microsoft::WRL::ComPtr DxFontInfo::_FindFontFace(gsl::not_null dwriteFactory, std::wstring& localeName, const bool withNearbyLookup) -{ - Microsoft::WRL::ComPtr fontFace; - - Microsoft::WRL::ComPtr fontCollection; - THROW_IF_FAILED(dwriteFactory->GetSystemFontCollection(&fontCollection, false)); - - UINT32 familyIndex; - 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 (withNearbyLookup && !familyExists) - { - // May be null on OS below Windows 10. If null, just skip the attempt. - if (const auto nearbyCollection = _NearbyCollection(dwriteFactory)) - { - THROW_IF_FAILED(nearbyCollection->FindFamilyName(_familyName.data(), &familyIndex, &familyExists)); - fontCollection = nearbyCollection; - } - } - - if (familyExists) - { - Microsoft::WRL::ComPtr fontFamily; - THROW_IF_FAILED(fontCollection->GetFontFamily(familyIndex, &fontFamily)); - - Microsoft::WRL::ComPtr font; - THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(GetWeight(), GetStretch(), GetStyle(), &font)); - - Microsoft::WRL::ComPtr fontFace0; - THROW_IF_FAILED(font->CreateFontFace(&fontFace0)); - - THROW_IF_FAILED(fontFace0.As(&fontFace)); - - // Retrieve metrics in case the font we created was different than what was requested. - _weight = font->GetWeight(); - _stretch = font->GetStretch(); - _style = font->GetStyle(); - - // Dig the family name out at the end to return it. - _familyName = _GetFontFamilyName(fontFamily.Get(), localeName); - } - - return fontFace; -} - -// 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 DxFontInfo::_GetFontFamilyName(gsl::not_null const fontFamily, - std::wstring& localeName) -{ - // See: https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nn-dwrite-idwritefontcollection - Microsoft::WRL::ComPtr 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 = L"en-us"; - 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)); - localeName.resize(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. - THROW_IF_FAILED(familyNames->GetLocaleName(index, localeName.data(), length + 1)); - } - - // 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)); - - // Make our output buffer and resize it so it is allocated. - std::wstring retVal; - retVal.resize(length); - - // FINALLY, go fetch the string name. - // 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. - THROW_IF_FAILED(familyNames->GetString(index, retVal.data(), length + 1)); - - // and return it. - return retVal; -} - -// 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: -// - dwriteFactory - The DWrite factory to use -// Return Value: -// - DirectWrite font collection. May be null if one cannot be created. -[[nodiscard]] IDWriteFontCollection* DxFontInfo::_NearbyCollection(gsl::not_null dwriteFactory) -{ - if (_nearbyCollection) - { - return _nearbyCollection.Get(); - } - - // The convenience interfaces for loading fonts from files - // are only available on Windows 10+. - ::Microsoft::WRL::ComPtr factory6; - if (FAILED(dwriteFactory->QueryInterface(&factory6))) - { - return nullptr; - } - - ::Microsoft::WRL::ComPtr systemFontCollection; - THROW_IF_FAILED(factory6->GetSystemFontCollection(false, &systemFontCollection, 0)); - - ::Microsoft::WRL::ComPtr systemFontSet; - THROW_IF_FAILED(systemFontCollection->GetFontSet(&systemFontSet)); - - ::Microsoft::WRL::ComPtr fontSetBuilder2; - THROW_IF_FAILED(factory6->CreateFontSetBuilder(&fontSetBuilder2)); - - THROW_IF_FAILED(fontSetBuilder2->AddFontSet(systemFontSet.Get())); - - // 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(); - for (auto& p : knownPaths) - { - fontSetBuilder2->AddFontFile(p.c_str()); - } - - ::Microsoft::WRL::ComPtr fontSet; - THROW_IF_FAILED(fontSetBuilder2->CreateFontSet(&fontSet)); - - ::Microsoft::WRL::ComPtr fontCollection; - THROW_IF_FAILED(factory6->CreateFontCollectionFromFontSet(fontSet.Get(), &fontCollection)); - - _nearbyCollection = fontCollection; - return _nearbyCollection.Get(); -} - -// Routine Description: -// - Digs through the directory that the current executable is running within to find -// any TTF files sitting next to it. -// Arguments: -// - -// Return Value: -// - Iterable collection of filesystem paths, one per font file that was found -[[nodiscard]] std::vector DxFontInfo::s_GetNearbyFonts() -{ - std::vector 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(nullptr) }; - const auto folder{ module.parent_path() }; - - for (const auto& p : std::filesystem::directory_iterator(folder)) - { - if (til::ends_with(p.path().native(), L".ttf")) - { - paths.push_back(p.path()); - } - } - - return paths; -} diff --git a/src/renderer/dx/DxFontInfo.h b/src/renderer/dx/DxFontInfo.h deleted file mode 100644 index 9b336accd..000000000 --- a/src/renderer/dx/DxFontInfo.h +++ /dev/null @@ -1,83 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include -#include -#include -#include - -namespace Microsoft::Console::Render -{ - class DxFontInfo - { - public: - DxFontInfo() noexcept; - - DxFontInfo(std::wstring_view familyName, - unsigned int weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch); - - DxFontInfo(std::wstring_view familyName, - DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch); - - bool operator==(const DxFontInfo& other) const noexcept; - - std::wstring_view GetFamilyName() const noexcept; - void SetFamilyName(const std::wstring_view familyName); - - DWRITE_FONT_WEIGHT GetWeight() const noexcept; - void SetWeight(const DWRITE_FONT_WEIGHT weight) noexcept; - - DWRITE_FONT_STYLE GetStyle() const noexcept; - void SetStyle(const DWRITE_FONT_STYLE style) noexcept; - - DWRITE_FONT_STRETCH GetStretch() const noexcept; - void SetStretch(const DWRITE_FONT_STRETCH stretch) noexcept; - - bool GetFallback() const noexcept; - - IDWriteFontCollection* GetNearbyCollection() const noexcept; - - void SetFromEngine(const std::wstring_view familyName, - const DWRITE_FONT_WEIGHT weight, - const DWRITE_FONT_STYLE style, - const DWRITE_FONT_STRETCH stretch); - - [[nodiscard]] ::Microsoft::WRL::ComPtr ResolveFontFaceWithFallback(gsl::not_null dwriteFactory, - std::wstring& localeName); - - private: - [[nodiscard]] ::Microsoft::WRL::ComPtr _FindFontFace(gsl::not_null dwriteFactory, - std::wstring& localeName, - const bool withNearbyLookup); - - [[nodiscard]] std::wstring _GetFontFamilyName(gsl::not_null const fontFamily, - std::wstring& localeName); - - [[nodiscard]] IDWriteFontCollection* _NearbyCollection(gsl::not_null dwriteFactory); - - [[nodiscard]] static std::vector s_GetNearbyFonts(); - - ::Microsoft::WRL::ComPtr _nearbyCollection; - - // The font name we should be looking for - std::wstring _familyName; - - // The weight (bold, light, etc.) - DWRITE_FONT_WEIGHT _weight; - - // Normal, italic, etc. - DWRITE_FONT_STYLE _style; - - // The stretch of the font is the spacing between each letter - DWRITE_FONT_STRETCH _stretch; - - // Indicates whether we couldn't match the user request and had to choose from a hardcoded default list. - bool _didFallback; - }; -} diff --git a/src/renderer/dx/DxFontRenderData.cpp b/src/renderer/dx/DxFontRenderData.cpp index 96c44ccb6..745b954f9 100644 --- a/src/renderer/dx/DxFontRenderData.cpp +++ b/src/renderer/dx/DxFontRenderData.cpp @@ -10,16 +10,14 @@ #include 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"; -static constexpr size_t TAG_LENGTH = 4; +static constexpr const wchar_t* FALLBACK_FONT_FACES[] = { L"Consolas", L"Lucida Console", L"Courier New" }; +static constexpr wchar_t FALLBACK_LOCALE[]{ L"en-us" }; using namespace Microsoft::Console::Render; DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr dwriteFactory) noexcept : _dwriteFactory(dwriteFactory), _fontSize{}, - _glyphCell{}, _lineMetrics{}, _lineSpacing{} { @@ -49,49 +47,46 @@ DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr dwr return _systemFontFallback; } -[[nodiscard]] std::wstring DxFontRenderData::UserLocaleName() +void DxFontRenderData::_RefreshUserLocaleName() { - if (_userLocaleName.empty()) - { - std::array localeName; + std::array buffer; + const wchar_t* localeName = buffer.data(); + auto length = GetUserDefaultLocaleName(buffer.data(), gsl::narrow_cast(buffer.size())); - const auto returnCode = GetUserDefaultLocaleName(localeName.data(), gsl::narrow(localeName.size())); - if (returnCode) - { - _userLocaleName = { localeName.data() }; - } - else - { - _userLocaleName = { FALLBACK_LOCALE.data(), FALLBACK_LOCALE.size() }; - } + if (length <= 0) + { + localeName = &FALLBACK_LOCALE[0]; + length = sizeof(FALLBACK_LOCALE); } - return _userLocaleName; + // length is including the trailing null byte. + // See GetUserDefaultLocaleName()'s docs. + _userLocaleName = wil::make_process_heap_string_nothrow(localeName, length - 1); } -[[nodiscard]] til::size DxFontRenderData::GlyphCell() noexcept +[[nodiscard]] til::size DxFontRenderData::GlyphCell() const noexcept { return _glyphCell; } -[[nodiscard]] DxFontRenderData::LineMetrics DxFontRenderData::GetLineMetrics() noexcept +[[nodiscard]] DxFontRenderData::LineMetrics DxFontRenderData::GetLineMetrics() const noexcept { return _lineMetrics; } -[[nodiscard]] DWRITE_FONT_WEIGHT DxFontRenderData::DefaultFontWeight() noexcept +[[nodiscard]] DWRITE_FONT_WEIGHT DxFontRenderData::DefaultFontWeight() const noexcept { - return _defaultFontInfo.GetWeight(); + return _userFontWeight; } [[nodiscard]] DWRITE_FONT_STYLE DxFontRenderData::DefaultFontStyle() noexcept { - return _defaultFontInfo.GetStyle(); + return DWRITE_FONT_STYLE_NORMAL; } [[nodiscard]] DWRITE_FONT_STRETCH DxFontRenderData::DefaultFontStretch() noexcept { - return _defaultFontInfo.GetStretch(); + return DWRITE_FONT_STRETCH_NORMAL; } [[nodiscard]] const std::vector& DxFontRenderData::DefaultFontFeatures() const noexcept @@ -99,81 +94,40 @@ DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr dwr return _featureVector; } -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::DefaultTextFormat() +[[nodiscard]] IDWriteTextFormat* DxFontRenderData::DefaultTextFormat() const noexcept { - return TextFormatWithAttribute(_defaultFontInfo.GetWeight(), _defaultFontInfo.GetStyle(), _defaultFontInfo.GetStretch()); + return _textFormats[0][0].Get(); } -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::DefaultFontFace() +[[nodiscard]] IDWriteFontFace1* DxFontRenderData::DefaultFontFace() const noexcept { - return FontFaceWithAttribute(_defaultFontInfo.GetWeight(), _defaultFontInfo.GetStyle(), _defaultFontInfo.GetStretch()); + return _fontFaces[0][0].Get(); } -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::DefaultBoxDrawingEffect() +[[nodiscard]] IBoxDrawingEffect* DxFontRenderData::DefaultBoxDrawingEffect() { if (!_boxDrawingEffect) { // Calculate and cache the box effect for the base font. Scale is 1.0f because the base font is exactly the scale we want already. - THROW_IF_FAILED(s_CalculateBoxEffect(DefaultTextFormat().Get(), _glyphCell.width(), DefaultFontFace().Get(), 1.0f, &_boxDrawingEffect)); + THROW_IF_FAILED(s_CalculateBoxEffect(DefaultTextFormat(), _glyphCell.width(), DefaultFontFace(), 1.0f, &_boxDrawingEffect)); } - return _boxDrawingEffect; + return _boxDrawingEffect.Get(); } -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::TextFormatWithAttribute(DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch) +[[nodiscard]] IDWriteTextFormat* DxFontRenderData::TextFormatWithAttribute(DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style) const noexcept { - const auto textFormatIt = _textFormatMap.find(_ToMapKey(weight, style, stretch)); - if (textFormatIt == _textFormatMap.end()) - { - DxFontInfo fontInfo = _defaultFontInfo; - fontInfo.SetWeight(weight); - fontInfo.SetStyle(style); - fontInfo.SetStretch(stretch); - - // Create the font with the fractional pixel height size. - // It should have an integer pixel width by our math. - // Then below, apply the line spacing to the format to position the floating point pixel height characters - // into a cell that has an integer pixel height leaving some padding above/below as necessary to round them out. - std::wstring localeName = UserLocaleName(); - Microsoft::WRL::ComPtr textFormat; - THROW_IF_FAILED(_BuildTextFormat(fontInfo, localeName).As(&textFormat)); - THROW_IF_FAILED(textFormat->SetLineSpacing(_lineSpacing.method, _lineSpacing.height, _lineSpacing.baseline)); - THROW_IF_FAILED(textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR)); - THROW_IF_FAILED(textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP)); - - _textFormatMap.emplace(_ToMapKey(weight, style, stretch), textFormat); - return textFormat; - } - else - { - return textFormatIt->second; - } + return _textFormats[style != DWRITE_FONT_STYLE_NORMAL][weight != _userFontWeight].Get(); } -[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::FontFaceWithAttribute(DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch) +[[nodiscard]] IDWriteFontFace1* DxFontRenderData::FontFaceWithAttribute(DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style) const noexcept { - const auto fontFaceIt = _fontFaceMap.find(_ToMapKey(weight, style, stretch)); - if (fontFaceIt == _fontFaceMap.end()) - { - DxFontInfo fontInfo = _defaultFontInfo; - fontInfo.SetWeight(weight); - fontInfo.SetStyle(style); - fontInfo.SetStretch(stretch); + return _fontFaces[style != DWRITE_FONT_STYLE_NORMAL][weight != _userFontWeight].Get(); +} - std::wstring fontLocaleName = UserLocaleName(); - Microsoft::WRL::ComPtr fontFace = fontInfo.ResolveFontFaceWithFallback(_dwriteFactory.Get(), fontLocaleName); - - _fontFaceMap.emplace(_ToMapKey(weight, style, stretch), fontFace); - return fontFace; - } - else - { - return fontFaceIt->second; - } +const std::vector& DxFontRenderData::GetAxisVector(DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style) const noexcept +{ + return _textFormatAxes[style != DWRITE_FONT_STYLE_NORMAL][weight != _userFontWeight]; } // Routine Description: @@ -185,29 +139,147 @@ DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr dwr // Return Value: // - S_OK or relevant DirectX error [[nodiscard]] HRESULT DxFontRenderData::UpdateFont(const FontInfoDesired& desired, FontInfo& actual, const int dpi, const std::unordered_map& features, const std::unordered_map& axes) noexcept +try { - try + std::vector fontFeatures; + if (!features.empty()) { - _userLocaleName.clear(); - _textFormatMap.clear(); - _fontFaceMap.clear(); - _boxDrawingEffect.Reset(); + fontFeatures.reserve(features.size() + 3); - // Initialize the default font info and build everything from here. - _defaultFontInfo = DxFontInfo(desired.GetFaceName(), - desired.GetWeight(), - DWRITE_FONT_STYLE_NORMAL, - DWRITE_FONT_STRETCH_NORMAL); + // All of these features are enabled by default by DirectWrite. + // If you want to (and can) peek into the source of DirectWrite + // you can look for the "GenericDefaultGsubFeatures" and "GenericDefaultGposFeatures" arrays. + // Gsub is for GetGlyphs() and Gpos for GetGlyphPlacements(). + // + // GH#10774: Apparently specifying all of the features is just redundant. + fontFeatures.emplace_back(DWRITE_FONT_FEATURE{ DWRITE_FONT_FEATURE_TAG_STANDARD_LIGATURES, 1 }); + fontFeatures.emplace_back(DWRITE_FONT_FEATURE{ DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_LIGATURES, 1 }); + fontFeatures.emplace_back(DWRITE_FONT_FEATURE{ DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_ALTERNATES, 1 }); - _SetFeatures(features); - _SetAxes(axes); - - _BuildFontRenderData(desired, actual, dpi); + for (const auto& p : features) + { + if (p.first.size() == 4) + { + const auto s = p.first.data(); + switch (const auto tag = DWRITE_MAKE_FONT_FEATURE_TAG(s[0], s[1], s[2], s[3])) + { + case DWRITE_FONT_FEATURE_TAG_STANDARD_LIGATURES: + fontFeatures[0].parameter = p.second; + break; + case DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_LIGATURES: + fontFeatures[1].parameter = p.second; + break; + case DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_ALTERNATES: + fontFeatures[2].parameter = p.second; + break; + default: + fontFeatures.emplace_back(DWRITE_FONT_FEATURE{ tag, p.second }); + break; + } + } + } + } + + std::vector fontAxisValues; + if (!axes.empty()) + { + fontAxisValues.reserve(axes.size() + 3); + + // Just a few lines below we configure IDWriteTextFormat3 instances with these font axes. + // We need at least all 3 of these axes below, in order to ensure that if we get a request for an italic + // text format, we can supply an italic one, no matter what font axes the user set (or didn't set). + fontAxisValues.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_WEIGHT, -1.0f }); + fontAxisValues.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_ITALIC, -1.0f }); + fontAxisValues.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_SLANT, -1.0f }); + + for (const auto& p : axes) + { + if (p.first.size() == 4) + { + const auto s = p.first.data(); + switch (const auto tag = DWRITE_MAKE_FONT_AXIS_TAG(s[0], s[1], s[2], s[3])) + { + case DWRITE_FONT_AXIS_TAG_WEIGHT: + fontAxisValues[0].value = p.second; + break; + case DWRITE_FONT_AXIS_TAG_ITALIC: + fontAxisValues[1].value = p.second; + break; + case DWRITE_FONT_AXIS_TAG_SLANT: + fontAxisValues[2].value = p.second; + break; + default: + fontAxisValues.emplace_back(DWRITE_FONT_AXIS_VALUE{ tag, p.second }); + break; + } + } + } + } + + _RefreshUserLocaleName(); + _BuildFontRenderData(desired, actual, dpi); + _featureVector = std::move(fontFeatures); + _userFontWeight = static_cast(actual.GetWeight()); + _boxDrawingEffect.Reset(); + + { + // Just a few lines above we hardcode indices 0/1/2 in fontAxisValues to the weight/italic/slant axes. + // If they're -1.0f they haven't been set by the user and must be filled by us. + // When we call SetFontAxisValues() we basically override (disable) DirectWrite's internal font axes, + // and if either of the 3 aren't set we'd make it impossible for the user to see bold/italic text. +#pragma warning(suppress : 26494) // Variable 'standardAxes' is uninitialized. Always initialize an object (type.5). + std::array standardAxes; + + if (!fontAxisValues.empty()) + { + Expects(fontAxisValues.size() >= standardAxes.size()); + memcpy(standardAxes.data(), fontAxisValues.data(), sizeof(standardAxes)); + } + + for (auto italic = 0; italic < 2; ++italic) + { + for (auto bold = 0; bold < 2; ++bold) + { + const auto fontWeight = bold ? DWRITE_FONT_WEIGHT_BOLD : _userFontWeight; + const auto fontStyle = italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; + auto& textFormat = _textFormats[italic][bold]; + + // If the given face name couldn't be found this will call _NearbyCollection at some point. + // Thanks to that we can use _nearbyFontCollection.Get() below to get the font collection for our textFormat as well. + const auto font = _ResolveFontWithFallback(actual.GetFaceName(), fontWeight, fontStyle); + + Microsoft::WRL::ComPtr face; + THROW_IF_FAILED(font->CreateFontFace(&face)); + THROW_IF_FAILED(face.As(&_fontFaces[italic][bold])); + + THROW_IF_FAILED(_dwriteFactory->CreateTextFormat(actual.GetFaceName().c_str(), _nearbyFontCollection.Get(), font->GetWeight(), font->GetStyle(), DWRITE_FONT_STRETCH_NORMAL, _fontSize, _userLocaleName.get(), &textFormat)); + THROW_IF_FAILED(textFormat->SetLineSpacing(_lineSpacing.method, _lineSpacing.height, _lineSpacing.baseline)); + THROW_IF_FAILED(textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR)); + THROW_IF_FAILED(textFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_NO_WRAP)); + + if (!fontAxisValues.empty()) + { + Microsoft::WRL::ComPtr textFormat3; + if (SUCCEEDED(textFormat.As(&textFormat3))) + { + // The wght axis defaults to the font weight. + fontAxisValues[0].value = bold || standardAxes[0].value == -1.0f ? static_cast(fontWeight) : standardAxes[0].value; + // The ital axis defaults to 1 if this is italic and 0 otherwise. + fontAxisValues[1].value = italic ? 1.0f : (standardAxes[1].value == -1.0f ? 0.0f : standardAxes[1].value); + // The slnt axis defaults to -12 if this is italic and 0 otherwise. + fontAxisValues[2].value = italic ? -12.0f : (standardAxes[2].value == -1.0f ? 0.0f : standardAxes[2].value); + + THROW_IF_FAILED(textFormat3->SetFontAxisValues(fontAxisValues.data(), gsl::narrow_cast(fontAxisValues.size()))); + _textFormatAxes[italic][bold] = fontAxisValues; + } + } + } + } } - CATCH_RETURN(); return S_OK; } +CATCH_RETURN() // Routine Description: // - Calculates the box drawing scale/translate matrix values to fit a box glyph into the cell as perfectly as possible. @@ -219,7 +291,7 @@ DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr dwr // - effect - Receives the effect to apply to box drawing characters. If no effect is received, special treatment isn't required. // Return Value: // - S_OK, GSL/WIL errors, DirectWrite errors, or math errors. -[[nodiscard]] HRESULT STDMETHODCALLTYPE DxFontRenderData::s_CalculateBoxEffect(IDWriteTextFormat* format, size_t widthPixels, IDWriteFontFace1* face, float fontScale, IBoxDrawingEffect** effect) noexcept +[[nodiscard]] HRESULT DxFontRenderData::s_CalculateBoxEffect(IDWriteTextFormat* format, size_t widthPixels, IDWriteFontFace1* face, float fontScale, IBoxDrawingEffect** effect) noexcept try { // Check for bad in parameters. @@ -454,247 +526,14 @@ CATCH_RETURN() // - Returns whether the user set or updated any of the font features to be applied bool DxFontRenderData::DidUserSetFeatures() const noexcept { - return _didUserSetFeatures; + return !_featureVector.empty(); } // Routine Description: // - Returns whether the user set or updated any of the font axes to be applied bool DxFontRenderData::DidUserSetAxes() const noexcept { - return _didUserSetAxes; -} - -// Routine Description: -// - Function called to inform us whether to use the user set weight -// in the font axes -// - Called by CustomTextLayout, when the text attribute is bold we should -// ignore the user set weight, otherwise setting the bold font axis -// breaks the bold font attribute -// Arguments: -// - inhibitUserWeight: boolean that tells us if we should use the user set weight -// in the font axes -void DxFontRenderData::InhibitUserWeight(bool inhibitUserWeight) noexcept -{ - _inhibitUserWeight = inhibitUserWeight; -} - -// Routine Description: -// - Returns whether the set italic in the font axes -// Return Value: -// - True if the user set the italic axis to 1, -// false if the italic axis is not present or the italic axis is set to 0 -bool DxFontRenderData::DidUserSetItalic() const noexcept -{ - return _didUserSetItalic; -} - -// Routine Description: -// - Updates our internal map of font features with the given features -// - NOTE TO CALLER: Make sure to call _BuildFontRenderData after calling this for the feature changes -// to take place -// Arguments: -// - features - the features to update our map with -void DxFontRenderData::_SetFeatures(const std::unordered_map& features) -{ - // Populate the feature map with the standard list first - std::unordered_map featureMap{ - { DWRITE_MAKE_FONT_FEATURE_TAG('c', 'a', 'l', 't'), 1 }, // Contextual Alternates - { DWRITE_MAKE_FONT_FEATURE_TAG('l', 'i', 'g', 'a'), 1 }, // Standard Ligatures - { DWRITE_MAKE_FONT_FEATURE_TAG('c', 'l', 'i', 'g'), 1 }, // Contextual Ligatures - { DWRITE_MAKE_FONT_FEATURE_TAG('k', 'e', 'r', 'n'), 1 } // Kerning - }; - - // Update our feature map with the provided features - if (!features.empty()) - { - for (const auto [tag, param] : features) - { - if (tag.length() == TAG_LENGTH) - { - featureMap.insert_or_assign(DWRITE_MAKE_FONT_FEATURE_TAG(til::at(tag, 0), til::at(tag, 1), til::at(tag, 2), til::at(tag, 3)), param); - } - } - _didUserSetFeatures = true; - } - else - { - _didUserSetFeatures = false; - } - - // Convert the data to DWRITE_FONT_FEATURE and store it in a vector for CustomTextLayout - _featureVector.clear(); - for (const auto [tag, param] : featureMap) - { - _featureVector.push_back(DWRITE_FONT_FEATURE{ tag, param }); - } -} - -// Routine Description: -// - Updates our internal map of font axes with the given axes -// - NOTE TO CALLER: Make sure to call _BuildFontRenderData after calling this for the axes changes -// to take place -// Arguments: -// - axes - the axes to update our map with -void DxFontRenderData::_SetAxes(const std::unordered_map& axes) -{ - // Clear out the old vector and booleans in case this is a hot reload - _axesVector = std::vector{}; - _didUserSetAxes = false; - _didUserSetItalic = false; - - // Update our axis map with the provided axes - if (!axes.empty()) - { - // Store the weight aside: we will be creating a span of all the axes in the vector except the weight, - // and then we will add the weight to the vector - // We are doing this so that when the text attribute is bold, we can apply all the axes except the weight - std::optional weightAxis; - - // Since we are calling an 'emplace_back' after creating the span, - // there is a chance a reallocation happens (if the vector needs to grow), which would make the span point to - // deallocated memory. To avoid this, make sure to reserve enough memory in the vector. - _axesVector.reserve(axes.size()); - -#pragma warning(suppress : 26445) // the analyzer doesn't like reference to string_view - for (const auto& [axis, value] : axes) - { - if (axis.length() == TAG_LENGTH) - { - const auto dwriteFontAxis = DWRITE_FONT_AXIS_VALUE{ DWRITE_MAKE_FONT_AXIS_TAG(til::at(axis, 0), til::at(axis, 1), til::at(axis, 2), til::at(axis, 3)), value }; - if (dwriteFontAxis.axisTag != DWRITE_FONT_AXIS_TAG_WEIGHT) - { - _axesVector.emplace_back(dwriteFontAxis); - } - else - { - weightAxis = dwriteFontAxis; - } - _didUserSetItalic |= dwriteFontAxis.axisTag == DWRITE_FONT_AXIS_TAG_ITALIC && value == 1; - } - } - - // Make the span, which has all the axes except the weight - _axesVectorWithoutWeight = gsl::make_span(_axesVector); - - // Add the weight axis to the vector if needed - if (weightAxis) - { - _axesVector.emplace_back(weightAxis.value()); - } - _didUserSetAxes = true; - } -} - -// Method Description: -// - Converts a DWRITE_FONT_STRETCH enum into the corresponding float value to -// create a DWRITE_FONT_AXIS_VALUE with -// Arguments: -// - fontStretch: the old DWRITE_FONT_STRETCH enum to be converted into an axis value -// Return value: -// - The float value corresponding to the passed in fontStretch -float DxFontRenderData::_FontStretchToWidthAxisValue(DWRITE_FONT_STRETCH fontStretch) noexcept -{ - // 10 elements from DWRITE_FONT_STRETCH_UNDEFINED (0) to DWRITE_FONT_STRETCH_ULTRA_EXPANDED (9) - static constexpr auto fontStretchEnumToVal = std::array{ 100.0f, 50.0f, 62.5f, 75.0f, 87.5f, 100.0f, 112.5f, 125.0f, 150.0f, 200.0f }; - - if (gsl::narrow_cast(fontStretch) > fontStretchEnumToVal.size()) - { - fontStretch = DWRITE_FONT_STRETCH_NORMAL; - } - - return til::at(fontStretchEnumToVal, fontStretch); -} - -// Method Description: -// - Converts a DWRITE_FONT_STYLE enum into the corresponding float value to -// create a DWRITE_FONT_AXIS_VALUE with -// Arguments: -// - fontStyle: the old DWRITE_FONT_STYLE enum to be converted into an axis value -// Return value: -// - The float value corresponding to the passed in fontStyle -float DxFontRenderData::_FontStyleToSlantFixedAxisValue(DWRITE_FONT_STYLE fontStyle) noexcept -{ - // DWRITE_FONT_STYLE_NORMAL (0), DWRITE_FONT_STYLE_OBLIQUE (1), DWRITE_FONT_STYLE_ITALIC (2) - static constexpr auto fontStyleEnumToVal = std::array{ 0.0f, -20.0f, -12.0f }; - - // Both DWRITE_FONT_STYLE_OBLIQUE and DWRITE_FONT_STYLE_ITALIC default to having slant. - // Though an italic font technically need not have slant (there exist upright ones), the - // vast majority of italic fonts are also slanted. Ideally the slant comes from the - // 'slnt' value in the STAT or fvar table, or the post table italic angle. - - if (gsl::narrow_cast(fontStyle) > fontStyleEnumToVal.size()) - { - fontStyle = DWRITE_FONT_STYLE_NORMAL; - } - - return til::at(fontStyleEnumToVal, fontStyle); -} - -// Method Description: -// - Fill any missing axis values that might be known but were unspecified, such as omitting -// the 'wght' axis tag but specifying the old DWRITE_FONT_WEIGHT enum -// - This function will only be called with a valid IDWriteTextFormat3 -// (on platforms where IDWriteTextFormat3 is supported) -// Arguments: -// - fontWeight: the old DWRITE_FONT_WEIGHT enum to be converted into an axis value -// - fontStretch: the old DWRITE_FONT_STRETCH enum to be converted into an axis value -// - fontStyle: the old DWRITE_FONT_STYLE enum to be converted into an axis value -// - fontSize: the number to convert into an axis value -// - format: the IDWriteTextFormat3 to get the defined axes from -// Return value: -// - The fully formed axes vector -#pragma warning(suppress : 26429) // the analyzer doesn't detect that our FAIL_FAST_IF_NULL macro \ - // checks format for nullness -std::vector DxFontRenderData::GetAxisVector(const DWRITE_FONT_WEIGHT fontWeight, - const DWRITE_FONT_STRETCH fontStretch, - const DWRITE_FONT_STYLE fontStyle, - IDWriteTextFormat3* format) -{ - FAIL_FAST_IF_NULL(format); - - const auto axesCount = format->GetFontAxisValueCount(); - std::vector axesVector; - axesVector.resize(axesCount); - format->GetFontAxisValues(axesVector.data(), axesCount); - - auto axisTagPresence = AxisTagPresence::None; - for (const auto& fontAxisValue : axesVector) - { - switch (fontAxisValue.axisTag) - { - case DWRITE_FONT_AXIS_TAG_WEIGHT: - WI_SetFlag(axisTagPresence, AxisTagPresence::Weight); - break; - case DWRITE_FONT_AXIS_TAG_WIDTH: - WI_SetFlag(axisTagPresence, AxisTagPresence::Width); - break; - case DWRITE_FONT_AXIS_TAG_ITALIC: - WI_SetFlag(axisTagPresence, AxisTagPresence::Italic); - break; - case DWRITE_FONT_AXIS_TAG_SLANT: - WI_SetFlag(axisTagPresence, AxisTagPresence::Slant); - break; - } - } - - if (WI_IsFlagClear(axisTagPresence, AxisTagPresence::Weight)) - { - axesVector.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_WEIGHT, gsl::narrow(fontWeight) }); - } - if (WI_IsFlagClear(axisTagPresence, AxisTagPresence::Width)) - { - axesVector.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_WIDTH, _FontStretchToWidthAxisValue(fontStretch) }); - } - if (WI_IsFlagClear(axisTagPresence, AxisTagPresence::Italic)) - { - axesVector.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_ITALIC, (fontStyle == DWRITE_FONT_STYLE_ITALIC ? 1.0f : 0.0f) }); - } - if (WI_IsFlagClear(axisTagPresence, AxisTagPresence::Slant)) - { - axesVector.emplace_back(DWRITE_FONT_AXIS_VALUE{ DWRITE_FONT_AXIS_TAG_SLANT, _FontStyleToSlantFixedAxisValue(fontStyle) }); - } - - return axesVector; + return !_textFormatAxes[0][0].empty(); } // Routine Description: @@ -707,11 +546,13 @@ std::vector DxFontRenderData::GetAxisVector(const DWRITE // - None void DxFontRenderData::_BuildFontRenderData(const FontInfoDesired& desired, FontInfo& actual, const int dpi) { - std::wstring fontLocaleName = UserLocaleName(); // This is the first attempt to resolve font face after `UpdateFont`. // Note that the following line may cause property changes _inside_ `_defaultFontInfo` because the desired font may not exist. // See the implementation of `ResolveFontFaceWithFallback` for details. - const Microsoft::WRL::ComPtr face = _defaultFontInfo.ResolveFontFaceWithFallback(_dwriteFactory.Get(), fontLocaleName); + const auto font = _ResolveFontWithFallback(desired.GetFaceName(), static_cast(desired.GetWeight()), DWRITE_FONT_STYLE_NORMAL); + + Microsoft::WRL::ComPtr face; + THROW_IF_FAILED(font->CreateFontFace(&face)); DWRITE_FONT_METRICS1 fontMetrics; face->GetMetrics(&fontMetrics); @@ -720,9 +561,6 @@ void DxFontRenderData::_BuildFontRenderData(const FontInfoDesired& desired, Font UINT16 spaceGlyphIndex; THROW_IF_FAILED(face->GetGlyphIndicesW(&spaceCodePoint, 1, &spaceGlyphIndex)); - INT32 advanceInDesignUnits; - THROW_IF_FAILED(face->GetDesignGlyphAdvances(1, &spaceGlyphIndex, &advanceInDesignUnits)); - DWRITE_GLYPH_METRICS spaceMetrics = { 0 }; THROW_IF_FAILED(face->GetDesignGlyphMetrics(&spaceGlyphIndex, 1, &spaceMetrics)); @@ -746,7 +584,7 @@ void DxFontRenderData::_BuildFontRenderData(const FontInfoDesired& desired, Font // Now we play trickery with the font size. Scale by the DPI to get the height we expect. heightDesired *= (static_cast(dpi) / static_cast(USER_DEFAULT_SCREEN_DPI)); - const float widthAdvance = static_cast(advanceInDesignUnits) / fontMetrics.designUnitsPerEm; + const float widthAdvance = static_cast(spaceMetrics.advanceWidth) / fontMetrics.designUnitsPerEm; // Use the real pixel height desired by the "em" factor for the width to get the number of pixels // we will need per character in width. This will almost certainly result in fractional X-dimension pixels. @@ -832,15 +670,13 @@ void DxFontRenderData::_BuildFontRenderData(const FontInfoDesired& desired, Font const COORD scaled = coordSize; - actual.SetFromEngine(_defaultFontInfo.GetFamilyName(), + actual.SetFromEngine(desired.GetFaceName().c_str(), desired.GetFamily(), - DefaultTextFormat()->GetFontWeight(), + desired.GetWeight(), false, scaled, unscaled); - actual.SetFallback(_defaultFontInfo.GetFallback()); - LineMetrics lineMetrics; // There is no font metric for the grid line width, so we use a small // multiple of the font size, which typically rounds to a pixel. @@ -894,31 +730,215 @@ void DxFontRenderData::_BuildFontRenderData(const FontInfoDesired& desired, Font _glyphCell = actual.GetSize(); } -Microsoft::WRL::ComPtr DxFontRenderData::_BuildTextFormat(const DxFontInfo& fontInfo, const std::wstring_view localeName) +// 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: +// - dwriteFactory - The DWrite factory to use +// - localeName - Locale to search for appropriate fonts +// Return Value: +// - Smart pointer holding interface reference for queryable font data. +[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::_ResolveFontWithFallback(std::wstring familyName, DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style) { - Microsoft::WRL::ComPtr format; - THROW_IF_FAILED(_dwriteFactory->CreateTextFormat(fontInfo.GetFamilyName().data(), - fontInfo.GetNearbyCollection(), - fontInfo.GetWeight(), - fontInfo.GetStyle(), - fontInfo.GetStretch(), - _fontSize, - localeName.data(), - &format)); - - // If the OS supports IDWriteTextFormat3, set the font axes - ::Microsoft::WRL::ComPtr format3; - if (!FAILED(format->QueryInterface(IID_PPV_ARGS(&format3)))) + if (familyName.empty()) { - if (_inhibitUserWeight && !_axesVectorWithoutWeight.empty()) + familyName = L"Consolas"; + } + if (!weight) + { + weight = DWRITE_FONT_WEIGHT_NORMAL; + } + if (!style) + { + style = DWRITE_FONT_STYLE_NORMAL; + } + + // First attempt to find exactly what the user asked for. + Microsoft::WRL::ComPtr font; + + // GH#10211 - wrap this all up in a try/catch. If the nearby fonts are + // corrupted, then we don't want to throw out of this top half of this + // method. We still want to fall back to a font that's reasonable, below. + try + { + font = _FindFont(familyName.c_str(), weight, style); + + if (!font) { - format3->SetFontAxisValues(_axesVectorWithoutWeight.data(), gsl::narrow(_axesVectorWithoutWeight.size())); + // 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 (!font && !familyName.empty()) + { + const auto lastSpace = familyName.find_last_of(UNICODE_SPACE); + + // value is unsigned and npos will be greater than size. + // if we didn't find anything to trim, leave. + if (lastSpace >= familyName.size()) + { + break; + } + + // 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 + font = _FindFont(familyName.c_str(), weight, style); + } } - else if (!_inhibitUserWeight && !_axesVector.empty()) + } + CATCH_LOG(); + + // 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 (!font) + { + for (const auto fallbackFont : FALLBACK_FONT_FACES) { - format3->SetFontAxisValues(_axesVector.data(), gsl::narrow(_axesVector.size())); + // With these fonts, don't attempt the nearby lookup. We're looking + // for system fonts only. If one of the nearby fonts is causing us + // problems (like in GH#10211), then we don't want to go anywhere + + // near it in this part. + font = _FindFont(fallbackFont, weight, style); + if (font) + { + break; + } + + font = _FindFont(fallbackFont, DWRITE_FONT_WEIGHT_NORMAL, DWRITE_FONT_STYLE_NORMAL); + if (font) + { + break; + } } } - return format; + THROW_HR_IF_NULL(E_FAIL, font); + return font; +} + +// Routine Description: +// - Locates a suitable font face from the given information +// Arguments: +// - dwriteFactory - The DWrite factory to use +// - localeName - Locale to search for appropriate fonts +// Return Value: +// - Smart pointer holding interface reference for queryable font data. +[[nodiscard]] Microsoft::WRL::ComPtr DxFontRenderData::_FindFont(const wchar_t* familyName, DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style) +{ + Microsoft::WRL::ComPtr font; + + Microsoft::WRL::ComPtr fontCollection; + THROW_IF_FAILED(_dwriteFactory->GetSystemFontCollection(&fontCollection, false)); + + UINT32 familyIndex; + BOOL familyExists; + THROW_IF_FAILED(fontCollection->FindFamilyName(familyName, &familyIndex, &familyExists)); + + // If the system collection missed, try the files sitting next to our binary. + if (!familyExists) + { + // May be null on OS below Windows 10. If null, just skip the attempt. + if (const auto nearbyCollection = _NearbyCollection()) + { + THROW_IF_FAILED(nearbyCollection->FindFamilyName(familyName, &familyIndex, &familyExists)); + fontCollection = nearbyCollection; + } + } + + if (familyExists) + { + Microsoft::WRL::ComPtr fontFamily; + THROW_IF_FAILED(fontCollection->GetFontFamily(familyIndex, &fontFamily)); + THROW_IF_FAILED(fontFamily->GetFirstMatchingFont(weight, DWRITE_FONT_STRETCH_NORMAL, style, &font)); + } + + return font; +} + +// 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: +// - dwriteFactory - The DWrite factory to use +// Return Value: +// - DirectWrite font collection. May be null if one cannot be created. +[[nodiscard]] IDWriteFontCollection* DxFontRenderData::_NearbyCollection() +{ + if (_nearbyFontCollection) + { + return _nearbyFontCollection.Get(); + } + + // The convenience interfaces for loading fonts from files + // are only available on Windows 10+. + ::Microsoft::WRL::ComPtr factory6; + if (FAILED(_dwriteFactory->QueryInterface(&factory6))) + { + return nullptr; + } + + ::Microsoft::WRL::ComPtr systemFontCollection; + THROW_IF_FAILED(factory6->GetSystemFontCollection(false, &systemFontCollection, 0)); + + ::Microsoft::WRL::ComPtr systemFontSet; + THROW_IF_FAILED(systemFontCollection->GetFontSet(&systemFontSet)); + + ::Microsoft::WRL::ComPtr fontSetBuilder2; + THROW_IF_FAILED(factory6->CreateFontSetBuilder(&fontSetBuilder2)); + + THROW_IF_FAILED(fontSetBuilder2->AddFontSet(systemFontSet.Get())); + + // 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(); + for (auto& p : knownPaths) + { + fontSetBuilder2->AddFontFile(p.c_str()); + } + + ::Microsoft::WRL::ComPtr fontSet; + THROW_IF_FAILED(fontSetBuilder2->CreateFontSet(&fontSet)); + + ::Microsoft::WRL::ComPtr fontCollection; + THROW_IF_FAILED(factory6->CreateFontCollectionFromFontSet(fontSet.Get(), &fontCollection)); + + _nearbyFontCollection = fontCollection; + return _nearbyFontCollection.Get(); +} + +// Routine Description: +// - Digs through the directory that the current executable is running within to find +// any TTF files sitting next to it. +// Arguments: +// - +// Return Value: +// - Iterable collection of filesystem paths, one per font file that was found +[[nodiscard]] std::vector DxFontRenderData::s_GetNearbyFonts() +{ + std::vector 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(nullptr) }; + const auto folder{ module.parent_path() }; + + for (const auto& p : std::filesystem::directory_iterator(folder)) + { + if (til::ends_with_insensitive_ascii(p.path().native(), L".ttf")) + { + paths.push_back(p.path()); + } + } + + return paths; } diff --git a/src/renderer/dx/DxFontRenderData.h b/src/renderer/dx/DxFontRenderData.h index ce69169c5..06d6c02a3 100644 --- a/src/renderer/dx/DxFontRenderData.h +++ b/src/renderer/dx/DxFontRenderData.h @@ -4,7 +4,6 @@ #pragma once #include "../../renderer/inc/FontInfoDesired.hpp" -#include "DxFontInfo.h" #include "BoxDrawingEffect.h" #include @@ -46,97 +45,69 @@ namespace Microsoft::Console::Render [[nodiscard]] Microsoft::WRL::ComPtr SystemFontFallback(); - // A locale that can be used on construction of assorted DX objects that want to know one. - [[nodiscard]] std::wstring UserLocaleName(); - - [[nodiscard]] til::size GlyphCell() noexcept; - [[nodiscard]] LineMetrics GetLineMetrics() noexcept; + [[nodiscard]] til::size GlyphCell() const noexcept; + [[nodiscard]] LineMetrics GetLineMetrics() const noexcept; // The weight of default font - [[nodiscard]] DWRITE_FONT_WEIGHT DefaultFontWeight() noexcept; + [[nodiscard]] DWRITE_FONT_WEIGHT DefaultFontWeight() const noexcept; // The style of default font - [[nodiscard]] DWRITE_FONT_STYLE DefaultFontStyle() noexcept; + [[nodiscard]] static DWRITE_FONT_STYLE DefaultFontStyle() noexcept; // The stretch of default font - [[nodiscard]] DWRITE_FONT_STRETCH DefaultFontStretch() noexcept; + [[nodiscard]] static DWRITE_FONT_STRETCH DefaultFontStretch() noexcept; // The font features of the default font [[nodiscard]] const std::vector& DefaultFontFeatures() const noexcept; // The DirectWrite format object representing the size and other text properties to be applied (by default) - [[nodiscard]] Microsoft::WRL::ComPtr DefaultTextFormat(); + [[nodiscard]] IDWriteTextFormat* DefaultTextFormat() const noexcept; // The DirectWrite font face to use while calculating layout (by default) - [[nodiscard]] Microsoft::WRL::ComPtr DefaultFontFace(); + [[nodiscard]] IDWriteFontFace1* DefaultFontFace() const noexcept; // Box drawing scaling effects that are cached for the base font across layouts - [[nodiscard]] Microsoft::WRL::ComPtr DefaultBoxDrawingEffect(); + [[nodiscard]] IBoxDrawingEffect* DefaultBoxDrawingEffect(); // The attributed variants of the format object representing the size and other text properties - [[nodiscard]] Microsoft::WRL::ComPtr TextFormatWithAttribute(DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch); + [[nodiscard]] IDWriteTextFormat* TextFormatWithAttribute(DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style) const noexcept; // The attributed variants of the font face to use while calculating layout - [[nodiscard]] Microsoft::WRL::ComPtr FontFaceWithAttribute(DWRITE_FONT_WEIGHT weight, - DWRITE_FONT_STYLE style, - DWRITE_FONT_STRETCH stretch); + [[nodiscard]] IDWriteFontFace1* FontFaceWithAttribute(DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style) const noexcept; + + [[nodiscard]] const std::vector& GetAxisVector(DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style) const noexcept; [[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& desired, FontInfo& fiFontInfo, const int dpi, const std::unordered_map& features = {}, const std::unordered_map& axes = {}) noexcept; - [[nodiscard]] static HRESULT STDMETHODCALLTYPE s_CalculateBoxEffect(IDWriteTextFormat* format, size_t widthPixels, IDWriteFontFace1* face, float fontScale, IBoxDrawingEffect** effect) noexcept; + [[nodiscard]] static HRESULT s_CalculateBoxEffect(IDWriteTextFormat* format, size_t widthPixels, IDWriteFontFace1* face, float fontScale, IBoxDrawingEffect** effect) noexcept; bool DidUserSetFeatures() const noexcept; bool DidUserSetAxes() const noexcept; - void InhibitUserWeight(bool inhibitUserWeight) noexcept; - bool DidUserSetItalic() const noexcept; - - std::vector GetAxisVector(const DWRITE_FONT_WEIGHT fontWeight, - const DWRITE_FONT_STRETCH fontStretch, - const DWRITE_FONT_STYLE fontStyle, - IDWriteTextFormat3* format); private: - using FontAttributeMapKey = uint32_t; - - bool _inhibitUserWeight{ false }; - bool _didUserSetItalic{ false }; - bool _didUserSetFeatures{ false }; - bool _didUserSetAxes{ false }; - // The font features to apply to the text - std::vector _featureVector; - - // The font axes to apply to the text - std::vector _axesVector; - gsl::span _axesVectorWithoutWeight; - - // We use this to identify font variants with different attributes. - static FontAttributeMapKey _ToMapKey(DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style, DWRITE_FONT_STRETCH stretch) noexcept - { - return (weight << 16) | (style << 8) | stretch; - }; - - void _SetFeatures(const std::unordered_map& features); - void _SetAxes(const std::unordered_map& axes); - float _FontStretchToWidthAxisValue(DWRITE_FONT_STRETCH fontStretch) noexcept; - float _FontStyleToSlantFixedAxisValue(DWRITE_FONT_STYLE fontStyle) noexcept; + void _RefreshUserLocaleName(); void _BuildFontRenderData(const FontInfoDesired& desired, FontInfo& actual, const int dpi); - Microsoft::WRL::ComPtr _BuildTextFormat(const DxFontInfo& fontInfo, const std::wstring_view localeName); + ::Microsoft::WRL::ComPtr _ResolveFontWithFallback(std::wstring familyName, DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style); + [[nodiscard]] Microsoft::WRL::ComPtr _FindFont(const wchar_t* familyName, DWRITE_FONT_WEIGHT weight, DWRITE_FONT_STYLE style); + [[nodiscard]] IDWriteFontCollection* _NearbyCollection(); + [[nodiscard]] static std::vector s_GetNearbyFonts(); - std::unordered_map> _textFormatMap; - std::unordered_map> _fontFaceMap; + ::Microsoft::WRL::ComPtr _fontFaces[2][2]; + ::Microsoft::WRL::ComPtr _textFormats[2][2]; + std::vector _textFormatAxes[2][2]; + std::vector _featureVector; ::Microsoft::WRL::ComPtr _boxDrawingEffect; ::Microsoft::WRL::ComPtr _systemFontFallback; + ::Microsoft::WRL::ComPtr _nearbyFontCollection; ::Microsoft::WRL::ComPtr _dwriteFactory; ::Microsoft::WRL::ComPtr _dwriteTextAnalyzer; - std::wstring _userLocaleName; - DxFontInfo _defaultFontInfo; + wil::unique_process_heap_string _userLocaleName; til::size _glyphCell; DWRITE_LINE_SPACING _lineSpacing; LineMetrics _lineMetrics; + DWRITE_FONT_WEIGHT _userFontWeight; float _fontSize; }; } diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index 4a99dbfac..5743abdc2 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -941,7 +941,7 @@ try { return _dwriteFactory->CreateTextLayout(string, gsl::narrow(stringLength), - _fontRenderData->DefaultTextFormat().Get(), + _fontRenderData->DefaultTextFormat(), _displaySizePixels.width(), _fontRenderData->GlyphCell().height() != 0 ? _fontRenderData->GlyphCell().height() : _displaySizePixels.height(), ppTextLayout); @@ -1502,7 +1502,7 @@ void DxEngine::WaitUntilCanRender() noexcept { // Throttle the DxEngine a bit down to ~60 FPS. // This improves throughput for rendering complex or colored text. - Sleep(8); + //Sleep(8); if (_swapChainFrameLatencyWaitableObject) { diff --git a/src/renderer/dx/lib/dx.vcxproj b/src/renderer/dx/lib/dx.vcxproj index 7eb6be151..a73fa32d5 100644 --- a/src/renderer/dx/lib/dx.vcxproj +++ b/src/renderer/dx/lib/dx.vcxproj @@ -18,7 +18,6 @@ - @@ -29,7 +28,6 @@ - diff --git a/src/renderer/dx/sources.inc b/src/renderer/dx/sources.inc index f94f2d5ba..328779d27 100644 --- a/src/renderer/dx/sources.inc +++ b/src/renderer/dx/sources.inc @@ -33,7 +33,6 @@ INCLUDES = \ SOURCES = \ $(SOURCES) \ ..\DxRenderer.cpp \ - ..\DxFontInfo.cpp \ ..\DxFontRenderData.cpp \ ..\CustomTextRenderer.cpp \ ..\CustomTextLayout.cpp \ diff --git a/src/renderer/inc/FontInfo.hpp b/src/renderer/inc/FontInfo.hpp index 2b5ee3564..90b327282 100644 --- a/src/renderer/inc/FontInfo.hpp +++ b/src/renderer/inc/FontInfo.hpp @@ -45,7 +45,6 @@ public: const bool fSetDefaultRasterFont, const COORD coordSize, const COORD coordSizeUnscaled) noexcept; - bool GetFallback() const noexcept; void SetFallback(const bool didFallback) noexcept; void ValidateFont() noexcept;