Implement cell size customizations (#14255)

Does what it says in the title. After this commit you can customize the height
and width of the terminal's cells. This commit supports parts of CSS'
`<length-percentage>` data type: Font-size relative sizes as multiples (`1.2`),
percentage (`120%`), or advance-width relative (`1.2ch`), as well as absolute
sizes in CSS pixels (`px`) or points (`pt`).

This PR is neither bug free in DxEngine, nor in AtlasEngine.
The former fails to implement glyph advance corrections (for instance #9381),
as well as disallowing glyphs to overlap rows. The latter has the same
overlap issue, but more severely as it tries to shrink glyphs to fit in.

Closes #3498
Closes #14068

## Validation Steps Performed
* Setting `height` to `1` creates 12pt tall rows 
* Setting `height` to `1ch` creates square cells 
* Setting `width` to `1` creates square cells 
* Setting `width` or `height` to `Npx` or `Npt` works 
* Trailing zeroes are trimmed properly during serialization 
* Patching the PR to allow >100 line heights and entering "100.123456"
  displays 6 fractional digits 
This commit is contained in:
Leonard Hecker 2023-02-14 22:42:14 +01:00 committed by GitHub
parent e4bba3cd9a
commit b6e6dd861d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 348 additions and 64 deletions

View File

@ -24,6 +24,13 @@
"pattern": "^(-?\\d+)?(,\\s?(-?\\d+)?)?$",
"type": "string"
},
"CSSLengthPercentage": {
"pattern": "^[+-]?\\d+(?:\\.\\d+)?(?:%|ch|pt|px)?$",
"type": [
"string",
"null"
]
},
"DynamicProfileSource": {
"enum": [
"Windows.Terminal.Wsl",
@ -314,6 +321,14 @@
}
},
"additionalProperties": false
},
"cellWidth": {
"$ref": "#/$defs/CSSLengthPercentage",
"description": "Override the width of the terminal's cells. The override works similar to CSS' letter-spacing. It defaults to the natural glyph advance width of the primary font rounded to the nearest pixel."
},
"cellHeight": {
"$ref": "#/$defs/CSSLengthPercentage",
"description": "Override the height of the terminal's cells. The override works similar to CSS' line-height. Defaults to the sum of the natural glyph ascend, descend and line-gap of the primary font rounded to the nearest pixel. The default is usually quite close to setting this to 1.2."
}
},
"type": "object"

View File

@ -728,6 +728,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
auto lock = _terminal->LockForWriting();
_cellWidth = CSSLengthPercentage::FromString(_settings->CellWidth().c_str());
_cellHeight = CSSLengthPercentage::FromString(_settings->CellHeight().c_str());
_runtimeOpacity = std::nullopt;
// Manually turn off acrylic if they turn off transparency.
@ -880,6 +882,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_actualFont = { fontFace, 0, fontWeight.Weight, _desiredFont.GetEngineSize(), CP_UTF8, false };
_actualFontFaceName = { fontFace };
_desiredFont.SetCellSize(_cellWidth, _cellHeight);
const auto before = _actualFont.GetSize();
_updateFont();
const auto after = _actualFont.GetSize();

View File

@ -258,6 +258,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
FontInfoDesired _desiredFont;
FontInfo _actualFont;
winrt::hstring _actualFontFaceName;
CSSLengthPercentage _cellWidth;
CSSLengthPercentage _cellHeight;
// storage location for the leading surrogate of a utf-16 surrogate pair
std::optional<wchar_t> _leadingSurrogate{ std::nullopt };

View File

@ -42,6 +42,8 @@ namespace Microsoft.Terminal.Control
String Padding { get; };
Windows.Foundation.Collections.IMap<String, UInt32> FontFeatures { get; };
Windows.Foundation.Collections.IMap<String, Single> FontAxes { get; };
String CellWidth { get; };
String CellHeight { get; };
Microsoft.Terminal.Control.IKeyBindings KeyBindings { get; };

View File

@ -49,6 +49,78 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
}
}
double AppearanceViewModel::LineHeight() const noexcept
{
const auto fontInfo = _appearance.SourceProfile().FontInfo();
const auto cellHeight = fontInfo.CellHeight();
const auto str = cellHeight.c_str();
auto& errnoRef = errno; // Nonzero cost, pay it once.
errnoRef = 0;
wchar_t* end;
const auto value = std::wcstod(str, &end);
return str == end || errnoRef == ERANGE ? NAN : value;
}
void AppearanceViewModel::LineHeight(const double value)
{
std::wstring str;
if (value >= 0.1 && value <= 10.0)
{
str = fmt::format(FMT_STRING(L"{:.6g}"), value);
}
const auto fontInfo = _appearance.SourceProfile().FontInfo();
if (fontInfo.CellHeight() != str)
{
if (str.empty())
{
fontInfo.ClearCellHeight();
}
else
{
fontInfo.CellHeight(str);
}
_NotifyChanges(L"HasLineHeight", L"LineHeight");
}
}
bool AppearanceViewModel::HasLineHeight() const
{
const auto fontInfo = _appearance.SourceProfile().FontInfo();
return fontInfo.HasCellHeight();
}
void AppearanceViewModel::ClearLineHeight()
{
LineHeight(NAN);
}
Model::FontConfig AppearanceViewModel::LineHeightOverrideSource() const
{
const auto fontInfo = _appearance.SourceProfile().FontInfo();
return fontInfo.CellHeightOverrideSource();
}
void AppearanceViewModel::SetFontWeightFromDouble(double fontWeight)
{
FontWeight(Converters::DoubleToFontWeight(fontWeight));
}
void AppearanceViewModel::SetBackgroundImageOpacityFromPercentageValue(double percentageValue)
{
BackgroundImageOpacity(Converters::PercentageValueToPercentage(percentageValue));
}
void AppearanceViewModel::SetBackgroundImagePath(winrt::hstring path)
{
BackgroundImagePath(path);
}
bool AppearanceViewModel::UseDesktopBGImage()
{
return BackgroundImagePath() == L"desktopWallpaper";
@ -123,10 +195,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
// > .NET rounds to 12 significant digits when displaying doubles, so we will [...]
// ...obviously not do that, because this is an UI element for humans. This prevents
// issues when displaying 32-bit floats, because WinUI is unaware about their existence.
SignificantDigitsNumberRounder rounder;
rounder.SignificantDigits(6);
// BODGY: Depends on WinUI internals.
_fontSizeBox().NumberFormatter().as<DecimalFormatter>().NumberRounder(rounder);
IncrementNumberRounder rounder;
rounder.Increment(1e-6);
for (const auto& box : { _fontSizeBox(), _lineHeightBox() })
{
// BODGY: Depends on WinUI internals.
box.NumberFormatter().as<DecimalFormatter>().NumberRounder(rounder);
}
}
INITIALIZE_BINDABLE_ENUM_SETTING(CursorShape, CursorStyle, winrt::Microsoft::Terminal::Core::CursorStyle, L"Profile_CursorShape", L"Content");

View File

@ -51,18 +51,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
public:
AppearanceViewModel(const Model::AppearanceConfig& appearance);
void SetFontWeightFromDouble(double fontWeight)
{
FontWeight(winrt::Microsoft::Terminal::Settings::Editor::Converters::DoubleToFontWeight(fontWeight));
}
void SetBackgroundImageOpacityFromPercentageValue(double percentageValue)
{
BackgroundImageOpacity(winrt::Microsoft::Terminal::Settings::Editor::Converters::PercentageValueToPercentage(percentageValue));
}
void SetBackgroundImagePath(winrt::hstring path)
{
BackgroundImagePath(path);
}
double LineHeight() const noexcept;
void LineHeight(const double value);
bool HasLineHeight() const;
void ClearLineHeight();
Model::FontConfig LineHeightOverrideSource() const;
void SetFontWeightFromDouble(double fontWeight);
void SetBackgroundImageOpacityFromPercentageValue(double percentageValue);
void SetBackgroundImagePath(winrt::hstring path);
// background image
bool UseDesktopBGImage();
@ -100,6 +96,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation
private:
Model::AppearanceConfig _appearance;
winrt::hstring _lastBgImagePath;
float _cachedLineHeight = 0;
};
struct Appearances : AppearancesT<Appearances>

View File

@ -38,6 +38,7 @@ namespace Microsoft.Terminal.Settings.Editor
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(String, FontFace);
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Single, FontSize);
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Double, LineHeight);
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(Windows.UI.Text.FontWeight, FontWeight);
OBSERVABLE_PROJECTED_APPEARANCE_SETTING(String, DarkColorSchemeName);

View File

@ -219,7 +219,6 @@
Visibility="{x:Bind Appearance.IsDefault, Mode=OneWay}">
<muxc:NumberBox x:Name="_fontSizeBox"
x:Uid="Profile_FontSizeBox"
AcceptsExpression="False"
LargeChange="10"
Maximum="128"
Minimum="1"
@ -228,6 +227,22 @@
Value="{x:Bind Appearance.FontSize, Mode=TwoWay}" />
</local:SettingContainer>
<!-- Line Height -->
<local:SettingContainer x:Uid="Profile_LineHeight"
ClearSettingValue="{x:Bind Appearance.ClearLineHeight}"
HasSettingValue="{x:Bind Appearance.HasLineHeight, Mode=OneWay}"
SettingOverrideSource="{x:Bind Appearance.LineHeightOverrideSource, Mode=OneWay}"
Visibility="{x:Bind Appearance.IsDefault, Mode=OneWay}">
<muxc:NumberBox x:Name="_lineHeightBox"
x:Uid="Profile_LineHeightBox"
LargeChange="0.1"
Maximum="10"
Minimum="0.1"
SmallChange="0.1"
Style="{StaticResource NumberBoxSettingStyle}"
Value="{x:Bind Appearance.LineHeight, Mode=TwoWay}" />
</local:SettingContainer>
<!-- Font Weight -->
<local:SettingContainer x:Name="FontWeightContainer"
x:Uid="Profile_FontWeight"

View File

@ -874,6 +874,22 @@
<value>Size of the font in points.</value>
<comment>A description for what the "font size" setting does. Presented near "Profile_FontSize".</comment>
</data>
<data name="Profile_LineHeight.Header" xml:space="preserve">
<value>Line height</value>
<comment>Header for a control that sets the text line height.</comment>
</data>
<data name="Profile_LineHeightBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Line height</value>
<comment>Header for a control that sets the text line height.</comment>
</data>
<data name="Profile_LineHeight.HelpText" xml:space="preserve">
<value>Sets the height of each line in the terminal as a multiple of the font size. The default depends on your font and is usually around 1.2.</value>
<comment>A description for what the "line height" setting does. Presented near "Profile_LineHeight".</comment>
</data>
<data name="Profile_LineHeightBox.PlaceholderText" xml:space="preserve">
<value>1.2</value>
<comment>"1.2" is a decimal number.</comment>
</data>
<data name="Profile_FontWeightComboBox.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Font weight</value>
<comment>Name for a control to select the weight (i.e. bold, thin, etc.) of the text in the app.</comment>

View File

@ -295,7 +295,7 @@ Model::Profile CascadiaSettings::DuplicateProfile(const Model::Profile& source)
MTSM_PROFILE_SETTINGS(DUPLICATE_PROFILE_SETTINGS)
#undef DUPLICATE_PROFILE_SETTINGS
// These two aren't in MTSM_PROFILE_SETTINGS because they're special
// These aren't in MTSM_PROFILE_SETTINGS because they're special
DUPLICATE_SETTING_MACRO(TabColor);
DUPLICATE_SETTING_MACRO(Padding);

View File

@ -78,11 +78,6 @@ void FontConfig::LayerJson(const Json::Value& json)
}
}
bool FontConfig::HasAnyOptionSet() const
{
return HasFontFace() || HasFontSize() || HasFontWeight();
}
winrt::Microsoft::Terminal::Settings::Model::Profile FontConfig::SourceProfile()
{
return _sourceProfile.get();

View File

@ -35,7 +35,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
static winrt::com_ptr<FontConfig> CopyFontInfo(const FontConfig* source, winrt::weak_ref<Profile> sourceProfile);
Json::Value ToJson() const;
void LayerJson(const Json::Value& json);
bool HasAnyOptionSet() const;
Model::Profile SourceProfile();

View File

@ -18,8 +18,9 @@ namespace Microsoft.Terminal.Settings.Model
INHERITABLE_FONT_SETTING(String, FontFace);
INHERITABLE_FONT_SETTING(Single, 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);
INHERITABLE_FONT_SETTING(String, CellWidth);
INHERITABLE_FONT_SETTING(String, CellHeight);
}
}

View File

@ -100,7 +100,9 @@ Author(s):
X(float, FontSize, "size", DEFAULT_FONT_SIZE) \
X(winrt::Windows::UI::Text::FontWeight, FontWeight, "weight", DEFAULT_FONT_WEIGHT) \
X(IFontAxesMap, FontAxes, "axes") \
X(IFontFeatureMap, FontFeatures, "features")
X(IFontFeatureMap, FontFeatures, "features") \
X(winrt::hstring, CellWidth, "cellWidth") \
X(winrt::hstring, CellHeight, "cellHeight")
#define MTSM_APPEARANCE_SETTINGS(X) \
X(Core::CursorStyle, CursorShape, "cursorShape", Core::CursorStyle::Bar) \

View File

@ -324,11 +324,9 @@ Json::Value Profile::ToJson() const
MTSM_PROFILE_SETTINGS(PROFILE_SETTINGS_TO_JSON)
#undef PROFILE_SETTINGS_TO_JSON
// Font settings
const auto fontInfoImpl = winrt::get_self<FontConfig>(_FontInfo);
if (fontInfoImpl->HasAnyOptionSet())
if (auto fontJSON = winrt::get_self<FontConfig>(_FontInfo)->ToJson(); !fontJSON.empty())
{
json[JsonKey(FontInfoKey)] = winrt::get_self<FontConfig>(_FontInfo)->ToJson();
json[JsonKey(FontInfoKey)] = std::move(fontJSON);
}
if (_UnfocusedAppearance)

View File

@ -271,11 +271,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
_ProfileSource = profile.Source();
_UseAcrylic = profile.UseAcrylic();
_FontFace = profile.FontInfo().FontFace();
_FontSize = profile.FontInfo().FontSize();
_FontWeight = profile.FontInfo().FontWeight();
_FontFeatures = profile.FontInfo().FontFeatures();
_FontAxes = profile.FontInfo().FontAxes();
const auto fontInfo = profile.FontInfo();
_FontFace = fontInfo.FontFace();
_FontSize = fontInfo.FontSize();
_FontWeight = fontInfo.FontWeight();
_FontFeatures = fontInfo.FontFeatures();
_FontAxes = fontInfo.FontAxes();
_CellWidth = fontInfo.CellWidth();
_CellHeight = fontInfo.CellHeight();
_Padding = profile.Padding();
_Commandline = profile.Commandline();

View File

@ -126,6 +126,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
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, CellWidth);
INHERITABLE_SETTING(Model::TerminalSettings, hstring, CellHeight);
INHERITABLE_SETTING(Model::TerminalSettings, Model::ColorScheme, AppliedColorScheme);
INHERITABLE_SETTING(Model::TerminalSettings, hstring, BackgroundImage);

View File

@ -61,6 +61,8 @@
X(winrt::Windows::UI::Text::FontWeight, FontWeight) \
X(IFontFeatureMap, FontFeatures) \
X(IFontAxesMap, FontAxes) \
X(winrt::hstring, CellWidth) \
X(winrt::hstring, CellHeight) \
X(winrt::Microsoft::Terminal::Control::IKeyBindings, KeyBindings, nullptr) \
X(winrt::hstring, Commandline) \
X(winrt::hstring, StartingDirectory) \

View File

@ -627,20 +627,23 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
const auto designUnitsPerPx = fontSizeInPx / static_cast<float>(metrics.designUnitsPerEm);
const auto ascent = static_cast<float>(metrics.ascent) * designUnitsPerPx;
const auto descent = static_cast<float>(metrics.descent) * designUnitsPerPx;
const auto lineGap = static_cast<float>(metrics.lineGap) * designUnitsPerPx;
const auto underlinePosition = static_cast<float>(-metrics.underlinePosition) * designUnitsPerPx;
const auto underlineThickness = static_cast<float>(metrics.underlineThickness) * designUnitsPerPx;
const auto strikethroughPosition = static_cast<float>(-metrics.strikethroughPosition) * designUnitsPerPx;
const auto strikethroughThickness = static_cast<float>(metrics.strikethroughThickness) * designUnitsPerPx;
const auto advanceWidth = static_cast<float>(glyphMetrics.advanceWidth) * designUnitsPerPx;
const auto advanceHeight = ascent + descent + lineGap;
// NOTE: Line-gaps shouldn't be taken into account for lineHeight calculations.
// Terminals don't really have "gaps" between lines and instead the expectation
// is that two full block characters above each other don't leave any gaps
// between the lines. "Terminus TTF" for instance sets a line-gap of 90 units
// even though its font bitmap only covers the ascend/descend height.
const auto baseline = std::roundf(ascent);
const auto lineHeight = std::roundf(baseline + descent);
auto adjustedWidth = std::roundf(fontInfoDesired.GetCellWidth().Resolve(advanceWidth, _api.dpi, fontSizeInPx, advanceWidth));
auto adjustedHeight = std::roundf(fontInfoDesired.GetCellHeight().Resolve(advanceHeight, _api.dpi, fontSizeInPx, advanceWidth));
// Protection against bad user values in GetCellWidth/Y.
// AtlasEngine fails hard with 0 cell sizes.
adjustedWidth = std::max(1.0f, adjustedWidth);
adjustedHeight = std::max(1.0f, adjustedHeight);
const auto baseline = std::roundf(ascent + (lineGap + adjustedHeight - advanceHeight) / 2.0f);
const auto underlinePos = std::roundf(baseline + underlinePosition);
const auto underlineWidth = std::max(1.0f, std::roundf(underlineThickness));
const auto strikethroughPos = std::roundf(baseline + strikethroughPosition);
@ -667,10 +670,10 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
const auto doubleUnderlineGap = std::max(1.0f, std::roundf(1.2f / 72.0f * _api.dpi));
doubleUnderlinePosBottom = std::max(doubleUnderlinePosBottom, doubleUnderlinePosTop + doubleUnderlineGap + thinLineWidth);
// Our cells can't overlap each other so we additionally clamp the bottom line to be inside the cell boundaries.
doubleUnderlinePosBottom = std::min(doubleUnderlinePosBottom, lineHeight - thinLineWidth);
doubleUnderlinePosBottom = std::min(doubleUnderlinePosBottom, adjustedHeight - thinLineWidth);
const auto cellWidth = gsl::narrow<u16>(std::lroundf(advanceWidth));
const auto cellHeight = gsl::narrow<u16>(lineHeight);
const auto cellWidth = gsl::narrow<u16>(std::lroundf(adjustedWidth));
const auto cellHeight = gsl::narrow<u16>(std::lroundf(adjustedHeight));
{
til::size coordSize;
@ -708,7 +711,7 @@ void AtlasEngine::_resolveFontMetrics(const wchar_t* requestedFaceName, const Fo
fontMetrics->fontName = std::move(fontName);
fontMetrics->fontSizeInDIP = fontSizeInDIP;
fontMetrics->baselineInDIP = baseline / static_cast<float>(_api.dpi) * 96.0f;
fontMetrics->advanceScale = cellWidth / advanceWidth;
fontMetrics->advanceScale = cellWidth / adjustedWidth;
fontMetrics->cellSize = { cellWidth, cellHeight };
fontMetrics->fontWeight = fontWeightU16;
fontMetrics->underlinePos = underlinePosU16;

View File

@ -1148,6 +1148,14 @@ void AtlasEngine::_recreateFontDependentResources()
// fonts making them look fairly unslightly. With no option to easily disable this
// feature in Windows Terminal, it's better left disabled by default.
const DWRITE_LINE_SPACING lineSpacing{
.method = DWRITE_LINE_SPACING_METHOD_UNIFORM,
.height = _r.cellSizeDIP.y,
.baseline = _api.fontMetrics.baselineInDIP,
.fontLineGapUsage = DWRITE_FONT_LINE_GAP_USAGE_ENABLED,
};
THROW_IF_FAILED(textFormat.query<IDWriteTextFormat2>()->SetLineSpacing(&lineSpacing));
if (!_api.fontAxisValues.empty())
{
if (const auto textFormat3 = textFormat.try_query<IDWriteTextFormat3>())

View File

@ -412,8 +412,8 @@ namespace Microsoft::Console::Render
wil::com_ptr<IDWriteFontCollection> fontCollection;
wil::com_ptr<IDWriteFontFamily> fontFamily;
std::wstring fontName;
float baselineInDIP = 0.0f;
float fontSizeInDIP = 0.0f;
f32 baselineInDIP = 0.0f;
f32 fontSizeInDIP = 0.0f;
f32 advanceScale = 0;
u16x2 cellSize;
u16 fontWeight = 0;

View File

@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "../inc/CSSLengthPercentage.h"
CSSLengthPercentage CSSLengthPercentage::FromString(const wchar_t* str)
{
auto& errnoRef = errno; // Nonzero cost, pay it once.
errnoRef = 0;
wchar_t* end;
auto value = std::wcstof(str, &end);
if (str == end || errnoRef == ERANGE)
{
return {};
}
auto referenceFrame = ReferenceFrame::FontSize;
if (*end)
{
if (wcscmp(end, L"%") == 0)
{
value /= 100.0f;
}
else if (wcscmp(end, L"px") == 0)
{
referenceFrame = ReferenceFrame::Absolute;
value /= 96.0f;
}
else if (wcscmp(end, L"pt") == 0)
{
referenceFrame = ReferenceFrame::Absolute;
value /= 72.0f;
}
else if (wcscmp(end, L"ch") == 0)
{
referenceFrame = ReferenceFrame::AdvanceWidth;
}
else
{
return {};
}
}
CSSLengthPercentage obj;
obj._value = value;
obj._referenceFrame = referenceFrame;
return obj;
}
CSSLengthPercentage::ReferenceFrame CSSLengthPercentage::GetReferenceFrame() const noexcept
{
return _referenceFrame;
}
float CSSLengthPercentage::Resolve(float factor) const noexcept
{
return _value * factor;
}
float CSSLengthPercentage::Resolve(float fallback, float dpi, float fontSize, float advanceWidth) const noexcept
{
switch (_referenceFrame)
{
case ReferenceFrame::Absolute:
return _value * dpi;
case ReferenceFrame::FontSize:
return _value * fontSize;
case ReferenceFrame::AdvanceWidth:
return _value * advanceWidth;
default:
return fallback;
}
}

View File

@ -23,6 +23,22 @@ FontInfoDesired::FontInfoDesired(const FontInfo& fiFont) noexcept :
{
}
void FontInfoDesired::SetCellSize(const CSSLengthPercentage& cellWidth, const CSSLengthPercentage& cellHeight) noexcept
{
_cellWidth = cellWidth;
_cellHeight = cellHeight;
}
const CSSLengthPercentage& FontInfoDesired::GetCellWidth() const noexcept
{
return _cellWidth;
}
const CSSLengthPercentage& FontInfoDesired::GetCellHeight() const noexcept
{
return _cellHeight;
}
float FontInfoDesired::GetFontSize() const noexcept
{
return _fontSize;

View File

@ -11,6 +11,7 @@
<Import Project="$(SolutionDir)src\common.build.pre.props" />
<Import Project="$(SolutionDir)src\common.nugetversions.props" />
<ItemGroup>
<ClCompile Include="..\CSSLengthPercentage.cpp" />
<ClCompile Include="..\FontInfo.cpp" />
<ClCompile Include="..\FontInfoBase.cpp" />
<ClCompile Include="..\FontInfoDesired.cpp" />
@ -25,6 +26,7 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\..\inc\Cluster.hpp" />
<ClInclude Include="..\..\inc\CSSLengthPercentage.h" />
<ClInclude Include="..\..\inc\FontInfo.hpp" />
<ClInclude Include="..\..\inc\FontInfoBase.hpp" />
<ClInclude Include="..\..\inc\FontInfoDesired.hpp" />

View File

@ -45,6 +45,9 @@
<ClCompile Include="..\RenderSettings.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="..\CSSLengthPercentage.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\precomp.h">
@ -89,8 +92,11 @@
<ClInclude Include="..\FontCache.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="..\..\inc\CSSLengthPercentage.h">
<Filter>Header Files\inc</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
</ItemGroup>
</Project>
</Project>

View File

@ -706,6 +706,7 @@ std::vector<DWRITE_FONT_AXIS_VALUE> DxFontRenderData::GetAxisVector(const DWRITE
// - None
void DxFontRenderData::_BuildFontRenderData(const FontInfoDesired& desired, FontInfo& actual, const int dpi)
{
const auto dpiF = static_cast<float>(dpi);
auto 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.
@ -737,27 +738,20 @@ void DxFontRenderData::_BuildFontRenderData(const FontInfoDesired& desired, Font
// - 12 ppi font * (96 dpi / 96 dpi) * (96 dpi / 72 points per inch) = 16 pixels tall font for 100% display (96 dpi is 100%)
// - 12 ppi font * (144 dpi / 96 dpi) * (96 dpi / 72 points per inch) = 24 pixels tall font for 150% display (144 dpi is 150%)
// - 12 ppi font * (192 dpi / 96 dpi) * (96 dpi / 72 points per inch) = 32 pixels tall font for 200% display (192 dpi is 200%)
auto heightDesired = desired.GetEngineSize().height * USER_DEFAULT_SCREEN_DPI / POINTS_PER_INCH;
const auto heightDesired = desired.GetEngineSize().height / POINTS_PER_INCH * dpiF;
// The advance is the number of pixels left-to-right (X dimension) for the given font.
// We're finding a proportional factor here with the design units in "ems", not an actual pixel measurement.
// Now we play trickery with the font size. Scale by the DPI to get the height we expect.
heightDesired *= (static_cast<float>(dpi) / static_cast<float>(USER_DEFAULT_SCREEN_DPI));
const auto widthAdvance = static_cast<float>(advanceInDesignUnits) / 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.
const auto widthApprox = heightDesired * widthAdvance;
// Since we can't deal with columns of the presentation grid being fractional pixels in width, round to the nearest whole pixel.
const auto widthExact = round(widthApprox);
const auto widthAdvanceInPx = heightDesired * widthAdvance;
// Now reverse the "em" factor from above to turn the exact pixel width into a (probably) fractional
// height in pixels of each character. It's easier for us to pad out height and align vertically
// than it is horizontally.
const auto fontSize = widthExact / widthAdvance;
const auto fontSize = roundf(widthAdvanceInPx) / widthAdvance;
_fontSize = fontSize;
// Now figure out the basic properties of the character height which include ascent and descent
@ -809,8 +803,12 @@ void DxFontRenderData::_BuildFontRenderData(const FontInfoDesired& desired, Font
const auto fullPixelAscent = ceil(ascent + halfGap);
const auto fullPixelDescent = ceil(descent + halfGap);
lineSpacing.height = fullPixelAscent + fullPixelDescent;
lineSpacing.baseline = fullPixelAscent;
const auto defaultHeight = fullPixelAscent + fullPixelDescent;
const auto lineHeight = desired.GetCellHeight().Resolve(defaultHeight, dpiF, heightDesired, widthAdvanceInPx);
const auto baseline = fullPixelAscent + (lineHeight - defaultHeight) / 2.0f;
lineSpacing.height = roundf(lineHeight);
lineSpacing.baseline = roundf(baseline);
// According to MSDN (https://docs.microsoft.com/en-us/windows/win32/api/dwrite_3/ne-dwrite_3-dwrite_font_line_gap_usage)
// Setting "ENABLED" means we've included the line gapping in the spacing numbers given.
@ -818,6 +816,9 @@ void DxFontRenderData::_BuildFontRenderData(const FontInfoDesired& desired, Font
_lineSpacing = lineSpacing;
const auto widthApprox = desired.GetCellWidth().Resolve(widthAdvanceInPx, dpiF, heightDesired, widthAdvanceInPx);
const auto widthExact = roundf(widthApprox);
// The scaled size needs to represent the pixel box that each character will fit within for the purposes
// of hit testing math and other such multiplication/division.
til::size coordSize;
@ -861,8 +862,8 @@ void DxFontRenderData::_BuildFontRenderData(const FontInfoDesired& desired, Font
// Offsets are relative to the base line of the font, so we subtract
// from the ascent to get an offset relative to the top of the cell.
lineMetrics.underlineOffset = fullPixelAscent - lineMetrics.underlineOffset;
lineMetrics.strikethroughOffset = fullPixelAscent - lineMetrics.strikethroughOffset;
lineMetrics.underlineOffset = lineSpacing.baseline - lineMetrics.underlineOffset;
lineMetrics.strikethroughOffset = lineSpacing.baseline - lineMetrics.strikethroughOffset;
// For double underlines we need a second offset, just below the first,
// but with a bit of a gap (about double the grid line width).

View File

@ -0,0 +1,33 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
struct CSSLengthPercentage
{
enum class ReferenceFrame : uint8_t
{
// This indicates the object is empty/unset.
// No need to call Resolve().
None,
// Call Resolve() with factor set to the target DPI (e.g. 96 for DIP).
// Returns an absolute length value scaled by that DPI.
Absolute,
// Call Resolve() with factor set to the font size
// in an arbitrary DPI. Returns a value relative to it.
FontSize,
// Call Resolve() with factor set to the "0" glyph advance width
// in an arbitrary DPI. Returns a value relative to it.
AdvanceWidth,
};
static CSSLengthPercentage FromString(const wchar_t* str);
ReferenceFrame GetReferenceFrame() const noexcept;
float Resolve(float factor) const noexcept;
float Resolve(float fallback, float dpi, float fontSize, float advanceWidth) const noexcept;
private:
float _value = 0;
ReferenceFrame _referenceFrame = ReferenceFrame::None;
};

View File

@ -20,6 +20,7 @@ Author(s):
#include "FontInfoBase.hpp"
#include "FontInfo.hpp"
#include "CSSLengthPercentage.h"
class FontInfoDesired : public FontInfoBase
{
@ -33,6 +34,10 @@ public:
bool operator==(const FontInfoDesired& other) = delete;
void SetCellSize(const CSSLengthPercentage& cellWidth, const CSSLengthPercentage& cellHeight) noexcept;
const CSSLengthPercentage& GetCellWidth() const noexcept;
const CSSLengthPercentage& GetCellHeight() const noexcept;
float GetFontSize() const noexcept;
til::size GetEngineSize() const noexcept;
bool IsDefaultRasterFont() const noexcept;
@ -40,4 +45,6 @@ public:
private:
til::size _coordSizeDesired;
float _fontSize;
CSSLengthPercentage _cellWidth;
CSSLengthPercentage _cellHeight;
};