Version 6.4.0.0.beta1, tag libreoffice-6.4.0.0.beta1
[LibreOffice.git] / vcl / win / gdi / DWriteTextRenderer.cxx
blob734b68b0780a2f867a9505dad7e5305227cc0254
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <win/salgdi.h>
21 #include <win/saldata.hxx>
22 #include <outdev.h>
24 #include <win/DWriteTextRenderer.hxx>
26 #include <sft.hxx>
27 #include <sallayout.hxx>
29 #include <shlwapi.h>
30 #include <winver.h>
32 #include <comphelper/windowserrorstring.hxx>
33 #include <sal/log.hxx>
35 namespace
38 D2DTextAntiAliasMode lclGetSystemTextAntiAliasMode()
40 D2DTextAntiAliasMode eMode = D2DTextAntiAliasMode::Default;
42 BOOL bFontSmoothing;
43 if (!SystemParametersInfoW(SPI_GETFONTSMOOTHING, 0, &bFontSmoothing, 0))
44 return eMode;
46 if (bFontSmoothing)
48 eMode = D2DTextAntiAliasMode::AntiAliased;
50 UINT nType;
51 if (SystemParametersInfoW(SPI_GETFONTSMOOTHINGTYPE, 0, &nType, 0) && nType == FE_FONTSMOOTHINGCLEARTYPE)
52 eMode = D2DTextAntiAliasMode::ClearType;
54 else
56 eMode = D2DTextAntiAliasMode::Aliased;
59 return eMode;
62 IDWriteRenderingParams* lclSetRenderingMode(IDWriteFactory* pDWriteFactory, DWRITE_RENDERING_MODE eRenderingMode)
64 IDWriteRenderingParams* pDefaultParameters = nullptr;
65 pDWriteFactory->CreateRenderingParams(&pDefaultParameters);
67 IDWriteRenderingParams* pParameters = nullptr;
68 pDWriteFactory->CreateCustomRenderingParams(
69 pDefaultParameters->GetGamma(),
70 pDefaultParameters->GetEnhancedContrast(),
71 pDefaultParameters->GetClearTypeLevel(),
72 pDefaultParameters->GetPixelGeometry(),
73 eRenderingMode,
74 &pParameters);
75 return pParameters;
78 #ifdef SAL_LOG_WARN
79 HRESULT checkResult(HRESULT hr, const char* file, size_t line)
81 if (FAILED(hr))
83 OUString sLocationString = OUString::createFromAscii(file) + ":" + OUString::number(line) + " ";
84 SAL_DETAIL_LOG_STREAM(SAL_DETAIL_ENABLE_LOG_WARN, ::SAL_DETAIL_LOG_LEVEL_WARN,
85 "vcl.gdi", sLocationString.toUtf8().getStr(),
86 "HRESULT failed with: 0x" << OUString::number(hr, 16) << ": " << WindowsErrorStringFromHRESULT(hr));
88 return hr;
91 #define CHECKHR(funct) checkResult(funct, __FILE__, __LINE__)
92 #else
93 #define CHECKHR(funct) (funct)
94 #endif
97 } // end anonymous namespace
99 D2DWriteTextOutRenderer::D2DWriteTextOutRenderer()
100 : mpD2DFactory(nullptr),
101 mpDWriteFactory(nullptr),
102 mpGdiInterop(nullptr),
103 mpRT(nullptr),
104 mRTProps(D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT,
105 D2D1::PixelFormat(DXGI_FORMAT_B8G8R8A8_UNORM, D2D1_ALPHA_MODE_PREMULTIPLIED),
106 0, 0)),
107 mpFontFace(nullptr),
108 mlfEmHeight(0.0f),
109 mhDC(nullptr),
110 meTextAntiAliasMode(D2DTextAntiAliasMode::Default)
112 HRESULT hr = S_OK;
113 hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, __uuidof(ID2D1Factory), nullptr, reinterpret_cast<void **>(&mpD2DFactory));
114 hr = DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast<IUnknown**>(&mpDWriteFactory));
115 if (SUCCEEDED(hr))
117 hr = mpDWriteFactory->GetGdiInterop(&mpGdiInterop);
118 hr = CreateRenderTarget();
120 meTextAntiAliasMode = lclGetSystemTextAntiAliasMode();
123 D2DWriteTextOutRenderer::~D2DWriteTextOutRenderer()
125 if (mpRT)
126 mpRT->Release();
127 if (mpGdiInterop)
128 mpGdiInterop->Release();
129 if (mpDWriteFactory)
130 mpDWriteFactory->Release();
131 if (mpD2DFactory)
132 mpD2DFactory->Release();
135 void D2DWriteTextOutRenderer::applyTextAntiAliasMode()
137 D2D1_TEXT_ANTIALIAS_MODE eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
138 DWRITE_RENDERING_MODE eRenderingMode = DWRITE_RENDERING_MODE_DEFAULT;
139 switch (meTextAntiAliasMode)
141 case D2DTextAntiAliasMode::Default:
142 eRenderingMode = DWRITE_RENDERING_MODE_DEFAULT;
143 eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_DEFAULT;
144 break;
145 case D2DTextAntiAliasMode::Aliased:
146 eRenderingMode = DWRITE_RENDERING_MODE_ALIASED;
147 eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_ALIASED;
148 break;
149 case D2DTextAntiAliasMode::AntiAliased:
150 eRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC;
151 eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_GRAYSCALE;
152 break;
153 case D2DTextAntiAliasMode::ClearType:
154 eRenderingMode = DWRITE_RENDERING_MODE_CLEARTYPE_GDI_CLASSIC;
155 eTextAAMode = D2D1_TEXT_ANTIALIAS_MODE_CLEARTYPE;
156 break;
157 default:
158 break;
160 mpRT->SetTextRenderingParams(lclSetRenderingMode(mpDWriteFactory, eRenderingMode));
161 mpRT->SetTextAntialiasMode(eTextAAMode);
164 HRESULT D2DWriteTextOutRenderer::CreateRenderTarget()
166 if (mpRT)
168 mpRT->Release();
169 mpRT = nullptr;
171 HRESULT hr = CHECKHR(mpD2DFactory->CreateDCRenderTarget(&mRTProps, &mpRT));
172 if (SUCCEEDED(hr))
173 applyTextAntiAliasMode();
174 return hr;
177 void D2DWriteTextOutRenderer::changeTextAntiAliasMode(D2DTextAntiAliasMode eMode)
179 if (meTextAntiAliasMode != eMode)
181 meTextAntiAliasMode = eMode;
182 applyTextAntiAliasMode();
186 bool D2DWriteTextOutRenderer::Ready() const
188 return mpGdiInterop && mpRT;
191 HRESULT D2DWriteTextOutRenderer::BindDC(HDC hDC, tools::Rectangle const & rRect)
193 RECT const rc = { rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom() };
194 return CHECKHR(mpRT->BindDC(hDC, &rc));
197 bool D2DWriteTextOutRenderer::operator ()(GenericSalLayout const & rLayout, SalGraphics& rGraphics, HDC hDC)
199 bool bRetry = false;
200 bool bResult = false;
201 int nCount = 0;
204 bRetry = false;
205 bResult = performRender(rLayout, rGraphics, hDC, bRetry);
206 nCount++;
207 } while (bRetry && nCount < 3);
208 return bResult;
211 bool D2DWriteTextOutRenderer::performRender(GenericSalLayout const & rLayout, SalGraphics& rGraphics, HDC hDC, bool& bRetry)
213 if (!Ready())
214 return false;
216 HRESULT hr = S_OK;
217 hr = BindDC(hDC);
219 if (hr == D2DERR_RECREATE_TARGET)
221 CreateRenderTarget();
222 bRetry = true;
223 return false;
225 if (FAILED(hr))
227 // If for any reason we can't bind fallback to legacy APIs.
228 return ExTextOutRenderer()(rLayout, rGraphics, hDC);
231 mlfEmHeight = 0;
232 if (!GetDWriteFaceFromHDC(hDC, &mpFontFace, &mlfEmHeight))
233 return false;
235 const WinFontInstance& rWinFont = static_cast<const WinFontInstance&>(rLayout.GetFont());
236 float fHScale = rWinFont.getHScale();
238 tools::Rectangle bounds;
239 bool succeeded = rLayout.GetBoundRect(bounds);
240 if (succeeded)
242 hr = BindDC(hDC, bounds); // Update the bounding rect.
243 succeeded &= SUCCEEDED(hr);
246 ID2D1SolidColorBrush* pBrush = nullptr;
247 if (succeeded)
249 COLORREF bgrTextColor = GetTextColor(hDC);
250 D2D1::ColorF aD2DColor(GetRValue(bgrTextColor) / 255.0f, GetGValue(bgrTextColor) / 255.0f, GetBValue(bgrTextColor) / 255.0f);
251 succeeded &= SUCCEEDED(CHECKHR(mpRT->CreateSolidColorBrush(aD2DColor, &pBrush)));
254 if (succeeded)
256 mpRT->BeginDraw();
258 int nStart = 0;
259 Point aPos(0, 0);
260 const GlyphItem* pGlyph;
261 while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
263 UINT16 glyphIndices[] = { pGlyph->glyphId() };
264 FLOAT glyphAdvances[] = { static_cast<FLOAT>(pGlyph->m_nNewWidth) / fHScale };
265 DWRITE_GLYPH_OFFSET glyphOffsets[] = { { 0.0f, 0.0f }, };
266 D2D1_POINT_2F baseline = { static_cast<FLOAT>(aPos.X() - bounds.Left()) / fHScale,
267 static_cast<FLOAT>(aPos.Y() - bounds.Top()) };
268 WinFontTransformGuard aTransformGuard(mpRT, fHScale, rLayout, baseline);
269 DWRITE_GLYPH_RUN glyphs = {
270 mpFontFace,
271 mlfEmHeight,
273 glyphIndices,
274 glyphAdvances,
275 glyphOffsets,
276 false,
280 mpRT->DrawGlyphRun(baseline, &glyphs, pBrush);
283 hr = CHECKHR(mpRT->EndDraw());
286 if (pBrush)
287 pBrush->Release();
289 ReleaseFont();
291 if (hr == D2DERR_RECREATE_TARGET)
293 CreateRenderTarget();
294 bRetry = true;
297 return succeeded;
300 bool D2DWriteTextOutRenderer::BindFont(HDC hDC)
302 // A TextOutRender can only be bound to one font at a time, so the
303 assert(mpFontFace == nullptr);
304 if (mpFontFace)
306 ReleaseFont();
307 return false;
310 // Initially bind to an empty rectangle to get access to the font face,
311 // we'll update it once we've calculated a bounding rect in DrawGlyphs
312 if (FAILED(BindDC(mhDC = hDC)))
313 return false;
315 mlfEmHeight = 0;
316 return GetDWriteFaceFromHDC(hDC, &mpFontFace, &mlfEmHeight);
319 bool D2DWriteTextOutRenderer::ReleaseFont()
321 mpFontFace->Release();
322 mpFontFace = nullptr;
323 mhDC = nullptr;
325 return true;
328 // GetGlyphInkBoxes
329 // The inkboxes returned have their origin on the baseline, to a -ve value
330 // of Top() means the glyph extends abs(Top()) many pixels above the
331 // baseline, and +ve means the ink starts that many pixels below.
332 std::vector<tools::Rectangle> D2DWriteTextOutRenderer::GetGlyphInkBoxes(uint16_t const * pGid, uint16_t const * pGidEnd) const
334 ptrdiff_t nGlyphs = pGidEnd - pGid;
335 if (nGlyphs < 0)
336 return std::vector<tools::Rectangle>();
338 DWRITE_FONT_METRICS aFontMetrics;
339 mpFontFace->GetMetrics(&aFontMetrics);
341 std::vector<DWRITE_GLYPH_METRICS> metrics(nGlyphs);
342 if (!SUCCEEDED(CHECKHR(mpFontFace->GetDesignGlyphMetrics(pGid, nGlyphs, metrics.data()))))
343 return std::vector<tools::Rectangle>();
345 std::vector<tools::Rectangle> aOut(nGlyphs);
346 auto pOut = aOut.begin();
347 for (auto &m : metrics)
349 const long left = m.leftSideBearing,
350 top = m.topSideBearing - m.verticalOriginY,
351 right = m.advanceWidth - m.rightSideBearing,
352 bottom = INT32(m.advanceHeight) - m.verticalOriginY - m.bottomSideBearing;
354 // Scale to screen space.
355 pOut->SetLeft( std::floor(left * mlfEmHeight / aFontMetrics.designUnitsPerEm) );
356 pOut->SetTop( std::floor(top * mlfEmHeight / aFontMetrics.designUnitsPerEm) );
357 pOut->SetRight( std::ceil(right * mlfEmHeight / aFontMetrics.designUnitsPerEm) );
358 pOut->SetBottom( std::ceil(bottom * mlfEmHeight / aFontMetrics.designUnitsPerEm) );
360 ++pOut;
363 return aOut;
366 bool D2DWriteTextOutRenderer::GetDWriteFaceFromHDC(HDC hDC, IDWriteFontFace ** ppFontFace, float * lfSize) const
368 bool succeeded = SUCCEEDED(CHECKHR(mpGdiInterop->CreateFontFaceFromHdc(hDC, ppFontFace)));
370 if (succeeded)
372 LOGFONTW aLogFont;
373 HFONT hFont = static_cast<HFONT>(::GetCurrentObject(hDC, OBJ_FONT));
375 GetObjectW(hFont, sizeof(LOGFONTW), &aLogFont);
376 float dpix, dpiy;
377 mpRT->GetDpi(&dpix, &dpiy);
378 *lfSize = aLogFont.lfHeight * 96.0f / dpiy;
380 assert(*lfSize < 0);
381 *lfSize *= -1;
384 return succeeded;
387 WinFontTransformGuard::WinFontTransformGuard(ID2D1RenderTarget* pRenderTarget, float fHScale,
388 const GenericSalLayout& rLayout,
389 const D2D1_POINT_2F& rBaseline)
390 : mpRenderTarget(pRenderTarget)
392 pRenderTarget->GetTransform(&maTransform);
393 D2D1::Matrix3x2F aTransform = maTransform;
394 if (fHScale != 1.0f)
396 aTransform
397 = aTransform * D2D1::Matrix3x2F::Scale(D2D1::Size(fHScale, 1.0f), D2D1::Point2F(0, 0));
400 if (rLayout.GetOrientation() != 0)
402 // DWrite angle is in clockwise degrees, our orientation is in counter-clockwise 10th
403 // degrees.
404 aTransform = aTransform
405 * D2D1::Matrix3x2F::Rotation(
406 -static_cast<FLOAT>(rLayout.GetOrientation()) / 10, rBaseline);
408 mpRenderTarget->SetTransform(aTransform);
411 WinFontTransformGuard::~WinFontTransformGuard() { mpRenderTarget->SetTransform(maTransform); }
413 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */