diff --git a/Common/Gfx/CanvasD2D.cpp b/Common/Gfx/CanvasD2D.cpp index c8ca8057..3b0276bf 100644 --- a/Common/Gfx/CanvasD2D.cpp +++ b/Common/Gfx/CanvasD2D.cpp @@ -313,13 +313,13 @@ void CanvasD2D::DrawTextW(const WCHAR* str, UINT strLen, const TextFormat& forma { TextFormatD2D& formatD2D = (TextFormatD2D&)format; formatD2D.CreateLayout( - str, strLen, rect.Width, rect.Height); + str, strLen, rect.Width, rect.Height, !m_AccurateText && m_TextAntiAliasing); - const float xOffset = formatD2D.m_TextFormat->GetFontSize() / 6.0f; const float xPos = [&]() { if (!m_AccurateText) { + const float xOffset = formatD2D.m_TextFormat->GetFontSize() / 6.0f; switch (formatD2D.GetHorizontalAlignment()) { case HorizontalAlignment::Left: return rect.X + xOffset; @@ -330,22 +330,11 @@ void CanvasD2D::DrawTextW(const WCHAR* str, UINT strLen, const TextFormat& forma return rect.X; } (); - if (!m_AccurateText && m_TextAntiAliasing) - { - const float emOffset = xOffset / 25.0f; - - DWRITE_TEXT_RANGE range = {0, strLen}; - Microsoft::WRL::ComPtr textLayout; - formatD2D.m_TextLayout.As(&textLayout); - - textLayout->SetCharacterSpacing(emOffset, emOffset, 0.0f, range); - } - // TODO: Check for transformation. m_Target->PushAxisAlignedClip(ToRectF(rect), D2D1_ANTIALIAS_MODE_ALIASED); m_Target->DrawTextLayout( - D2D1::Point2F(xPos, rect.Y - 1.0f), + D2D1::Point2F(xPos, rect.Y - formatD2D.m_LineGap), formatD2D.m_TextLayout.Get(), solidBrush.Get()); @@ -355,52 +344,27 @@ void CanvasD2D::DrawTextW(const WCHAR* str, UINT strLen, const TextFormat& forma bool CanvasD2D::MeasureTextW(const WCHAR* str, UINT strLen, const TextFormat& format, Gdiplus::RectF& rect) { - Microsoft::WRL::ComPtr textLayout; - HRESULT hr = c_DWFactory->CreateTextLayout( - str, - strLen, - ((TextFormatD2D&)format).m_TextFormat.Get(), - 10000, - 10000, - textLayout.GetAddressOf()); - if (SUCCEEDED(hr)) - { - const DWRITE_TEXT_METRICS metrics = - Util::GetAdjustedDWriteTextLayoutMetrics(textLayout.Get(), !m_AccurateText); - rect.Width = metrics.width; - rect.Height = metrics.height; - return true; - } - - return false; + TextFormatD2D& formatD2D = (TextFormatD2D&)format; + const DWRITE_TEXT_METRICS metrics = formatD2D.GetMetrics(str, strLen, !m_AccurateText); + rect.Width = metrics.width; + rect.Height = metrics.height; + return true; } bool CanvasD2D::MeasureTextLinesW(const WCHAR* str, UINT strLen, const TextFormat& format, Gdiplus::RectF& rect, UINT& lines) { - ((TextFormatD2D&)format).m_TextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP); + TextFormatD2D& formatD2D = (TextFormatD2D&)format; + formatD2D.m_TextFormat->SetWordWrapping(DWRITE_WORD_WRAPPING_WRAP); - Microsoft::WRL::ComPtr textLayout; - HRESULT hr = c_DWFactory->CreateTextLayout( - str, - strLen, - ((TextFormatD2D&)format).m_TextFormat.Get(), - rect.Width, - 10000, - textLayout.GetAddressOf()); - if (SUCCEEDED(hr)) - { - const DWRITE_TEXT_METRICS metrics = - Util::GetAdjustedDWriteTextLayoutMetrics(textLayout.Get(), !m_AccurateText); - rect.Width = metrics.width; - lines = metrics.lineCount; + const DWRITE_TEXT_METRICS metrics = formatD2D.GetMetrics(str, strLen, !m_AccurateText, rect.Width); + rect.Width = metrics.width; + rect.Height = metrics.height; + lines = metrics.lineCount; - // GDI+ draws multi-line text even though the last line may be clipped slightly at the bottom. - // This is a workaround to emulate that behaviour. - rect.Height = metrics.height + 1.0f; - return true; - } - - return false; + // GDI+ draws multi-line text even though the last line may be clipped slightly at the bottom. + // This is a workaround to emulate that behaviour. + rect.Height += 1.0f; + return true; } void CanvasD2D::DrawBitmap(Gdiplus::Bitmap* bitmap, const Gdiplus::Rect& dstRect, const Gdiplus::Rect& srcRect) diff --git a/Common/Gfx/TextFormatD2D.cpp b/Common/Gfx/TextFormatD2D.cpp index 1f367a70..015138a5 100644 --- a/Common/Gfx/TextFormatD2D.cpp +++ b/Common/Gfx/TextFormatD2D.cpp @@ -22,7 +22,9 @@ namespace Gfx { -TextFormatD2D::TextFormatD2D() +TextFormatD2D::TextFormatD2D() : + m_ExtraHeight(), + m_LineGap() { } @@ -37,7 +39,8 @@ void TextFormatD2D::Dispose() m_InlineEllipsis.Reset(); } -void TextFormatD2D::CreateLayout(const WCHAR* str, UINT strLen, float maxW, float maxH) +void TextFormatD2D::CreateLayout( + const WCHAR* str, UINT strLen, float maxW, float maxH, bool gdiEmulation) { bool strChanged = false; if (strLen != m_LastString.length() || @@ -63,6 +66,17 @@ void TextFormatD2D::CreateLayout(const WCHAR* str, UINT strLen, float maxW, floa { CanvasD2D::c_DWFactory->CreateTextLayout( str, strLen, m_TextFormat.Get(), maxW, maxH, m_TextLayout.ReleaseAndGetAddressOf()); + + if (gdiEmulation && m_TextLayout) + { + Microsoft::WRL::ComPtr textLayout1; + m_TextLayout.As(&textLayout1); + + const float xOffset = m_TextFormat->GetFontSize() / 6.0f; + const float emOffset = xOffset / 24.0f; + const DWRITE_TEXT_RANGE range = {0, strLen}; + textLayout1->SetCharacterSpacing(emOffset, emOffset, 0.0f, range); + } } } @@ -80,6 +94,7 @@ void TextFormatD2D::SetProperties( DWRITE_FONT_STYLE dwriteFontStyle = italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; DWRITE_FONT_STRETCH dwriteFontStretch = DWRITE_FONT_STRETCH_NORMAL; + const float dwriteFontSize = size * (4.0f / 3.0f); // |fontFamily| uses the GDI/GDI+ font naming convention so try to create DirectWrite font // using the GDI family name and then create a text format using the DirectWrite family name @@ -95,7 +110,7 @@ void TextFormatD2D::SetProperties( dwriteFontWeight, dwriteFontStyle, dwriteFontStretch, - size * (4.0f / 3.0f), + dwriteFontSize, L"", &m_TextFormat); } @@ -135,7 +150,7 @@ void TextFormatD2D::SetProperties( dwriteFontWeight, dwriteFontStyle, dwriteFontStretch, - size * (4.0f / 3.0f), + dwriteFontSize, L"", &m_TextFormat); } @@ -144,6 +159,36 @@ void TextFormatD2D::SetProperties( { SetHorizontalAlignment(GetHorizontalAlignment()); SetVerticalAlignment(GetVerticalAlignment()); + + // TODO: Clean this up and check for errors. + Microsoft::WRL::ComPtr collection; + m_TextFormat->GetFontCollection(collection.GetAddressOf()); + + UINT32 familyNameIndex; + BOOL exists; + collection->FindFamilyName(dwriteFamilyName, &familyNameIndex, &exists); + + Microsoft::WRL::ComPtr fontFamily; + collection->GetFontFamily(familyNameIndex, fontFamily.GetAddressOf()); + + Microsoft::WRL::ComPtr font; + fontFamily->GetFirstMatchingFont( + m_TextFormat->GetFontWeight(), + m_TextFormat->GetFontStretch(), + m_TextFormat->GetFontStyle(), + font.GetAddressOf()); + + DWRITE_FONT_METRICS fmetrics; + font->GetMetrics(&fmetrics); + + // GDI+ compatibility: GDI+ adds extra padding below the string when |m_AccurateText| is + // |false|. The bottom padding seems to be based on the font metrics so we can calculate it + // once and keep using it regardless of the actual string. In some cases, GDI+ also adds + // the line gap to the overall height so we will store it as well. + const float pixelsPerDesignUnit = dwriteFontSize / (float)fmetrics.designUnitsPerEm; + m_ExtraHeight = + (((float)fmetrics.designUnitsPerEm / 8.0f) - fmetrics.lineGap) * pixelsPerDesignUnit; + m_LineGap = fmetrics.lineGap * pixelsPerDesignUnit; } else { @@ -151,6 +196,71 @@ void TextFormatD2D::SetProperties( } } +DWRITE_TEXT_METRICS TextFormatD2D::GetMetrics( + const WCHAR* str, UINT strLen, bool gdiEmulation, float maxWidth) +{ + // GDI+ compatibility: If the last character is a newline, GDI+ measurements seem to ignore it. + bool strippedLastNewLine = false; + if (str[strLen - 1] == L'\n') + { + strippedLastNewLine = true; + --strLen; + + if (str[strLen - 1] == L'\r') + { + --strLen; + } + } + + DWRITE_TEXT_METRICS metrics = {0}; + Microsoft::WRL::ComPtr textLayout; + HRESULT hr = CanvasD2D::c_DWFactory->CreateTextLayout( + str, + strLen, + m_TextFormat.Get(), + maxWidth, + 10000, + textLayout.GetAddressOf()); + if (SUCCEEDED(hr)) + { + const float xOffset = m_TextFormat->GetFontSize() / 6.0f; + if (gdiEmulation) + { + Microsoft::WRL::ComPtr textLayout1; + textLayout.As(&textLayout1); + + const float emOffset = xOffset / 24.0f; + const DWRITE_TEXT_RANGE range = {0, strLen}; + textLayout1->SetCharacterSpacing(emOffset, emOffset, 0.0f, range); + } + + textLayout->GetMetrics(&metrics); + if (metrics.width > 0.0f) + { + if (gdiEmulation) + { + metrics.width += xOffset * 2; + metrics.height += m_ExtraHeight; + + // GDI+ compatibility: If the string contains a newline (even if it is the + // stripped last character), GDI+ adds the line gap to the overall height. + if (strippedLastNewLine || wmemchr(str, L'\n', strLen) != nullptr) + { + metrics.height += m_LineGap; + } + } + } + else + { + // GDI+ compatibility: Get rid of the height that DirectWrite assigns to zero-width + // strings. + metrics.height = 0.0f; + } + } + + return metrics; +} + void TextFormatD2D::SetTrimming(bool trim) { IDWriteInlineObject* inlineObject = nullptr; diff --git a/Common/Gfx/TextFormatD2D.h b/Common/Gfx/TextFormatD2D.h index d37e55b1..537853c2 100644 --- a/Common/Gfx/TextFormatD2D.h +++ b/Common/Gfx/TextFormatD2D.h @@ -54,13 +54,20 @@ private: // Creates a new DirectWrite text layout if |str| has changed since last call. Since creating // the layout is costly, it is more efficient to keep reusing the text layout until the text // changes. - void CreateLayout(const WCHAR* str, UINT strLen, float maxW, float maxH); + void CreateLayout(const WCHAR* str, UINT strLen, float maxW, float maxH, bool gdiEmulation); + + DWRITE_TEXT_METRICS GetMetrics( + const WCHAR* str, UINT strLen, bool gdiEmulation, float maxWidth = 10000.0f); Microsoft::WRL::ComPtr m_TextFormat; Microsoft::WRL::ComPtr m_TextLayout; Microsoft::WRL::ComPtr m_InlineEllipsis; std::wstring m_LastString; + + // Used to emulate GDI+ behaviour. + float m_ExtraHeight; + float m_LineGap; }; } // namespace Gfx diff --git a/Common/Gfx/Util/DWriteHelpers.cpp b/Common/Gfx/Util/DWriteHelpers.cpp index 1ab3c941..e4b1601c 100644 --- a/Common/Gfx/Util/DWriteHelpers.cpp +++ b/Common/Gfx/Util/DWriteHelpers.cpp @@ -23,31 +23,6 @@ namespace Gfx { namespace Util { -DWRITE_TEXT_METRICS GetAdjustedDWriteTextLayoutMetrics( - IDWriteTextLayout* textLayout, bool gdiEmulation) -{ - DWRITE_TEXT_METRICS metrics; - textLayout->GetMetrics(&metrics); - - if (metrics.width > 0.0f) - { - if (gdiEmulation) - { - float size = 0.0f; - textLayout->GetFontSize(0, &size); - metrics.width = floor(metrics.width + (size / 2.05f) + (metrics.width / 55.0f) - 0.5f); - metrics.height = floor(metrics.height + (size / 9.25f) + 0.3f); - } - } - else - { - // Get rid of the height that DirectWrite assigns to zero-width strings. - metrics.height = 0.0f; - } - - return metrics; -} - HRESULT GetDWritePropertiesFromGDIProperties( IDWriteFactory* factory, const WCHAR* gdiFamilyName, const bool gdiBold, const bool gdiItalic, DWRITE_FONT_WEIGHT& dwriteFontWeight, DWRITE_FONT_STYLE& dwriteFontStyle, diff --git a/Common/Gfx/Util/DWriteHelpers.h b/Common/Gfx/Util/DWriteHelpers.h index 278f0b36..1140e2a5 100644 --- a/Common/Gfx/Util/DWriteHelpers.h +++ b/Common/Gfx/Util/DWriteHelpers.h @@ -24,11 +24,6 @@ namespace Gfx { namespace Util { -// If |gdiEmulation| is true, the returns metrics have similar characteristics to those provided -// by GDI+. -DWRITE_TEXT_METRICS GetAdjustedDWriteTextLayoutMetrics( - IDWriteTextLayout* textLayout, bool gdiEmulation); - // Maps the GDI family name and italic/bold flags to the DirectWrite family name, weight, style, // and stretch. HRESULT GetDWritePropertiesFromGDIProperties(