summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Eves <tim_eves@sil.org>2016-03-08 14:46:49 +0700
committerMartin Hosken <martin_hosken@sil.org>2016-03-09 03:02:12 +0000
commit64172644c898d4f5f29494b2f8b5fbcdc2cd9824 (patch)
treed8a392cde559ebad8cc6a29b626b177b88764077
parent45701913f642b17aabd67b52de9002cc79cf07ae (diff)
DirectWrite rendering for Graphite to work around ExTextOut bug
ExTextOut has a bug on Windows 7 and above where it incorrectly positions certain diacritics, using DirectWrite and Direct2D fixes this. Implemented on-demand loading of the DLL so the old ExTextOut based renderer will be used when drwite and d2d1 cannot be found allowing this work on Windows XP (where this bug doesn't seem to occur) Change-Id: I767d62c8188511e745373b61ba51e7e2745f7b8b Reviewed-on: https://gerrit.libreoffice.org/23020 Tested-by: Jenkins <ci@libreoffice.org> Reviewed-by: Michael Meeks <michael.meeks@collabora.com> Reviewed-by: Martin Hosken <martin_hosken@sil.org>
-rw-r--r--config_host/config_graphite.h.in2
-rw-r--r--vcl/win/gdi/winlayout.cxx372
2 files changed, 354 insertions, 20 deletions
diff --git a/config_host/config_graphite.h.in b/config_host/config_graphite.h.in
index 46e624dc01a1..953c0066b325 100644
--- a/config_host/config_graphite.h.in
+++ b/config_host/config_graphite.h.in
@@ -3,4 +3,6 @@
#define ENABLE_GRAPHITE 0
+#define ENABLE_GRAPHITE_DWRITE 1
+
#endif
diff --git a/vcl/win/gdi/winlayout.cxx b/vcl/win/gdi/winlayout.cxx
index d43f4e1ac135..cccae83e2924 100644
--- a/vcl/win/gdi/winlayout.cxx
+++ b/vcl/win/gdi/winlayout.cxx
@@ -33,7 +33,6 @@
#include "sft.hxx"
#include "sallayout.hxx"
-#include "glyphy/demo.hxx"
#include <cstdio>
#include <cstdlib>
@@ -54,8 +53,16 @@ typedef std::unordered_map<int,int> IntMap;
#if ENABLE_GRAPHITE
#include <i18nlangtag/languagetag.hxx>
#include <graphite_features.hxx>
+#if ENABLE_GRAPHITE_DWRITE
+#include <d2d1.h>
+#include <dwrite.h>
+#endif
#endif
+// This needs to come after any includes for d2d1.h, otherwise we get lots of errors
+#include "glyphy/demo.hxx"
+
+
#define DROPPED_OUTGLYPH 0xFFFF
namespace
@@ -3562,6 +3569,346 @@ sal_GlyphId GraphiteLayoutWinImpl::getKashidaGlyph(int & rWidth)
return mrFont.GetMinKashidaGlyph();
}
+class TextOutRenderer
+{
+protected:
+ explicit TextOutRenderer() = default;
+ TextOutRenderer(const TextOutRenderer &) = delete;
+ TextOutRenderer & operator = (const TextOutRenderer &) = delete;
+
+public:
+ static TextOutRenderer & get();
+
+ virtual ~TextOutRenderer() = default;
+
+
+ virtual bool operator ()(WinLayout const &rLayout, HDC hDC,
+ const Rectangle* pRectToErase,
+ Point* pPos, int* pGetNextGlypInfo) = 0;
+};
+
+class ExTextOutRenderer : public TextOutRenderer
+{
+ ExTextOutRenderer(const ExTextOutRenderer &) = delete;
+ ExTextOutRenderer & operator = (const ExTextOutRenderer &) = delete;
+
+public:
+ explicit ExTextOutRenderer() = default;
+ virtual ~ExTextOutRenderer() override = default;
+
+ bool operator ()(WinLayout const &rLayout, HDC hDC,
+ const Rectangle* pRectToErase,
+ Point* pPos, int* pGetNextGlypInfo) override;
+};
+
+#if ENABLE_GRAPHITE_DWRITE
+
+class D2DWriteTextOutRenderer : public TextOutRenderer
+{
+ typedef HRESULT(WINAPI *pD2D1CreateFactory_t)(D2D1_FACTORY_TYPE,
+ REFIID, const D2D1_FACTORY_OPTIONS *, void **);
+ typedef HRESULT(WINAPI *pDWriteCreateFactory_t)(DWRITE_FACTORY_TYPE,
+ REFIID, IUnknown **);
+
+ static HINSTANCE mmD2d1, mmDWrite;
+ static pD2D1CreateFactory_t D2D1CreateFactory;
+ static pDWriteCreateFactory_t DWriteCreateFactory;
+
+public:
+ static bool InitModules();
+
+ explicit D2DWriteTextOutRenderer();
+ virtual ~D2DWriteTextOutRenderer() override;
+
+ bool operator ()(WinLayout const &rLayout, HDC hDC,
+ const Rectangle* pRectToErase,
+ Point* pPos, int* pGetNextGlypInfo) override;
+
+private:
+ static void CleanupModules();
+
+ // This is a singleton object disable copy ctor and assignemnt operator
+ D2DWriteTextOutRenderer(const D2DWriteTextOutRenderer &) = delete;
+ D2DWriteTextOutRenderer & operator = (const D2DWriteTextOutRenderer &) = delete;
+
+ bool GetDWriteFaceFromHDC(HDC hDC, IDWriteFontFace ** ppFontFace, float * lfSize) const;
+ bool GetDWriteInkBox(IDWriteFontFace & rFontFace, WinLayout const &rLayout, float const lfEmHeight, Rectangle &) const;
+
+ inline
+ HRESULT CreateRenderTarget() {
+ if (mpRT) mpRT->Release(); mpRT = NULL;
+ return mpD2DFactory->CreateDCRenderTarget(&mRTProps, &mpRT);
+ }
+
+ inline
+ bool Ready() const { return mpGdiInterop && mpRT; }
+
+ inline
+ bool BindDC(HDC hDC, Rectangle const & rRect = Rectangle(0,0,0,0)) {
+ RECT const rc = { rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom() };
+ return SUCCEEDED(mpRT->BindDC(hDC, &rc));
+ }
+
+ ID2D1Factory * mpD2DFactory;
+ IDWriteFactory * mpDWriteFactory;
+ IDWriteGdiInterop * mpGdiInterop;
+ ID2D1DCRenderTarget * mpRT;
+ const D2D1_RENDER_TARGET_PROPERTIES mRTProps;
+};
+
+HINSTANCE D2DWriteTextOutRenderer::mmD2d1 = nullptr,
+ D2DWriteTextOutRenderer::mmDWrite = nullptr;
+D2DWriteTextOutRenderer::pD2D1CreateFactory_t D2DWriteTextOutRenderer::D2D1CreateFactory = nullptr;
+D2DWriteTextOutRenderer::pDWriteCreateFactory_t D2DWriteTextOutRenderer::DWriteCreateFactory = nullptr;
+
+bool D2DWriteTextOutRenderer::InitModules()
+{
+ mmD2d1 = LoadLibrary("D2d1.dll");
+ mmDWrite = LoadLibrary("dwrite.dll");
+ if (mmD2d1 && mmDWrite)
+ {
+ D2D1CreateFactory = pD2D1CreateFactory_t(GetProcAddress(mmD2d1, "D2D1CreateFactory"));
+ DWriteCreateFactory = pDWriteCreateFactory_t(GetProcAddress(mmDWrite, "DWriteCreateFactory"));
+ }
+
+ if (!D2D1CreateFactory || !DWriteCreateFactory)
+ {
+ CleanupModules();
+ return false;
+ }
+
+ return true;
+}
+
+void D2DWriteTextOutRenderer::CleanupModules()
+{
+ if (mmD2d1) FreeLibrary(mmD2d1);
+ if (mmDWrite) FreeLibrary(mmDWrite);
+
+ mmD2d1 = nullptr;
+ mmDWrite = nullptr;
+ D2D1CreateFactory = nullptr;
+ DWriteCreateFactory = nullptr;
+}
+#endif // ENABLE_GRAPHITE_DWRITE
+
+TextOutRenderer & TextOutRenderer::get()
+{
+#if ENABLE_GRAPHITE_DWRITE
+ static std::unique_ptr<TextOutRenderer> _impl(D2DWriteTextOutRenderer::InitModules()
+ ? static_cast<TextOutRenderer*>(new D2DWriteTextOutRenderer())
+ : static_cast<TextOutRenderer*>(new ExTextOutRenderer()));
+#else // ENABLE_GRAPHITE_DWRITE
+ static std::unique_ptr<TextOutRenderer> _impl(static_cast<TextOutRenderer*>(new ExTextOutRenderer()));
+#endif // ENABLE_GRAPHITE_DWRITE
+
+ return *_impl;
+}
+
+
+bool ExTextOutRenderer::operator ()(WinLayout const &rLayout, HDC hDC,
+ const Rectangle* pRectToErase,
+ Point* pPos, int* pGetNextGlypInfo)
+{
+ if (pRectToErase)
+ {
+ RECT aRect = { pRectToErase->Left(), pRectToErase->Top(), pRectToErase->Left() + pRectToErase->GetWidth(), pRectToErase->Top() + pRectToErase->GetHeight() };
+ FillRect(hDC, &aRect, static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH)));
+ }
+
+ const int MAX_GLYPHS = 2;
+ sal_GlyphId glyphIntStr[MAX_GLYPHS];
+ int nGlyphs = 0;
+ WORD glyphWStr[MAX_GLYPHS];
+ do
+ {
+ nGlyphs = rLayout.GetNextGlyphs(1, glyphIntStr, *pPos, *pGetNextGlypInfo);
+ if (nGlyphs < 1)
+ break;
+
+ std::copy_n(glyphIntStr, nGlyphs, glyphWStr);
+ ExtTextOutW(hDC, pPos->X(), pPos->Y(), ETO_GLYPH_INDEX, NULL, LPCWSTR(&glyphWStr), nGlyphs, NULL);
+ } while (!pRectToErase);
+
+ return (pRectToErase && nGlyphs >= 1);
+}
+
+#if ENABLE_GRAPHITE_DWRITE
+
+D2DWriteTextOutRenderer::D2DWriteTextOutRenderer()
+ : mpD2DFactory(NULL),
+ mpDWriteFactory(NULL),
+ mpGdiInterop(NULL),
+ mpRT(NULL),
+ mRTProps(D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT,
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
+ 0, 0, D2D1_RENDER_TARGET_USAGE_NONE, D2D1_FEATURE_LEVEL_DEFAULT))
+{
+ HRESULT hr = S_OK;
+ hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), NULL, reinterpret_cast<void **>(&mpD2DFactory));
+ hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(&mpDWriteFactory));
+ if (SUCCEEDED(hr))
+ {
+ hr = mpDWriteFactory->GetGdiInterop(&mpGdiInterop);
+ hr = CreateRenderTarget();
+ }
+}
+
+D2DWriteTextOutRenderer::~D2DWriteTextOutRenderer()
+{
+ if (mpRT) mpRT->Release();
+ if (mpGdiInterop) mpGdiInterop->Release();
+ if (mpDWriteFactory) mpDWriteFactory->Release();
+ if (mpD2DFactory) mpD2DFactory->Release();
+
+ CleanupModules();
+}
+
+bool D2DWriteTextOutRenderer::operator ()(WinLayout const &rLayout, HDC hDC,
+ const Rectangle* pRectToErase,
+ Point* pPos, int* pGetNextGlypInfo)
+{
+ if (!Ready())
+ return false;
+
+ // Initially bind to an empty rectangle to get access to the font face,
+ // we'll update it once we've calculated a bounding rect.
+ if (!BindDC(hDC))
+ return false;
+
+ IDWriteFontFace * pFontFace = NULL;
+ float lfEmHeight = 0;
+ if (!GetDWriteFaceFromHDC(hDC, &pFontFace, &lfEmHeight))
+ return false;
+
+ Rectangle bounds;
+ bool succeeded = GetDWriteInkBox(*pFontFace, rLayout, lfEmHeight, bounds);
+ if (pRectToErase)
+ {
+ bounds.Intersection(*pRectToErase);
+ bounds.Union(*pRectToErase);
+ }
+ succeeded &= BindDC(hDC, bounds); // Update the bounding rect.
+
+ ID2D1SolidColorBrush* pBlackBrush = NULL;
+ succeeded &= SUCCEEDED(mpRT->CreateSolidColorBrush(D2D1::ColorF(GetTextColor(hDC)), &pBlackBrush));
+
+ HRESULT hr = S_OK;
+ if (succeeded)
+ {
+ if (pRectToErase)
+ mpRT->Clear(D2D1::ColorF(D2D1::ColorF::White));
+
+ const int MAX_GLYPHS = 2;
+ sal_GlyphId glyphIntStr[MAX_GLYPHS];
+ UINT16 glyphIndices[MAX_GLYPHS];
+ long glyphIntAdv[MAX_GLYPHS];
+ FLOAT glyphAdvances[MAX_GLYPHS];
+ DWRITE_GLYPH_OFFSET glyphOffsets[MAX_GLYPHS] = { { 0.0f, 0.0f }, };
+ int nGlyphs = 0;
+
+ mpRT->BeginDraw();
+ do
+ {
+ nGlyphs = rLayout.GetNextGlyphs(1, glyphIntStr, *pPos, *pGetNextGlypInfo, glyphIntAdv);
+ if (nGlyphs < 1)
+ break;
+
+ std::copy_n(glyphIntStr, nGlyphs, glyphIndices);
+ std::copy_n(glyphIntAdv, nGlyphs, glyphAdvances);
+
+ D2D1_POINT_2F baseline = { pPos->X() - bounds.Left(), pPos->Y() - bounds.Top() };
+ DWRITE_GLYPH_RUN glyphs = {
+ pFontFace,
+ lfEmHeight,
+ nGlyphs,
+ glyphIndices,
+ glyphAdvances,
+ glyphOffsets,
+ false,
+ 0
+ };
+
+ mpRT->DrawGlyphRun(baseline, &glyphs, pBlackBrush);
+ } while (!pRectToErase);
+
+ hr = mpRT->EndDraw();
+ }
+
+ if (pBlackBrush)
+ pBlackBrush->Release();
+
+ if (pFontFace)
+ pFontFace->Release();
+
+ if (hr == D2DERR_RECREATE_TARGET)
+ CreateRenderTarget();
+
+ return (SUCCEEDED(hr) && succeeded && pRectToErase);
+}
+
+bool D2DWriteTextOutRenderer::GetDWriteFaceFromHDC(HDC hDC, IDWriteFontFace ** ppFontFace, float * lfSize) const
+{
+ bool const succeeded = SUCCEEDED(mpGdiInterop->CreateFontFaceFromHdc(hDC, ppFontFace));
+
+ if (succeeded)
+ {
+ LOGFONTW aLogFont;
+ HFONT hFont = (HFONT)::GetCurrentObject(hDC, OBJ_FONT);
+
+ GetObjectW(hFont, sizeof(LOGFONTW), &aLogFont);
+ float dpix, dpiy;
+ mpRT->GetDpi(&dpix, &dpiy);
+ *lfSize = aLogFont.lfHeight * 96.0f / dpiy;
+
+ assert(*lfSize < 0);
+ *lfSize *= -1;
+ }
+
+ return succeeded;
+}
+
+bool D2DWriteTextOutRenderer::GetDWriteInkBox(IDWriteFontFace & rFontFace, WinLayout const &rLayout, float const lfEmHeight, Rectangle & rOut) const
+{
+ rOut.SetEmpty();
+
+ DWRITE_FONT_METRICS aFontMetrics;
+ rFontFace.GetMetrics(&aFontMetrics);
+
+ Point aPos;
+ for (int nStart = 0;;)
+ {
+ sal_GlyphId nLGlyph;
+ if (rLayout.GetNextGlyphs(1, &nLGlyph, aPos, nStart) < 1)
+ break;
+
+ // get bounding rectangle of individual glyph
+ UINT16 indices[1] = { nLGlyph };
+ DWRITE_GLYPH_METRICS metrics[1];
+ if (!SUCCEEDED(rFontFace.GetDesignGlyphMetrics(indices, 1, metrics, false)))
+ return false;
+
+ // We accumulate glyph boxes design unit space
+ Rectangle aGlyphInkBox(
+ metrics->leftSideBearing,
+ metrics->topSideBearing - metrics->verticalOriginY,
+ metrics->advanceWidth - metrics->rightSideBearing,
+ INT32(metrics->advanceHeight) - metrics->verticalOriginY - metrics->bottomSideBearing);
+ aGlyphInkBox += aPos * aFontMetrics.designUnitsPerEm / lfEmHeight;
+ rOut.Union(aGlyphInkBox);
+ }
+
+ // Scale to screen space.
+ rOut.Left() = std::lround(rOut.Left()*lfEmHeight / aFontMetrics.designUnitsPerEm);
+ rOut.Top() = std::lround(rOut.Top()*lfEmHeight / aFontMetrics.designUnitsPerEm);
+ rOut.Right() = 1+std::lround(rOut.Right()*lfEmHeight / aFontMetrics.designUnitsPerEm);
+ rOut.Bottom() = 1+std::lround(rOut.Bottom()*lfEmHeight / aFontMetrics.designUnitsPerEm);
+
+ return true;
+}
+
+#endif // ENABLE_GRAPHITE_DWRITE
+
float gr_fontAdvance(const void* appFontHandle, gr_uint16 glyphId)
{
HDC hDC = reinterpret_cast<HDC>(const_cast<void*>(appFontHandle));
@@ -3650,30 +3997,15 @@ bool GraphiteWinLayout::DrawTextImpl(HDC hDC,
Point* pPos,
int* pGetNextGlypInfo) const
{
- if (pRectToErase)
- {
- RECT aRect = { pRectToErase->Left(), pRectToErase->Top(), pRectToErase->Left()+pRectToErase->GetWidth(), pRectToErase->Top()+pRectToErase->GetHeight() };
- FillRect(hDC, &aRect, static_cast<HBRUSH>(GetStockObject(WHITE_BRUSH)));
- }
-
HFONT hOrigFont = DisableFontScaling();
maImpl.DrawBase() = WinLayout::maDrawBase;
maImpl.DrawOffset() = WinLayout::maDrawOffset;
- const int MAX_GLYPHS = 2;
- sal_GlyphId glyphIntStr[MAX_GLYPHS];
- WORD glyphWStr[MAX_GLYPHS];
- int nGlyphs = 0;
- do
- {
- nGlyphs = maImpl.GetNextGlyphs(1, glyphIntStr, *pPos, *pGetNextGlypInfo);
- if (nGlyphs < 1)
- break;
- std::copy(glyphIntStr, glyphIntStr + nGlyphs, glyphWStr);
- ExtTextOutW(hDC, pPos->X(), pPos->Y(), ETO_GLYPH_INDEX, NULL, (LPCWSTR)&(glyphWStr), nGlyphs, NULL);
- } while (!pRectToErase);
+
+ TextOutRenderer & render = TextOutRenderer::get();
+ bool const ok = render(*this, hDC, pRectToErase, pPos, pGetNextGlypInfo);
if( hOrigFont )
DeleteFont(SelectFont(hDC, hOrigFont));
- return (pRectToErase && nGlyphs >= 1);
+ return ok;
}
bool GraphiteWinLayout::CacheGlyphs(SalGraphics& /*rGraphics*/) const