Allow users to set font features and font axes (#10525)

Adds support for users to be able to set font features and axes (see the spec for more details!)

## Detailed Description

**CustomTextLayout**
- Asks the `DxFontRenderData` for the font features when getting glyphs
- _If any features have been set/updated, we always skip the "isTextSimple" shortcut_
- Asks the `_formatInUse` for any font axes when mapping characters in `_AnalyzeFontFallback`

**DxFontRenderData**
- Stores a map of font features (initialized to the [standard feature list])
- Stores a map of font axes
- Has methods to add font features/axes to the map or update existing ones
- Has methods to retrieve the font features/axes
- Sets the font axes in the `IDWriteTextFormat` when creating it

## Validation Steps Performed
It works!

[standard feature list]: ac5aef67d1/DrawableObject.ixx (L802)

Specified in #10457
Related to #1790 
Closes #759
Closes #5828
This commit is contained in:
PankajBhojwani 2021-07-22 16:15:44 -07:00 committed by GitHub
parent 335f69e099
commit 4c16cb278e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 529 additions and 58 deletions

View file

@ -1,6 +1,9 @@
apc
calt
ccmp
Apc
clickable
clig
copyable
dalet
dcs
@ -14,6 +17,7 @@ dzhe
Enum'd
formattings
ftp
fvar
geeksforgeeks
ghe
gje
@ -27,7 +31,9 @@ It'd
kje
liga
lje
locl
maxed
mkmk
mru
nje
ogonek
@ -37,10 +43,12 @@ postmodern
ptys
qof
qps
rclt
reimplementation
reserialization
reserialize
reserializes
rlig
runtimes
shcha
slnt

View file

@ -155,6 +155,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Set up the DX Engine
auto dxEngine = std::make_unique<::Microsoft::Console::Render::DxEngine>();
_renderer->AddRenderEngine(dxEngine.get());
_renderEngine = std::move(dxEngine);
// Initialize our font with the renderer
// We don't have to care about DPI. We'll get a change message immediately if it's not 96
@ -168,12 +169,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Then, using the font, get the number of characters that can fit.
// Resize our terminal connection to match that size, and initialize the terminal with that size.
const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize);
LOG_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
LOG_IF_FAILED(_renderEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() }));
// Update DxEngine's SelectionBackground
dxEngine->SetSelectionBackground(til::color{ _settings.SelectionBackground() });
_renderEngine->SetSelectionBackground(til::color{ _settings.SelectionBackground() });
const auto vp = dxEngine->GetViewportInCharacters(viewInPixels);
const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels);
const auto width = vp.Width();
const auto height = vp.Height();
_connection.Resize(height, width);
@ -188,27 +189,26 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// after Enable, then it'll be possible to paint the frame once
// _before_ the warning handler is set up, and then warnings from
// the first paint will be ignored!
dxEngine->SetWarningCallback(std::bind(&ControlCore::_rendererWarning, this, std::placeholders::_1));
_renderEngine->SetWarningCallback(std::bind(&ControlCore::_rendererWarning, this, std::placeholders::_1));
// Tell the DX Engine to notify us when the swap chain changes.
// We do this after we initially set the swapchain so as to avoid unnecessary callbacks (and locking problems)
dxEngine->SetCallback(std::bind(&ControlCore::_renderEngineSwapChainChanged, this));
_renderEngine->SetCallback(std::bind(&ControlCore::_renderEngineSwapChainChanged, this));
dxEngine->SetRetroTerminalEffect(_settings.RetroTerminalEffect());
dxEngine->SetPixelShaderPath(_settings.PixelShaderPath());
dxEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering());
dxEngine->SetSoftwareRendering(_settings.SoftwareRendering());
_renderEngine->SetRetroTerminalEffect(_settings.RetroTerminalEffect());
_renderEngine->SetPixelShaderPath(_settings.PixelShaderPath());
_renderEngine->SetForceFullRepaintRendering(_settings.ForceFullRepaintRendering());
_renderEngine->SetSoftwareRendering(_settings.SoftwareRendering());
_updateAntiAliasingMode(dxEngine.get());
_updateAntiAliasingMode(_renderEngine.get());
// GH#5098: Inform the engine of the opacity of the default text background.
if (_settings.UseAcrylic())
{
dxEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast<float>(_settings.TintOpacity()));
_renderEngine->SetDefaultTextBackgroundOpacity(::base::saturated_cast<float>(_settings.TintOpacity()));
}
THROW_IF_FAILED(dxEngine->Enable());
_renderEngine = std::move(dxEngine);
THROW_IF_FAILED(_renderEngine->Enable());
_initializedTerminal = true;
} // scope for TerminalLock
@ -603,9 +603,34 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_terminal->SetFontInfo(_actualFont);
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
// actually fail. We need a way to gracefully fallback.
_renderer->TriggerFontChange(newDpi, _desiredFont, _actualFont);
if (_renderEngine)
{
std::unordered_map<std::wstring_view, uint32_t> featureMap;
if (const auto fontFeatures = _settings.FontFeatures())
{
featureMap.reserve(fontFeatures.Size());
for (const auto& [tag, param] : fontFeatures)
{
featureMap.emplace(tag, param);
}
}
std::unordered_map<std::wstring_view, float> axesMap;
if (const auto fontAxes = _settings.FontAxes())
{
axesMap.reserve(fontAxes.Size());
for (const auto& [axis, value] : fontAxes)
{
axesMap.emplace(axis, value);
}
}
// TODO: MSFT:20895307 If the font doesn't exist, this doesn't
// actually fail. We need a way to gracefully fallback.
LOG_IF_FAILED(_renderEngine->UpdateDpi(newDpi));
LOG_IF_FAILED(_renderEngine->UpdateFont(_desiredFont, _actualFont, featureMap, axesMap));
}
// If the actual font isn't what was requested...
if (_actualFont.GetFaceName() != _desiredFont.GetFaceName())

View file

@ -36,6 +36,8 @@ namespace Microsoft.Terminal.Control
Int32 FontSize;
Windows.UI.Text.FontWeight FontWeight;
String Padding;
Windows.Foundation.Collections.IMap<String, UInt32> FontFeatures;
Windows.Foundation.Collections.IMap<String, Single> FontAxes;
Microsoft.Terminal.Control.IKeyBindings KeyBindings;

View file

@ -335,6 +335,8 @@ winrt::Microsoft::Terminal::Settings::Model::Profile CascadiaSettings::Duplicate
DUPLICATE_SETTING_MACRO_SUB(font, target, FontFace);
DUPLICATE_SETTING_MACRO_SUB(font, target, FontSize);
DUPLICATE_SETTING_MACRO_SUB(font, target, FontWeight);
DUPLICATE_SETTING_MACRO_SUB(font, target, FontFeatures);
DUPLICATE_SETTING_MACRO_SUB(font, target, FontAxes);
}
{

View file

@ -14,6 +14,8 @@ static constexpr std::string_view FontInfoKey{ "font" };
static constexpr std::string_view FontFaceKey{ "face" };
static constexpr std::string_view FontSizeKey{ "size" };
static constexpr std::string_view FontWeightKey{ "weight" };
static constexpr std::string_view FontFeaturesKey{ "features" };
static constexpr std::string_view FontAxesKey{ "axes" };
static constexpr std::string_view LegacyFontFaceKey{ "fontFace" };
static constexpr std::string_view LegacyFontSizeKey{ "fontSize" };
static constexpr std::string_view LegacyFontWeightKey{ "fontWeight" };
@ -29,6 +31,8 @@ winrt::com_ptr<FontConfig> FontConfig::CopyFontInfo(const winrt::com_ptr<FontCon
fontInfo->_FontFace = source->_FontFace;
fontInfo->_FontSize = source->_FontSize;
fontInfo->_FontWeight = source->_FontWeight;
fontInfo->_FontAxes = source->_FontAxes;
fontInfo->_FontFeatures = source->_FontFeatures;
return fontInfo;
}
@ -39,6 +43,8 @@ Json::Value FontConfig::ToJson() const
JsonUtils::SetValueForKey(json, FontFaceKey, _FontFace);
JsonUtils::SetValueForKey(json, FontSizeKey, _FontSize);
JsonUtils::SetValueForKey(json, FontWeightKey, _FontWeight);
JsonUtils::SetValueForKey(json, FontAxesKey, _FontAxes);
JsonUtils::SetValueForKey(json, FontFeaturesKey, _FontFeatures);
return json;
}
@ -65,6 +71,8 @@ void FontConfig::LayerJson(const Json::Value& json)
JsonUtils::GetValueForKey(fontInfoJson, FontFaceKey, _FontFace);
JsonUtils::GetValueForKey(fontInfoJson, FontSizeKey, _FontSize);
JsonUtils::GetValueForKey(fontInfoJson, FontWeightKey, _FontWeight);
JsonUtils::GetValueForKey(fontInfoJson, FontFeaturesKey, _FontFeatures);
JsonUtils::GetValueForKey(fontInfoJson, FontAxesKey, _FontAxes);
}
else
{

View file

@ -23,6 +23,9 @@ Author(s):
#include "IInheritable.h"
#include <DefaultSettings.h>
using IFontAxesMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, float>;
using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, uint32_t>;
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
{
struct FontConfig : FontConfigT<FontConfig>, IInheritable<FontConfig>
@ -39,6 +42,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
INHERITABLE_SETTING(Model::FontConfig, hstring, FontFace, DEFAULT_FONT_FACE);
INHERITABLE_SETTING(Model::FontConfig, int32_t, FontSize, DEFAULT_FONT_SIZE);
INHERITABLE_SETTING(Model::FontConfig, Windows::UI::Text::FontWeight, FontWeight, DEFAULT_FONT_WEIGHT);
INHERITABLE_SETTING(Model::FontConfig, IFontAxesMap, FontAxes);
INHERITABLE_SETTING(Model::FontConfig, IFontFeatureMap, FontFeatures);
private:
winrt::weak_ref<Profile> _sourceProfile;

View file

@ -8,6 +8,8 @@ import "Profile.idl";
_BASE_INHERITABLE_SETTING(Type, Name); \
Microsoft.Terminal.Settings.Model.FontConfig Name##OverrideSource { get; }
#define COMMA ,
namespace Microsoft.Terminal.Settings.Model
{
[default_interface] runtimeclass FontConfig {
@ -16,5 +18,8 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_FONT_SETTING(String, FontFace);
INHERITABLE_FONT_SETTING(Int32, FontSize);
INHERITABLE_FONT_SETTING(Windows.UI.Text.FontWeight, FontWeight);
INHERITABLE_FONT_SETTING(Windows.Foundation.Collections.IMap<String COMMA UInt32>, FontFeatures);
INHERITABLE_FONT_SETTING(Windows.Foundation.Collections.IMap<String COMMA Single>, FontAxes);
}
}

View file

@ -177,6 +177,58 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils
}
};
template<typename T>
struct ConversionTrait<std::unordered_map<std::string, T>>
{
std::unordered_map<std::string, T> FromJson(const Json::Value& json) const
{
std::unordered_map<std::string, T> val;
val.reserve(json.size());
ConversionTrait<T> trait;
for (auto it = json.begin(), end = json.end(); it != end; ++it)
{
GetValue(*it, val[it.name()], trait);
}
return val;
}
bool CanConvert(const Json::Value& json) const
{
if (!json.isObject())
{
return false;
}
ConversionTrait<T> trait;
for (const auto& v : json)
{
if (!trait.CanConvert(v))
{
return false;
}
}
return true;
}
Json::Value ToJson(const std::unordered_map<std::string, T>& val)
{
Json::Value json{ Json::objectValue };
for (const auto& [k, v] : val)
{
SetValueForKey(json, k, v);
}
return json;
}
std::string TypeDescription() const
{
return fmt::format("map (string, {})", ConversionTrait<T>{}.TypeDescription());
}
};
#ifdef WINRT_BASE_H
template<>
struct ConversionTrait<winrt::hstring> : public ConversionTrait<std::wstring>
@ -206,6 +258,58 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils
return ConversionTrait<std::wstring>::CanConvert(json) || json.isNull();
}
};
template<typename T>
struct ConversionTrait<winrt::Windows::Foundation::Collections::IMap<winrt::hstring, T>>
{
winrt::Windows::Foundation::Collections::IMap<winrt::hstring, T> FromJson(const Json::Value& json) const
{
std::unordered_map<winrt::hstring, T> val;
val.reserve(json.size());
ConversionTrait<T> trait;
for (auto it = json.begin(), end = json.end(); it != end; ++it)
{
GetValue(*it, val[winrt::to_hstring(it.name())], trait);
}
return winrt::single_threaded_map<winrt::hstring, T>(std::move(val));
}
bool CanConvert(const Json::Value& json) const
{
if (!json.isObject())
{
return false;
}
ConversionTrait<T> trait;
for (const auto& v : json)
{
if (!trait.CanConvert(v))
{
return false;
}
}
return true;
}
Json::Value ToJson(const winrt::Windows::Foundation::Collections::IMap<winrt::hstring, T>& val)
{
Json::Value json{ Json::objectValue };
for (const auto& [k, v] : val)
{
SetValueForKey(json, til::u16u8(k), v);
}
return json;
}
std::string TypeDescription() const
{
return fmt::format("map (string, {})", ConversionTrait<T>{}.TypeDescription());
}
};
#endif
template<>

View file

@ -279,6 +279,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
_FontFace = profile.FontInfo().FontFace();
_FontSize = profile.FontInfo().FontSize();
_FontWeight = profile.FontInfo().FontWeight();
_FontFeatures = profile.FontInfo().FontFeatures();
_FontAxes = profile.FontInfo().FontAxes();
_Padding = profile.Padding();
_Commandline = profile.Commandline();

View file

@ -21,6 +21,9 @@ Author(s):
#include <DefaultSettings.h>
#include <conattrs.hpp>
using IFontAxesMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, float>;
using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, uint32_t>;
// fwdecl unittest classes
namespace SettingsModelLocalTests
{
@ -123,6 +126,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
INHERITABLE_SETTING(Model::TerminalSettings, int32_t, FontSize, DEFAULT_FONT_SIZE);
INHERITABLE_SETTING(Model::TerminalSettings, winrt::Windows::UI::Text::FontWeight, FontWeight);
INHERITABLE_SETTING(Model::TerminalSettings, IFontAxesMap, FontAxes);
INHERITABLE_SETTING(Model::TerminalSettings, IFontFeatureMap, FontFeatures);
INHERITABLE_SETTING(Model::TerminalSettings, hstring, BackgroundImage);
INHERITABLE_SETTING(Model::TerminalSettings, double, BackgroundImageOpacity, 1.0);

View file

@ -8,6 +8,9 @@ Licensed under the MIT license.
#include <DefaultSettings.h>
#include <conattrs.hpp>
using IFontFeatureMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, uint32_t>;
using IFontAxesMap = winrt::Windows::Foundation::Collections::IMap<winrt::hstring, float>;
namespace ControlUnitTests
{
class MockControlSettings : public winrt::implements<MockControlSettings, winrt::Microsoft::Terminal::Core::ICoreSettings, winrt::Microsoft::Terminal::Control::IControlSettings, winrt::Microsoft::Terminal::Core::ICoreAppearance, winrt::Microsoft::Terminal::Control::IControlAppearance>
@ -80,6 +83,9 @@ namespace ControlUnitTests
WINRT_PROPERTY(winrt::hstring, PixelShaderPath);
WINRT_PROPERTY(IFontFeatureMap, FontFeatures);
WINRT_PROPERTY(IFontAxesMap, FontAxes);
private:
std::array<winrt::Microsoft::Terminal::Core::Color, COLOR_TABLE_SIZE> _ColorTable;

View file

@ -357,7 +357,7 @@ CATCH_RETURN()
_glyphIndices.resize(totalGlyphsArrayCount);
}
if (_isEntireTextSimple)
if (_isEntireTextSimple && !_fontRenderData->DidUserSetFeatures())
{
// When the entire text is simple, we can skip GetGlyphs and directly retrieve glyph indices and
// advances(in font design unit). With the help of font metrics, we can calculate the actual glyph
@ -396,10 +396,18 @@ CATCH_RETURN()
std::vector<DWRITE_SHAPING_TEXT_PROPERTIES> textProps(textLength);
std::vector<DWRITE_SHAPING_GLYPH_PROPERTIES> glyphProps(maxGlyphCount);
// Get the features to apply to the font
auto features = _fontRenderData->DefaultFontFeatures();
DWRITE_FONT_FEATURE* featureList = features.data();
DWRITE_TYPOGRAPHIC_FEATURES typographicFeatures = { &featureList[0], gsl::narrow<uint32_t>(features.size()) };
DWRITE_TYPOGRAPHIC_FEATURES const* typographicFeaturesPointer = &typographicFeatures;
const uint32_t fontFeatureLengths[] = { textLength };
// Get the glyphs from the text, retrying if needed.
int tries = 0;
#pragma warning(suppress : 26485) // so we can pass in the fontFeatureLengths to GetGlyphs without the analyzer complaining
HRESULT hr = S_OK;
do
{
@ -412,9 +420,9 @@ CATCH_RETURN()
&run.script,
_localeName.data(),
(run.isNumberSubstituted) ? _numberSubstitution.Get() : nullptr,
nullptr, // features
nullptr, // featureLengths
0, // featureCount
&typographicFeaturesPointer, // features
&fontFeatureLengths[0], // featureLengths
1, // featureCount
maxGlyphCount, // maxGlyphCount
&_glyphClusters.at(textStart),
&textProps.at(0),
@ -462,9 +470,9 @@ CATCH_RETURN()
(run.bidiLevel & 1), // isRightToLeft
&run.script,
_localeName.data(),
nullptr, // features
nullptr, // featureRangeLengths
0, // featureRanges
&typographicFeaturesPointer, // features
&fontFeatureLengths[0], // featureLengths
1, // featureCount
&_glyphAdvances.at(glyphStart),
&_glyphOffsets.at(glyphStart));
@ -1264,29 +1272,71 @@ CATCH_RETURN();
fallback = _fontRenderData->SystemFontFallback();
}
// Walk through and analyze the entire string
while (textLength > 0)
::Microsoft::WRL::ComPtr<IDWriteFontFallback1> fallback1;
::Microsoft::WRL::ComPtr<IDWriteTextFormat3> format3;
// If the OS supports IDWriteFontFallback1 and IDWriteTextFormat3, we can use the
// 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))))
{
UINT32 mappedLength = 0;
::Microsoft::WRL::ComPtr<IDWriteFont> mappedFont;
FLOAT scale = 0.0f;
const auto axesVector = _fontRenderData->GetAxisVector(weight, stretch, style, format3.Get());
// Walk through and analyze the entire string
while (textLength > 0)
{
UINT32 mappedLength = 0;
::Microsoft::WRL::ComPtr<IDWriteFontFace5> mappedFont;
FLOAT scale = 0.0f;
fallback->MapCharacters(source,
textPosition,
textLength,
collection.Get(),
familyName.data(),
weight,
style,
stretch,
&mappedLength,
&mappedFont,
&scale);
fallback1->MapCharacters(source,
textPosition,
textLength,
collection.Get(),
familyName.data(),
axesVector.data(),
gsl::narrow<uint32_t>(axesVector.size()),
&mappedLength,
&scale,
&mappedFont);
RETURN_IF_FAILED(_SetMappedFont(textPosition, mappedLength, mappedFont.Get(), scale));
RETURN_IF_FAILED(_SetMappedFontFace(textPosition, mappedLength, mappedFont, scale));
textPosition += mappedLength;
textLength -= mappedLength;
textPosition += mappedLength;
textLength -= mappedLength;
}
}
else
{
// The chunk of code below is very similar to the one above, unfortunately this needs
// to stay for Win7 compatibility reasons. It is also not possible to combine the two
// because they call different versions of MapCharacters
// Walk through and analyze the entire string
while (textLength > 0)
{
UINT32 mappedLength = 0;
::Microsoft::WRL::ComPtr<IDWriteFont> mappedFont;
FLOAT scale = 0.0f;
fallback->MapCharacters(source,
textPosition,
textLength,
collection.Get(),
familyName.data(),
weight,
style,
stretch,
&mappedLength,
&mappedFont,
&scale);
RETURN_LAST_ERROR_IF(!mappedFont);
::Microsoft::WRL::ComPtr<IDWriteFontFace> face;
RETURN_IF_FAILED(mappedFont->CreateFontFace(&face));
RETURN_IF_FAILED(_SetMappedFontFace(textPosition, mappedLength, face, scale));
textPosition += mappedLength;
textLength -= mappedLength;
}
}
}
CATCH_RETURN();
@ -1300,14 +1350,14 @@ CATCH_RETURN();
// Arguments:
// - textPosition - the index to start the substring operation
// - textLength - the length of the substring operation
// - font - the font that applies to the substring range
// - fontFace - the fontFace that applies to the substring range
// - scale - the scale of the font to apply
// Return Value:
// - S_OK or appropriate STL/GSL failure code.
[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::_SetMappedFont(UINT32 textPosition,
UINT32 textLength,
_In_ IDWriteFont* const font,
FLOAT const scale)
[[nodiscard]] HRESULT STDMETHODCALLTYPE CustomTextLayout::_SetMappedFontFace(UINT32 textPosition,
UINT32 textLength,
const ::Microsoft::WRL::ComPtr<IDWriteFontFace>& fontFace,
FLOAT const scale)
{
try
{
@ -1317,14 +1367,9 @@ CATCH_RETURN();
{
auto& run = _FetchNextRun(textLength);
if (font != nullptr)
if (fontFace != nullptr)
{
// Get font face from font metadata
::Microsoft::WRL::ComPtr<IDWriteFontFace> face;
RETURN_IF_FAILED(font->CreateFontFace(&face));
// QI for Face5 interface from base face interface, store into run
RETURN_IF_FAILED(face.As(&run.fontFace));
RETURN_IF_FAILED(fontFace.As(&run.fontFace));
}
else
{

View file

@ -126,7 +126,7 @@ namespace Microsoft::Console::Render
void _OrderRuns();
[[nodiscard]] HRESULT STDMETHODCALLTYPE _AnalyzeFontFallback(IDWriteTextAnalysisSource* const source, UINT32 textPosition, UINT32 textLength);
[[nodiscard]] HRESULT STDMETHODCALLTYPE _SetMappedFont(UINT32 textPosition, UINT32 textLength, IDWriteFont* const font, FLOAT const scale);
[[nodiscard]] HRESULT STDMETHODCALLTYPE _SetMappedFontFace(UINT32 textPosition, UINT32 textLength, const ::Microsoft::WRL::ComPtr<IDWriteFontFace>& fontFace, FLOAT const scale);
[[nodiscard]] HRESULT STDMETHODCALLTYPE _AnalyzeBoxDrawing(gsl::not_null<IDWriteTextAnalysisSource*> const source, UINT32 textPosition, UINT32 textLength);
[[nodiscard]] HRESULT STDMETHODCALLTYPE _SetBoxEffect(UINT32 textPosition, UINT32 textLength);

View file

@ -12,6 +12,7 @@
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;
using namespace Microsoft::Console::Render;
@ -93,6 +94,11 @@ DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr<IDWriteFactory1> dwr
return _defaultFontInfo.GetStretch();
}
[[nodiscard]] const std::vector<DWRITE_FONT_FEATURE>& DxFontRenderData::DefaultFontFeatures() const noexcept
{
return _featureVector;
}
[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteTextFormat> DxFontRenderData::DefaultTextFormat()
{
return TextFormatWithAttribute(_defaultFontInfo.GetWeight(), _defaultFontInfo.GetStyle(), _defaultFontInfo.GetStretch());
@ -178,7 +184,7 @@ DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr<IDWriteFactory1> dwr
// - dpi - The DPI of the screen
// Return Value:
// - S_OK or relevant DirectX error
[[nodiscard]] HRESULT DxFontRenderData::UpdateFont(const FontInfoDesired& desired, FontInfo& actual, const int dpi) noexcept
[[nodiscard]] HRESULT DxFontRenderData::UpdateFont(const FontInfoDesired& desired, FontInfo& actual, const int dpi, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept
{
try
{
@ -193,6 +199,9 @@ DxFontRenderData::DxFontRenderData(::Microsoft::WRL::ComPtr<IDWriteFactory1> dwr
DWRITE_FONT_STYLE_NORMAL,
DWRITE_FONT_STRETCH_NORMAL);
_SetFeatures(features);
_SetAxes(axes);
_BuildFontRenderData(desired, actual, dpi);
}
CATCH_RETURN();
@ -441,6 +450,195 @@ try
}
CATCH_RETURN()
// Routine Description:
// - Returns whether the user set or updated any of the font features to be applied
bool DxFontRenderData::DidUserSetFeatures() const noexcept
{
return _didUserSetFeatures;
}
// 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<std::wstring_view, uint32_t>& features)
{
// Populate the feature map with the standard list first
std::unordered_map<DWRITE_FONT_FEATURE_TAG, uint32_t> featureMap{
{ DWRITE_MAKE_FONT_FEATURE_TAG('r', 'l', 'i', 'g'), 1 }, // Required Ligatures
{ DWRITE_MAKE_FONT_FEATURE_TAG('r', 'c', 'l', 't'), 1 }, // Required Contextual Alternates
{ DWRITE_MAKE_FONT_FEATURE_TAG('l', 'o', 'c', 'l'), 1 }, // Localized Forms
{ DWRITE_MAKE_FONT_FEATURE_TAG('c', 'c', 'm', 'p'), 1 }, // Glyph Composition / Decomposition
{ 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
{ DWRITE_MAKE_FONT_FEATURE_TAG('m', 'a', 'r', 'k'), 1 }, // Mark Positioning
{ DWRITE_MAKE_FONT_FEATURE_TAG('m', 'k', 'm', 'k'), 1 }, // Mark to Mark Positioning
{ DWRITE_MAKE_FONT_FEATURE_TAG('d', 'i', 's', 't'), 1 } // Distances
};
// 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<std::wstring_view, float>& axes)
{
_axesVector.clear();
// Update our axis map with the provided axes
#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 dwriteTag = DWRITE_MAKE_FONT_AXIS_TAG(til::at(axis, 0), til::at(axis, 1), til::at(axis, 2), til::at(axis, 3));
_axesVector.emplace_back(DWRITE_FONT_AXIS_VALUE{ dwriteTag, value });
}
}
}
// 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<size_t>(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<size_t>(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<DWRITE_FONT_AXIS_VALUE> 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<DWRITE_FONT_AXIS_VALUE> 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<float>(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;
}
// Routine Description:
// - Build the needed data for rendering according to the font used
// Arguments:
@ -649,5 +847,14 @@ Microsoft::WRL::ComPtr<IDWriteTextFormat> DxFontRenderData::_BuildTextFormat(con
_fontSize,
localeName.data(),
&format));
// If the OS supports IDWriteTextFormat3, set the font axes
::Microsoft::WRL::ComPtr<IDWriteTextFormat3> format3;
if (!_axesVector.empty() && !FAILED(format->QueryInterface(IID_PPV_ARGS(&format3))))
{
DWRITE_FONT_AXIS_VALUE const* axesList = _axesVector.data();
format3->SetFontAxisValues(axesList, gsl::narrow<uint32_t>(_axesVector.size()));
}
return format;
}

View file

@ -16,6 +16,16 @@
namespace Microsoft::Console::Render
{
enum class AxisTagPresence : BYTE
{
None = 0x00,
Weight = 0x01,
Width = 0x02,
Italic = 0x04,
Slant = 0x08,
};
DEFINE_ENUM_FLAG_OPERATORS(AxisTagPresence);
class DxFontRenderData
{
public:
@ -51,6 +61,9 @@ namespace Microsoft::Console::Render
// The stretch of default font
[[nodiscard]] DWRITE_FONT_STRETCH DefaultFontStretch() noexcept;
// The font features of the default font
[[nodiscard]] const std::vector<DWRITE_FONT_FEATURE>& DefaultFontFeatures() const noexcept;
// The DirectWrite format object representing the size and other text properties to be applied (by default)
[[nodiscard]] Microsoft::WRL::ComPtr<IDWriteTextFormat> DefaultTextFormat();
@ -70,19 +83,37 @@ namespace Microsoft::Console::Render
DWRITE_FONT_STYLE style,
DWRITE_FONT_STRETCH stretch);
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& desired, FontInfo& fiFontInfo, const int dpi) noexcept;
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& desired, FontInfo& fiFontInfo, const int dpi, const std::unordered_map<std::wstring_view, uint32_t>& features = {}, const std::unordered_map<std::wstring_view, float>& axes = {}) noexcept;
[[nodiscard]] static HRESULT STDMETHODCALLTYPE s_CalculateBoxEffect(IDWriteTextFormat* format, size_t widthPixels, IDWriteFontFace1* face, float fontScale, IBoxDrawingEffect** effect) noexcept;
bool DidUserSetFeatures() const noexcept;
std::vector<DWRITE_FONT_AXIS_VALUE> GetAxisVector(const DWRITE_FONT_WEIGHT fontWeight,
const DWRITE_FONT_STRETCH fontStretch,
const DWRITE_FONT_STYLE fontStyle,
IDWriteTextFormat3* format);
private:
using FontAttributeMapKey = uint32_t;
bool _didUserSetFeatures{ false };
// The font features to apply to the text
std::vector<DWRITE_FONT_FEATURE> _featureVector;
// The font axes to apply to the text
std::vector<DWRITE_FONT_AXIS_VALUE> _axesVector;
// 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<std::wstring_view, uint32_t>& features);
void _SetAxes(const std::unordered_map<std::wstring_view, float>& axes);
float _FontStretchToWidthAxisValue(DWRITE_FONT_STRETCH fontStretch) noexcept;
float _FontStyleToSlantFixedAxisValue(DWRITE_FONT_STYLE fontStyle) noexcept;
void _BuildFontRenderData(const FontInfoDesired& desired, FontInfo& actual, const int dpi);
Microsoft::WRL::ComPtr<IDWriteTextFormat> _BuildTextFormat(const DxFontInfo fontInfo, const std::wstring_view localeName);

View file

@ -1978,15 +1978,30 @@ CATCH_RETURN()
// Routine Description:
// - Updates the font used for drawing
// - This is the version that complies with the IRenderEngine interface
// Arguments:
// - pfiFontInfoDesired - Information specifying the font that is requested
// - fiFontInfo - Filled with the nearest font actually chosen for drawing
// Return Value:
// - S_OK or relevant DirectX error
[[nodiscard]] HRESULT DxEngine::UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo) noexcept
{
return UpdateFont(pfiFontInfoDesired, fiFontInfo, {}, {});
}
// Routine Description:
// - Updates the font used for drawing
// Arguments:
// - pfiFontInfoDesired - Information specifying the font that is requested
// - fiFontInfo - Filled with the nearest font actually chosen for drawing
// - features - The map of font features to use
// - axes - The map of font axes to use
// Return Value:
// - S_OK or relevant DirectX error
[[nodiscard]] HRESULT DxEngine::UpdateFont(const FontInfoDesired& pfiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept
try
{
RETURN_IF_FAILED(_fontRenderData->UpdateFont(pfiFontInfoDesired, fiFontInfo, _dpi));
RETURN_IF_FAILED(_fontRenderData->UpdateFont(pfiFontInfoDesired, fiFontInfo, _dpi, features, axes));
// Prepare the text layout.
_customLayout = WRL::Make<CustomTextLayout>(_fontRenderData.get());

View file

@ -109,6 +109,7 @@ namespace Microsoft::Console::Render
const gsl::not_null<IRenderData*> pData,
const bool isSettingDefaultBrushes) noexcept override;
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo) noexcept override;
[[nodiscard]] HRESULT UpdateFont(const FontInfoDesired& fiFontInfoDesired, FontInfo& fiFontInfo, const std::unordered_map<std::wstring_view, uint32_t>& features, const std::unordered_map<std::wstring_view, float>& axes) noexcept;
[[nodiscard]] HRESULT UpdateDpi(int const iDpi) noexcept override;
[[nodiscard]] HRESULT UpdateViewport(const SMALL_RECT srNewViewport) noexcept override;