xref: /core/vcl/win/gdi/winlayout.cxx (revision 16ab4c5c)
1 
2 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 /*
4  * This file is part of the LibreOffice project.
5  *
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/.
9  *
10  * This file incorporates work covered by the following license notice:
11  *
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 .
19  */
20 
21 #include <config_features.h>
22 
23 #include <memory>
24 #include <osl/module.h>
25 #include <osl/file.h>
26 #include <sal/log.hxx>
27 
28 #include <comphelper/windowserrorstring.hxx>
29 #include <comphelper/scopeguard.hxx>
30 
31 #include <opengl/win/gdiimpl.hxx>
32 #include <opengl/win/winlayout.hxx>
33 
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>
39 
40 #include <win/DWriteTextRenderer.hxx>
41 #include <win/scoped_gdi.hxx>
42 
43 #include <sft.hxx>
44 #include <sallayout.hxx>
45 
46 #include <cstdio>
47 #include <cstdlib>
48 
49 #include <rtl/character.hxx>
50 
51 #include <boost/functional/hash.hpp>
52 #include <algorithm>
53 
54 #include <shlwapi.h>
55 #include <winver.h>
56 
57 GlobalWinGlyphCache * GlobalWinGlyphCache::get()
58 {
59     SalData *data = GetSalData();
60     if (!data->m_pGlobalWinGlyphCache)
61     {
62         if (OpenGLHelper::isVCLOpenGLEnabled())
63             data->m_pGlobalWinGlyphCache.reset(new OpenGLGlobalWinGlyphCache);
64     }
65     return data->m_pGlobalWinGlyphCache.get();
66 }
67 
68 bool WinFontInstance::CacheGlyphToAtlas(HDC hDC, HFONT hFont, int nGlyphIndex,
69                                         SalGraphics& rGraphics, const GenericSalLayout& rLayout)
70 {
71     WinGlyphDrawElement aElement;
72 
73     ScopedHDC aHDC(CreateCompatibleDC(hDC));
74 
75     if (!aHDC)
76     {
77         SAL_WARN("vcl.gdi", "CreateCompatibleDC failed: " << WindowsErrorString(GetLastError()));
78         return false;
79     }
80 
81     const HFONT hOrigFont = static_cast<HFONT>(SelectObject(aHDC.get(), hFont));
82     if (hOrigFont == nullptr)
83     {
84         SAL_WARN("vcl.gdi", "SelectObject failed: " << WindowsErrorString(GetLastError()));
85         return false;
86     }
87     const ::comphelper::ScopeGuard aHFONTrestoreScopeGuard(
88         [&aHDC,hOrigFont]() { SelectFont(aHDC.get(), hOrigFont); });
89 
90     // For now we assume DWrite is present and we won't bother with fallback paths.
91     D2DWriteTextOutRenderer * pTxt = dynamic_cast<D2DWriteTextOutRenderer *>(&TextOutRenderer::get(true));
92     if (!pTxt)
93         return false;
94 
95     pTxt->changeTextAntiAliasMode(D2DTextAntiAliasMode::AntiAliased);
96 
97     if (!pTxt->BindFont(aHDC.get()))
98     {
99         SAL_WARN("vcl.gdi", "Binding of font failed. The font might not be supported by DirectWrite.");
100         return false;
101     }
102     const ::comphelper::ScopeGuard aFontReleaseScopeGuard([&pTxt]() { pTxt->ReleaseFont(); });
103 
104     std::vector<WORD> aGlyphIndices(1);
105     aGlyphIndices[0] = nGlyphIndex;
106     // Fetch the ink boxes and calculate the size of the atlas.
107     tools::Rectangle bounds(0, 0, 0, 0);
108     auto aInkBoxes = pTxt->GetGlyphInkBoxes(aGlyphIndices.data(), aGlyphIndices.data() + 1);
109     if (aInkBoxes.empty())
110         return false;
111 
112     for (auto &box : aInkBoxes)
113         bounds.Union(box + Point(bounds.Right(), 0));
114 
115     // bounds.Top() is the offset from the baseline at (0,0) to the top of the
116     // inkbox.
117     aElement.mnBaselineOffset = -bounds.Top();
118     aElement.mnHeight = bounds.getHeight();
119     aElement.mbVertical = false;
120 
121     // Try hard to avoid overlap as we want to be able to use
122     // individual rectangles for each glyph. The ABC widths don't
123     // take anti-aliasing into consideration. Let's hope that leaving
124     // "extra" space between glyphs will help.
125     std::vector<float> aGlyphAdv(1);   // offsets between glyphs
126     std::vector<DWRITE_GLYPH_OFFSET> aGlyphOffset(1, {0.0f, 0.0f});
127     std::vector<int> aEnds(1); // end of each glyph box
128     float fHScale = getHScale();
129     float totWidth = 0;
130     {
131         int overhang = aInkBoxes[0].Left();
132         int blackWidth = aInkBoxes[0].getWidth() * fHScale; // width of non-AA pixels
133         aElement.maLeftOverhangs = overhang;
134 
135         aGlyphAdv[0] = blackWidth + aElement.getExtraSpace();
136         aGlyphOffset[0].advanceOffset = -overhang;
137 
138         totWidth += aGlyphAdv[0];
139         aEnds[0] = totWidth;
140     }
141     // Leave extra space also at top and bottom
142     int nBitmapWidth = totWidth;
143     int nBitmapHeight = bounds.getHeight() + aElement.getExtraSpace();
144 
145     UINT nPos = 0;
146 
147     aElement.maLocation.SetLeft(nPos);
148     aElement.maLocation.SetRight(aEnds[0]);
149     aElement.maLocation.SetTop(0);
150     aElement.maLocation.SetBottom(bounds.getHeight() + aElement.getExtraSpace());
151     nPos = aEnds[0];
152 
153     std::unique_ptr<CompatibleDC> aDC(CompatibleDC::create(rGraphics, 0, 0, nBitmapWidth, nBitmapHeight));
154 
155     SetTextColor(aDC->getCompatibleHDC(), RGB(0, 0, 0));
156     SetBkColor(aDC->getCompatibleHDC(), RGB(255, 255, 255));
157 
158     aDC->fill(RGB(0xff, 0xff, 0xff));
159 
160     pTxt->BindDC(aDC->getCompatibleHDC(), tools::Rectangle(0, 0, nBitmapWidth, nBitmapHeight));
161     auto pRT = pTxt->GetRenderTarget();
162 
163     ID2D1SolidColorBrush* pBrush = nullptr;
164     if (!SUCCEEDED(pRT->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::Black), &pBrush)))
165         return false;
166 
167     D2D1_POINT_2F baseline = {
168         static_cast<FLOAT>(aElement.getExtraOffset()),
169         static_cast<FLOAT>(aElement.getExtraOffset() + aElement.mnBaselineOffset)
170     };
171 
172     DWRITE_GLYPH_RUN glyphs = {
173         pTxt->GetFontFace(),
174         pTxt->GetEmHeight(),
175         1,
176         aGlyphIndices.data(),
177         aGlyphAdv.data(),
178         aGlyphOffset.data(),
179         false,
180         0
181     };
182 
183     WinFontTransformGuard aTransformGuard(pRT, fHScale, rLayout, baseline);
184     pRT->BeginDraw();
185     pRT->DrawGlyphRun(baseline, &glyphs, pBrush);
186     HRESULT hResult = pRT->EndDraw();
187 
188     pBrush->Release();
189 
190     switch (hResult)
191     {
192     case S_OK:
193         break;
194     case D2DERR_RECREATE_TARGET:
195         pTxt->CreateRenderTarget();
196         break;
197     default:
198         SAL_WARN("vcl.gdi", "DrawGlyphRun-EndDraw failed: " << WindowsErrorString(GetLastError()));
199         return false;
200     }
201 
202     if (!GlobalWinGlyphCache::get()->AllocateTexture(aElement, aDC.get()))
203         return false;
204 
205     maWinGlyphCache.PutDrawElementInCache(std::move(aElement), nGlyphIndex);
206 
207     return true;
208 }
209 
210 TextOutRenderer & TextOutRenderer::get(bool bUseDWrite)
211 {
212     SalData *const pSalData = GetSalData();
213 
214     if (!pSalData)
215     {   // don't call this after DeInitVCL()
216         fprintf(stderr, "TextOutRenderer fatal error: no SalData");
217         abort();
218     }
219 
220     if (bUseDWrite)
221     {
222         if (!pSalData->m_pD2DWriteTextOutRenderer)
223         {
224             pSalData->m_pD2DWriteTextOutRenderer.reset(new D2DWriteTextOutRenderer());
225         }
226         return *pSalData->m_pD2DWriteTextOutRenderer;
227     }
228     if (!pSalData->m_pExTextOutRenderer)
229     {
230         pSalData->m_pExTextOutRenderer.reset(new ExTextOutRenderer);
231     }
232     return *pSalData->m_pExTextOutRenderer;
233 }
234 
235 
236 bool ExTextOutRenderer::operator ()(GenericSalLayout const &rLayout,
237     SalGraphics & /*rGraphics*/,
238     HDC hDC)
239 {
240     HFONT hFont = static_cast<HFONT>(GetCurrentObject( hDC, OBJ_FONT ));
241     ScopedHFONT hAltFont;
242     bool bUseAltFont = false;
243     bool bShift = false;
244     if (rLayout.GetFont().GetFontSelectPattern().mbVertical)
245     {
246         LOGFONTW aLogFont;
247         GetObjectW(hFont, sizeof(aLogFont), &aLogFont);
248         if (aLogFont.lfFaceName[0] == '@')
249         {
250             memmove(&aLogFont.lfFaceName[0], &aLogFont.lfFaceName[1],
251                 sizeof(aLogFont.lfFaceName)-sizeof(aLogFont.lfFaceName[0]));
252             hAltFont.reset(CreateFontIndirectW(&aLogFont));
253         }
254         else
255         {
256             bShift = true;
257             aLogFont.lfEscapement += 2700;
258             aLogFont.lfOrientation = aLogFont.lfEscapement;
259             hAltFont.reset(CreateFontIndirectW(&aLogFont));
260         }
261     }
262 
263     UINT nTextAlign = GetTextAlign ( hDC );
264     int nStart = 0;
265     Point aPos(0, 0);
266     const GlyphItem* pGlyph;
267     while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
268     {
269         WORD glyphWStr[] = { pGlyph->glyphId() };
270         if (hAltFont && pGlyph->IsVertical() == bUseAltFont)
271         {
272             bUseAltFont = !bUseAltFont;
273             SelectFont(hDC, bUseAltFont ? hAltFont.get() : hFont);
274         }
275         if (bShift && pGlyph->IsVertical())
276             SetTextAlign(hDC, TA_TOP|TA_LEFT);
277 
278         ExtTextOutW(hDC, aPos.X(), aPos.Y(), ETO_GLYPH_INDEX, nullptr, LPCWSTR(&glyphWStr), 1, nullptr);
279 
280         if (bShift && pGlyph->IsVertical())
281             SetTextAlign(hDC, nTextAlign);
282     }
283     if (hAltFont)
284     {
285         if (bUseAltFont)
286             SelectFont(hDC, hFont);
287     }
288 
289     return true;
290 }
291 
292 std::unique_ptr<GenericSalLayout> WinSalGraphics::GetTextLayout(int nFallbackLevel)
293 {
294     assert(mpWinFontEntry[nFallbackLevel]);
295     if (!mpWinFontEntry[nFallbackLevel])
296         return nullptr;
297 
298     assert(mpWinFontEntry[nFallbackLevel]->GetFontFace());
299 
300     mpWinFontEntry[nFallbackLevel]->SetGraphics(this);
301     return std::make_unique<GenericSalLayout>(*mpWinFontEntry[nFallbackLevel]);
302 }
303 
304 WinFontInstance::WinFontInstance(const WinFontFace& rPFF, const FontSelectPattern& rFSP)
305     : LogicalFontInstance(rPFF, rFSP)
306     , m_pGraphics(nullptr)
307     , m_hFont(nullptr)
308     , m_fScale(1.0f)
309 {
310 }
311 
312 WinFontInstance::~WinFontInstance()
313 {
314     if (m_hFont)
315         ::DeleteFont(m_hFont);
316 }
317 
318 bool WinFontInstance::hasHScale() const
319 {
320     const FontSelectPattern &rPattern = GetFontSelectPattern();
321     int nHeight(rPattern.mnHeight);
322     int nWidth(rPattern.mnWidth ? rPattern.mnWidth * GetAverageWidthFactor() : nHeight);
323     return nWidth != nHeight;
324 }
325 
326 float WinFontInstance::getHScale() const
327 {
328     const FontSelectPattern& rPattern = GetFontSelectPattern();
329     int nHeight(rPattern.mnHeight);
330     if (!nHeight)
331         return 1.0;
332     float nWidth(rPattern.mnWidth ? rPattern.mnWidth * GetAverageWidthFactor() : nHeight);
333     return nWidth / nHeight;
334 }
335 
336 namespace {
337 
338 struct BlobReference
339 {
340     hb_blob_t* mpBlob;
341     BlobReference(hb_blob_t* pBlob) : mpBlob(pBlob)
342     {
343         hb_blob_reference(mpBlob);
344     }
345     BlobReference(BlobReference const & other)
346         : mpBlob(other.mpBlob)
347     {
348         hb_blob_reference(mpBlob);
349     }
350     ~BlobReference() { hb_blob_destroy(mpBlob); }
351 };
352 
353 }
354 
355 using BlobCacheKey = std::pair<rtl::Reference<PhysicalFontFace>, hb_tag_t>;
356 
357 namespace {
358 
359 struct BlobCacheKeyHash
360 {
361     std::size_t operator()(BlobCacheKey const& rKey) const
362     {
363         std::size_t seed = 0;
364         boost::hash_combine(seed, rKey.first.get());
365         boost::hash_combine(seed, rKey.second);
366         return seed;
367     }
368 };
369 
370 }
371 
372 static hb_blob_t* getFontTable(hb_face_t* /*face*/, hb_tag_t nTableTag, void* pUserData)
373 {
374     static o3tl::lru_map<BlobCacheKey, BlobReference, BlobCacheKeyHash> gCache(50);
375 
376     WinFontInstance* pFont = static_cast<WinFontInstance*>(pUserData);
377     HDC hDC = pFont->GetGraphics()->getHDC();
378     HFONT hFont = pFont->GetHFONT();
379     assert(hDC);
380     assert(hFont);
381 
382     BlobCacheKey cacheKey { rtl::Reference<PhysicalFontFace>(pFont->GetFontFace()), nTableTag };
383     auto it = gCache.find(cacheKey);
384     if (it != gCache.end())
385     {
386         hb_blob_reference(it->second.mpBlob);
387         return it->second.mpBlob;
388     }
389 
390     sal_uLong nLength = 0;
391     unsigned char* pBuffer = nullptr;
392 
393     HGDIOBJ hOrigFont = SelectObject(hDC, hFont);
394     nLength = ::GetFontData(hDC, OSL_NETDWORD(nTableTag), 0, nullptr, 0);
395     if (nLength > 0 && nLength != GDI_ERROR)
396     {
397         pBuffer = new unsigned char[nLength];
398         ::GetFontData(hDC, OSL_NETDWORD(nTableTag), 0, pBuffer, nLength);
399     }
400     SelectObject(hDC, hOrigFont);
401 
402     if (!pBuffer)
403         return nullptr;
404 
405     hb_blob_t* pBlob = hb_blob_create(reinterpret_cast<const char*>(pBuffer), nLength, HB_MEMORY_MODE_READONLY,
406                                pBuffer, [](void* data){ delete[] static_cast<unsigned char*>(data); });
407     if (!pBlob)
408         return pBlob;
409     gCache.insert({cacheKey, BlobReference(pBlob)});
410     return pBlob;
411 }
412 
413 hb_font_t* WinFontInstance::ImplInitHbFont()
414 {
415     assert(m_pGraphics);
416     hb_font_t* pHbFont = InitHbFont(hb_face_create_for_tables(getFontTable, this, nullptr));
417 
418     // Calculate the AverageWidthFactor, see LogicalFontInstance::GetScale().
419     if (GetFontSelectPattern().mnWidth)
420     {
421         double nUPEM = hb_face_get_upem(hb_font_get_face(pHbFont));
422 
423         LOGFONTW aLogFont;
424         GetObjectW(m_hFont, sizeof(LOGFONTW), &aLogFont);
425 
426         // Set the height (font size) to EM to minimize rounding errors.
427         aLogFont.lfHeight = -nUPEM;
428         // Set width to the default to get the original value in the metrics.
429         aLogFont.lfWidth = 0;
430 
431         TEXTMETRICW aFontMetric;
432         {
433             // Get the font metrics.
434             HDC hDC = m_pGraphics->getHDC();
435             ScopedSelectedHFONT hFont(hDC, CreateFontIndirectW(&aLogFont));
436             GetTextMetricsW(hDC, &aFontMetric);
437         }
438 
439         SetAverageWidthFactor(nUPEM / aFontMetric.tmAveCharWidth);
440     }
441 
442     return pHbFont;
443 }
444 
445 void WinFontInstance::SetGraphics(WinSalGraphics *pGraphics)
446 {
447     m_pGraphics = pGraphics;
448     if (m_hFont)
449         return;
450     HFONT hOrigFont;
451     m_hFont = m_pGraphics->ImplDoSetFont(GetFontSelectPattern(), GetFontFace(), m_fScale, hOrigFont);
452     SelectObject(m_pGraphics->getHDC(), hOrigFont);
453 }
454 
455 bool WinSalGraphics::CacheGlyphs(const GenericSalLayout& rLayout)
456 {
457     static bool bDoGlyphCaching = (std::getenv("SAL_DISABLE_GLYPH_CACHING") == nullptr);
458     if (!bDoGlyphCaching)
459         return false;
460 
461     if (rLayout.GetOrientation())
462         // Our caching is incomplete, skip it for non-horizontal text.
463         return false;
464 
465     HDC hDC = getHDC();
466     WinFontInstance& rFont = *static_cast<WinFontInstance*>(&rLayout.GetFont());
467     HFONT hFONT = rFont.GetHFONT();
468 
469     int nStart = 0;
470     Point aPos(0, 0);
471     const GlyphItem* pGlyph;
472     while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
473     {
474         if (!rFont.GetWinGlyphCache().IsGlyphCached(pGlyph->glyphId()))
475         {
476             if (!rFont.CacheGlyphToAtlas(hDC, hFONT, pGlyph->glyphId(), *this, rLayout))
477                 return false;
478         }
479     }
480 
481     return true;
482 }
483 
484 bool WinSalGraphics::DrawCachedGlyphs(const GenericSalLayout& rLayout)
485 {
486     HDC hDC = getHDC();
487 
488     tools::Rectangle aRect;
489     rLayout.GetBoundRect(aRect);
490 
491     COLORREF color = GetTextColor(hDC);
492     Color salColor(GetRValue(color), GetGValue(color), GetBValue(color));
493 
494     WinSalGraphicsImplBase *pImpl = dynamic_cast<WinSalGraphicsImplBase*>(mpImpl.get());
495     if (!pImpl->UseTextDraw())
496         return false;
497 
498     WinFontInstance& rFont = *static_cast<WinFontInstance*>(&rLayout.GetFont());
499 
500     int nStart = 0;
501     Point aPos(0, 0);
502     const GlyphItem* pGlyph;
503     while (rLayout.GetNextGlyph(&pGlyph, aPos, nStart))
504     {
505         WinGlyphDrawElement& rElement(rFont.GetWinGlyphCache().GetDrawElement(pGlyph->glyphId()));
506         const CompatibleDC::Texture* texture = rElement.maTexture.get();
507 
508         if (!texture || !texture->isValid())
509             return false;
510 
511         SalTwoRect a2Rects(0, 0,
512                            texture->GetWidth(), texture->GetHeight(),
513                            aPos.X() - rElement.getExtraOffset() + rElement.maLeftOverhangs,
514                            aPos.Y() - rElement.mnBaselineOffset - rElement.getExtraOffset(),
515                            texture->GetWidth(), texture->GetHeight());
516 
517         pImpl->DeferredTextDraw(texture, salColor, a2Rects);
518     }
519 
520     return true;
521 }
522 
523 static void PruneGlyphCache()
524 {
525     GlobalWinGlyphCache::get()->Prune();
526 }
527 
528 void WinSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout, HDC hDC, bool bUseDWrite)
529 {
530     TextOutRenderer &render = TextOutRenderer::get(bUseDWrite);
531     render(rLayout, *this, hDC);
532 }
533 
534 void WinSalGraphics::DrawTextLayout(const GenericSalLayout& rLayout)
535 {
536     WinSalGraphicsImplBase* pImpl = dynamic_cast<WinSalGraphicsImplBase*>(mpImpl.get());
537     if( !mbPrinter && pImpl->DrawTextLayout(rLayout))
538         return; // handled by pImpl
539 
540     HDC hDC = getHDC();
541     const WinFontInstance* pWinFont = static_cast<const WinFontInstance*>(&rLayout.GetFont());
542     const HFONT hLayoutFont = pWinFont->GetHFONT();
543     bool bUseClassic = !pImpl->UseTextDraw() || mbPrinter;
544 
545     // Our DirectWrite renderer is incomplete, skip it for vertical text where glyphs are not
546     // rotated.
547     bool bForceGDI = rLayout.GetFont().GetFontSelectPattern().mbVertical;
548 
549     if (bUseClassic)
550     {
551         // no OpenGL, just classic rendering
552         const HFONT hOrigFont = ::SelectFont(hDC, hLayoutFont);
553         DrawTextLayout(rLayout, hDC, false);
554         ::SelectFont(hDC, hOrigFont);
555     }
556     // if we can't draw the cached OpenGL glyphs, try to draw a full OpenGL layout
557     else if (!bForceGDI && CacheGlyphs(rLayout) && DrawCachedGlyphs(rLayout))
558     {
559         PruneGlyphCache();
560     }
561     else
562     {
563         PruneGlyphCache(); // prune the cache from the failed calls above
564 
565         // We have to render the text to a hidden texture, and draw it.
566         //
567         // Note that Windows GDI does not really support the alpha correctly
568         // when drawing - ie. it draws nothing to the alpha channel when
569         // rendering the text, even the antialiasing is done as 'real' pixels,
570         // not alpha...
571         //
572         // Luckily, this does not really limit us:
573         //
574         // To blend properly, we draw the texture, but then use it as an alpha
575         // channel for solid color (that will define the text color).  This
576         // destroys the subpixel antialiasing - turns it into 'classic'
577         // antialiasing - but that is the best we can do, because the subpixel
578         // antialiasing needs to know what is in the background: When the
579         // background is white, or white-ish, it does the subpixel, but when
580         // there is a color, it just darkens the color (and does this even
581         // when part of the character is on a colored background, and part on
582         // white).  It has to work this way, the results would look strange
583         // otherwise.
584         //
585         // For the GL rendering to work even with the subpixel antialiasing,
586         // we would need to get the current texture from the screen, let GDI
587         // draw the text to it (so that it can decide well where to use the
588         // subpixel and where not), and draw the result - but in that case we
589         // don't need alpha anyway.
590         //
591         // TODO: check the performance of this 2nd approach at some stage and
592         // switch to that if it performs well.
593 
594         tools::Rectangle aRect;
595         rLayout.GetBoundRect(aRect);
596         if( aRect.IsEmpty())
597             return;
598 
599         pImpl->PreDrawText();
600 
601         std::unique_ptr<CompatibleDC> aDC(CompatibleDC::create(*this, aRect.Left(), aRect.Top(), aRect.GetWidth(), aRect.GetHeight()));
602 
603         // we are making changes to the DC, make sure we got a new one
604         assert(aDC->getCompatibleHDC() != hDC);
605 
606         RECT aWinRect = { aRect.Left(), aRect.Top(), aRect.Left() + aRect.GetWidth(), aRect.Top() + aRect.GetHeight() };
607         ::FillRect(aDC->getCompatibleHDC(), &aWinRect, static_cast<HBRUSH>(::GetStockObject(WHITE_BRUSH)));
608 
609         // setup the hidden DC with black color and white background, we will
610         // use the result of the text drawing later as a mask only
611         const HFONT hOrigFont = ::SelectFont(aDC->getCompatibleHDC(), hLayoutFont);
612 
613         ::SetTextColor(aDC->getCompatibleHDC(), RGB(0, 0, 0));
614         ::SetBkColor(aDC->getCompatibleHDC(), RGB(255, 255, 255));
615 
616         UINT nTextAlign = ::GetTextAlign(hDC);
617         ::SetTextAlign(aDC->getCompatibleHDC(), nTextAlign);
618 
619         COLORREF color = ::GetTextColor(hDC);
620         Color salColor(GetRValue(color), GetGValue(color), GetBValue(color));
621 
622         // the actual drawing
623         DrawTextLayout(rLayout, aDC->getCompatibleHDC(), !bForceGDI);
624 
625         std::unique_ptr<CompatibleDC::Texture> xTexture(aDC->getAsMaskTexture());
626         if (xTexture)
627             pImpl->DrawTextMask(xTexture.get(), salColor, aDC->getTwoRect());
628 
629         ::SelectFont(aDC->getCompatibleHDC(), hOrigFont);
630 
631         pImpl->PostDrawText();
632     }
633 }
634 
635 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
636