summaryrefslogtreecommitdiffstats
path: root/vcl/win/gdi/DWriteTextRenderer.cxx
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--vcl/win/gdi/DWriteTextRenderer.cxx420
1 files changed, 420 insertions, 0 deletions
diff --git a/vcl/win/gdi/DWriteTextRenderer.cxx b/vcl/win/gdi/DWriteTextRenderer.cxx
new file mode 100644
index 000000000..835e09e7e
--- /dev/null
+++ b/vcl/win/gdi/DWriteTextRenderer.cxx
@@ -0,0 +1,420 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <sal/config.h>
+
+#include <win/salgdi.h>
+#include <win/saldata.hxx>
+#include <ImplOutDevData.hxx>
+
+#include <win/DWriteTextRenderer.hxx>
+
+#include <sft.hxx>
+#include <sallayout.hxx>
+
+#include <shlwapi.h>
+#include <winver.h>
+
+#include <comphelper/windowserrorstring.hxx>
+#include <o3tl/safeint.hxx>
+#include <sal/log.hxx>
+
+namespace
+{
+
+D2DTextAntiAliasMode lclGetSystemTextAntiAliasMode()
+{
+ D2DTextAntiAliasMode eMode = D2DTextAntiAliasMode::Default;
+
+ BOOL bFontSmoothing;
+ if (!SystemParametersInfoW(SPI_GETFONTSMOOTHING, 0, &bFontSmoothing, 0))
+ return eMode;
+
+ if (bFontSmoothing)
+ {
+ eMode = D2DTextAntiAliasMode::AntiAliased;
+
+ UINT nType;
+ if (SystemParametersInfoW(SPI_GETFONTSMOOTHINGTYPE, 0, &nType, 0) && nType == FE_FONTSMOOTHINGCLEARTYPE)
+ eMode = D2DTextAntiAliasMode::ClearType;
+ }
+ else
+ {
+ eMode = D2DTextAntiAliasMode::Aliased;
+ }
+
+ return eMode;
+}
+
+IDWriteRenderingParams* lclSetRenderingMode(IDWriteFactory* pDWriteFactory, DWRITE_RENDERING_MODE eRenderingMode)
+{
+ IDWriteRenderingParams* pDefaultParameters = nullptr;
+ pDWriteFactory->CreateRenderingParams(&pDefaultParameters);
+
+ IDWriteRenderingParams* pParameters = nullptr;
+ pDWriteFactory->CreateCustomRenderingParams(
+ pDefaultParameters->GetGamma(),
+ pDefaultParameters->GetEnhancedContrast(),
+ pDefaultParameters->GetClearTypeLevel(),
+ pDefaultParameters->GetPixelGeometry(),
+ eRenderingMode,
+ &pParameters);
+ return pParameters;
+}
+
+#ifdef SAL_LOG_WARN
+HRESULT checkResult(HRESULT hr, const char* file, size_t line)
+{
+ if (FAILED(hr))
+ {
+ OUString sLocationString = OUString::createFromAscii(file) + ":" + OUString::number(line) + " ";
+ SAL_DETAIL_LOG_STREAM(SAL_DETAIL_ENABLE_LOG_WARN, ::SAL_DETAIL_LOG_LEVEL_WARN,
+ "vcl.gdi", sLocationString.toUtf8().getStr(),
+ "HRESULT failed with: 0x" << OUString::number(hr, 16) << ": " << WindowsErrorStringFromHRESULT(hr));
+ }
+ return hr;
+}
+
+#define CHECKHR(funct) checkResult(funct, __FILE__, __LINE__)
+#else
+#define CHECKHR(funct) (funct)
+#endif
+
+
+} // end anonymous namespace
+
+D2DWriteTextOutRenderer::D2DWriteTextOutRenderer(bool bRenderingModeNatural)
+ : mpD2DFactory(nullptr),
+ mpDWriteFactory(nullptr),
+ mpGdiInterop(nullptr),
+ mpRT(nullptr),
+ mRTProps(D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT,
+ D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
+ 0, 0)),
+ mpFontFace(nullptr),
+ mlfEmHeight(0.0f),
+ mhDC(nullptr),
+ mbRenderingModeNatural(bRenderingModeNatural),
+ meTextAntiAliasMode(D2DTextAntiAliasMode::Default)
+{
+ HRESULT hr = S_OK;
+ hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), nullptr, 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(bRenderingModeNatural);
+ }
+ meTextAntiAliasMode = lclGetSystemTextAntiAliasMode();
+}
+
+D2DWriteTextOutRenderer::~D2DWriteTextOutRenderer()
+{
+ if (mpRT)
+ mpRT->Release();
+ if (mpGdiInterop)
+ mpGdiInterop->Release();
+ if (mpDWriteFactory)
+ mpDWriteFactory->Release();
+ if (mpD2DFactory)
+ mpD2DFactory->Release();
+}
+
+void D2DWriteTextOutRenderer::applyTextAntiAliasMode(bool bRenderingModeNatural)
+{
+ D2D1_TEXT_ANTIALIAS_MODE eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
+ DWRITE_RENDERING_MODE eRenderingMode = DWRITE_RENDERING_MODE_DEFAULT;
+ switch (meTextAntiAliasMode)
+ {
+ case D2DTextAntiAliasMode::Default:
+ eRenderingMode = DWRITE_RENDERING_MODE_DEFAULT;
+ eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
+ break;
+ case D2DTextAntiAliasMode::Aliased:
+ eRenderingMode = DWRITE_RENDERING_MODE_ALIASED;
+ eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
+ break;
+ case D2DTextAntiAliasMode::AntiAliased:
+ eRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC;
+ eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
+ break;
+ case D2DTextAntiAliasMode::ClearType:
+ eRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC;
+ eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
+ break;
+ default:
+ break;
+ }
+
+ if (bRenderingModeNatural)
+ eRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_NATURAL;
+
+ mpRT->SetTextRenderingParams(lclSetRenderingMode(mpDWriteFactory, eRenderingMode));
+ mpRT->SetTextAntialiasMode(eTextAAMode);
+}
+
+HRESULT D2DWriteTextOutRenderer::CreateRenderTarget(bool bRenderingModeNatural)
+{
+ if (mpRT)
+ {
+ mpRT->Release();
+ mpRT = nullptr;
+ }
+ HRESULT hr = CHECKHR(mpD2DFactory->CreateDCRenderTarget(&mRTProps, &mpRT));
+ if (SUCCEEDED(hr))
+ applyTextAntiAliasMode(bRenderingModeNatural);
+ return hr;
+}
+
+bool D2DWriteTextOutRenderer::Ready() const
+{
+ return mpGdiInterop && mpRT;
+}
+
+HRESULT D2DWriteTextOutRenderer::BindDC(HDC hDC, tools::Rectangle const & rRect)
+{
+ RECT const rc = {
+ o3tl::narrowing<LONG>(rRect.Left()), o3tl::narrowing<LONG>(rRect.Top()),
+ o3tl::narrowing<LONG>(rRect.Right()), o3tl::narrowing<LONG>(rRect.Bottom()) };
+ return CHECKHR(mpRT->BindDC(hDC, &rc));
+}
+
+bool D2DWriteTextOutRenderer::operator()(GenericSalLayout const & rLayout, SalGraphics& rGraphics, HDC hDC, bool bRenderingModeNatural)
+{
+ bool bRetry = false;
+ bool bResult = false;
+ int nCount = 0;
+ do
+ {
+ bRetry = false;
+ bResult = performRender(rLayout, rGraphics, hDC, bRetry, bRenderingModeNatural);
+ nCount++;
+ } while (bRetry && nCount < 3);
+ return bResult;
+}
+
+bool D2DWriteTextOutRenderer::performRender(GenericSalLayout const & rLayout, SalGraphics& rGraphics, HDC hDC, bool& bRetry, bool bRenderingModeNatural)
+{
+ if (!Ready())
+ return false;
+
+ HRESULT hr = S_OK;
+ hr = BindDC(hDC);
+
+ if (hr == D2DERR_RECREATE_TARGET)
+ {
+ CreateRenderTarget(bRenderingModeNatural);
+ bRetry = true;
+ return false;
+ }
+ if (FAILED(hr))
+ {
+ // If for any reason we can't bind fallback to legacy APIs.
+ return ExTextOutRenderer()(rLayout, rGraphics, hDC, bRenderingModeNatural);
+ }
+
+ mlfEmHeight = 0;
+ if (!GetDWriteFaceFromHDC(hDC, &mpFontFace, &mlfEmHeight))
+ return false;
+
+ const WinFontInstance& rWinFont = static_cast<const WinFontInstance&>(rLayout.GetFont());
+ float fHScale = rWinFont.getHScale();
+
+ tools::Rectangle bounds;
+ bool succeeded = rLayout.GetBoundRect(bounds);
+ if (succeeded)
+ {
+ hr = BindDC(hDC, bounds); // Update the bounding rect.
+ succeeded &= SUCCEEDED(hr);
+ }
+
+ ID2D1SolidColorBrush* pBrush = nullptr;
+ if (succeeded)
+ {
+ COLORREF bgrTextColor = GetTextColor(hDC);
+ D2D1::ColorF aD2DColor(GetRValue(bgrTextColor) / 255.0f, GetGValue(bgrTextColor) / 255.0f, GetBValue(bgrTextColor) / 255.0f);
+ succeeded &= SUCCEEDED(CHECKHR(mpRT->CreateSolidColorBrush(aD2DColor, &pBrush)));
+ }
+
+ if (succeeded)
+ {
+ mpRT->BeginDraw();
+
+ int nStart = 0;
+ DevicePoint aPos;
+ const GlyphItem* pGlyph;
+ while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
+ {
+ UINT16 glyphIndices[] = { pGlyph->glyphId() };
+ FLOAT glyphAdvances[] = { static_cast<FLOAT>(pGlyph->newWidth()) / fHScale };
+ DWRITE_GLYPH_OFFSET glyphOffsets[] = { { 0.0f, 0.0f }, };
+ D2D1_POINT_2F baseline = { static_cast<FLOAT>(aPos.getX() - bounds.Left()) / fHScale,
+ static_cast<FLOAT>(aPos.getY() - bounds.Top()) };
+ WinFontTransformGuard aTransformGuard(mpRT, fHScale, rLayout, baseline, pGlyph->IsVertical());
+ DWRITE_GLYPH_RUN glyphs = {
+ mpFontFace,
+ mlfEmHeight,
+ 1,
+ glyphIndices,
+ glyphAdvances,
+ glyphOffsets,
+ false,
+ 0
+ };
+
+ mpRT->DrawGlyphRun(baseline, &glyphs, pBrush);
+ }
+
+ hr = CHECKHR(mpRT->EndDraw());
+ }
+
+ if (pBrush)
+ pBrush->Release();
+
+ ReleaseFont();
+
+ if (hr == D2DERR_RECREATE_TARGET)
+ {
+ CreateRenderTarget(bRenderingModeNatural);
+ bRetry = true;
+ }
+
+ return succeeded;
+}
+
+bool D2DWriteTextOutRenderer::BindFont(HDC hDC)
+{
+ // A TextOutRender can only be bound to one font at a time, so the
+ assert(mpFontFace == nullptr);
+ if (mpFontFace)
+ {
+ ReleaseFont();
+ 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 in DrawGlyphs
+ if (FAILED(BindDC(mhDC = hDC)))
+ return false;
+
+ mlfEmHeight = 0;
+ return GetDWriteFaceFromHDC(hDC, &mpFontFace, &mlfEmHeight);
+}
+
+bool D2DWriteTextOutRenderer::ReleaseFont()
+{
+ mpFontFace->Release();
+ mpFontFace = nullptr;
+ mhDC = nullptr;
+
+ return true;
+}
+
+// GetGlyphInkBoxes
+// The inkboxes returned have their origin on the baseline, to a -ve value
+// of Top() means the glyph extends abs(Top()) many pixels above the
+// baseline, and +ve means the ink starts that many pixels below.
+std::vector<tools::Rectangle> D2DWriteTextOutRenderer::GetGlyphInkBoxes(uint16_t const * pGid, uint16_t const * pGidEnd) const
+{
+ ptrdiff_t nGlyphs = pGidEnd - pGid;
+ if (nGlyphs < 0)
+ return std::vector<tools::Rectangle>();
+
+ DWRITE_FONT_METRICS aFontMetrics;
+ mpFontFace->GetMetrics(&aFontMetrics);
+
+ std::vector<DWRITE_GLYPH_METRICS> metrics(nGlyphs);
+ if (!SUCCEEDED(CHECKHR(mpFontFace->GetDesignGlyphMetrics(pGid, nGlyphs, metrics.data()))))
+ return std::vector<tools::Rectangle>();
+
+ std::vector<tools::Rectangle> aOut(nGlyphs);
+ auto pOut = aOut.begin();
+ for (auto &m : metrics)
+ {
+ const auto left = m.leftSideBearing;
+ const auto top = m.topSideBearing - m.verticalOriginY;
+ const auto right = m.advanceWidth - m.rightSideBearing;
+ const auto bottom = INT32(m.advanceHeight) - m.verticalOriginY - m.bottomSideBearing;
+
+ // Scale to screen space.
+ pOut->SetLeft( std::floor(left * mlfEmHeight / aFontMetrics.designUnitsPerEm) );
+ pOut->SetTop( std::floor(top * mlfEmHeight / aFontMetrics.designUnitsPerEm) );
+ pOut->SetRight( std::ceil(right * mlfEmHeight / aFontMetrics.designUnitsPerEm) );
+ pOut->SetBottom( std::ceil(bottom * mlfEmHeight / aFontMetrics.designUnitsPerEm) );
+
+ ++pOut;
+ }
+
+ return aOut;
+}
+
+bool D2DWriteTextOutRenderer::GetDWriteFaceFromHDC(HDC hDC, IDWriteFontFace ** ppFontFace, float * lfSize) const
+{
+ bool succeeded = SUCCEEDED(CHECKHR(mpGdiInterop->CreateFontFaceFromHdc(hDC, ppFontFace)));
+
+ if (succeeded)
+ {
+ LOGFONTW aLogFont;
+ HFONT hFont = static_cast<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;
+}
+
+WinFontTransformGuard::WinFontTransformGuard(ID2D1RenderTarget* pRenderTarget, float fHScale,
+ const GenericSalLayout& rLayout,
+ const D2D1_POINT_2F& rBaseline,
+ bool bIsVertical)
+ : mpRenderTarget(pRenderTarget)
+{
+ pRenderTarget->GetTransform(&maTransform);
+ D2D1::Matrix3x2F aTransform = maTransform;
+ if (fHScale != 1.0f)
+ {
+ aTransform
+ = aTransform * D2D1::Matrix3x2F::Scale(D2D1::Size(fHScale, 1.0f), D2D1::Point2F(0, 0));
+ }
+
+ Degree10 angle = rLayout.GetOrientation();
+
+ if (bIsVertical)
+ angle += 900_deg10;
+
+ if (angle)
+ {
+ // DWrite angle is in clockwise degrees, our orientation is in counter-clockwise 10th
+ // degrees.
+ aTransform = aTransform
+ * D2D1::Matrix3x2F::Rotation(
+ -toDegrees(angle), rBaseline);
+ }
+ mpRenderTarget->SetTransform(aTransform);
+}
+
+WinFontTransformGuard::~WinFontTransformGuard() { mpRenderTarget->SetTransform(maTransform); }
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */