Version 7.1.7.1, tag libreoffice-7.1.7.1
[LibreOffice.git] / vcl / win / gdi / winlayout.cxx
blob7863a3c353d2d7dd4e0d7e91a8bcc783a3ea9e4f
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 <config_features.h>
23 #include <memory>
24 #include <osl/module.h>
25 #include <osl/file.h>
26 #include <sal/log.hxx>
28 #include <comphelper/windowserrorstring.hxx>
29 #include <comphelper/scopeguard.hxx>
31 #include <opengl/win/gdiimpl.hxx>
32 #include <opengl/win/winlayout.hxx>
34 #include <vcl/opengl/OpenGLHelper.hxx>
35 #include <win/salgdi.h>
36 #include <win/saldata.hxx>
37 #include <win/wingdiimpl.hxx>
38 #include <outdev.h>
40 #include <win/DWriteTextRenderer.hxx>
41 #include <win/scoped_gdi.hxx>
43 #include <sft.hxx>
44 #include <sallayout.hxx>
46 #include <cstdio>
47 #include <cstdlib>
49 #include <rtl/character.hxx>
51 #include <boost/functional/hash.hpp>
52 #include <algorithm>
54 #include <shlwapi.h>
55 #include <winver.h>
57 GlobalWinGlyphCache* GlobalWinGlyphCache::get()
59 SalData* data = GetSalData();
60 if (!data->m_pGlobalWinGlyphCache)
62 if (OpenGLHelper::isVCLOpenGLEnabled())
63 data->m_pGlobalWinGlyphCache.reset(new OpenGLGlobalWinGlyphCache);
65 return data->m_pGlobalWinGlyphCache.get();
68 bool WinFontInstance::CacheGlyphToAtlas(HDC hDC, HFONT hFont, int nGlyphIndex,
69 SalGraphics& rGraphics, const GenericSalLayout& rLayout)
71 WinGlyphDrawElement aElement;
73 ScopedHDC aHDC(CreateCompatibleDC(hDC));
75 if (!aHDC)
77 SAL_WARN("vcl.gdi", "CreateCompatibleDC failed: " << WindowsErrorString(GetLastError()));
78 return false;
81 const HFONT hOrigFont = static_cast<HFONT>(SelectObject(aHDC.get(), hFont));
82 if (hOrigFont == nullptr)
84 SAL_WARN("vcl.gdi", "SelectObject failed: " << WindowsErrorString(GetLastError()));
85 return false;
87 const ::comphelper::ScopeGuard aHFONTrestoreScopeGuard(
88 [&aHDC, hOrigFont]() { SelectFont(aHDC.get(), hOrigFont); });
90 // For now we assume DWrite is present and we won't bother with fallback paths.
91 D2DWriteTextOutRenderer* pTxt
92 = dynamic_cast<D2DWriteTextOutRenderer*>(&TextOutRenderer::get(true));
93 if (!pTxt)
94 return false;
96 pTxt->changeTextAntiAliasMode(D2DTextAntiAliasMode::AntiAliased);
98 if (!pTxt->BindFont(aHDC.get()))
100 SAL_WARN("vcl.gdi",
101 "Binding of font failed. The font might not be supported by DirectWrite.");
102 return false;
104 const ::comphelper::ScopeGuard aFontReleaseScopeGuard([&pTxt]() { pTxt->ReleaseFont(); });
106 std::vector<WORD> aGlyphIndices(1);
107 aGlyphIndices[0] = nGlyphIndex;
108 // Fetch the ink boxes and calculate the size of the atlas.
109 tools::Rectangle bounds(0, 0, 0, 0);
110 auto aInkBoxes = pTxt->GetGlyphInkBoxes(aGlyphIndices.data(), aGlyphIndices.data() + 1);
111 if (aInkBoxes.empty())
112 return false;
114 for (auto& box : aInkBoxes)
115 bounds.Union(box + Point(bounds.Right(), 0));
117 // bounds.Top() is the offset from the baseline at (0,0) to the top of the
118 // inkbox.
119 aElement.mnBaselineOffset = -bounds.Top();
120 aElement.mnHeight = bounds.getHeight();
121 aElement.mbVertical = false;
123 // Try hard to avoid overlap as we want to be able to use
124 // individual rectangles for each glyph. The ABC widths don't
125 // take anti-aliasing into consideration. Let's hope that leaving
126 // "extra" space between glyphs will help.
127 std::vector<float> aGlyphAdv(1); // offsets between glyphs
128 std::vector<DWRITE_GLYPH_OFFSET> aGlyphOffset(1, { 0.0f, 0.0f });
129 std::vector<int> aEnds(1); // end of each glyph box
130 float fHScale = getHScale();
131 float totWidth = 0;
133 int overhang = aInkBoxes[0].Left();
134 int blackWidth = aInkBoxes[0].getWidth() * fHScale; // width of non-AA pixels
135 aElement.maLeftOverhangs = overhang;
137 aGlyphAdv[0] = blackWidth + aElement.getExtraSpace();
138 aGlyphOffset[0].advanceOffset = -overhang;
140 totWidth += aGlyphAdv[0];
141 aEnds[0] = totWidth;
143 // Leave extra space also at top and bottom
144 int nBitmapWidth = totWidth;
145 int nBitmapHeight = bounds.getHeight() + aElement.getExtraSpace();
147 UINT nPos = 0;
149 aElement.maLocation.SetLeft(nPos);
150 aElement.maLocation.SetRight(aEnds[0]);
151 aElement.maLocation.SetTop(0);
152 aElement.maLocation.SetBottom(bounds.getHeight() + aElement.getExtraSpace());
153 nPos = aEnds[0];
155 std::unique_ptr<CompatibleDC> aDC(
156 CompatibleDC::create(rGraphics, 0, 0, nBitmapWidth, nBitmapHeight));
158 SetTextColor(aDC->getCompatibleHDC(), RGB(0, 0, 0));
159 SetBkColor(aDC->getCompatibleHDC(), RGB(255, 255, 255));
161 aDC->fill(RGB(0xff, 0xff, 0xff));
163 pTxt->BindDC(aDC->getCompatibleHDC(), tools::Rectangle(0, 0, nBitmapWidth, nBitmapHeight));
164 auto pRT = pTxt->GetRenderTarget();
166 ID2D1SolidColorBrush* pBrush = nullptr;
167 if (!SUCCEEDED(pRT->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &pBrush)))
168 return false;
170 D2D1_POINT_2F baseline
171 = { static_cast<FLOAT>(aElement.getExtraOffset()),
172 static_cast<FLOAT>(aElement.getExtraOffset() + aElement.mnBaselineOffset) };
174 DWRITE_GLYPH_RUN glyphs
175 = { pTxt->GetFontFace(), pTxt->GetEmHeight(), 1, aGlyphIndices.data(),
176 aGlyphAdv.data(), aGlyphOffset.data(), false, 0 };
178 WinFontTransformGuard aTransformGuard(pRT, fHScale, rLayout, baseline);
179 pRT->BeginDraw();
180 pRT->DrawGlyphRun(baseline, &glyphs, pBrush);
181 HRESULT hResult = pRT->EndDraw();
183 pBrush->Release();
185 switch (hResult)
187 case S_OK:
188 break;
189 case D2DERR_RECREATE_TARGET:
190 pTxt->CreateRenderTarget();
191 break;
192 default:
193 SAL_WARN("vcl.gdi",
194 "DrawGlyphRun-EndDraw failed: " << WindowsErrorString(GetLastError()));
195 return false;
198 if (!GlobalWinGlyphCache::get()->AllocateTexture(aElement, aDC.get()))
199 return false;
201 maWinGlyphCache.PutDrawElementInCache(std::move(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;
231 bool ExTextOutRenderer::operator()(GenericSalLayout const& rLayout, SalGraphics& /*rGraphics*/,
232 HDC hDC)
234 HFONT hFont = static_cast<HFONT>(GetCurrentObject(hDC, OBJ_FONT));
235 ScopedHFONT hAltFont;
236 bool bUseAltFont = false;
237 bool bShift = false;
238 if (rLayout.GetFont().GetFontSelectPattern().mbVertical)
240 LOGFONTW aLogFont;
241 GetObjectW(hFont, sizeof(aLogFont), &aLogFont);
242 if (aLogFont.lfFaceName[0] == '@')
244 memmove(&aLogFont.lfFaceName[0], &aLogFont.lfFaceName[1],
245 sizeof(aLogFont.lfFaceName) - sizeof(aLogFont.lfFaceName[0]));
246 hAltFont.reset(CreateFontIndirectW(&aLogFont));
248 else
250 bShift = true;
251 aLogFont.lfEscapement += 2700;
252 aLogFont.lfOrientation = aLogFont.lfEscapement;
253 hAltFont.reset(CreateFontIndirectW(&aLogFont));
257 UINT nTextAlign = GetTextAlign(hDC);
258 int nStart = 0;
259 Point aPos(0, 0);
260 const GlyphItem* pGlyph;
261 while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
263 wchar_t glyphWStr = pGlyph->glyphId();
264 if (hAltFont && pGlyph->IsVertical() == bUseAltFont)
266 bUseAltFont = !bUseAltFont;
267 SelectFont(hDC, bUseAltFont ? hAltFont.get() : hFont);
269 if (bShift && pGlyph->IsVertical())
270 SetTextAlign(hDC, TA_TOP | TA_LEFT);
272 ExtTextOutW(hDC, aPos.X(), aPos.Y(), ETO_GLYPH_INDEX, nullptr, &glyphWStr, 1, nullptr);
274 if (bShift && pGlyph->IsVertical())
275 SetTextAlign(hDC, nTextAlign);
277 if (hAltFont)
279 if (bUseAltFont)
280 SelectFont(hDC, hFont);
283 return true;
286 std::unique_ptr<GenericSalLayout> WinSalGraphics::GetTextLayout(int nFallbackLevel)
288 assert(mpWinFontEntry[nFallbackLevel]);
289 if (!mpWinFontEntry[nFallbackLevel])
290 return nullptr;
292 assert(mpWinFontEntry[nFallbackLevel]->GetFontFace());
294 mpWinFontEntry[nFallbackLevel]->SetGraphics(this);
295 return std::make_unique<GenericSalLayout>(*mpWinFontEntry[nFallbackLevel]);
298 WinFontInstance::WinFontInstance(const WinFontFace& rPFF, const FontSelectPattern& rFSP)
299 : LogicalFontInstance(rPFF, rFSP)
300 , m_pGraphics(nullptr)
301 , m_hFont(nullptr)
302 , m_fScale(1.0f)
306 WinFontInstance::~WinFontInstance()
308 if (m_hFont)
309 ::DeleteFont(m_hFont);
312 bool WinFontInstance::hasHScale() const
314 const FontSelectPattern& rPattern = GetFontSelectPattern();
315 int nHeight(rPattern.mnHeight);
316 int nWidth(rPattern.mnWidth ? rPattern.mnWidth * GetAverageWidthFactor() : nHeight);
317 return nWidth != nHeight;
320 float WinFontInstance::getHScale() const
322 const FontSelectPattern& rPattern = GetFontSelectPattern();
323 int nHeight(rPattern.mnHeight);
324 if (!nHeight)
325 return 1.0;
326 float nWidth(rPattern.mnWidth ? rPattern.mnWidth * GetAverageWidthFactor() : nHeight);
327 return nWidth / nHeight;
330 namespace
332 struct BlobReference
334 hb_blob_t* mpBlob;
335 BlobReference(hb_blob_t* pBlob)
336 : mpBlob(pBlob)
338 hb_blob_reference(mpBlob);
340 BlobReference(BlobReference const& other)
341 : mpBlob(other.mpBlob)
343 hb_blob_reference(mpBlob);
345 ~BlobReference() { hb_blob_destroy(mpBlob); }
349 using BlobCacheKey = std::pair<rtl::Reference<PhysicalFontFace>, hb_tag_t>;
351 namespace
353 struct BlobCacheKeyHash
355 std::size_t operator()(BlobCacheKey const& rKey) const
357 std::size_t seed = 0;
358 boost::hash_combine(seed, rKey.first.get());
359 boost::hash_combine(seed, rKey.second);
360 return seed;
365 static hb_blob_t* getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData)
367 static o3tl::lru_map<BlobCacheKey, BlobReference, BlobCacheKeyHash> gCache(50);
369 WinFontInstance* pFont = static_cast<WinFontInstance*>(pUserData);
370 HDC hDC = pFont->GetGraphics()->getHDC();
371 HFONT hFont = pFont->GetHFONT();
372 assert(hDC);
373 assert(hFont);
375 BlobCacheKey cacheKey{ rtl::Reference<PhysicalFontFace>(pFont->GetFontFace()), nTableTag };
376 auto it = gCache.find(cacheKey);
377 if (it != gCache.end())
379 hb_blob_reference(it->second.mpBlob);
380 return it->second.mpBlob;
383 sal_uLong nLength = 0;
384 unsigned char* pBuffer = nullptr;
386 HGDIOBJ hOrigFont = SelectObject(hDC, hFont);
387 nLength = ::GetFontData(hDC, OSL_NETDWORD(nTableTag), 0, nullptr, 0);
388 if (nLength > 0 && nLength != GDI_ERROR)
390 pBuffer = new unsigned char[nLength];
391 ::GetFontData(hDC, OSL_NETDWORD(nTableTag), 0, pBuffer, nLength);
393 SelectObject(hDC, hOrigFont);
395 if (!pBuffer)
396 return nullptr;
398 hb_blob_t* pBlob
399 = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength, HB_MEMORY_MODE_READONLY,
400 pBuffer, [](void* data) { delete[] static_cast<unsigned char*>(data); });
401 if (!pBlob)
402 return pBlob;
403 gCache.insert({ cacheKey, BlobReference(pBlob) });
404 return pBlob;
407 hb_font_t* WinFontInstance::ImplInitHbFont()
409 assert(m_pGraphics);
410 hb_font_t* pHbFont = InitHbFont(hb_face_create_for_tables(getFontTable, this, nullptr));
412 // Calculate the AverageWidthFactor, see LogicalFontInstance::GetScale().
413 if (GetFontSelectPattern().mnWidth)
415 double nUPEM = hb_face_get_upem(hb_font_get_face(pHbFont));
417 LOGFONTW aLogFont;
418 GetObjectW(m_hFont, sizeof(LOGFONTW), &aLogFont);
420 // Set the height (font size) to EM to minimize rounding errors.
421 aLogFont.lfHeight = -nUPEM;
422 // Set width to the default to get the original value in the metrics.
423 aLogFont.lfWidth = 0;
425 TEXTMETRICW aFontMetric;
427 // Get the font metrics.
428 HDC hDC = m_pGraphics->getHDC();
429 ScopedSelectedHFONT hFont(hDC, CreateFontIndirectW(&aLogFont));
430 GetTextMetricsW(hDC, &aFontMetric);
433 SetAverageWidthFactor(nUPEM / aFontMetric.tmAveCharWidth);
436 return pHbFont;
439 void WinFontInstance::SetGraphics(WinSalGraphics* pGraphics)
441 m_pGraphics = pGraphics;
442 if (m_hFont)
443 return;
444 HFONT hOrigFont;
445 m_hFont = m_pGraphics->ImplDoSetFont(GetFontSelectPattern(), GetFontFace(), hOrigFont);
446 SelectObject(m_pGraphics->getHDC(), hOrigFont);
449 bool WinSalGraphics::CacheGlyphs(const GenericSalLayout& rLayout)
451 static bool bDoGlyphCaching = (std::getenv("SAL_DISABLE_GLYPH_CACHING") == nullptr);
452 if (!bDoGlyphCaching)
453 return false;
455 if (rLayout.GetOrientation())
456 // Our caching is incomplete, skip it for non-horizontal text.
457 return false;
459 HDC hDC = getHDC();
460 WinFontInstance& rFont = *static_cast<WinFontInstance*>(&rLayout.GetFont());
461 HFONT hFONT = rFont.GetHFONT();
463 int nStart = 0;
464 Point aPos(0, 0);
465 const GlyphItem* pGlyph;
466 while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
468 if (!rFont.GetWinGlyphCache().IsGlyphCached(pGlyph->glyphId()))
470 if (!rFont.CacheGlyphToAtlas(hDC, hFONT, pGlyph->glyphId(), *this, rLayout))
471 return false;
475 return true;
478 bool WinSalGraphics::DrawCachedGlyphs(const GenericSalLayout& rLayout)
480 HDC hDC = getHDC();
482 tools::Rectangle aRect;
483 rLayout.GetBoundRect(aRect);
485 COLORREF color = GetTextColor(hDC);
486 Color salColor(GetRValue(color), GetGValue(color), GetBValue(color));
488 WinSalGraphicsImplBase* pImpl = dynamic_cast<WinSalGraphicsImplBase*>(mpImpl.get());
489 if (!pImpl->UseTextDraw())
490 return false;
492 WinFontInstance& rFont = *static_cast<WinFontInstance*>(&rLayout.GetFont());
494 int nStart = 0;
495 Point aPos(0, 0);
496 const GlyphItem* pGlyph;
497 while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
499 WinGlyphDrawElement& rElement(rFont.GetWinGlyphCache().GetDrawElement(pGlyph->glyphId()));
500 const CompatibleDC::Texture* texture = rElement.maTexture.get();
502 if (!texture || !texture->isValid())
503 return false;
505 SalTwoRect a2Rects(0, 0, texture->GetWidth(), texture->GetHeight(),
506 aPos.X() - rElement.getExtraOffset() + rElement.maLeftOverhangs,
507 aPos.Y() - rElement.mnBaselineOffset - rElement.getExtraOffset(),
508 texture->GetWidth(), texture->GetHeight());
510 pImpl->DeferredTextDraw(texture, salColor, a2Rects);
513 return true;
516 static void PruneGlyphCache() { GlobalWinGlyphCache::get()->Prune(); }
518 void WinSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout, HDC hDC, bool bUseDWrite)
520 TextOutRenderer& render = TextOutRenderer::get(bUseDWrite);
521 render(rLayout, *this, hDC);
524 void WinSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
526 WinSalGraphicsImplBase* pImpl = dynamic_cast<WinSalGraphicsImplBase*>(mpImpl.get());
527 if (!mbPrinter && pImpl->DrawTextLayout(rLayout))
528 return; // handled by pImpl
530 HDC hDC = getHDC();
531 const WinFontInstance* pWinFont = static_cast<const WinFontInstance*>(&rLayout.GetFont());
532 const HFONT hLayoutFont = pWinFont->GetHFONT();
533 bool bUseClassic = !pImpl->UseTextDraw() || mbPrinter;
535 // Our DirectWrite renderer is incomplete, skip it for vertical text where glyphs are not
536 // rotated.
537 bool bForceGDI = rLayout.GetFont().GetFontSelectPattern().mbVertical;
539 if (bUseClassic)
541 // no OpenGL, just classic rendering
542 const HFONT hOrigFont = ::SelectFont(hDC, hLayoutFont);
543 DrawTextLayout(rLayout, hDC, false);
544 ::SelectFont(hDC, hOrigFont);
546 // if we can't draw the cached OpenGL glyphs, try to draw a full OpenGL layout
547 else if (!bForceGDI && CacheGlyphs(rLayout) && DrawCachedGlyphs(rLayout))
549 PruneGlyphCache();
551 else
553 PruneGlyphCache(); // prune the cache from the failed calls above
555 // We have to render the text to a hidden texture, and draw it.
557 // Note that Windows GDI does not really support the alpha correctly
558 // when drawing - ie. it draws nothing to the alpha channel when
559 // rendering the text, even the antialiasing is done as 'real' pixels,
560 // not alpha...
562 // Luckily, this does not really limit us:
564 // To blend properly, we draw the texture, but then use it as an alpha
565 // channel for solid color (that will define the text color). This
566 // destroys the subpixel antialiasing - turns it into 'classic'
567 // antialiasing - but that is the best we can do, because the subpixel
568 // antialiasing needs to know what is in the background: When the
569 // background is white, or white-ish, it does the subpixel, but when
570 // there is a color, it just darkens the color (and does this even
571 // when part of the character is on a colored background, and part on
572 // white). It has to work this way, the results would look strange
573 // otherwise.
575 // For the GL rendering to work even with the subpixel antialiasing,
576 // we would need to get the current texture from the screen, let GDI
577 // draw the text to it (so that it can decide well where to use the
578 // subpixel and where not), and draw the result - but in that case we
579 // don't need alpha anyway.
581 // TODO: check the performance of this 2nd approach at some stage and
582 // switch to that if it performs well.
584 tools::Rectangle aRect;
585 rLayout.GetBoundRect(aRect);
586 if (aRect.IsEmpty())
587 return;
589 pImpl->PreDrawText();
591 std::unique_ptr<CompatibleDC> aDC(CompatibleDC::create(
592 *this, aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight()));
594 // we are making changes to the DC, make sure we got a new one
595 assert(aDC->getCompatibleHDC() != hDC);
597 RECT aWinRect = { aRect.Left(), aRect.Top(), aRect.Left() + aRect.GetWidth(),
598 aRect.Top() + aRect.GetHeight() };
599 ::FillRect(aDC->getCompatibleHDC(), &aWinRect,
600 static_cast<HBRUSH>(::GetStockObject(WHITE_BRUSH)));
602 // setup the hidden DC with black color and white background, we will
603 // use the result of the text drawing later as a mask only
604 const HFONT hOrigFont = ::SelectFont(aDC->getCompatibleHDC(), hLayoutFont);
606 ::SetTextColor(aDC->getCompatibleHDC(), RGB(0, 0, 0));
607 ::SetBkColor(aDC->getCompatibleHDC(), RGB(255, 255, 255));
609 UINT nTextAlign = ::GetTextAlign(hDC);
610 ::SetTextAlign(aDC->getCompatibleHDC(), nTextAlign);
612 COLORREF color = ::GetTextColor(hDC);
613 Color salColor(GetRValue(color), GetGValue(color), GetBValue(color));
615 // the actual drawing
616 DrawTextLayout(rLayout, aDC->getCompatibleHDC(), !bForceGDI);
618 std::unique_ptr<CompatibleDC::Texture> xTexture(aDC->getAsMaskTexture());
619 if (xTexture)
620 pImpl->DrawTextMask(xTexture.get(), salColor, aDC->getTwoRect());
622 ::SelectFont(aDC->getCompatibleHDC(), hOrigFont);
624 pImpl->PostDrawText();
628 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */