2 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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 .
22 #include <osl/module.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>
37 #include <win/DWriteTextRenderer.hxx>
38 #include <win/scoped_gdi.hxx>
41 #include <sallayout.hxx>
46 #include <rtl/character.hxx>
48 #include <boost/functional/hash.hpp>
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
));
71 SAL_WARN("vcl.gdi", "CreateCompatibleDC failed: " << WindowsErrorString(GetLastError()));
75 const HFONT hOrigFont
= static_cast<HFONT
>(SelectObject(aHDC
.get(), hFont
));
76 if (hOrigFont
== nullptr)
78 SAL_WARN("vcl.gdi", "SelectObject failed: " << WindowsErrorString(GetLastError()));
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));
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.");
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())
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
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();
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];
135 // Leave extra space also at top and bottom
136 int nBitmapWidth
= totWidth
;
137 int nBitmapHeight
= bounds
.getHeight() + aElement
.getExtraSpace();
141 aElement
.maLocation
.SetLeft(nPos
);
142 aElement
.maLocation
.SetRight(aEnds
[0]);
143 aElement
.maLocation
.SetTop(0);
144 aElement
.maLocation
.SetBottom(bounds
.getHeight() + aElement
.getExtraSpace());
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
)))
161 D2D1_POINT_2F baseline
= {
162 static_cast<FLOAT
>(aElement
.getExtraOffset()),
163 static_cast<FLOAT
>(aElement
.getExtraOffset() + aElement
.mnBaselineOffset
)
166 DWRITE_GLYPH_RUN glyphs
= {
170 aGlyphIndices
.data(),
177 WinFontTransformGuard
aTransformGuard(pRT
, fHScale
, rLayout
, baseline
);
179 pRT
->DrawGlyphRun(baseline
, &glyphs
, pBrush
);
180 HRESULT hResult
= pRT
->EndDraw();
188 case D2DERR_RECREATE_TARGET
:
189 pTxt
->CreateRenderTarget();
192 SAL_WARN("vcl.gdi", "DrawGlyphRun-EndDraw failed: " << WindowsErrorString(GetLastError()));
196 if (!OpenGLGlyphCache::ReserveTextureSpace(aElement
, nBitmapWidth
, nBitmapHeight
))
198 if (!aDC
.copyToTexture(aElement
.maTexture
))
201 maOpenGLGlyphCache
.PutDrawElementInCache(aElement
, nGlyphIndex
);
206 TextOutRenderer
& TextOutRenderer::get(bool bUseDWrite
)
208 SalData
*const pSalData
= GetSalData();
211 { // don't call this after DeInitVCL()
212 fprintf(stderr
, "TextOutRenderer fatal error: no SalData");
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*/,
236 HFONT hFont
= static_cast<HFONT
>(GetCurrentObject( hDC
, OBJ_FONT
));
237 ScopedHFONT hAltFont
;
238 bool bUseAltFont
= false;
240 if (rLayout
.GetFont().GetFontSelectPattern().mbVertical
)
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
));
253 aLogFont
.lfEscapement
+= 2700;
254 aLogFont
.lfOrientation
= aLogFont
.lfEscapement
;
255 hAltFont
.reset(CreateFontIndirectW(&aLogFont
));
259 UINT nTextAlign
= GetTextAlign ( hDC
);
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
);
282 SelectFont(hDC
, hFont
);
288 std::unique_ptr
<GenericSalLayout
> WinSalGraphics::GetTextLayout(int nFallbackLevel
)
290 assert(mpWinFontEntry
[nFallbackLevel
]);
291 if (!mpWinFontEntry
[nFallbackLevel
])
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)
308 WinFontInstance::~WinFontInstance()
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
);
328 float nWidth(rPattern
.mnWidth
? rPattern
.mnWidth
* GetAverageWidthFactor() : nHeight
);
329 return nWidth
/ nHeight
;
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
);
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();
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
);
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
); });
395 gCache
.insert({cacheKey
, BlobReference(pBlob
)});
399 hb_font_t
* WinFontInstance::ImplInitHbFont()
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
));
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
);
431 void WinFontInstance::SetGraphics(WinSalGraphics
*pGraphics
)
433 m_pGraphics
= pGraphics
;
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
)
447 if (rLayout
.GetOrientation())
448 // Our caching is incomplete, skip it for non-horizontal text.
452 WinFontInstance
& rFont
= *static_cast<WinFontInstance
*>(&rLayout
.GetFont());
453 HFONT hFONT
= rFont
.GetHFONT();
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
))
470 bool WinSalGraphics::DrawCachedGlyphs(const GenericSalLayout
& rLayout
)
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());
484 WinFontInstance
& rFont
= *static_cast<WinFontInstance
*>(&rLayout
.GetFont());
488 const GlyphItem
* pGlyph
;
489 while (rLayout
.GetNextGlyph(&pGlyph
, aPos
, nStart
))
491 OpenGLGlyphDrawElement
& rElement(rFont
.GetOpenGLGlyphCache().GetDrawElement(pGlyph
->glyphId()));
492 OpenGLTexture
& rTexture
= rElement
.maTexture
;
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
);
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
)
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
525 bool bForceGDI
= rLayout
.GetFont().GetFontSelectPattern().mbVertical
;
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,
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
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());
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());
601 pImpl
->DrawMask(*xTexture
, salColor
, aDC
.getTwoRect());
603 ::SelectFont(aDC
.getCompatibleHDC(), hOrigFont
);
610 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */