Add initial bold font support to the GDI renderer (#19441)

Render SGR1 as bold in 256 and true colors, where "bold is intense" is
not applicable.
Implemented by creating 2 extra fonts: bold for 1 and bold italic for 1
+ 3.

No non-trivial changes, just extensions.
LOGFONT also supports Underline and StrikeOut, but they seem to be
already covered by other means, so no combinatorial explosion of fonts
expected.

Refs #18919

Co-authored-by: Leonard Hecker <lhecker@microsoft.com>
This commit is contained in:
Alex Alabuzhev 2025-10-14 23:00:13 +01:00 committed by GitHub
parent 819987c90e
commit 84cc3e3e52
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 108 additions and 70 deletions

View File

@ -102,8 +102,25 @@ namespace Microsoft::Console::Render
HDC _hdcMemoryContext;
bool _isTrueTypeFont;
UINT _fontCodepage;
HFONT _hfont;
HFONT _hfontItalic;
enum class FontType : uint8_t
{
// Indices for _hfonts array below
Default,
Bold,
Italic,
BoldItalic,
// The number of fonts in _hfonts array below
FontCount,
// Other
Undefined = FontCount,
Soft
};
std::array<HFONT, static_cast<size_t>(FontType::FontCount)> _hfonts{};
TEXTMETRICW _tmFontMetrics;
FontResource _softFont;
@ -147,13 +164,6 @@ namespace Microsoft::Console::Render
COLORREF _lastFg;
COLORREF _lastBg;
enum class FontType : uint8_t
{
Undefined,
Default,
Italic,
Soft
};
FontType _lastFontType;
bool _fontHasWesternScript = false;
@ -194,8 +204,7 @@ namespace Microsoft::Console::Render
[[nodiscard]] HRESULT _GetProposedFont(const FontInfoDesired& FontDesired,
_Out_ FontInfo& Font,
const int iDpi,
_Inout_ wil::unique_hfont& hFont,
_Inout_ wil::unique_hfont& hFontItalic) noexcept;
_Inout_ std::array<wil::unique_hfont, static_cast<size_t>(FontType::FontCount)>& hFonts) noexcept;
til::size _GetFontSize() const;
bool _IsMinimized() const;

View File

@ -34,8 +34,6 @@ GdiEngine::GdiEngine() :
_currentLineRendition(LineRendition::SingleWidth),
_fPaintStarted(false),
_invalidCharacters{},
_hfont(nullptr),
_hfontItalic(nullptr),
_pool{ til::pmr::get_default_resource() }, // It's important the pool is first so it can be given to the others on construction.
_polyStrings{ &_pool },
_polyWidths{ &_pool }
@ -90,16 +88,13 @@ GdiEngine::~GdiEngine()
_hbitmapMemorySurface = nullptr;
}
if (_hfont != nullptr)
for (auto& hfont : _hfonts)
{
LOG_HR_IF(E_FAIL, !(DeleteObject(_hfont)));
_hfont = nullptr;
}
if (_hfontItalic != nullptr)
{
LOG_HR_IF(E_FAIL, !(DeleteObject(_hfontItalic)));
_hfontItalic = nullptr;
if (hfont != nullptr)
{
LOG_HR_IF(E_FAIL, !(DeleteObject(hfont)));
hfont = nullptr;
}
}
if (_hdcMemoryContext != nullptr)
@ -300,24 +295,48 @@ GdiEngine::~GdiEngine()
}
// If the font type has changed, select an appropriate font variant or soft font.
const auto usingItalicFont = textAttributes.IsItalic();
const auto fontType = usingSoftFont ? FontType::Soft :
usingItalicFont ? FontType::Italic :
FontType::Default;
const auto fontType = [&] {
if (usingSoftFont)
{
return FontType::Soft;
}
// TODO: GH-18919 "Intense text as bold" option
const auto usingBoldFont = textAttributes.IsBold(false);
const auto usingItalicFont = textAttributes.IsItalic();
if (usingBoldFont && !usingItalicFont)
{
return FontType::Bold;
}
if (!usingBoldFont && usingItalicFont)
{
return FontType::Italic;
}
if (usingBoldFont && usingItalicFont)
{
return FontType::BoldItalic;
}
return FontType::Default;
}();
if (fontType != _lastFontType)
{
switch (fontType)
{
case FontType::Default:
case FontType::Bold:
case FontType::Italic:
case FontType::BoldItalic:
SelectFont(_hdcMemoryContext, _hfonts[static_cast<size_t>(fontType)]);
break;
case FontType::Soft:
SelectFont(_hdcMemoryContext, _softFont);
break;
case FontType::Italic:
SelectFont(_hdcMemoryContext, _hfontItalic);
break;
case FontType::Default:
default:
SelectFont(_hdcMemoryContext, _hfont);
break;
__assume(false);
}
_lastFontType = fontType;
_fontHasWesternScript = FontHasWesternScript(_hdcMemoryContext);
@ -336,11 +355,11 @@ GdiEngine::~GdiEngine()
// - S_OK if set successfully or relevant GDI error via HRESULT.
[[nodiscard]] HRESULT GdiEngine::UpdateFont(const FontInfoDesired& FontDesired, _Out_ FontInfo& Font) noexcept
{
wil::unique_hfont hFont, hFontItalic;
RETURN_IF_FAILED(_GetProposedFont(FontDesired, Font, _iCurrentDpi, hFont, hFontItalic));
std::array<wil::unique_hfont, static_cast<size_t>(FontType::FontCount)> hFonts;
RETURN_IF_FAILED(_GetProposedFont(FontDesired, Font, _iCurrentDpi, hFonts));
// Select into DC
RETURN_HR_IF_NULL(E_FAIL, SelectFont(_hdcMemoryContext, hFont.get()));
RETURN_HR_IF_NULL(E_FAIL, SelectFont(_hdcMemoryContext, hFonts[static_cast<size_t>(FontType::Default)].get()));
// Save off the font metrics for various other calculations
RETURN_HR_IF(E_FAIL, !(GetTextMetricsW(_hdcMemoryContext, &_tmFontMetrics)));
@ -454,26 +473,19 @@ GdiEngine::~GdiEngine()
// Now find the size of a 0 in this current font and save it for conversions done later.
_coordFontLast = Font.GetSize();
// Persist font for cleanup (and free existing if necessary)
if (_hfont != nullptr)
for (auto& hfont : _hfonts)
{
LOG_HR_IF(E_FAIL, !(DeleteObject(_hfont)));
_hfont = nullptr;
// Persist font for cleanup (and free existing if necessary)
if (hfont != nullptr)
{
LOG_HR_IF(E_FAIL, !(DeleteObject(hfont)));
hfont = nullptr;
}
// Save the font.
hfont = hFonts[&hfont - _hfonts.data()].release();
}
// Save the font.
_hfont = hFont.release();
// Persist italic font for cleanup (and free existing if necessary)
if (_hfontItalic != nullptr)
{
LOG_HR_IF(E_FAIL, !(DeleteObject(_hfontItalic)));
_hfontItalic = nullptr;
}
// Save the italic font.
_hfontItalic = hFontItalic.release();
// Save raster vs. TrueType and codepage data in case we need to convert.
_isTrueTypeFont = Font.IsTrueTypeFont();
_fontCodepage = Font.GetCodePage();
@ -500,10 +512,10 @@ GdiEngine::~GdiEngine()
{
// If we previously called SelectFont(_hdcMemoryContext, _softFont), it will
// still hold a reference to the _softFont object we're planning to overwrite.
// --> First revert back to the standard _hfont, lest we have dangling pointers.
// --> First revert back to the standard font, lest we have dangling pointers.
if (_lastFontType == FontType::Soft)
{
RETURN_HR_IF_NULL(E_FAIL, SelectFont(_hdcMemoryContext, _hfont));
RETURN_HR_IF_NULL(E_FAIL, SelectFont(_hdcMemoryContext, _hfonts[static_cast<size_t>(FontType::Default)]));
_lastFontType = FontType::Default;
}
@ -550,8 +562,8 @@ GdiEngine::~GdiEngine()
// - S_OK if set successfully or relevant GDI error via HRESULT.
[[nodiscard]] HRESULT GdiEngine::GetProposedFont(const FontInfoDesired& FontDesired, _Out_ FontInfo& Font, const int iDpi) noexcept
{
wil::unique_hfont hFont, hFontItalic;
return _GetProposedFont(FontDesired, Font, iDpi, hFont, hFontItalic);
std::array<wil::unique_hfont, static_cast<size_t>(FontType::FontCount)> hFonts;
return _GetProposedFont(FontDesired, Font, iDpi, hFonts);
}
// Method Description:
@ -576,15 +588,13 @@ GdiEngine::~GdiEngine()
// - FontDesired - reference to font information we should use while instantiating a font.
// - Font - the actual font
// - iDpi - The DPI we will have when rendering
// - hFont - A smart pointer to receive a handle to a ready-to-use GDI font.
// - hFontItalic - A smart pointer to receive a handle to an italic variant of the font.
// - hFonts - An array of smart pointers to receive handles to ready-to-use GDI fonts, corresponding to FontType enum.
// Return Value:
// - S_OK if set successfully or relevant GDI error via HRESULT.
[[nodiscard]] HRESULT GdiEngine::_GetProposedFont(const FontInfoDesired& FontDesired,
_Out_ FontInfo& Font,
const int iDpi,
_Inout_ wil::unique_hfont& hFont,
_Inout_ wil::unique_hfont& hFontItalic) noexcept
_Inout_ std::array<wil::unique_hfont, static_cast<size_t>(FontType::FontCount)>& hFonts) noexcept
{
wil::unique_hdc hdcTemp(CreateCompatibleDC(_hdcMemoryContext));
RETURN_HR_IF_NULL(E_FAIL, hdcTemp.get());
@ -600,8 +610,10 @@ GdiEngine::~GdiEngine()
// We do this because, for instance, if we ask GDI for an 8x12 OEM_FIXED_FONT,
// it may very well decide to choose Courier New instead of the Terminal raster.
#pragma prefast(suppress : 38037, "raster fonts get special handling, we need to get it this way")
hFont.reset((HFONT)GetStockObject(OEM_FIXED_FONT));
hFontItalic.reset((HFONT)GetStockObject(OEM_FIXED_FONT));
for (auto& hFont : hFonts)
{
hFont.reset((HFONT)GetStockObject(OEM_FIXED_FONT));
}
}
else
{
@ -658,18 +670,35 @@ GdiEngine::~GdiEngine()
FontDesired.FillLegacyNameBuffer(lf.lfFaceName);
// Create font.
hFont.reset(CreateFontIndirectW(&lf));
RETURN_HR_IF_NULL(E_FAIL, hFont.get());
struct FontStyle
{
LONG weight;
BYTE italic;
};
// Create italic variant of the font.
lf.lfItalic = TRUE;
hFontItalic.reset(CreateFontIndirectW(&lf));
RETURN_HR_IF_NULL(E_FAIL, hFontItalic.get());
static constexpr FontStyle s_fontStyles[] = {
{ FW_NORMAL, FALSE }, // Default
{ FW_BOLD, FALSE }, // Bold
{ FW_NORMAL, TRUE }, // Italic
{ FW_BOLD, TRUE }, // BoldItalic
};
static_assert(std::size(s_fontStyles) == static_cast<size_t>(FontType::FontCount));
for (const auto& style : s_fontStyles)
{
lf.lfWeight = style.weight == FW_NORMAL ? FontDesired.GetWeight() : style.weight;
lf.lfItalic = style.italic;
// Create font.
auto& hFont = hFonts[&style - s_fontStyles];
hFont.reset(CreateFontIndirectW(&lf));
RETURN_HR_IF_NULL(E_FAIL, hFont.get());
}
}
// Select into DC
wil::unique_hfont hFontOld(SelectFont(hdcTemp.get(), hFont.get()));
wil::unique_hfont hFontOld(SelectFont(hdcTemp.get(), hFonts[static_cast<size_t>(FontType::Default)].get()));
RETURN_HR_IF_NULL(E_FAIL, hFontOld.get());
// Save off the font metrics for various other calculations