Gfx: Make AccurateText=0 more consistent with GDI+ for D2D

The height is now based on a formula that should be exactly equal to that provided by GDI+. The padding should now be equal on the left and right sides now. There may be some discrepancies in overall width, but it should always be reasonably to close to GDI+.

In addition, this makes D2D behavior match GDI+ when a newline character is the last character of the text.
This commit is contained in:
Birunthan Mohanathas 2013-08-10 13:54:53 +03:00
parent 63f5eed742
commit 6e04fd983f
5 changed files with 140 additions and 89 deletions

View File

@ -313,13 +313,13 @@ void CanvasD2D::DrawTextW(const WCHAR* str, UINT strLen, const TextFormat& forma
{ {
TextFormatD2D& formatD2D = (TextFormatD2D&)format; TextFormatD2D& formatD2D = (TextFormatD2D&)format;
formatD2D.CreateLayout( 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 = [&]() const float xPos = [&]()
{ {
if (!m_AccurateText) if (!m_AccurateText)
{ {
const float xOffset = formatD2D.m_TextFormat->GetFontSize() / 6.0f;
switch (formatD2D.GetHorizontalAlignment()) switch (formatD2D.GetHorizontalAlignment())
{ {
case HorizontalAlignment::Left: return rect.X + xOffset; 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; return rect.X;
} (); } ();
if (!m_AccurateText && m_TextAntiAliasing)
{
const float emOffset = xOffset / 25.0f;
DWRITE_TEXT_RANGE range = {0, strLen};
Microsoft::WRL::ComPtr<IDWriteTextLayout1> textLayout;
formatD2D.m_TextLayout.As(&textLayout);
textLayout->SetCharacterSpacing(emOffset, emOffset, 0.0f, range);
}
// TODO: Check for transformation. // TODO: Check for transformation.
m_Target->PushAxisAlignedClip(ToRectF(rect), D2D1_ANTIALIAS_MODE_ALIASED); m_Target->PushAxisAlignedClip(ToRectF(rect), D2D1_ANTIALIAS_MODE_ALIASED);
m_Target->DrawTextLayout( m_Target->DrawTextLayout(
D2D1::Point2F(xPos, rect.Y - 1.0f), D2D1::Point2F(xPos, rect.Y - formatD2D.m_LineGap),
formatD2D.m_TextLayout.Get(), formatD2D.m_TextLayout.Get(),
solidBrush.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) bool CanvasD2D::MeasureTextW(const WCHAR* str, UINT strLen, const TextFormat& format, Gdiplus::RectF& rect)
{ {
Microsoft::WRL::ComPtr<IDWriteTextLayout> textLayout; TextFormatD2D& formatD2D = (TextFormatD2D&)format;
HRESULT hr = c_DWFactory->CreateTextLayout( const DWRITE_TEXT_METRICS metrics = formatD2D.GetMetrics(str, strLen, !m_AccurateText);
str, rect.Width = metrics.width;
strLen, rect.Height = metrics.height;
((TextFormatD2D&)format).m_TextFormat.Get(), return true;
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;
} }
bool CanvasD2D::MeasureTextLinesW(const WCHAR* str, UINT strLen, const TextFormat& format, Gdiplus::RectF& rect, UINT& lines) 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<IDWriteTextLayout> textLayout; const DWRITE_TEXT_METRICS metrics = formatD2D.GetMetrics(str, strLen, !m_AccurateText, rect.Width);
HRESULT hr = c_DWFactory->CreateTextLayout( rect.Width = metrics.width;
str, rect.Height = metrics.height;
strLen, lines = metrics.lineCount;
((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;
// GDI+ draws multi-line text even though the last line may be clipped slightly at the bottom. // 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. // This is a workaround to emulate that behaviour.
rect.Height = metrics.height + 1.0f; rect.Height += 1.0f;
return true; return true;
}
return false;
} }
void CanvasD2D::DrawBitmap(Gdiplus::Bitmap* bitmap, const Gdiplus::Rect& dstRect, const Gdiplus::Rect& srcRect) void CanvasD2D::DrawBitmap(Gdiplus::Bitmap* bitmap, const Gdiplus::Rect& dstRect, const Gdiplus::Rect& srcRect)

View File

@ -22,7 +22,9 @@
namespace Gfx { namespace Gfx {
TextFormatD2D::TextFormatD2D() TextFormatD2D::TextFormatD2D() :
m_ExtraHeight(),
m_LineGap()
{ {
} }
@ -37,7 +39,8 @@ void TextFormatD2D::Dispose()
m_InlineEllipsis.Reset(); 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; bool strChanged = false;
if (strLen != m_LastString.length() || if (strLen != m_LastString.length() ||
@ -63,6 +66,17 @@ void TextFormatD2D::CreateLayout(const WCHAR* str, UINT strLen, float maxW, floa
{ {
CanvasD2D::c_DWFactory->CreateTextLayout( CanvasD2D::c_DWFactory->CreateTextLayout(
str, strLen, m_TextFormat.Get(), maxW, maxH, m_TextLayout.ReleaseAndGetAddressOf()); str, strLen, m_TextFormat.Get(), maxW, maxH, m_TextLayout.ReleaseAndGetAddressOf());
if (gdiEmulation && m_TextLayout)
{
Microsoft::WRL::ComPtr<IDWriteTextLayout1> 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 = DWRITE_FONT_STYLE dwriteFontStyle =
italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL; italic ? DWRITE_FONT_STYLE_ITALIC : DWRITE_FONT_STYLE_NORMAL;
DWRITE_FONT_STRETCH dwriteFontStretch = DWRITE_FONT_STRETCH_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 // |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 // using the GDI family name and then create a text format using the DirectWrite family name
@ -95,7 +110,7 @@ void TextFormatD2D::SetProperties(
dwriteFontWeight, dwriteFontWeight,
dwriteFontStyle, dwriteFontStyle,
dwriteFontStretch, dwriteFontStretch,
size * (4.0f / 3.0f), dwriteFontSize,
L"", L"",
&m_TextFormat); &m_TextFormat);
} }
@ -135,7 +150,7 @@ void TextFormatD2D::SetProperties(
dwriteFontWeight, dwriteFontWeight,
dwriteFontStyle, dwriteFontStyle,
dwriteFontStretch, dwriteFontStretch,
size * (4.0f / 3.0f), dwriteFontSize,
L"", L"",
&m_TextFormat); &m_TextFormat);
} }
@ -144,6 +159,36 @@ void TextFormatD2D::SetProperties(
{ {
SetHorizontalAlignment(GetHorizontalAlignment()); SetHorizontalAlignment(GetHorizontalAlignment());
SetVerticalAlignment(GetVerticalAlignment()); SetVerticalAlignment(GetVerticalAlignment());
// TODO: Clean this up and check for errors.
Microsoft::WRL::ComPtr<IDWriteFontCollection> collection;
m_TextFormat->GetFontCollection(collection.GetAddressOf());
UINT32 familyNameIndex;
BOOL exists;
collection->FindFamilyName(dwriteFamilyName, &familyNameIndex, &exists);
Microsoft::WRL::ComPtr<IDWriteFontFamily> fontFamily;
collection->GetFontFamily(familyNameIndex, fontFamily.GetAddressOf());
Microsoft::WRL::ComPtr<IDWriteFont> 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 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<IDWriteTextLayout> 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<IDWriteTextLayout1> 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) void TextFormatD2D::SetTrimming(bool trim)
{ {
IDWriteInlineObject* inlineObject = nullptr; IDWriteInlineObject* inlineObject = nullptr;

View File

@ -54,13 +54,20 @@ private:
// Creates a new DirectWrite text layout if |str| has changed since last call. Since creating // 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 // the layout is costly, it is more efficient to keep reusing the text layout until the text
// changes. // 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<IDWriteTextFormat> m_TextFormat; Microsoft::WRL::ComPtr<IDWriteTextFormat> m_TextFormat;
Microsoft::WRL::ComPtr<IDWriteTextLayout> m_TextLayout; Microsoft::WRL::ComPtr<IDWriteTextLayout> m_TextLayout;
Microsoft::WRL::ComPtr<IDWriteInlineObject> m_InlineEllipsis; Microsoft::WRL::ComPtr<IDWriteInlineObject> m_InlineEllipsis;
std::wstring m_LastString; std::wstring m_LastString;
// Used to emulate GDI+ behaviour.
float m_ExtraHeight;
float m_LineGap;
}; };
} // namespace Gfx } // namespace Gfx

View File

@ -23,31 +23,6 @@
namespace Gfx { namespace Gfx {
namespace Util { 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( HRESULT GetDWritePropertiesFromGDIProperties(
IDWriteFactory* factory, const WCHAR* gdiFamilyName, const bool gdiBold, const bool gdiItalic, IDWriteFactory* factory, const WCHAR* gdiFamilyName, const bool gdiBold, const bool gdiItalic,
DWRITE_FONT_WEIGHT& dwriteFontWeight, DWRITE_FONT_STYLE& dwriteFontStyle, DWRITE_FONT_WEIGHT& dwriteFontWeight, DWRITE_FONT_STYLE& dwriteFontStyle,

View File

@ -24,11 +24,6 @@
namespace Gfx { namespace Gfx {
namespace Util { 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, // Maps the GDI family name and italic/bold flags to the DirectWrite family name, weight, style,
// and stretch. // and stretch.
HRESULT GetDWritePropertiesFromGDIProperties( HRESULT GetDWritePropertiesFromGDIProperties(