diff --git a/src/renderer/dx/CustomTextLayout.cpp b/src/renderer/dx/CustomTextLayout.cpp index 43b491cb54e..41a677c7bbb 100644 --- a/src/renderer/dx/CustomTextLayout.cpp +++ b/src/renderer/dx/CustomTextLayout.cpp @@ -4,6 +4,7 @@ #include "precomp.h" #include "CustomTextLayout.h" +#include "CustomTextRenderer.h" #include #include @@ -19,19 +20,27 @@ using namespace Microsoft::Console::Render; // - factory - DirectWrite factory reference in case we need other DirectWrite objects for our layout // - analyzer - DirectWrite text analyzer from the factory that has been cached at a level above this layout (expensive to create) // - format - The DirectWrite format object representing the size and other text properties to be applied (by default) to a layout +// - formatItalic - The italic variant of the format object representing the size and other text properties for italic text // - font - The DirectWrite font face to use while calculating layout (by default, will fallback if necessary) +// - fontItalic - The italic variant of the font face to use while calculating layout for italic text // - width - The count of pixels available per column (the expected pixel width of every column) // - boxEffect - Box drawing scaling effects that are cached for the base font across layouts. CustomTextLayout::CustomTextLayout(gsl::not_null const factory, gsl::not_null const analyzer, gsl::not_null const format, + gsl::not_null const formatItalic, gsl::not_null const font, + gsl::not_null const fontItalic, size_t const width, IBoxDrawingEffect* const boxEffect) : _factory{ factory.get() }, _analyzer{ analyzer.get() }, _format{ format.get() }, + _formatItalic{ formatItalic.get() }, + _formatInUse{ format.get() }, _font{ font.get() }, + _fontItalic{ fontItalic.get() }, + _fontInUse{ font.get() }, _boxDrawingEffect{ boxEffect }, _localeName{}, _numberSubstitution{}, @@ -113,6 +122,9 @@ CATCH_RETURN() RETURN_HR_IF_NULL(E_INVALIDARG, columns); *columns = 0; + _formatInUse = _format.Get(); + _fontInUse = _font.Get(); + RETURN_IF_FAILED(_AnalyzeTextComplexity()); RETURN_IF_FAILED(_AnalyzeRuns()); RETURN_IF_FAILED(_ShapeGlyphRuns()); @@ -144,6 +156,10 @@ CATCH_RETURN() FLOAT originX, FLOAT originY) noexcept { + const auto drawingContext = static_cast(clientDrawingContext); + _formatInUse = drawingContext->useItalicFont ? _formatItalic.Get() : _format.Get(); + _fontInUse = drawingContext->useItalicFont ? _fontItalic.Get() : _font.Get(); + RETURN_IF_FAILED(_AnalyzeTextComplexity()); RETURN_IF_FAILED(_AnalyzeRuns()); RETURN_IF_FAILED(_ShapeGlyphRuns()); @@ -183,7 +199,7 @@ CATCH_RETURN() const HRESULT hr = _analyzer->GetTextComplexity( _text.c_str(), textLength, - _font.Get(), + _fontInUse, &isTextSimple, &uiLengthRead, &_glyphIndices.at(glyphStart)); @@ -240,7 +256,7 @@ CATCH_RETURN() { if (!run.fontFace) { - run.fontFace = _font; + run.fontFace = _fontInUse; } } @@ -360,7 +376,7 @@ CATCH_RETURN() USHORT designUnitsPerEm = metrics.designUnitsPerEm; - RETURN_IF_FAILED(_font->GetDesignGlyphAdvances( + RETURN_IF_FAILED(_fontInUse->GetDesignGlyphAdvances( textLength, &_glyphIndices.at(glyphStart), &_glyphDesignUnitAdvances.at(glyphStart), @@ -368,7 +384,7 @@ CATCH_RETURN() for (size_t i = glyphStart; i < _glyphAdvances.size(); i++) { - _glyphAdvances.at(i) = (float)_glyphDesignUnitAdvances.at(i) / designUnitsPerEm * _format->GetFontSize() * run.fontScale; + _glyphAdvances.at(i) = (float)_glyphDesignUnitAdvances.at(i) / designUnitsPerEm * _formatInUse->GetFontSize() * run.fontScale; } // Set all the clusters as sequential. In a simple run, we're going 1 to 1. @@ -433,7 +449,7 @@ CATCH_RETURN() _glyphAdvances.resize(std::max(gsl::narrow_cast(glyphStart) + gsl::narrow_cast(actualGlyphCount), _glyphAdvances.size())); _glyphOffsets.resize(std::max(gsl::narrow_cast(glyphStart) + gsl::narrow_cast(actualGlyphCount), _glyphOffsets.size())); - const auto fontSizeFormat = _format->GetFontSize(); + const auto fontSizeFormat = _formatInUse->GetFontSize(); const auto fontSize = fontSizeFormat * run.fontScale; hr = _analyzer->GetGlyphPlacements( @@ -913,7 +929,7 @@ CATCH_RETURN(); // internal storage representation into something that matches DWrite's structures. DWRITE_GLYPH_RUN glyphRun; glyphRun.bidiLevel = run.bidiLevel; - glyphRun.fontEmSize = _format->GetFontSize() * run.fontScale; + glyphRun.fontEmSize = _formatInUse->GetFontSize() * run.fontScale; glyphRun.fontFace = run.fontFace.Get(); glyphRun.glyphAdvances = &_glyphAdvances.at(run.glyphStart); glyphRun.glyphCount = run.glyphCount; @@ -1226,7 +1242,7 @@ CATCH_RETURN(); { // Get the font fallback first ::Microsoft::WRL::ComPtr format1; - if (FAILED(_format.As(&format1))) + if (FAILED(_formatInUse->QueryInterface(IID_PPV_ARGS(&format1)))) { // If IDWriteTextFormat1 does not exist, return directly as this OS version doesn't have font fallback. return S_FALSE; @@ -1318,7 +1334,7 @@ CATCH_RETURN(); } else { - run.fontFace = _font; + run.fontFace = _fontInUse; } // Store the font scale as well. @@ -1458,7 +1474,7 @@ try else { ::Microsoft::WRL::ComPtr eff; - RETURN_IF_FAILED(s_CalculateBoxEffect(_format.Get(), _width, run.fontFace.Get(), run.fontScale, &eff)); + RETURN_IF_FAILED(s_CalculateBoxEffect(_formatInUse, _width, run.fontFace.Get(), run.fontScale, &eff)); // store data in the run run.drawingEffect = std::move(eff); diff --git a/src/renderer/dx/CustomTextLayout.h b/src/renderer/dx/CustomTextLayout.h index fe197425794..ca9fd9b9651 100644 --- a/src/renderer/dx/CustomTextLayout.h +++ b/src/renderer/dx/CustomTextLayout.h @@ -22,8 +22,10 @@ namespace Microsoft::Console::Render CustomTextLayout(gsl::not_null const factory, gsl::not_null const analyzer, - gsl::not_null const format, - gsl::not_null const font, + gsl::not_null const normalFormat, + gsl::not_null const italicFormat, + gsl::not_null const normalFont, + gsl::not_null const italicFont, size_t const width, IBoxDrawingEffect* const boxEffect); @@ -160,11 +162,15 @@ namespace Microsoft::Console::Render // DirectWrite analyzer const ::Microsoft::WRL::ComPtr _analyzer; - // DirectWrite text format + // DirectWrite text formats const ::Microsoft::WRL::ComPtr _format; + const ::Microsoft::WRL::ComPtr _formatItalic; + IDWriteTextFormat* _formatInUse; - // DirectWrite font face + // DirectWrite font faces const ::Microsoft::WRL::ComPtr _font; + const ::Microsoft::WRL::ComPtr _fontItalic; + IDWriteFontFace1* _fontInUse; // Box drawing effect const ::Microsoft::WRL::ComPtr _boxDrawingEffect; diff --git a/src/renderer/dx/CustomTextRenderer.h b/src/renderer/dx/CustomTextRenderer.h index 3ff50664434..5d25bd62828 100644 --- a/src/renderer/dx/CustomTextRenderer.h +++ b/src/renderer/dx/CustomTextRenderer.h @@ -24,6 +24,7 @@ namespace Microsoft::Console::Render renderTarget(renderTarget), foregroundBrush(foregroundBrush), backgroundBrush(backgroundBrush), + useItalicFont(false), forceGrayscaleAA(forceGrayscaleAA), dwriteFactory(dwriteFactory), spacing(spacing), @@ -37,6 +38,7 @@ namespace Microsoft::Console::Render ID2D1RenderTarget* renderTarget; ID2D1SolidColorBrush* foregroundBrush; ID2D1SolidColorBrush* backgroundBrush; + bool useItalicFont; bool forceGrayscaleAA; IDWriteFactory* dwriteFactory; DWRITE_LINE_SPACING spacing; diff --git a/src/renderer/dx/DxRenderer.cpp b/src/renderer/dx/DxRenderer.cpp index 265223d7136..a4ec368761a 100644 --- a/src/renderer/dx/DxRenderer.cpp +++ b/src/renderer/dx/DxRenderer.cpp @@ -1909,6 +1909,7 @@ CATCH_RETURN() // If we have a drawing context, it may be choosing its antialiasing based // on the colors. Update it if it exists. + // Also record whether we need to render the text with an italic font. // We only need to do this here because this is called all the time on painting frames // and will update it in a timely fashion. Changing the AA mode or opacity do affect // it, but we will always hit updating the drawing brushes so we don't @@ -1916,6 +1917,7 @@ CATCH_RETURN() if (_drawingContext) { _drawingContext->forceGrayscaleAA = _ShouldForceGrayscaleAA(); + _drawingContext->useItalicFont = textAttributes.IsItalic(); } if (textAttributes.IsHyperlink()) @@ -1943,8 +1945,10 @@ try fiFontInfo, _dpi, _dwriteTextFormat, + _dwriteTextFormatItalic, _dwriteTextAnalyzer, _dwriteFontFace, + _dwriteFontFaceItalic, _lineMetrics)); _glyphCell = fiFontInfo.GetSize(); @@ -1952,8 +1956,15 @@ try // 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. RETURN_IF_FAILED(CustomTextLayout::s_CalculateBoxEffect(_dwriteTextFormat.Get(), _glyphCell.width(), _dwriteFontFace.Get(), 1.0f, &_boxDrawingEffect)); - // Prepare the text layout - _customLayout = WRL::Make(_dwriteFactory.Get(), _dwriteTextAnalyzer.Get(), _dwriteTextFormat.Get(), _dwriteFontFace.Get(), _glyphCell.width(), _boxDrawingEffect.Get()); + // Prepare the text layout. + _customLayout = WRL::Make(_dwriteFactory.Get(), + _dwriteTextAnalyzer.Get(), + _dwriteTextFormat.Get(), + _dwriteTextFormatItalic.Get(), + _dwriteFontFace.Get(), + _dwriteFontFaceItalic.Get(), + _glyphCell.width(), + _boxDrawingEffect.Get()); return S_OK; } @@ -2033,16 +2044,20 @@ float DxEngine::GetScaling() const noexcept int const iDpi) noexcept { Microsoft::WRL::ComPtr format; + Microsoft::WRL::ComPtr formatItalic; Microsoft::WRL::ComPtr analyzer; Microsoft::WRL::ComPtr face; + Microsoft::WRL::ComPtr faceItalic; LineMetrics lineMetrics; return _GetProposedFont(pfiFontInfoDesired, pfiFontInfo, iDpi, format, + formatItalic, analyzer, face, + faceItalic, lineMetrics); } @@ -2311,8 +2326,10 @@ CATCH_RETURN(); FontInfo& actual, const int dpi, Microsoft::WRL::ComPtr& textFormat, + Microsoft::WRL::ComPtr& textFormatItalic, Microsoft::WRL::ComPtr& textAnalyzer, Microsoft::WRL::ComPtr& fontFace, + Microsoft::WRL::ComPtr& fontFaceItalic, LineMetrics& lineMetrics) const noexcept { try @@ -2447,11 +2464,33 @@ CATCH_RETURN(); THROW_IF_FAILED(format.As(&textFormat)); + // We also need to create an italic variant of the font face and text + // format, based on the same parameters, but using an italic style. + std::wstring fontNameItalic = fontName; + DWRITE_FONT_WEIGHT weightItalic = weight; + DWRITE_FONT_STYLE styleItalic = DWRITE_FONT_STYLE_ITALIC; + DWRITE_FONT_STRETCH stretchItalic = stretch; + + const auto faceItalic = _ResolveFontFaceWithFallback(fontNameItalic, weightItalic, stretchItalic, styleItalic, fontLocaleName); + + Microsoft::WRL::ComPtr formatItalic; + THROW_IF_FAILED(_dwriteFactory->CreateTextFormat(fontNameItalic.data(), + nullptr, + weightItalic, + styleItalic, + stretchItalic, + fontSize, + localeName.data(), + &formatItalic)); + + THROW_IF_FAILED(formatItalic.As(&textFormatItalic)); + Microsoft::WRL::ComPtr analyzer; THROW_IF_FAILED(_dwriteFactory->CreateTextAnalyzer(&analyzer)); THROW_IF_FAILED(analyzer.As(&textAnalyzer)); fontFace = face; + fontFaceItalic = faceItalic; THROW_IF_FAILED(textFormat->SetLineSpacing(lineSpacing.method, lineSpacing.height, lineSpacing.baseline)); THROW_IF_FAILED(textFormat->SetParagraphAlignment(DWRITE_PARAGRAPH_ALIGNMENT_NEAR)); diff --git a/src/renderer/dx/DxRenderer.hpp b/src/renderer/dx/DxRenderer.hpp index 601289d4ef4..49d0ab55039 100644 --- a/src/renderer/dx/DxRenderer.hpp +++ b/src/renderer/dx/DxRenderer.hpp @@ -196,7 +196,9 @@ namespace Microsoft::Console::Render ::Microsoft::WRL::ComPtr _dwriteFactory; ::Microsoft::WRL::ComPtr _dwriteTextFormat; + ::Microsoft::WRL::ComPtr _dwriteTextFormatItalic; ::Microsoft::WRL::ComPtr _dwriteFontFace; + ::Microsoft::WRL::ComPtr _dwriteFontFaceItalic; ::Microsoft::WRL::ComPtr _dwriteTextAnalyzer; ::Microsoft::WRL::ComPtr _customLayout; ::Microsoft::WRL::ComPtr _customRenderer; @@ -317,8 +319,10 @@ namespace Microsoft::Console::Render FontInfo& actual, const int dpi, ::Microsoft::WRL::ComPtr& textFormat, + ::Microsoft::WRL::ComPtr& textFormatItalic, ::Microsoft::WRL::ComPtr& textAnalyzer, ::Microsoft::WRL::ComPtr& fontFace, + ::Microsoft::WRL::ComPtr& fontFaceItalic, LineMetrics& lineMetrics) const noexcept; [[nodiscard]] til::size _GetClientSize() const; diff --git a/src/renderer/gdi/gdirenderer.hpp b/src/renderer/gdi/gdirenderer.hpp index 65a8c0248c9..28f745039cd 100644 --- a/src/renderer/gdi/gdirenderer.hpp +++ b/src/renderer/gdi/gdirenderer.hpp @@ -87,6 +87,7 @@ namespace Microsoft::Console::Render bool _isTrueTypeFont; UINT _fontCodepage; HFONT _hfont; + HFONT _hfontItalic; TEXTMETRICW _tmFontMetrics; static const size_t s_cPolyTextCache = 80; @@ -122,6 +123,7 @@ namespace Microsoft::Console::Render COLORREF _lastFg; COLORREF _lastBg; + bool _lastFontItalic; [[nodiscard]] HRESULT _InvalidCombine(const RECT* const prc) noexcept; [[nodiscard]] HRESULT _InvalidOffset(const POINT* const ppt) noexcept; @@ -152,7 +154,8 @@ namespace Microsoft::Console::Render [[nodiscard]] HRESULT _GetProposedFont(const FontInfoDesired& FontDesired, _Out_ FontInfo& Font, const int iDpi, - _Inout_ wil::unique_hfont& hFont) noexcept; + _Inout_ wil::unique_hfont& hFont, + _Inout_ wil::unique_hfont& hFontItalic) noexcept; COORD _GetFontSize() const; bool _IsMinimized() const; diff --git a/src/renderer/gdi/state.cpp b/src/renderer/gdi/state.cpp index b7891803c56..fd03bb45ad4 100644 --- a/src/renderer/gdi/state.cpp +++ b/src/renderer/gdi/state.cpp @@ -29,8 +29,10 @@ GdiEngine::GdiEngine() : _fInvalidRectUsed(false), _lastFg(INVALID_COLOR), _lastBg(INVALID_COLOR), + _lastFontItalic(false), _fPaintStarted(false), - _hfont((HFONT)INVALID_HANDLE_VALUE) + _hfont(nullptr), + _hfontItalic(nullptr) { ZeroMemory(_pPolyText, sizeof(POLYTEXTW) * s_cPolyTextCache); _rcInvalid = { 0 }; @@ -88,6 +90,12 @@ GdiEngine::~GdiEngine() _hfont = nullptr; } + if (_hfontItalic != nullptr) + { + LOG_HR_IF(E_FAIL, !(DeleteObject(_hfontItalic))); + _hfontItalic = nullptr; + } + if (_hdcMemoryContext != nullptr) { LOG_HR_IF(E_FAIL, !(DeleteObject(_hdcMemoryContext))); @@ -128,6 +136,9 @@ GdiEngine::~GdiEngine() LOG_HR_IF_NULL(E_FAIL, SelectFont(_hdcMemoryContext, _hfont)); } + // Record the fact that the selected font is not italic. + _lastFontItalic = false; + if (nullptr != hdcRealWindow) { LOG_HR_IF(E_FAIL, !(ReleaseDC(_hwndTargetWindow, hdcRealWindow))); @@ -210,6 +221,14 @@ GdiEngine::~GdiEngine() RETURN_IF_FAILED(s_SetWindowLongWHelper(_hwndTargetWindow, GWL_CONSOLE_BKCOLOR, colorBackground)); } + // If the italic attribute has changed, select an appropriate font variant. + const auto fontItalic = textAttributes.IsItalic(); + if (fontItalic != _lastFontItalic) + { + SelectFont(_hdcMemoryContext, fontItalic ? _hfontItalic : _hfont); + _lastFontItalic = fontItalic; + } + return S_OK; } @@ -223,12 +242,15 @@ 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; - RETURN_IF_FAILED(_GetProposedFont(FontDesired, Font, _iCurrentDpi, hFont)); + wil::unique_hfont hFont, hFontItalic; + RETURN_IF_FAILED(_GetProposedFont(FontDesired, Font, _iCurrentDpi, hFont, hFontItalic)); // Select into DC RETURN_HR_IF_NULL(E_FAIL, SelectFont(_hdcMemoryContext, hFont.get())); + // Record the fact that the selected font is not italic. + _lastFontItalic = false; + // Save off the font metrics for various other calculations RETURN_HR_IF(E_FAIL, !(GetTextMetricsW(_hdcMemoryContext, &_tmFontMetrics))); @@ -300,6 +322,16 @@ GdiEngine::~GdiEngine() // 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(); @@ -346,8 +378,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; - return _GetProposedFont(FontDesired, Font, iDpi, hFont); + wil::unique_hfont hFont, hFontItalic; + return _GetProposedFont(FontDesired, Font, iDpi, hFont, hFontItalic); } // Method Description: @@ -373,12 +405,14 @@ GdiEngine::~GdiEngine() // - 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. // 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) noexcept + _Inout_ wil::unique_hfont& hFont, + _Inout_ wil::unique_hfont& hFontItalic) noexcept { wil::unique_hdc hdcTemp(CreateCompatibleDC(_hdcMemoryContext)); RETURN_HR_IF_NULL(E_FAIL, hdcTemp.get()); @@ -395,6 +429,7 @@ GdiEngine::~GdiEngine() // 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)); } else { @@ -454,6 +489,11 @@ GdiEngine::~GdiEngine() // Create font. hFont.reset(CreateFontIndirectW(&lf)); RETURN_HR_IF_NULL(E_FAIL, hFont.get()); + + // Create italic variant of the font. + lf.lfItalic = TRUE; + hFontItalic.reset(CreateFontIndirectW(&lf)); + RETURN_HR_IF_NULL(E_FAIL, hFontItalic.get()); } // Select into DC