bump product version to 6.4.0.3
[LibreOffice.git] / vcl / win / gdi / winlayout.cxx
blob9f4da19502453c1f7f587ad633006c1c86bba5b4
2 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 /*
4 * This file is part of the LibreOffice project.
6 * This Source Code Form is subject to the terms of the Mozilla Public
7 * License, v. 2.0. If a copy of the MPL was not distributed with this
8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
10 * This file incorporates work covered by the following license notice:
12 * Licensed to the Apache Software Foundation (ASF) under one or more
13 * contributor license agreements. See the NOTICE file distributed
14 * with this work for additional information regarding copyright
15 * ownership. The ASF licenses this file to you under the Apache
16 * License, Version 2.0 (the "License"); you may not use this file
17 * except in compliance with the License. You may obtain a copy of
18 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <memory>
22 #include <osl/module.h>
23 #include <osl/file.h>
24 #include <sal/log.hxx>
26 #include <comphelper/windowserrorstring.hxx>
27 #include <comphelper/scopeguard.hxx>
29 #include <opengl/texture.hxx>
30 #include <opengl/win/gdiimpl.hxx>
32 #include <vcl/opengl/OpenGLHelper.hxx>
33 #include <win/salgdi.h>
34 #include <win/saldata.hxx>
35 #include <outdev.h>
37 #include <win/DWriteTextRenderer.hxx>
38 #include <win/scoped_gdi.hxx>
40 #include <sft.hxx>
41 #include <sallayout.hxx>
43 #include <cstdio>
44 #include <cstdlib>
46 #include <rtl/character.hxx>
48 #include <boost/functional/hash.hpp>
49 #include <algorithm>
51 #include <shlwapi.h>
52 #include <winver.h>
54 GlobalOpenGLGlyphCache * GlobalOpenGLGlyphCache::get()
56 SalData *data = GetSalData();
57 if (!data->m_pGlobalOpenGLGlyphCache)
58 data->m_pGlobalOpenGLGlyphCache.reset(new GlobalOpenGLGlyphCache);
59 return data->m_pGlobalOpenGLGlyphCache.get();
62 bool WinFontInstance::CacheGlyphToAtlas(HDC hDC, HFONT hFont, int nGlyphIndex,
63 SalGraphics& rGraphics, const GenericSalLayout& rLayout)
65 OpenGLGlyphDrawElement aElement;
67 ScopedHDC aHDC(CreateCompatibleDC(hDC));
69 if (!aHDC)
71 SAL_WARN("vcl.gdi", "CreateCompatibleDC failed: " << WindowsErrorString(GetLastError()));
72 return false;
75 const HFONT hOrigFont = static_cast<HFONT>(SelectObject(aHDC.get(), hFont));
76 if (hOrigFont == nullptr)
78 SAL_WARN("vcl.gdi", "SelectObject failed: " << WindowsErrorString(GetLastError()));
79 return false;
81 const ::comphelper::ScopeGuard aHFONTrestoreScopeGuard(
82 [&aHDC,hOrigFont]() { SelectFont(aHDC.get(), hOrigFont); });
84 // For now we assume DWrite is present and we won't bother with fallback paths.
85 D2DWriteTextOutRenderer * pTxt = dynamic_cast<D2DWriteTextOutRenderer *>(&TextOutRenderer::get(true));
86 if (!pTxt)
87 return false;
89 pTxt->changeTextAntiAliasMode(D2DTextAntiAliasMode::AntiAliased);
91 if (!pTxt->BindFont(aHDC.get()))
93 SAL_WARN("vcl.gdi", "Binding of font failed. The font might not be supported by DirectWrite.");
94 return false;
96 const ::comphelper::ScopeGuard aFontReleaseScopeGuard([&pTxt]() { pTxt->ReleaseFont(); });
98 std::vector<WORD> aGlyphIndices(1);
99 aGlyphIndices[0] = nGlyphIndex;
100 // Fetch the ink boxes and calculate the size of the atlas.
101 tools::Rectangle bounds(0, 0, 0, 0);
102 auto aInkBoxes = pTxt->GetGlyphInkBoxes(aGlyphIndices.data(), aGlyphIndices.data() + 1);
103 if (aInkBoxes.empty())
104 return false;
106 for (auto &box : aInkBoxes)
107 bounds.Union(box + Point(bounds.Right(), 0));
109 // bounds.Top() is the offset from the baseline at (0,0) to the top of the
110 // inkbox.
111 aElement.mnBaselineOffset = -bounds.Top();
112 aElement.mnHeight = bounds.getHeight();
113 aElement.mbVertical = false;
115 // Try hard to avoid overlap as we want to be able to use
116 // individual rectangles for each glyph. The ABC widths don't
117 // take anti-aliasing into consideration. Let's hope that leaving
118 // "extra" space between glyphs will help.
119 std::vector<float> aGlyphAdv(1); // offsets between glyphs
120 std::vector<DWRITE_GLYPH_OFFSET> aGlyphOffset(1, {0.0f, 0.0f});
121 std::vector<int> aEnds(1); // end of each glyph box
122 float fHScale = getHScale();
123 float totWidth = 0;
125 int overhang = aInkBoxes[0].Left();
126 int blackWidth = aInkBoxes[0].getWidth() * fHScale; // width of non-AA pixels
127 aElement.maLeftOverhangs = overhang;
129 aGlyphAdv[0] = blackWidth + aElement.getExtraSpace();
130 aGlyphOffset[0].advanceOffset = -overhang;
132 totWidth += aGlyphAdv[0];
133 aEnds[0] = totWidth;
135 // Leave extra space also at top and bottom
136 int nBitmapWidth = totWidth;
137 int nBitmapHeight = bounds.getHeight() + aElement.getExtraSpace();
139 UINT nPos = 0;
141 aElement.maLocation.SetLeft(nPos);
142 aElement.maLocation.SetRight(aEnds[0]);
143 aElement.maLocation.SetTop(0);
144 aElement.maLocation.SetBottom(bounds.getHeight() + aElement.getExtraSpace());
145 nPos = aEnds[0];
147 OpenGLCompatibleDC aDC(rGraphics, 0, 0, nBitmapWidth, nBitmapHeight);
149 SetTextColor(aDC.getCompatibleHDC(), RGB(0, 0, 0));
150 SetBkColor(aDC.getCompatibleHDC(), RGB(255, 255, 255));
152 aDC.fill(RGB(0xff, 0xff, 0xff));
154 pTxt->BindDC(aDC.getCompatibleHDC(), tools::Rectangle(0, 0, nBitmapWidth, nBitmapHeight));
155 auto pRT = pTxt->GetRenderTarget();
157 ID2D1SolidColorBrush* pBrush = nullptr;
158 if (!SUCCEEDED(pRT->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &pBrush)))
159 return false;
161 D2D1_POINT_2F baseline = {
162 static_cast<FLOAT>(aElement.getExtraOffset()),
163 static_cast<FLOAT>(aElement.getExtraOffset() + aElement.mnBaselineOffset)
166 DWRITE_GLYPH_RUN glyphs = {
167 pTxt->GetFontFace(),
168 pTxt->GetEmHeight(),
170 aGlyphIndices.data(),
171 aGlyphAdv.data(),
172 aGlyphOffset.data(),
173 false,
177 WinFontTransformGuard aTransformGuard(pRT, fHScale, rLayout, baseline);
178 pRT->BeginDraw();
179 pRT->DrawGlyphRun(baseline, &glyphs, pBrush);
180 HRESULT hResult = pRT->EndDraw();
182 pBrush->Release();
184 switch (hResult)
186 case S_OK:
187 break;
188 case D2DERR_RECREATE_TARGET:
189 pTxt->CreateRenderTarget();
190 break;
191 default:
192 SAL_WARN("vcl.gdi", "DrawGlyphRun-EndDraw failed: " << WindowsErrorString(GetLastError()));
193 return false;
196 if (!OpenGLGlyphCache::ReserveTextureSpace(aElement, nBitmapWidth, nBitmapHeight))
197 return false;
198 if (!aDC.copyToTexture(aElement.maTexture))
199 return false;
201 maOpenGLGlyphCache.PutDrawElementInCache(aElement, nGlyphIndex);
203 return true;
206 TextOutRenderer & TextOutRenderer::get(bool bUseDWrite)
208 SalData *const pSalData = GetSalData();
210 if (!pSalData)
211 { // don't call this after DeInitVCL()
212 fprintf(stderr, "TextOutRenderer fatal error: no SalData");
213 abort();
216 if (bUseDWrite)
218 if (!pSalData->m_pD2DWriteTextOutRenderer)
220 pSalData->m_pD2DWriteTextOutRenderer.reset(new D2DWriteTextOutRenderer());
222 return *pSalData->m_pD2DWriteTextOutRenderer;
224 if (!pSalData->m_pExTextOutRenderer)
226 pSalData->m_pExTextOutRenderer.reset(new ExTextOutRenderer);
228 return *pSalData->m_pExTextOutRenderer;
232 bool ExTextOutRenderer::operator ()(GenericSalLayout const &rLayout,
233 SalGraphics & /*rGraphics*/,
234 HDC hDC)
236 HFONT hFont = static_cast<HFONT>(GetCurrentObject( hDC, OBJ_FONT ));
237 ScopedHFONT hAltFont;
238 bool bUseAltFont = false;
239 bool bShift = false;
240 if (rLayout.GetFont().GetFontSelectPattern().mbVertical)
242 LOGFONTW aLogFont;
243 GetObjectW(hFont, sizeof(aLogFont), &aLogFont);
244 if (aLogFont.lfFaceName[0] == '@')
246 memmove(&aLogFont.lfFaceName[0], &aLogFont.lfFaceName[1],
247 sizeof(aLogFont.lfFaceName)-sizeof(aLogFont.lfFaceName[0]));
248 hAltFont.reset(CreateFontIndirectW(&aLogFont));
250 else
252 bShift = true;
253 aLogFont.lfEscapement += 2700;
254 aLogFont.lfOrientation = aLogFont.lfEscapement;
255 hAltFont.reset(CreateFontIndirectW(&aLogFont));
259 UINT nTextAlign = GetTextAlign ( hDC );
260 int nStart = 0;
261 Point aPos(0, 0);
262 const GlyphItem* pGlyph;
263 while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
265 WORD glyphWStr[] = { pGlyph->glyphId() };
266 if (hAltFont && pGlyph->IsVertical() == bUseAltFont)
268 bUseAltFont = !bUseAltFont;
269 SelectFont(hDC, bUseAltFont ? hAltFont.get() : hFont);
271 if (bShift && pGlyph->IsVertical())
272 SetTextAlign(hDC, TA_TOP|TA_LEFT);
274 ExtTextOutW(hDC, aPos.X(), aPos.Y(), ETO_GLYPH_INDEX, nullptr, LPCWSTR(&glyphWStr), 1, nullptr);
276 if (bShift && pGlyph->IsVertical())
277 SetTextAlign(hDC, nTextAlign);
279 if (hAltFont)
281 if (bUseAltFont)
282 SelectFont(hDC, hFont);
285 return true;
288 std::unique_ptr<GenericSalLayout> WinSalGraphics::GetTextLayout(int nFallbackLevel)
290 assert(mpWinFontEntry[nFallbackLevel]);
291 if (!mpWinFontEntry[nFallbackLevel])
292 return nullptr;
294 assert(mpWinFontEntry[nFallbackLevel]->GetFontFace());
296 mpWinFontEntry[nFallbackLevel]->SetGraphics(this);
297 return std::make_unique<GenericSalLayout>(*mpWinFontEntry[nFallbackLevel]);
300 WinFontInstance::WinFontInstance(const WinFontFace& rPFF, const FontSelectPattern& rFSP)
301 : LogicalFontInstance(rPFF, rFSP)
302 , m_pGraphics(nullptr)
303 , m_hFont(nullptr)
304 , m_fScale(1.0f)
308 WinFontInstance::~WinFontInstance()
310 if (m_hFont)
311 ::DeleteFont(m_hFont);
314 bool WinFontInstance::hasHScale() const
316 const FontSelectPattern &rPattern = GetFontSelectPattern();
317 int nHeight(rPattern.mnHeight);
318 int nWidth(rPattern.mnWidth ? rPattern.mnWidth * GetAverageWidthFactor() : nHeight);
319 return nWidth != nHeight;
322 float WinFontInstance::getHScale() const
324 const FontSelectPattern& rPattern = GetFontSelectPattern();
325 int nHeight(rPattern.mnHeight);
326 if (!nHeight)
327 return 1.0;
328 float nWidth(rPattern.mnWidth ? rPattern.mnWidth * GetAverageWidthFactor() : nHeight);
329 return nWidth / nHeight;
332 struct BlobReference
334 hb_blob_t* mpBlob;
335 BlobReference(hb_blob_t* pBlob) : mpBlob(pBlob)
337 hb_blob_reference(mpBlob);
339 BlobReference(BlobReference const & other)
340 : mpBlob(other.mpBlob)
342 hb_blob_reference(mpBlob);
344 ~BlobReference() { hb_blob_destroy(mpBlob); }
346 using BlobCacheKey = std::pair<rtl::Reference<PhysicalFontFace>, hb_tag_t>;
347 struct BlobCacheKeyHash
349 std::size_t operator()(BlobCacheKey const& rKey) const
351 std::size_t seed = 0;
352 boost::hash_combine(seed, rKey.first.get());
353 boost::hash_combine(seed, rKey.second);
354 return seed;
358 static hb_blob_t* getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData)
360 static o3tl::lru_map<BlobCacheKey, BlobReference, BlobCacheKeyHash> gCache(50);
362 WinFontInstance* pFont = static_cast<WinFontInstance*>(pUserData);
363 HDC hDC = pFont->GetGraphics()->getHDC();
364 HFONT hFont = pFont->GetHFONT();
365 assert(hDC);
366 assert(hFont);
368 BlobCacheKey cacheKey { rtl::Reference<PhysicalFontFace>(pFont->GetFontFace()), nTableTag };
369 auto it = gCache.find(cacheKey);
370 if (it != gCache.end())
372 hb_blob_reference(it->second.mpBlob);
373 return it->second.mpBlob;
376 sal_uLong nLength = 0;
377 unsigned char* pBuffer = nullptr;
379 HGDIOBJ hOrigFont = SelectObject(hDC, hFont);
380 nLength = ::GetFontData(hDC, OSL_NETDWORD(nTableTag), 0, nullptr, 0);
381 if (nLength > 0 && nLength != GDI_ERROR)
383 pBuffer = new unsigned char[nLength];
384 ::GetFontData(hDC, OSL_NETDWORD(nTableTag), 0, pBuffer, nLength);
386 SelectObject(hDC, hOrigFont);
388 if (!pBuffer)
389 return nullptr;
391 hb_blob_t* pBlob = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength, HB_MEMORY_MODE_READONLY,
392 pBuffer, [](void* data){ delete[] static_cast<unsigned char*>(data); });
393 if (!pBlob)
394 return pBlob;
395 gCache.insert({cacheKey, BlobReference(pBlob)});
396 return pBlob;
399 hb_font_t* WinFontInstance::ImplInitHbFont()
401 assert(m_pGraphics);
402 hb_font_t* pHbFont = InitHbFont(hb_face_create_for_tables(getFontTable, this, nullptr));
404 // Calculate the AverageWidthFactor, see LogicalFontInstance::GetScale().
405 if (GetFontSelectPattern().mnWidth)
407 double nUPEM = hb_face_get_upem(hb_font_get_face(pHbFont));
409 LOGFONTW aLogFont;
410 GetObjectW(m_hFont, sizeof(LOGFONTW), &aLogFont);
412 // Set the height (font size) to EM to minimize rounding errors.
413 aLogFont.lfHeight = -nUPEM;
414 // Set width to the default to get the original value in the metrics.
415 aLogFont.lfWidth = 0;
417 TEXTMETRICW aFontMetric;
419 // Get the font metrics.
420 HDC hDC = m_pGraphics->getHDC();
421 ScopedSelectedHFONT hFont(hDC, CreateFontIndirectW(&aLogFont));
422 GetTextMetricsW(hDC, &aFontMetric);
425 SetAverageWidthFactor(nUPEM / aFontMetric.tmAveCharWidth);
428 return pHbFont;
431 void WinFontInstance::SetGraphics(WinSalGraphics *pGraphics)
433 m_pGraphics = pGraphics;
434 if (m_hFont)
435 return;
436 HFONT hOrigFont;
437 m_hFont = m_pGraphics->ImplDoSetFont(GetFontSelectPattern(), GetFontFace(), m_fScale, hOrigFont);
438 SelectObject(m_pGraphics->getHDC(), hOrigFont);
441 bool WinSalGraphics::CacheGlyphs(const GenericSalLayout& rLayout)
443 static bool bDoGlyphCaching = (std::getenv("SAL_DISABLE_GLYPH_CACHING") == nullptr);
444 if (!bDoGlyphCaching)
445 return false;
447 if (rLayout.GetOrientation())
448 // Our caching is incomplete, skip it for non-horizontal text.
449 return false;
451 HDC hDC = getHDC();
452 WinFontInstance& rFont = *static_cast<WinFontInstance*>(&rLayout.GetFont());
453 HFONT hFONT = rFont.GetHFONT();
455 int nStart = 0;
456 Point aPos(0, 0);
457 const GlyphItem* pGlyph;
458 while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
460 if (!rFont.GetOpenGLGlyphCache().IsGlyphCached(pGlyph->glyphId()))
462 if (!rFont.CacheGlyphToAtlas(hDC, hFONT, pGlyph->glyphId(), *this, rLayout))
463 return false;
467 return true;
470 bool WinSalGraphics::DrawCachedGlyphs(const GenericSalLayout& rLayout)
472 HDC hDC = getHDC();
474 tools::Rectangle aRect;
475 rLayout.GetBoundRect(aRect);
477 COLORREF color = GetTextColor(hDC);
478 Color salColor(GetRValue(color), GetGValue(color), GetBValue(color));
480 WinOpenGLSalGraphicsImpl *pImpl = dynamic_cast<WinOpenGLSalGraphicsImpl*>(mpImpl.get());
481 if (!pImpl)
482 return false;
484 WinFontInstance& rFont = *static_cast<WinFontInstance*>(&rLayout.GetFont());
486 int nStart = 0;
487 Point aPos(0, 0);
488 const GlyphItem* pGlyph;
489 while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
491 OpenGLGlyphDrawElement& rElement(rFont.GetOpenGLGlyphCache().GetDrawElement(pGlyph->glyphId()));
492 OpenGLTexture& rTexture = rElement.maTexture;
494 if (!rTexture)
495 return false;
497 SalTwoRect a2Rects(0, 0,
498 rTexture.GetWidth(), rTexture.GetHeight(),
499 aPos.X() - rElement.getExtraOffset() + rElement.maLeftOverhangs,
500 aPos.Y() - rElement.mnBaselineOffset - rElement.getExtraOffset(),
501 rTexture.GetWidth(), rTexture.GetHeight());
503 pImpl->DeferredTextDraw(rTexture, salColor, a2Rects);
506 return true;
509 void WinSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout, HDC hDC, bool bUseDWrite)
511 TextOutRenderer &render = TextOutRenderer::get(bUseDWrite);
512 render(rLayout, *this, hDC);
515 void WinSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
517 HDC hDC = getHDC();
519 const WinFontInstance* pWinFont = static_cast<const WinFontInstance*>(&rLayout.GetFont());
520 const HFONT hLayoutFont = pWinFont->GetHFONT();
521 bool bUseOpenGL = OpenGLHelper::isVCLOpenGLEnabled() && !mbPrinter;
523 // Our DirectWrite renderer is incomplete, skip it for vertical text where glyphs are not
524 // rotated.
525 bool bForceGDI = rLayout.GetFont().GetFontSelectPattern().mbVertical;
527 if (!bUseOpenGL)
529 // no OpenGL, just classic rendering
530 const HFONT hOrigFont = ::SelectFont(hDC, hLayoutFont);
531 DrawTextLayout(rLayout, hDC, false);
532 ::SelectFont(hDC, hOrigFont);
534 // if we can't draw the cached OpenGL glyphs, try to draw a full OpenGL layout
535 else if (bForceGDI || !CacheGlyphs(rLayout) || !DrawCachedGlyphs(rLayout))
537 // We have to render the text to a hidden texture, and draw it.
539 // Note that Windows GDI does not really support the alpha correctly
540 // when drawing - ie. it draws nothing to the alpha channel when
541 // rendering the text, even the antialiasing is done as 'real' pixels,
542 // not alpha...
544 // Luckily, this does not really limit us:
546 // To blend properly, we draw the texture, but then use it as an alpha
547 // channel for solid color (that will define the text color). This
548 // destroys the subpixel antialiasing - turns it into 'classic'
549 // antialiasing - but that is the best we can do, because the subpixel
550 // antialiasing needs to know what is in the background: When the
551 // background is white, or white-ish, it does the subpixel, but when
552 // there is a color, it just darkens the color (and does this even
553 // when part of the character is on a colored background, and part on
554 // white). It has to work this way, the results would look strange
555 // otherwise.
557 // For the GL rendering to work even with the subpixel antialiasing,
558 // we would need to get the current texture from the screen, let GDI
559 // draw the text to it (so that it can decide well where to use the
560 // subpixel and where not), and draw the result - but in that case we
561 // don't need alpha anyway.
563 // TODO: check the performance of this 2nd approach at some stage and
564 // switch to that if it performs well.
566 tools::Rectangle aRect;
567 rLayout.GetBoundRect(aRect);
569 WinOpenGLSalGraphicsImpl *pImpl = dynamic_cast<WinOpenGLSalGraphicsImpl*>(mpImpl.get());
571 if (pImpl)
573 pImpl->PreDraw();
575 OpenGLCompatibleDC aDC(*this, aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight());
577 // we are making changes to the DC, make sure we got a new one
578 assert(aDC.getCompatibleHDC() != hDC);
580 RECT aWinRect = { aRect.Left(), aRect.Top(), aRect.Left() + aRect.GetWidth(), aRect.Top() + aRect.GetHeight() };
581 ::FillRect(aDC.getCompatibleHDC(), &aWinRect, static_cast<HBRUSH>(::GetStockObject(WHITE_BRUSH)));
583 // setup the hidden DC with black color and white background, we will
584 // use the result of the text drawing later as a mask only
585 const HFONT hOrigFont = ::SelectFont(aDC.getCompatibleHDC(), hLayoutFont);
587 ::SetTextColor(aDC.getCompatibleHDC(), RGB(0, 0, 0));
588 ::SetBkColor(aDC.getCompatibleHDC(), RGB(255, 255, 255));
590 UINT nTextAlign = ::GetTextAlign(hDC);
591 ::SetTextAlign(aDC.getCompatibleHDC(), nTextAlign);
593 COLORREF color = ::GetTextColor(hDC);
594 Color salColor(GetRValue(color), GetGValue(color), GetBValue(color));
596 // the actual drawing
597 DrawTextLayout(rLayout, aDC.getCompatibleHDC(), !bForceGDI);
599 std::unique_ptr<OpenGLTexture> xTexture(aDC.getTexture());
600 if (xTexture)
601 pImpl->DrawMask(*xTexture, salColor, aDC.getTwoRect());
603 ::SelectFont(aDC.getCompatibleHDC(), hOrigFont);
605 pImpl->PostDraw();
610 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */