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 .
21 #include <config_features.h>
24 #include <osl/module.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>
40 #include <win/DWriteTextRenderer.hxx>
41 #include <win/scoped_gdi.hxx>
44 #include <sallayout.hxx>
49 #include <rtl/character.hxx>
51 #include <boost/functional/hash.hpp>
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
));
77 SAL_WARN("vcl.gdi", "CreateCompatibleDC failed: " << WindowsErrorString(GetLastError()));
81 const HFONT hOrigFont
= static_cast<HFONT
>(SelectObject(aHDC
.get(), hFont
));
82 if (hOrigFont
== nullptr)
84 SAL_WARN("vcl.gdi", "SelectObject failed: " << WindowsErrorString(GetLastError()));
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));
96 pTxt
->changeTextAntiAliasMode(D2DTextAntiAliasMode::AntiAliased
);
98 if (!pTxt
->BindFont(aHDC
.get()))
101 "Binding of font failed. The font might not be supported by DirectWrite.");
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())
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
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();
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];
143 // Leave extra space also at top and bottom
144 int nBitmapWidth
= totWidth
;
145 int nBitmapHeight
= bounds
.getHeight() + aElement
.getExtraSpace();
149 aElement
.maLocation
.SetLeft(nPos
);
150 aElement
.maLocation
.SetRight(aEnds
[0]);
151 aElement
.maLocation
.SetTop(0);
152 aElement
.maLocation
.SetBottom(bounds
.getHeight() + aElement
.getExtraSpace());
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
)))
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
);
180 pRT
->DrawGlyphRun(baseline
, &glyphs
, pBrush
);
181 HRESULT hResult
= pRT
->EndDraw();
189 case D2DERR_RECREATE_TARGET
:
190 pTxt
->CreateRenderTarget();
194 "DrawGlyphRun-EndDraw failed: " << WindowsErrorString(GetLastError()));
198 if (!GlobalWinGlyphCache::get()->AllocateTexture(aElement
, aDC
.get()))
201 maWinGlyphCache
.PutDrawElementInCache(std::move(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
;
231 bool ExTextOutRenderer::operator()(GenericSalLayout
const& rLayout
, SalGraphics
& /*rGraphics*/,
234 HFONT hFont
= static_cast<HFONT
>(GetCurrentObject(hDC
, OBJ_FONT
));
235 ScopedHFONT hAltFont
;
236 bool bUseAltFont
= false;
238 if (rLayout
.GetFont().GetFontSelectPattern().mbVertical
)
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
));
251 aLogFont
.lfEscapement
+= 2700;
252 aLogFont
.lfOrientation
= aLogFont
.lfEscapement
;
253 hAltFont
.reset(CreateFontIndirectW(&aLogFont
));
257 UINT nTextAlign
= GetTextAlign(hDC
);
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
);
280 SelectFont(hDC
, hFont
);
286 std::unique_ptr
<GenericSalLayout
> WinSalGraphics::GetTextLayout(int nFallbackLevel
)
288 assert(mpWinFontEntry
[nFallbackLevel
]);
289 if (!mpWinFontEntry
[nFallbackLevel
])
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)
306 WinFontInstance::~WinFontInstance()
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
);
326 float nWidth(rPattern
.mnWidth
? rPattern
.mnWidth
* GetAverageWidthFactor() : nHeight
);
327 return nWidth
/ nHeight
;
335 BlobReference(hb_blob_t
* 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
>;
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
);
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();
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
);
399 = hb_blob_create(reinterpret_cast<const char*>(pBuffer
), nLength
, HB_MEMORY_MODE_READONLY
,
400 pBuffer
, [](void* data
) { delete[] static_cast<unsigned char*>(data
); });
403 gCache
.insert({ cacheKey
, BlobReference(pBlob
) });
407 hb_font_t
* WinFontInstance::ImplInitHbFont()
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
));
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
);
439 void WinFontInstance::SetGraphics(WinSalGraphics
* pGraphics
)
441 m_pGraphics
= pGraphics
;
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
)
455 if (rLayout
.GetOrientation())
456 // Our caching is incomplete, skip it for non-horizontal text.
460 WinFontInstance
& rFont
= *static_cast<WinFontInstance
*>(&rLayout
.GetFont());
461 HFONT hFONT
= rFont
.GetHFONT();
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
))
478 bool WinSalGraphics::DrawCachedGlyphs(const GenericSalLayout
& rLayout
)
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())
492 WinFontInstance
& rFont
= *static_cast<WinFontInstance
*>(&rLayout
.GetFont());
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())
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
);
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
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
537 bool bForceGDI
= rLayout
.GetFont().GetFontSelectPattern().mbVertical
;
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
))
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,
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
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
);
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());
620 pImpl
->DrawTextMask(xTexture
.get(), salColor
, aDC
->getTwoRect());
622 ::SelectFont(aDC
->getCompatibleHDC(), hOrigFont
);
624 pImpl
->PostDrawText();
628 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */